MySQL กับการ JOIN กลุ่มข้อมูลแบบเลือกรายการเดียว ที่ช่วงเวลาเปิดใช้งานสัมพันธ์กัน

โดย SONGCHAI SAETERN
จากบทความ Retrieving the last record in each group จะเป็นการ JOIN แบบเลือกรายการล่าสุด เช่นกรณีที่ข้อมูลในตารางหลัก มีการเปลี่ยนแปลงรุ่น เปลี่ยนเวอร์ชั่น แต่ยังคงใช้รหัสเดิม และต้องการแค่ข้อมูลเวอร์ชั่นล่าสุดเรคอร์ดเดียวเท่านั้น


นอกจากนี้ยังมีอีกกรณี นั่นก็คือ เมื่อเราย้อนกลับไปดูข้อมูลที่เคยบันทึกเอาไว้ โดยอ้างอิงรหัสเดียวกัน ที่ปัจจุบันได้เปลี่ยนเวอร์ชั่นไปแล้ว ข้อมูลที่ JOIN มาจะแสดงอย่างไร??? จะรู้ได้อย่างไรว่า  ณ วันที่นั้นๆ ใช้ข้อมูลเรคอร์ดไหน???

(ถ้าเราเก็บไอดี PK ไปเลยจะไม่เจอปัญหานี้ แต่เนื่องจากฟิลด์ Code ดันเป็น Unique ID ซึ่งยึดเป็น PK ไปในตัวก็เลยคิดว่าไม่จำเป็นต้องสร้าง ID ขึ้นมาอีก)



ตัวอย่างข้อมูลที่เคยบันทึกไว้ด้วยชื่อหมวดหมู่ก่อนการเปลี่ยนแปลง

กรรไกรสองรายการ ที่ใช้หมวดหมู่เดียวกัน แต่คนละปี

กรรไกรตัดกิ่ง   ณ วันที่ 2017-06-01 ได้ทำรายการด้วยชื่อ  "วัสดุทำสวน"
กรรไกรเล็ก       ณ วันที่ 2018-01-31 ได้ทำรายการด้วยชื่อ  "วัสดุอุปกรณ์ทำสวน"

SELECT * FROM product
WHERE name LIKE '%กรรไกร%'

เรามาดูข้อมูลเมื่อ JOIN กับตารางหมวดหมู่กันดู


SELECT product.*,tb_category.name AS cate_name  FROM product
LEFT JOIN tb_category ON product.category_code = tb_category.code
WHERE product.name LIKE '%กรรไกร%'

จะเห็นว่าได้ข้อมูลมาหลายเรคอร์ด และเรคอร์ดที่ตรงกับข้อมูลที่เคยทำรายการจริงก็คือแถวที่ 3 และ แถวที่ 6 ที่ลูกศรสีแดงชี้


เราจะทำการ GROUP รหัสสินค้า เพื่อลดจำนวนเรคอร์ดที่ซ้ำซ้อนออกไป


SELECT
product.*
, tb_category.name AS cate_name
, tb_category.start_used_date
FROM
product
LEFT JOIN
tb_category ON product.category_code = tb_category.code
WHERE
product.name LIKE '%กรรไกร%'
GROUP BY
product.id

ผลปรากฏว่า ชื่อหมวดหมู่ที่แสดงไม่ถูกต้อง เมื่อสังเกตวันที่ start_used_date จะเห็นว่า ได้ดึงแค่เรคอร์ดแรกมาเท่านั้น ไม่ได้ดึงตามช่วงเวลาของข้อมูลจริง


วิธีแก้ไขก็คือ เพิ่มเงื่อนไขเข้าไปในการ JOIN 

โดยกำหนด Sub Query ให้เลือกเฉพาะรายการที่เปิดใช้งานก่อนนำไปใช้งานในครั้งนั้นๆ



SELECT
product.*
, cate.name AS cate_name
, cate.start_used_date
FROM
product
LEFT JOIN
tb_category cate ON cate.id = (
SELECT MAX(bb.id)
         FROM tb_category AS bb
         WHERE bb.code = product.category_code
         AND bb.start_used_date <= product.active_date
       )
WHERE
product.name LIKE '%กรรไกร%'
GROUP BY product.id


เมื่อเราเพิ่มเงื่อนไข start_used_date <= product.active_date เข้าไปใน Sub Query ก็จะได้ ID ของเรคอร์ดที่เปิดใช้งานในช่วง active_date ของรายการนั้นๆ ซึ่งตรงกับที่เกิดขึ้นจริง


หากเห็นว่าเป็นประโยชน์ หรือมีกรณีใกล้เคียงกัน ก็ลองเอาไปปรับใช้กันดูนะครับ


สำหรับแหล่งอ้างอิง  Retrieving the last record in each group  


PHP CI MANIA PHP Code Generator 
โปรแกรมช่วยสร้างโค้ด ลดเวลาการเขียนโปรแกรม เขียนโปรแกรมง่ายและสะดวกขึ้น
สนใจสั่งซื้อราคาสุดคุ้ม >> http://fastcoding.phpcodemania.com/

CodeIgniter ล็อกอินไปสักพัก Session ก็หมดอายุ

โดย SONGCHAI SAETERN

 ทำไม SESSION ใน CodeIgniter ถึงหมดอายุเร็วมากๆ!??



งง??? อยู่นานมากๆ จำได้ว่าเคยเจอปัญหานี้เมื่อครั้งที่เริ่มใช้ CodeIgnier ใหม่ๆ (จนกระทั่งเดี๋ยวนี้ก็ยังเป็นอยู่นะ ^^;) เมื่อครั้งที่ใช้ Version 2 แค่เปลี่ยนจาก Files ไปเป็น Database ก็หาย

แต่ตอนนี้เกิดขึ้นอีกแล้วววววว >O<


สรุปแล้วก็คือเกิดจากชื่อของ SESSION มันไปตรงกันกับ project อื่นๆ แล้วในฐานข้อมูล SESSION ดันใช้ Database ตัวเดียวกัน ci_sessions

เพราะมีเหตุจำเป็นต้องใช้ฐานข้อมูลเดียวกัน

PROJECT 1 ใช้ db_project.ci_sessions
PROJECT 2 ใช้ db_project.ci_sessions เช่นกัน!!


ดังนั้นเวลาเปิดหน้าเว็บ Project 2 ก็จะเกิดการสร้าง session id ใหม่ที่ตาราง ci_sessions เดียวกัน ดังนั้นใน Project 1 ก็จะโดนตัดไปโดยปริยาย


วิธีแก้ไขปัญหานี้ก็คือ

1. หลีกเลี่ยงการใช้ Database และ Table ตัวเดียวกันในการเก็บ SESSION
2. เปลี่ยน sess_cookie_name ให้เป็นชื่อที่ไม่ซ้ำกันใน config/config.php ของแต่ละ Project


ขอให้สนุกกับการเขียนโปรแกรม PHP ด้วย CodeIgniter Framework นะครับ



PHP CI MANIA PHP Code Generator 
โปรแกรมช่วยสร้างโค้ด ลดเวลาการเขียนโปรแกรม เขียนโปรแกรมง่ายและสะดวกขึ้น
สนใจสั่งซื้อราคาสุดคุ้ม >> http://fastcoding.phpcodemania.com/

ตัวแสดงจำนวนสินค้าในตะกร้าสินค้า (Shopping cart Counter)

โดย SONGCHAI SAETERN

Cart counter

การใช้งานไอคอนแสดงตัวนับจำนวนสินค้าในตะกร้าสินค้านี้ จะต้องใช้ Bootstrap Framework เข้ามาช่วยในการจัดรูปแบบด้วย


Bootstrap is the most popular HTML, CSS, and JS framework for developing responsive, mobile first projects on the web.


ในส่วนของไอคอนแสดงตัวเลขนั้นจะมีโค้ดอยู่ 2 ส่วนด้วยกัน 


ส่วนที่ 1 CSS

<style>
.cart-box{
position: fixed;
bottom: 40px;
left: 30px;
width: 48px;
height: 48px;
z-index: 2147483000;
cursor: pointer;
background-position: 50%;
}
.btn-circle {
width: 30px;
height: 30px;
text-align: center;
padding: 6px 0;
font-size: 12px;
line-height: 1.428571429;
border-radius: 15px;
}
.btn-circle.btn-lg {
width: 50px;
height: 50px;
padding: 10px 16px;
font-size: 18px;
line-height: 1.33;
border-radius: 25px;
}
.btn-circle.btn-xl {
width: 70px;
height: 70px;
padding: 10px 16px;
font-size: 24px;
line-height: 1.33;
border-radius: 35px;
}
.cart-items-count{
position:relative;
display:flex;
text-align:center;
justify-content: center;
top:-55px;
}
.notification-counter { 
position: absolute;
left: 8px;
background-color: rgba(212, 19, 13, 1);
color: #fff;
border-radius: 3px;
padding: 1px 3px;
font: 8px Verdana;
}
</style>

ส่วนที่ 2 HTML

<div class="cart-box" id="Normal">
<ul class="nav navbar-nav">
<li class="dropdown">
  <button href="#" class="draggable dropdown-toggle btn btn-primary btn-circle btn-xl" data-toggle="dropdown" role="button" aria-expanded="false"> <span class="glyphicon glyphicon-shopping-cart"></span></button>
   <span  class="cart-items-count"><span class=" notification-counter">243</span></span>
</li>
  </ul>
</div>

เมื่อเปิดหน้าเว็บขึ้นมาดูผลลัพธ์จะได้ดังนี้




หากต้องการเปลี่ยนตำแหน่ง ให้กำหนดค่าที่ CSS .cart_box{  ใหม่



ตัวนับจำนวนจะย้ายไปยังตำแหน่งใหม่ที่ต้องการ





โค้ดฉบับเต็มสำหรับทดลอง


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>Shopping Cart COUNTER</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" crossorigin="anonymous">
<link href="https://getbootstrap.com/docs/3.3/examples/non-responsive/non-responsive.css" rel="stylesheet">
<style>
.cart-box{
position: fixed;
  bottom: 40px;
  left: 30px;
  width: 48px;
  height: 48px;
  z-index: 2147483000;
  cursor: pointer;
  background-position: 50%;
}
.btn-circle {
  width: 30px;
  height: 30px;
  text-align: center;
  padding: 6px 0;
  font-size: 12px;
  line-height: 1.428571429;
  border-radius: 15px;
}
.btn-circle.btn-lg {
  width: 50px;
  height: 50px;
  padding: 10px 16px;
  font-size: 18px;
  line-height: 1.33;
  border-radius: 25px;
}
.btn-circle.btn-xl {
  width: 70px;
  height: 70px;
  padding: 10px 16px;
  font-size: 24px;
  line-height: 1.33;
  border-radius: 35px;
}
.cart-items-count{
position:relative;
display:flex;
text-align:center;
justify-content: center;
top:-55px;
}
.notification-counter { 
position: absolute;
left: 8px;
background-color: rgba(212, 19, 13, 1);
color: #fff;
border-radius: 3px;
padding: 1px 3px;
font: 8px Verdana;
}
</style>
    <!-- Custom styles for this template -->
 
</head>
<body>
    <!-- Fixed navbar -->
    <nav class="navbar navbar-default navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <!-- The mobile navbar-toggle button can be safely removed since you do not need it in a non-responsive implementation -->
          <a class="navbar-brand" href="#">Shopping Cart</a>
        </div>
        <!-- Note that the .navbar-collapse and .collapse classes have been removed from the #navbar -->
        <div id="navbar">
          <ul class="nav navbar-nav">
            <li class="active"><a href="#">Home</a></li>
            <li><a href="#about">Profile</a></li>
            <li><a href="#contact">Order</a></li>
            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown"
  role="button" aria-haspopup="true" aria-expanded="false">Sale <span class="caret"></span></a>
            </li>
          </ul>
          <form class="navbar-form navbar-left">
            <div class="form-group">
              <input type="text" class="form-control" placeholder="Search">
            </div>
            <button type="submit" class="btn btn-default">Search</button>
          </form>
          <ul class="nav navbar-nav navbar-right">
            <li><a href="#"></a></li>
            <li><a href="#"></a></li>
            <li><a href="#"></a></li>
          </ul>
        </div><!--/.nav-collapse -->
      </div>
    </nav>
 
    <div class="container">
<h1>แสดงตัวนับจำนวนรายการในตะกร้าสินค้า</h1>
<div class="cart-box" id="Normal">
<ul class="nav navbar-nav">
<li class="dropdown">
  <button href="#" class="draggable dropdown-toggle btn btn-primary btn-circle btn-xl" data-toggle="dropdown" role="button" aria-expanded="false"> <span class="glyphicon glyphicon-shopping-cart"></span></button>
   <span  class="cart-items-count"><span class=" notification-counter">243</span></span>
</li>
  </ul>
</div>
    </div> <!-- /container -->
</body>
</html>


ที่มา : https://bootsnipp.com/snippets/ae8qE

PHP CI MANIA PHP Code Generator 
โปรแกรมช่วยสร้างโค้ด ลดเวลาการเขียนโปรแกรม เขียนโปรแกรมง่ายและสะดวกขึ้น
สนใจสั่งซื้อราคาสุดคุ้ม >> http://fastcoding.phpcodemania.com/

วิธีสร้าง Dynamically Controllers ใน CodeIgniter Framework

โดย SONGCHAI SAETERN

How to create dynamically controllers function name in codeigniter.

1. แก้ไขไฟล์คอนฟิก application/config/routes.php

$route['product/(:any)'] = 'product/show/$1';

2. สร้างคอนโทรลเลอร์ที่ชื่อ product เอาไว้รับผ่านฟังก์ชั่น show

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Product extends MY_Controller {

/**
* Index Page for this controller.
*/
public function show($category='')
{
    echo $category;//อยากได้ข้อมูลอะไรก็เอาค่าที่ส่งมาไปค้นหา
}

}
3. ตัวอย่างเช่น
     http://localhost/my_project/index.php/product/new  => สินค้าใหม่
     http://localhost/my_project/index.php/product/hdd  => ฮาร์ดดิสก์
     http://localhost/my_project/index.php/product/ram  => แรม

สังเกตว่าเวลาลิงค์ไปหน้า Product ไม่จำเป็นต้องสร้างฟังก์ชั่น new(), hdd(), ram() แต่เราจะวิ่งไปที่ฟังก์ชั่น show() ตามที่กำหนดไว้ในไฟล์ routes.php


ที่มา : https://expressionengine.com/forums/archive/topic/227841/building-pages-dynamically-from-a-database

แก้ปัญหา base_url() ใน CodeIgniter แสดงแค่โดเมน เข้าไม่ถึง path ของโปรเจ็กต์

โดย SONGCHAI SAETERN

การเขียนโปรแกรม PHP ด้วย CodeIgniter Framework

ในบางครั้งเราอาจจะเจอปัญหา base_url() ที่เรียกใช้แล้วได้แค่ http://localhost หรือ http://www.your-domain-name.com เท่านั้น เข้าไปไม่ถึงใน Directory ที่ใช้เก็บโปรเจ็กต์ของเรา

วิธีแก้ไขก็คือ เข้าไปในไฟล์ config.php แล้วเพิ่มโค้ดด้านล่างนี้เข้าไปแทน





application/config/config.php
$potocal = 'http'.((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 's' : '');
$directory = str_replace('//','/',dirname($_SERVER['PHP_SELF']).'/');
$base_url = $potocal . '://'.$_SERVER['HTTP_HOST'] . $directory;

$config['base_url'] = $base_url;

แก้ไข 
คำสั่งด้านบนนี้ เป็นการหาค่า URL ของหน้าเว็บปัจจุบัน ไม่ได้กำหนดค่า URL ของโปรเจ็กต์

เช่นเมื่ออยู่ใน Controller ชื่อว่า Shop จะได้ http://localhost/soap_station/index.php/shop

ดังนั้นเปลี่ยนเป็น


$potocal = 'http'.((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 's' : '');
$directory = '/your_project_directory';
$base_url = $potocal . '://'.$_SERVER['HTTP_HOST'] . $directory;

$config['base_url'] = $base_url;




PHP CI MANIA PHP Code Generator 
โปรแกรมช่วยสร้างโค้ด ลดเวลาการเขียนโปรแกรม เขียนโปรแกรมง่ายและสะดวกขึ้น
สนใจสั่งซื้อราคาสุดคุ้ม >> http://fastcoding.phpcodemania.com/

แก้ปัญหาการเปิด Firewall ทำให้ Client เชื่อมต่อ MySQL ไม่ได้

โดย CyberMAN

"Allow programs to communicate through Windows Firewall"

เดิมทีปิด FireWall เอาไว้ โปรแกรมสามารถเชื่อมต่อฐานข้อมูลได้ปกติ


หลังจากเปิด Windows Firewall โปรแกรมไม่สามารถเชื่อมต่อฐานข้อมูล MySQL จากเครื่อง Client ได้


แต่เชื่อมผ่าน Localhost ได้


ให้เข้าไปเปิดที่ Control panel > Windows Firewall > Allow a program or feature through Windows Firewall เพื่อกำหนดให้ MySQL ที่เราติดตั้งไว้สามารถทำงานผ่าน Firewall ได้


หลังจากกด OK ลองกลับไปหน้าล็อกอินผ่านโปรแกรมจัดการฐานข้อมูลที่เครื่อง Client อีกครั้ง


ท่านใดใช้เครื่องมือจัดการฐานข้อมูลที่เครื่อง Client เชื่อมต่อไปยัง Server แล้วเชื่อมต่อไม่ได้ ลองตรวจสอบการตั้งค่า Windows Firewall ดูนะครับ









ข้อสังเกต



ไม่ได้เกิดจากการตั้ง Host name ของ MySQL User เพราะตั้งเป็น % เรียบร้อย (คือเชื่อมต่อได้จากทุก iP) แต่ถ้าตั้งค่า Host name แล้วแต่ยังไม่ได้จะมีข้อความแจ้งเตือนอีกแบบ

Access denied for '.....'@ 'เลขไอพีหรือComputername'

ซึ่งถ้าสั่งคิวรี่คำสั่ง

flush privileges;
 

ใน phpMyAdmin (ซึ่งล็อกอินด้วย user เดียวกันนี้ได้ปกติ) ก็จะทำการเชื่อมต่อจากไอพีด้านนอกได้

https://stackoverflow.com/questions/11760177/access-denied-for-root-user-in-mysql-command-line


บทความที่เกี่ยวข้อง

แก้ปัญหา Xampp เข้าผ่าน IP จากเครื่องอื่นไม่ได้ โดยการเปิด Firewall เชื่อมต่อ httpd ได้
https://phpcodemania.blogspot.com/2021/04/xampp-ip-firewall-httpd.html




PHP CI MANIA PHP Code Generator 
โปรแกรมช่วยสร้างโค้ด ลดเวลาการเขียนโปรแกรม เขียนโปรแกรมง่ายและสะดวกขึ้น
สนใจสั่งซื้อราคาสุดคุ้ม >> http://fastcoding.phpcodemania.com/

ระบบวัสดุคงเหลือ - การตีราคาสินค้าคงเหลือด้วยวิธีราคาทุน (Inventory Costs Method)

โดย SONGCHAI SAETERN

การตีราคาสินค้าคงเหลือด้วยวิธีราคาทุน (Inventory Costs Method)

หลังจากได้อ่านบทความในเว็บต่างๆก็สรุปได้ดังนี้

1. FIFO เข้าก่อน-ออกก่อน เหมาะสำหรับการตัดสต๊อกที่ใช้สำหรับของที่มีวันที่หมดอายุกำกับไว้ อาจจะเนื่องด้วยพนักงานสต๊อกจะต้องคอยเช็ควันที่หมดอายุบนสินค้าเสมอๆอยู่แล้ว

2. LIFO เข้าหลัง-ออกก่อน เหมาะสำหรับการตัดสต๊อกสินค้าที่ตกเทรนเร็วแบบคอมพิวเตอร์ หรือสินค้าเทคโนโลยีอื่นๆ ซึ่งส่วนใหญ่จะทำโปรโมชั่นมาล่อตาล่อใจให้ลูกค้าซื้อรุ่นใหม่ๆ ดังนั้นพนักงานก็ต้องตัดสต๊อกตัวที่ใหม่ที่สุดออกก่อน เพราะทิ้งไว้นานไปราคาทุน ณ ปัจจุบันจะไม่ตรงกับราคาที่ขายไป



3. Weighted Average ถัวเฉลี่ยน้ำหนัก เหมาะกับสินค้าที่ไม่มีวันหมดอายุกำกับ และราคาขึ้นลงไม่มากนัก (คอมพิวเตอร์หรือสินค้าเทคโนโลยีจะขึ้นลงทีเป็นหลักร้อยหลักพัน) ซึ่งเหมาะกับการนำมาใช้ทำสต๊อกวัสดุคงเหลือของสำนักงาน







ปัญหาของการใช้ Weighted Average หรือการถัวเฉลี่ยราคาทุนต่อหน่วย จะมีอยู่ 2 รูปแบบ

1. แบบ Periodic Inventory เป็นการคิดราคาทุนถัวเฉลี่ยของสินค้าคงเหลือปลายงวด นั่นก็คือ บันทึกการรับเข้าด้วยราคาต่อหน่วยที่ต่างกัน ส่วนตอนบันทึกจ่ายระบุเพียงจำนวนไม่ต้องระบุราคา เมื่อถึงปลายงวดก็หาราคาเฉลี่ยต่อหน่วยและนำไปคำนวณหาราคาทุนคงเหลือ เพื่อหักยอดรวมออก ก็จะได้เป็นต้นทุนจ่าย ณ วันที่ทำรายงาน
https://krurabieb.wordpress.com/2007/12/10/การตีราคาสินค้าคงเหลือ

ตัวอย่างแบบฟอร์มการขอเบิกจ่าย แบบไม่ระบุราคาต่อหน่วย


2. แบบ Perpetual Inventory จะเป็นการคิดราคาทุนเฉลี่ยเคลื่อนที่ (Moving Average Method) คือจะระบุราคาการเบิกจ่ายแต่ละครั้งลงไปด้วย ดังนั้นก็จะต้องคำนวณราคาทุนที่มีอยู่ก่อนกับการรับเข้ามาใหม่ทุกครั้งที่มีการเบิกจ่าย
https://krurabieb.wordpress.com/2007/12/10/การตีราคาสินค้าคงเหลือ


ตัวอย่างแบบฟอร์มขอเบิกจ่าย แบบระบุราคาต่อหน่วย

สำหรับปัญหาที่เจอในการถัวเฉลี่ยแบบ Moving Average ก็คือเรื่องจุดทศนิยมของราคาต่อหน่วย เมื่อหารไม่ลงตัว จะเกิดผลรวมที่ไม่ตรงกัน


กรณีหารลงตัว


กรณีหารไม่ลงตัว


วิธีแก้ปัญหาคือเก็บทศนิยม 4 ตำแหน่ง



แต่ถ้าจะเอาให้ชัวร์เก็บไป 6 ตำแหน่งเลยก็ได้ครับ แล้วก็เวลาแสดงในรายงานอาจจะแสดงแค่ 2 หลัก แล้วหมายเหตุไว้ว่า คำนวณราคาต่อหน่วยจากทศนิยม 6 ตำแหน่ง




ที่มา :  

การตีราคาสินค้าคงเหลือด้วยวิธีราคาทุน
https://krurabieb.wordpress.com/2007/12/10/การตีราคาสินค้าคงเหลือ

ต้นทุนขาย FIFO VS LIFO VS Weight Average
http://www.challengemetutor.com/articles/41997457/ต้นทุนขาย-FIFO-VS-LIFO-VS-Weight-Average.html


PHP CI MANIA PHP Code Generator 
โปรแกรมช่วยสร้างโค้ด ลดเวลาการเขียนโปรแกรม เขียนโปรแกรมง่ายและสะดวกขึ้น
สนใจสั่งซื้อราคาสุดคุ้ม >> http://fastcoding.phpcodemania.com/

JavaScript HTML DOM Animation การเคลื่อนย้าย element ในหน้าเว็บ

โดย SONGCHAI SAETERN

มาฝึกเขียน JavaScript กันเถอะ 

ในตัวอย่างนี้เมื่อคลิกที่ปุ่ม Click Me กรอบสี่เหลี่ยมสีแดงจะเคลื่อนจากตำแหน่งบนซ้าย ไปยังมุมล่างขวา ซึ่งถูกกำหนดพิกัดไว้ที่ 350 

if(!pX) pX=350;
if(!pY) pY=350;

ตำแหน่งกรอบสีแดงจะถูกบวกเพิ่มไปจนกระทั่งครบตามที่กำหนดไว้
      if(posY < pY) posY++;
      if(posX < pX) posX++;
      elem.style.top = posY + 'px';
      elem.style.left = posX + 'px'; 



และในพื้นที่สีเหลืองนี้ ได้กำหนดคำสั่ง OnClick เอาไว้เพื่อให้กรอบสีแดงเคลื่อนไปยังจุดที่คลิก

<div id ="container" onclick="sendPos(event)">


ในฟังก์ชั่น sendPos() จะรับค่า event ที่ได้จาก onclick และรับค่าตำแหน่ง offsetX, offsetY เพื่อส่งไปยังฟังก์ชั่น myMove() เพื่อเคลื่อนย้ายสีเหลี่ยมสีแดงไปยังตำแหน่งที่คลิก

function sendPos(event){
    myMove(event.offsetX  , event.offsetY );
}

สังเกตุว่าการเคลื่อนย้ายจะไม่เป็นเส้นตรง เพราะตำแหน่ง X และ Y ไม่เท่ากัน ดังนั้นเมื่อถึงตำแหน่งแนวไหนก่อน ก็จะเคลื่อนไปอีกแนวเท่านั้น


แนวทางการปรับปรุงโค้ดให้การเคลื่อนที่เป็นเส้นตรง

ยกตัวอย่าง x = 78 , y = 264
ให้ยึดค่าที่มากกว่าเป็นหลัก นั่นคือแกน y จะขยับอยู่ 264 ครั้ง
ดังนั้น ต้องนำค่าแกน x มาเฉลี่ยให้ได้ 264 ครั้งด้วยสูตร
 min / max  นั่นก็คือ  78 / 264 = 0.295

ซึ่งถ้าคูณกลับ 0.295 * 264 = 77.88 ซึ่งยังไม่ครบตำแหน่งจริงคือ 78
ดังนั้นจะมีการขยับอีกครั้ง 77.88 + 0.295 จะกลายเป็น 78.175

จะเห็นว่าเลยตำแหน่งจริงไป 0.175

ดังนั้น ต้องตรวจสอบว่าถ้าเลยตำแหน่งจริงเมื่อไหร่ต้องกำหนดให้เท่าตำแหน่งจริง





รายละเอียด

ตัวอย่างโค้ด JavaScript ทั้งหมด


<!DOCTYPE html>
<html>
<style>
#container {
  width: 400px;
  height: 400px;
  position: relative;
  background: yellow;
}
#animate {
  width: 50px;
  height: 50px;
  position: absolute;
  background-color: red;
}
</style>
<body>
<p>
<button onclick="myMove()">Click Me</button>
</p>
<p><span id="debug"></span> || <span id="current"></span></p>
<div id ="container" onclick="sendPos(event)">
<div id ="animate"></div>
</div>
<script>
function myMove(pX, pY) {
if(!pX) pX=350;
    if(!pY) pY=350;
document.getElementById("debug").innerHTML = pX + " : " + pY;
  var elem = document.getElementById("animate"); 
  var posX = 0;
  var posY = 0;
  var id = setInterval(frame, 5);
  function frame() {
      if (posX >= pX && posY >= pY) {
      clearInterval(id);
    } else {
      if(posY < pY) posY++;
      if(posX < pX) posX++;
      elem.style.top = posY + 'px';
      elem.style.left = posX + 'px';
      document.getElementById("current").innerHTML = posX + " : " + posY;
    }
  }
}
function sendPos(event){
    myMove(event.offsetX  , event.offsetY );
}
</script>
</body>
</html>

--------------------------

อ้างอิง

JavaScript HTML DOM Animation
https://www.w3schools.com/js/tryit.asp?filename=tryjs_dom_animate_3

How do I get the coordinates of a mouse click on a canvas element?
https://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element


PHP CI MANIA PHP Code Generator 
โปรแกรมช่วยสร้างโค้ด ลดเวลาการเขียนโปรแกรม เขียนโปรแกรมง่ายและสะดวกขึ้น
สนใจสั่งซื้อราคาสุดคุ้ม >> http://fastcoding.phpcodemania.com/

PHP กับจัดการฐานข้อมูลหมวดหมู่สินค้า MySQL แบบใช้ตารางเพียงตารางเดียวเท่านั้น

โดย SONGCHAI SAETERN

การเขียนโปรแกรม PHP แสดงหมวดหมู่สินค้า



สำหรับโครงสร้างฐานข้อมูลแบบทั่วไปเมื่อมีรายการย่อย เราจะใช้หลักการ One -> Many คือมี 1 ตารางหมวดหมู่หลัก และมีอีก 1 ตารางเป็นหมวดหมู่ย่อย






แต่ปัญหาของกรณีแยกตารางออกจากกันนี้ ตัวผู้เขียนพบปัญหาว่าเมื่อมีหมวดหมู่ย่อยลงไปอีก  3 ชั้นบ้าง 4 ชั้นบ้าง หรืออาจจะมีหมวดย่อยไม่จำกัด ก็จะทำให้การเขียนโปรแกรมนั้นยากขึ้น



เช่น การเขียนคำสั่ง SQL JOIN เพื่อดึงข้อมูลตารางที่ 2 มาแสดง

SELECT
       id, name
FROM
       tb_cate1 AS tbMaster
INNER JOIN
       tb_cate2 AS tbDetail ON tbMaster.id= tbDetail.ref_id
WHERE
       {เงื่อนไขที่ใช้ค้นหา}

และเมื่อมีการเชื่อมหลายๆตาราง ก็จะทำให้หน้าเว็บทำงานได้ช้าลง (อ่านเรื่อง "การทำ Index ให้ MySQL Database เพื่อเพิ่มความเร็ว")


ดังนั้นผู้เขียนจึงได้ทำการปรับปรุงตารางให้เหลือตารางเดียว และกำหนดฟิลด์ ref_parent_id สำหรับใช้อิงหมวดหมู่หลักของรายการนั้นๆ ซึ่งถ้ามีค่าเป็น 0 แสดงว่าเป็นหมวดหมู่หลักระดับบนสุดนั่นเอง
ตารางแสดงโครงสร้างฐานข้อมูลหมวดหมู่สินค้าแบบตารางเดียว


เรามาดูกันว่า จะเขียนโปรแกรม PHP เพื่อแสดงข้อมูลหมวดหมู่เหล่านี้ได้อย่างไร


[ โค้ด PHP ]
<?php
try {
$user = 'tobedev';
$pass = 'abcd.1234';
$db = 'test';
$charset = 'utf8';
$dbh = new PDO("mysql:host=localhost;dbname=$db;charset=$charset", $user, $pass);
} catch (PDOException $e) {
    print "Error!: " . $e->getMessage() . "<br/>";
    die();
}
//Query Data
$sth = $dbh->prepare("SELECT * FROM tb_category");
$sth->execute();
$result = $sth->fetchAll(PDO::FETCH_ASSOC);
$data = array();
$sub_data = array();
//Group data with parent_id
foreach($result as $row){
if($row['ref_parent_id'] > 0){
$sub_data[$row['ref_parent_id']][$row['id']] = $row;
}else{
$data[$row['id']] = $row;
}
}
// SET sub category
foreach($data as $row){
if($sub = findSubCategory($row['id'], $sub_data)){
$data[$row['id']]['sub'] = $sub;
}
}
//echo '<pre><h2>MAIN</h2>', print_r($data,true), '</pre>';
//echo '<pre><h2>SUB</h2>', print_r($sub_data,true), '</pre>';

function displayCategory($data, $sub=1){
echo '<br/>' . str_repeat("&nbsp;",$sub*4) . '- ' . $data['name'];
if(isset($data['sub'])){
$sub++;
//Recursive Function
foreach($data['sub'] as $row){
displayCategory($row, $sub);
}
}
}
function findSubCategory($id, $sub_data){
if(isset($sub_data[$id])){
foreach($sub_data[$id] as $row){
//Recursive Function
if($sub = findSubCategory($row['id'], $sub_data)){
$sub_data[$id][$row['id']]['sub'] = $sub;
}
}
return $sub_data[$id];
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>PHP - Category : PhpCodeMania.Blogspot.Com</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
  <h1>PHP - Product Category</h1>
  <pre>
  <?php
  foreach($data as $row){
displayCategory($row);
  }
  ?>
  </pre>
</div>
</body>
</html> 



ผลลัพธ์จากโปรแกรมที่ได้

การทำงานของโค้ด


$sth = $dbh->prepare("SELECT * FROM tb_category");
$sth->execute();
$result = $sth->fetchAll(PDO::FETCH_ASSOC);

ตัวแปร $result จะเก็บค่าเป็นอาร์เรย์ ที่ได้จาก tb_category

foreach($result as $row){
if($row['ref_parent_id'] > 0){
$sub_data[$row['ref_parent_id']][$row['id']] = $row;
}else{
$data[$row['id']] = $row;
}
}
โค้ดด้านบนนี้จะจัดรูปแบบข้อมูล เพื่อแยกระหว่าง
รายการหมวดหมู่หลัก = $data
รายการหมวดหมู่ย่อย = $sub_data


foreach($data as $row){
if($sub = findSubCategory($row['id'], $sub_data)){
$data[$row['id']]['sub'] = $sub;
}
}

การเรียกฟังก์ชั่น findSubCategory() เป็นการนำหมวดหมู่ย่อยใส่ลงไปในอาร์เรย์ sub ของ $data (หมวดหม่หลัก) แต่ละตัว


  foreach($data as $row){
displayCategory($row);
  }

ในส่วนของ <body> จะนำโค้ดด้านบนนี้ไปวางในตำแหน่งที่ต้องการแสดงผลหมวดหมู่ทั้งหมด โดยฟังก์ชั่น displayCategory() จะแสดงผลทั้งหมด

echo '<br/>' . str_repeat("&nbsp;",$sub*4) . '- ' . $data['name'];

ส่วนที่ต้องปรับแต่เพิ่มเติมคือการแสดงผลเมนูให้สวยงามอาจจะจัดรูปแบบด้วยแท็ก <li> หรือหา UI สวยๆจาก Bootstrap snip



[ โค้ด SQL สำหรับสร้างตารางในฐานข้อมูล ]



CREATE TABLE IF NOT EXISTS `tb_category` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `ref_parent_id` int(11) DEFAULT '0' COMMENT 'อ้างอิง id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8;


INSERT IGNORE INTO `tb_category` (`id`, `name`, `ref_parent_id`) VALUES
(1, 'วัสดุอุปกรณ์สำนักงาน', 0),
(2, 'วัสดุอุุปกรณ์ทำสวน', 0),
(3, 'วัสดุอุปกรณ์ไฟฟ้า', 0),
(4, 'วัสดุุอุปกรณ์ในครัว', 0),
(5, 'กรรไกร', 1),
(6, 'กระดาษ', 1),
(7, 'แม็ก', 1),
(8, 'จอบ', 2),
(9, 'มีดถาง', 2),
(10, 'สายไฟ', 3),
(11, 'กรรไกร 2 นิ้ว', 5),
(12, 'กรรไกร 4 นิ้ว', 5),
(13, 'กรรไกร 5 นิ้ว', 5),
(14, 'กระดาษ A3', 6),
(15, 'กระดาษ A4', 6),
(16, 'แม็กเบอร์ 4', 7),
(17, 'จอบด้ามไม้', 8),
(18, 'จอบด้ามเหล็ก', 8),
(19, 'มีปลายงอ', 9),
(20, 'มีดปลายแหลม', 9),
(21, 'เบอร์ 4/1', 16),
(22, 'เบอร์ 4/2', 16),
(23, 'ธรรมดา', 21),
(24, 'พิเศษ', 21);



แหล่งอ้างอิง

ความสัมพันธ์ของฐานข้อมูล
http://www.tbacud.ac.th/caidb/home/unit11.html

PHP Data Objects
http://php.net/manual/en/book.pdo.php

Bootstrap UI สวยๆ
https://bootsnipp.com/tags


PHP CI MANIA PHP Code Generator 
โปรแกรมช่วยสร้างโค้ด ลดเวลาการเขียนโปรแกรม เขียนโปรแกรมง่ายและสะดวกขึ้น
สนใจสั่งซื้อราคาสุดคุ้ม >> http://fastcoding.phpcodemania.com/

เว็บ StackOverFlow ใช้งานยังไง จะเอาโค้ดมาใช้งานจากส่วนไหน

โดย SONGCHAI SAETERN

StackOverFlow เพื่อนคู่คิดนักเขียนโปรแกรม



หลังจากที่เรานำข้อความที่ต้องการไปค้นหาใน Google จะได้ผลลัพธ์ดังรูปด้านล่างนี้
ผลลัพธ์การค้นหา

ให้เลือกบทความจาก StackOverFlow.Com ซึ่งเป็นแหล่งรวมคำตอบทุกคำถามของโปรแกรมเมอร์ทั่วโลกได้พึ่งพาอาศัยกันมาอย่างยาวนาน เมื่อคลิกเข้าไปจะมีจุดสังเกต 4 จุดดังนี้







1. สังเกตคะแนนโหวตของคำถาม ซึ่งยิ่งมากยิ่งดี ถ้าเป็น 0 กด Back กลับไปหาเข้าหน้าบทความอื่นได้เลย


2. จำนวนคำตอบ Answers อันนี้ช่วยให้เรามีตัวเลือกที่หลากหลายขึ้น
3. จำนวนคะแนนโหวตของแต่ละคำตอบ ช่วยให้เราได้โค้ดที่เวิร์กที่สุด ณ ตอนนั้น
4. ส่วนสุดท้าย เครื่องหมายเช็คถูกสีเขียว นั่นหมายถึงเจ้าของคำถามได้รับคำตอบที่พึงพอใจ

เมื่อได้โค้ดที่ตรงกับที่ต้องการ หรือใกล้เคียงกับที่กำลังตามหาก็จัดการคัดลอก แล้วแก้ไขให้ตรงกับหน้าโปรแกรมของเราก็เป็นอันใช้ได้ครับ


ขอให้สนุกกับการเขียนโปรแกรม PHP โดยมีเว็บไซต์ StackOverFlow.Com เคียงข้างกันตลอดเวลา


บทความแนะนำ
PHP กับจัดการฐานข้อมูลหมวดหมู่สินค้า MySQL แบบใช้ตารางเพียงตารางเดียวเท่านั้น



PHP CI MANIA PHP Code Generator 
โปรแกรมช่วยสร้างโค้ด ลดเวลาการเขียนโปรแกรม เขียนโปรแกรมง่ายและสะดวกขึ้น
สนใจสั่งซื้อราคาสุดคุ้ม >> http://fastcoding.phpcodemania.com/

การเขียนโปรแกรม PHP ดึงข้อมูล VAT จาก Web Service ของกรมสรรพากร ด้วย NuSoap

โดย SONGCHAI SAETERN

ตัวอย่างนี้จะเป็นการเขียนโปรแกรม PHP เพื่อตรวจสอบสถานะการจด VAT หรือ ตรวจสอบข้อมูลผู้ประกอบการ โดยเขียนเชื่อมต่อกับ Web Service ของกรมสรรพากร ซึ่งปกติแล้วเราสามารถตรวจสอบได้โดยตรงจากหน้า http://www.rd.go.th/publish/313.0.html 



 


ผลลัพธ์ที่เราใช้งานจะอยู่ในส่วนของ

echo 'VAT => TRUE'; 




และ

echo 'VAT => FALSE';



แต่ถ้าหากต้องการนำข้อมูลที่ได้ไปใช้จะอยู่ในตัวแปร $result ที่เก็บค่าแบบหลายมิติ
ดังที่ได้แสดงด้วยคำสั่ง

print_r($result);

หากต้องการข้อมูลในส่วนไหนก็ให้นำไปใช้งานโดยอ้างตามคีย์นั้นๆ เช่น


$result['ServiceResult']['vName']['anyType']  

จะได้ชื่อของผู้ประกอบการมานั่นเอง


ค้นหาเลขผู้เสียภาษีเพื่อทดสอบการทำงานที่เว็บไซต์ http://www.rd.go.th/publish/313.0.html




มาเริ่มเขียนโปรแกรมกันเลย

<html>
<head>
<title>WebService VAT</title>
</head>
<body>
  <?php
include("nusoap/nusoap.php");
$wsdl = 'https://rdws.rd.go.th/serviceRD3/vatserviceRD3.asmx?wsdl';
header('Content-Type: text/html; charset=utf-8');
        $soapclient = new nusoap_client($wsdl, true);
        $soapclient->soap_defencoding = 'UTF-8';
        $soapclient->decode_utf8 = false;
        $err = $soapclient->getError();
        if ($err) {
            echo '<h2>Constructor error</h2><pre>' . $err . '</pre>';
        }
$tax_id = isset($_REQUEST['tax_id']) ? $_REQUEST['tax_id'] : '';
        $var_name = array(
                            'username' => 'anonymous',
                            'password' => 'anonymous',
                            'TIN' => $tax_id,
'skip' => 0
        );
        $result = $soapclient->call('Service', $var_name);
if(!empty($result)){
// Check for a fault
if ($soapclient->fault) {
echo '<h2>Fault</h2><pre>';
print_r($result);
echo '</pre>';
} else {
// Check for errors
$err = $soapclient->getError();
if ($err) {
// Display the error
echo '<h2>Error</h2><pre>' . $err . '</pre>';
} else {
$data = $result['ServiceResult'];
if(isset($data['vName']['anyType']) && $data['vName']['anyType'] != ''){
echo 'VAT => TRUE';
}else{
echo 'VAT => FALSE';
}
}
}
}else{
echo 'VAT => FALSE';
}
// Display the result
echo '<hr/><h2>Result</h2><pre>';
print_r($result);
echo '</pre>';
  ?>
</body>
</html>

เว็บไซต์ที่เกี่ยวข้อง

Web service กรมสรรพากร
http://www.rd.go.th/publish/42535.0.html
http://www.rd.go.th/publish/313.0.html

NuSOAP for PHP 5.4 - 7.1
https://github.com/econea/nusoap

สร้างและเรียกเว็บเซอร์วิส ด้วย PHP
http://www.thaicreate.com/community/php-create-and-call-web-service.html



PHP CI MANIA PHP Code Generator 
โปรแกรมช่วยสร้างโค้ด ลดเวลาการเขียนโปรแกรม เขียนโปรแกรมง่ายและสะดวกขึ้น
สนใจสั่งซื้อราคาสุดคุ้ม >> http://fastcoding.phpcodemania.com/