PHPEXCEL getCalculatedValue or getFormattedValue returns fatal error

โดย SONGCHAI SAETERN

PHPEXCEL กับปัญหาเรียกใช้งาน rangeToArray() ที่มีการผสานเซลล์ไม่ได้

ก่อนหน้านี้ใช้ PHP 5.5 ก็ไม่เคยมีปัญหาเรื่องเซลล์ที่ผสานกัน
จนลองอัพเกรด PHP เป็น 7.1 ก็พบว่า ฟังก์ชั่น rangeToArray() ใช้ได้เฉพาะแถวที่ไม่ผสานเท่านั้น

$rowTotalCheck = $sheet->rangeToArray ( 'A3:' . $highestColumn . '3', NULL, TRUE, FALSE );
$col = $rowTotalCheck[0];
$total_check = $col[6];
$total_dif     = $col[7];

 ก็เลยต้องเปลี่ยนไปใช้ getCell('G3')->getValue();
$total_check = $sheet->getCell('G3')->getValue();
$total_dif= $sheet->getCell('I3')->getValue();

แต่ปรากฏว่า I3 เป็นสูตร SUM() ก็ได้ออกมาเป็นข้อความเฉยๆ

ก็เลยต้องหากันต่อไปจนได้ฟังก์ชั่น getCell('I3')->getOldCalculatedValue()

$total_dif     = $sheet->getCell('I3')->getOldCalculatedValue();


เมื่อลองรันดูก็พบว่าผ่านฉลุย

อ้างอิง
https://stackoverflow.com/questions/29966752/phpexcel-getcalculatedvalue-or-getformattedvalue-returns-fatal-error

อ่านเรื่องราวการอัพเดตเพิ่มเติมได้ที่

CodeIgniter 3 กับวิธีแก้ปัญหา PHP 7.1 ยกเลิกฟัง์กชั่น mcrypt_encrypt is deprecated

โดย SONGCHAI SAETERN

Deprecated function !!
mcrypt_encrypt(), mcrypt_decrypt(), mcrypt_create_iv()

สำหรับการแก้ไขก็คือ ใช้ฟังก์ชั่น openssl_encrypt() และ openssl_decrypt() แทนที่ฟังก์เดิม

สร้างไฟล์ MY_Encrypt.php ไว้ที่ไดเรกทอรี่ application/libraries/MY_Encrypt.php

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

/**
 * Override for solve deprecated function
 * mcrypt_encrypt(), mcrypt_decrypt(), mcrypt_create_iv()
 */
class MY_Encrypt extends CI_Encrypt
{
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Encrypt using Mcrypt
     *
     * @param    string
     * @param    string
     * @return    string
     */
    public function mcrypt_encode($data, $key)
    {
        $init_size = openssl_cipher_iv_length($cipher="AES-128-CBC");
        $init_vect = openssl_random_pseudo_bytes($init_size);
        return $this->_add_cipher_noise($init_vect . openssl_encrypt($data, $cipher, $key, $options=OPENSSL_RAW_DATA, $init_vect), $key);
    }

    // --------------------------------------------------------------------

    /**
     * Decrypt using Mcrypt
     *
     * @param    string
     * @param    string
     * @return    string
     */
    public function mcrypt_decode($data, $key)
    {
        $data = $this->_remove_cipher_noise($data, $key);
      
        $init_size = openssl_cipher_iv_length($cipher="AES-128-CBC");
      
        if ($init_size > self::strlen($data))
        {
            return FALSE;
        }

        $init_vect = self::substr($data, 0, $init_size);
        $data      = self::substr($data, $init_size);

        return rtrim(openssl_decrypt($data, $cipher, $key, $options=OPENSSL_RAW_DATA, $init_vect), "\0");
    }
  
    /**
     * Byte-safe strlen()
     *
     * @param    string    $str
     * @return    int
     */
    protected static function strlen($str)
    {
        return defined('MB_OVERLOAD_STRING')
            ? mb_strlen($str, '8bit')
            : strlen($str);
    }

    // --------------------------------------------------------------------

    /**
     * Byte-safe substr()
     *
     * @param    string    $str
     * @param    int    $start
     * @param    int    $length
     * @return    string
     */
    protected static function substr($str, $start, $length = NULL)
    {
        if (defined('MB_OVERLOAD_STRING'))
        {
            // mb_substr($str, $start, null, '8bit') returns an empty
            // string on PHP 5.3
            isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
            return mb_substr($str, $start, $length, '8bit');
        }

        return isset($length)
            ? substr($str, $start, $length)
            : substr($str, $start);
    }
}

อ่านเรื่องราวการอัพเดตเพิ่มเติมได้ที่

jQuery กับวิธีกำหนด checked ให้กับ checkbox ตามค่าที่บันทึกในฐานข้อมูล

โดย SONGCHAI SAETERN

jQuery selector by Attrubute matching value



จากภาพด้านบนนั้น คือการเลือกตัวเลือกตามข้อมูลที่อยู่ในฐานข้อมูล เช่น

ฟิลด์ level มีค่า 2

เมื่อนำมาใส่ในแอตทริบิวต์ data-record-value ของตัวเลือกทุกตัวเรียบร้อย เราสามารถตรวจสอบ checkbox ตัวที่มีค่า value ตรงกับในฐานข้อมูลที่ดึงออกมา หรือตรงกับแอตทริบิวต์ที่สร้างขึ้นมาด้วย jQuery ดังนี้

            $( "input[type='checkbox']" ).prop( "checked", function( i, val ) {
                return $(this).val() == $(this).data('record-value');
            });


โค้ดฉบับเต็มสำหรับนำไปทดสอบกันดูครับ ลองเปลี่ยนค่าใน $row['level'] จาก 2 เป็น 1 และ 3 ตามลำดับดูนะครับ

<?php
$row['level'] = 2;
?>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  </head>
<body>
 
    <form action="/action_page.php">
        <input type="checkbox" name="my_level" value="1" data-record-value="<?php echo $row['level'];?>"> พนักงานทั่วไป<br>
        <input type="checkbox" name="my_level" value="2" data-record-value="<?php echo $row['level'];?>"> ผู้จัดการ<br>
        <input type="checkbox" name="my_level" value="3" data-record-value="<?php echo $row['level'];?>"> ผู้ดูแลระบบ<br>
        <input type="button" value="Test" id="btnTest">
    </form>
    <div id="test">
    </div>
    <script>
    $('#btnTest').click(function(){
    $('#test').html('');
            $( "input[type='checkbox']" ).prop( "checked", function( i, val ) {
                $('#test').append('<br/>' + $(this).val() + ' = ' + $(this).data('record-value'));
                return $(this).val() == $(this).data('record-value');
            });
    });
    </script>
</body>
</html>

ทิ้งท้ายกันสักนิดนึง

โดยปกติแล้วเราอาจจะเคยเขียนแบบระบุ id หรือ element เฉพาะตัวกับค่าในฐานข้อมูลเพื่อ checked ยังตัวเลือกที่ต้องการได้เลย โดยไม่ต้องสร้างแอตทริบิวต์เพิ่มไปใน checkbox แต่ละตัว

เช่น

var level = <?php echo $row['level'];?>;
$("input[name=my_level][value=" + value + "]").prop('checked', true);

จะเป็นการสั่ง checked ที่ input ชื่อว่า my_level (ซึ่งมีทั้งหมด 3 ตัว) และเช็คถูกเฉพาะตัวที่มี value  = 2 แค่เพียงตัวเดียวเท่านั้น ซึ่งก็สะดวกดี


แต่!! เมื่อผมลองเขียน CodeIgniter แบบใช้ Template Parser Class 

การจะแทรกสคริปต์ท้าย View จึงเป็นสิ่งที่ผมหลีกเลี่ยง เพราะจะแยกไฟล์ .js ไปไว้ต่างหาก และสั่ง load เข้าไปในขั้นตอนเดียวกันกับการ load ไฟล์ View คือจะทำใน Controller นั่นเอง ดังนั้นในไฟล์ .js จะไม่มีการ echo ค่าใดๆ หรือไม่มีการแทรกคำสั่ง PHP ได้เลย

ดังนั้น

การสร้างแอตทริบิวต์ data-record-value หรือแอตทริบิวต์ใดๆก้ตามเพื่อระบุค่าจากฐานข้อมูลไว้ และใช้คำสั่งเปรียบเทียบค่า value ของ checkbox, radio button ทุกตัวที่ตรงกัน จะสามารถสร้าง checked พร้อมกันทั้งหมดในครั้งเดียว

$( "input[type='checkbox']" ).prop( "checked", function( i, val ) {
    return $(this).val() == $(this).data('record-value')
});

ยังไงก็ลองเอาไปเล่นดูนะครับ เผื่อจะใช้แล้วถูกใจ


---------------------------
จากหนึ่งในข้อจำกัดของโปรเจ็กต์ PHP CI MANIA

PHP ตรวจสอบรูปแบบเวลา HH:MM:SS

โดย SONGCHAI SAETERN

PHP Validate Time Format

<?php
function isTime($time)
{
if(strlen($time) == 5){
return preg_match("#([0-1]{1}[0-9]{1}|[2]{1}[0-3]{1}):[0-5]{1}[0-9]{1}#", $time);
}elseif(strlen($time) == 8){
return preg_match("#([0-1]{1}[0-9]{1}|[2]{1}[0-3]{1}):[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}#", $time);
}
return false;
}
$time1 = '00:12:60';
$time2 = '24:12:14';
$time3 = '15:12:21';
echo "<br>TEST FALSE , isTime('$time1') = " . (isTime($time1) > 0? 'TRUE' : 'FALSE');
echo "<br>TEST FALSE , isTime('$time2') = " . (isTime($time2) > 0? 'TRUE' : 'FALSE');
echo "<br>TEST <b>TRUE</b> , isTime('$time3') = " . (isTime($time3) > 0? '<b>TRUE</b>' : 'FALSE');
?>


ผลลัพธ์



อ้างอิง

http://burnignorance.com/php-programming-tips/how-to-check-time-format-hhmm-in-php/

Google Pie Chart ต่อแขนต่อขา ชี้ข้อมูลแต่ละส่วน จัดป้ายกำกับให้ดูสบายตา

โดย SONGCHAI SAETERN

Google Pie Chart

หลังจากค้นหาอยู่นาน เพราะคิดไม่ออกว่าหลังจากทดสอบโค้ดนี้เสร็จแล้วไปเก็บไว้ไหน พอค้นเจอก็รีบจัดการโพสต์ไว้บนบล็อกซะเลย


<html>
  <head>
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">
      google.charts.load("current", {packages:["corechart"]});
      google.charts.setOnLoadCallback(drawChart);
      function drawChart() {
        var data = google.visualization.arrayToDataTable([
          ['Task', 'Hours per Day'],
          ['Work',     8],
          ['Eat',      2],
          ['Journey',  2],
          ['Watch TV', 2],
          ['Coding',   4],
          ['Sleep',    6],
        ]);

        var options = {
          title: 'My Daily Activities',
          legend: { position: 'top' }
        };

        var chart = new google.visualization.PieChart(document.getElementById('piechart_3d'));
        chart.draw(data, options);
      
        var options = {
          is3D: true,
          pieSliceText: 'value-and-percentage',
          legend: { position: 'labeled' },
          backgroundColor: 'transparent'
        };
        var total = 0;
        for (var i = 0; i < data.getNumberOfRows(); i++) {
                total = total + data.getValue(i, 1);  
        }

        for (var i = 0; i < data.getNumberOfRows(); i++) {
            var label = data.getValue(i, 0);
            var val = data.getValue(i, 1) ;
            data.setFormattedValue(i, 0, label  + ' ('+val +')');  
        }
  
        var chart = new google.visualization.PieChart(document.getElementById('piechart_3d2'));
        chart.draw(data, options);
      }
    </script>
  </head>
  <body>
    <div id="piechart_3d" style="width: 900px; height: 500px;"></div>
    <div style="width: 900px; height: 400px; position: absolute;top:90px; background-color : white"></div>
    <div id="piechart_3d2" style="width: 900px; height: 500px; position: absolute;top:10px"></div>
  </body>
</html>

จากโค้ดด้านบนนี้ เมื่อนำไปรัน ก็จะได้กราฟวงกลม ที่มีเส้นชี้แสดงตัวเลขของแต่ละส่วนออกมาด้านนอก แล้วก็ส่วนของ ป้ายกำกับ ที่จัดใหม่ให้ดูสบายตาขึ้น ผลลัพธ์ที่ได้เป็นดังภาพ




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

คิดอย่างไรให้ประสบความสำเร็จ

โดย SONGCHAI SAETERN

คิดอย่างไรให้ประสบความสำเร็จ



“ความคิด” คือ “เครื่องมือ”

คนที่ใช้มันเป็นจะมีความสุขตลอดไป

แต่คนที่ถูกมันใช้จะตกนรกทั้งเป็น


-ขุนเขา สินธุเสน เขจรบุตร-

PHP หลักการสร้าง Auto Number เลขเอกสาร เลขอ้างอิง ต่างๆ

โดย SONGCHAI SAETERN

1. กำหนดรูปแบบ  เช่น YYMMnnnn , "BK" + YYMMnnnn
2. ทดลอง INSERT ในตารางสร้างรหัส ถ้าสร้างได้ใช้เลย ถ้าซ้ำ Generate ใหม่
3. ซ้ำเกินจำนวนที่กำหนด ให้หยุดก่อน ป้องกัน over flow
4. นำโค้ดที่ได้ ไปใช้จริง

#phpCiMania "เพิ่มเวลาเรียนรู้ ลดเวลาเขียนโค้ด"

[PHP] มาปรับแต่ง Template Parser Class ใน CodeIgniter Framework กันเถอะ

โดย SONGCHAI SAETERN

PHP : CodeIgniter Framework - Template Parser Class



การทำงานปกติของ Template Parser Class ใน CodeIgniter Framework มีดังนี้


สำหรับผลลัพธ์เมื่อสั่ง
$this->parser->parse('test_parser_view', $data);

ก็จะเกิดการแทรกข้อมูลในอาร์เรย์ $data ลงไปยังไฟล์ View ที่ระบุ และแสดงผลแบบ Loop แถวของตารางตามจำนวนอาร์เรย์ blog_entries และ data_list โดยอัตโนมัติ


จะเห็นว่าการใช้ Template Engine ก็ช่วยให้การเขียนโค้ดง่ายขึ้นได้เช่นกัน (แต่ก็จะต้องจัดรูปแบบข้อมูลให้เรียบร้อยใน Controller ก่อนจะนำมาแทรกลงใน Template ซึ่งใน PHP CI MANIA จะมีการสร้างฟังก์ชั่นเหล่านี้โดยอัตโนมัติ เช่น จัดรูปแบบวันที่ภาษาไทย จัดรูปแบบตัวเลข เป็นต้น)


ทุกอย่างก็ดูสะดวกสบายใช้ง่ายดีนี่นา แล้วทำไมถึงอยากจะปรับแต่งฟังก์ชั่นให้กับ Template Parser Class ด้วยล่ะ??

ซึ่งถ้าได้ติดตามบล็อกของผมมาโดยตลอด จะเห็นว่าก่อนหน้านี้ก็มีการปรับแต่งไปแล้วครั้งหนึ่งในบทความ PHP Codeigniter กับการใช้งาน Template Parser Class แล้วไม่สามารถเรียกใช้ตัวแปรด้านนอก Variable Pairs ที่กำหนดไว้ได้  ซึ่งมีปัญหาว่าในลูปไม่สามารถดึงค่าตัวแปร url ของหน้าเว็บจากอารเรย์ชั้นข้างนอกได้



หลังจากปรับแต่งก็ใช้วิธีใส่วงเล็บปีกกาซ้อนกัน 2 ชั้นเพื่อเรียกใช้ตัวแปรด้านนอกแทน

มาที่เรื่องของบทความนี้กันต่อ

เนื่องจากในโปรแกรม PHP CI MANIA ที่ผมกำลังพัฒนาอยู่นั้น จะมีส่วนของการแสดงตัวอย่างของ VIEW แต่ละตัวในขั้นตอนสร้างโค้ดได้ด้วย จึงทำให้เกิดบทความนี้ขึ้นมา


จะเห็นว่าเวลาในโหมด Preview เพื่อดูไฟล์ View ในตัวอย่างโค้ดด้านบนนั้น จะมีข้อความส่วนที่ผมไม่อยากให้แสดงออกมาโผล่ออกมาด้วย


ซึ่งในส่วนของการรับค่าอาร์เรย์ data_list ยังพอทำใจได้เพราะมันครอบ <li> เอาไว้
แต่ในส่วนของตาราง <table> จะมีวงเล็บ {blog_entries} ครอบแถวเอาไว้ พอมันครอบ <tr> แต่มันไม่ใช่แท็ก HTML ของ <table> มันก็เลยถูกเขี่ยกระเด็นออกไปด้านบนซะงั้น

นอกจากจะรกเกะกะสายตาแล้ว ยังทำให้ตำแหน่งตารางผิดไปจากตำแหน่งจริงอีกด้วย



และในภาพด้านบนนี้ ก็คือตัวอย่างการแสดงผลในโหมด Preview ที่กระผมต้องการนั่นเอง จะเห็นว่าส่วนของบล็อกที่ใช้รับค่า {data_list} และ {blog_entries} ได้หายไปแล้ว เพราะผมได้เอาไปซ่อนไว้ใน Attribute ของอีเลเมนต์ที่ต้องการวนลูปไปแล้วนั่นเอง


จากภาพด้านบนนี้ ผมได้สร้างแอตทริบิวต์สำหรับรับค่าอาร์เรย์จาก Controller ไว้ชื่อว่า parser-repeat ซึ่งจะมีด้วยกัน 2 จุด คือ
1) <li parser-repeat="data_list">{title}</li>
    จะสร้างแท็ก <li>  ที่นำข้อมูลจากอาร์เรย์ title ใน data_list มาแสดง จนครบทุกตัว
2) <tr parser-repeat="blog_entries">
    จะสร้างแท็ก <tr> ที่นำข้อมูลจากอาร์เรย์ title และ body ใน blog_entries มาแสดง จนครบทุกตัว



และเมื่อใช้คำสั่ง
$this->parser->parse_repeat('test_parser_repeat_view', $data);

ก็จะเรียกฟังก์ชั่นใหม่ที่ผมได้เพิ่มเข้าไป นั่นคือ parse_repeat() ซึ่งจะทำหน้าที่ค้นหา HTML ทุกตัวที่มี Attribute กำหนดไว้ว่า parser-repeat="{ชื่อ Key ของอาร์เรย์}" มาแทนที่ด้วยข้อมูลอาร์เรย์ในคีย์ชุดนั้นๆ




สำหรับซอร์สโค้ดหลังปรับแต่งเรียบร้อยมีดังนี้

application/libraries/MY_parser.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * Overrides the CI Template Parser to allow for multiple occurrences of the
 * same variable pair
 *
 */
class MY_Parser extends CI_Parser {
/**
* Parse a template
*
* Parses pseudo-variables contained in the specified template view,
* replacing them with the data in the second param
*
* @param string
* @param array
* @param bool
* @return string
*/
public function parse($template, $data, $return = FALSE)
{
$template = $this->CI->load->view($template, $data, TRUE);
$template = $this->_parse_double($template, $data); return $this->_parse($template, $data, $return);
}
public function parse_repeat($template, $data, $return = FALSE)
{
$template = $this->CI->load->view($template, $data, TRUE);
$template = $this->_parse_double($template, $data);
$template = $this->_parse_attribute($template, $data, $return);
return $this->_parse($template, $data, $return);
}
/**
* Parse a double key/value
*
* @param string
* @param array
* @return string
*/
protected function _parse_double($template, $data)
{
$replace = array();
preg_match_all("/\{\{(.*?)\}\}/si", $template, $matches);
foreach ($matches[1] as $match)
{
$key = '{{'.$match.'}}';
$replace[$key] = isset($data[$match]) ? $data[$match] : $key;
}
unset($data);
return strtr($template, $replace);
}
/**
* Parse list with attribute
*
* @param string
* @param array
* @return string
*/

protected function _parse_attribute($template, $data, $return = FALSE)
{
if ($template === '')
{
return FALSE;
}
$replace = array();
foreach ($data as $key => $val)
{
$replace = array_merge(
$replace,
is_array($val)
? $this->_parse_attribute_pair($key, $val, $template)
: $this->_parse_single($key, (string) $val, $template)
);
}
unset($data);
$template = strtr($template, $replace);
/* return only
if ($return === FALSE)
{
$this->CI->output->append_output($template);
}
*/
return $template;
}
protected function _parse_attribute_pair( $attr_value, $data, $string)
{
$replace = array();
$matches = $this->get_tag('parser-repeat', $attr_value, $string);
foreach ($matches as $match)
//if(!empty($matches))
{
$str = '';
foreach ($data as $row)
{
$temp = array();
foreach ($row as $key => $val)
{
if (is_array($val))
{
$pair = $this->_parse_attribute_pair($key, $val, $match[0]);
if ( ! empty($pair))
{
$temp = array_merge($temp, $pair);
}
continue;
}
$temp[$this->l_delim.$key.$this->r_delim] = $val;
}
$temp[' parser-repeat="'.$attr_value.'"'] = '';//Clear repeat attribute
$str .= strtr($match[0], $temp);
}
$replace[$match[0]] = $str;
}
return $replace;
}
private function get_tag( $attr, $value, $xml, $tag=null ) {
if( is_null($tag) ){
$tag = '\w+';
}else{
$tag = preg_quote($tag);
}
$attr = preg_quote($attr);
$value = preg_quote($value);
$tag_regex = "/<(".$tag.")[^>]*$attr\s*=\s*".
"(['\"])$value\\2[^>]*>(.*?)<\/\\1>/s";
preg_match_all($tag_regex,
$xml,
$matches,
PREG_PATTERN_ORDER);

$target = array();
if(!empty($matches)){
$match1 = isset($matches[0][0]) ? $matches[0][0] : '';
$match2 = isset($matches[3][0]) ? $matches[3][0] : '';
if($match1 != ''){
$target = array(array($match1, $match2));
}
}
return $target;
}
}
// END Parser Class
/* End of file MY_Parser.php */
/* Location: ./application/libraries/MY_Parser.php */


ไฟล์ Controller สำหรับทดสอบ
application/controllers/Template_engine.php
<?php
if (!defined('BASEPATH'))
    exit('No direct script access allowed');
class Template_engine extends CI_Controller
{
    public function __construct()
    {
        parent::__construct();
}
    public function index()
    {
    $this->parser->parse('labs/test_parser_view', array());
}
    public function parser_view()
    {
    $this->parser->parse('labs/test_parser_repeat_view', array());
}
public function test_repeat()
{
$data = array(
'blog_title'   => 'My Blog Title',
'blog_heading' => 'My Blog Heading',
'blog_entries' => array(
array('title' => 'Title 1', 'body' => 'Body 1'),
array('title' => 'Title 2', 'body' => 'Body 2'),
array('title' => 'Title 3', 'body' => 'Body 3'),
array('title' => 'Title 4', 'body' => 'Body 4'),
array('title' => 'Title 5', 'body' => 'Body 5')
),'data_list' => array(
array('title' => 'Title 1', 'body' => 'Body 1'),
array('title' => 'Title 2', 'body' => 'Body 2'),
array('title' => 'Title 3', 'body' => 'Body 3'),
),
);
$this->parser->parse_repeat('labs/test_parser_repeat_view', $data);
}
}
// ---------------------------- END Controller Class --------------------------------


ไฟล์ View สำหรับทดสอบ
1. สำหรับแสดงตัวอย่างแบบเดิม
application/views/test_parser_view.php
<html>
<head>
<title>การแสดงผลแบบ Parser ปกติ</title>
<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.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
    <body>
        <h3>{blog_heading}</h3> <ul>
{data_list}
<li>{title}</li>
{/data_list}
</ul> <table class="table table-bordered" border="1" style="width:500px">
<thead>
<tr bgcolor="#aaaaaa">
<td>Title</td>
<td>body</td>
</tr>
</thead>
<tbody>
{blog_entries}
<tr parser-repeat="blog_entries">
<td>{title}</td>
<td>{body}</td>
</tr>
{/blog_entries}
</tbody>
</table>
    </body>
</html>

2. สำหรับแสดงตัวอย่างแบบใหม่ (แทรกในแท็กของอีเลเมนต์)
application/views/test_parser_repeat_view.php
<html>
<head>
<title>การแสดงผลแบบใช้ Parser Repeat</title>
<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.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
    <body>
        <h3>{blog_heading}</h3>
<ul>
<li parser-repeat="data_list">{title}</li>
</ul>
<table class="table table-bordered" border="1" style="width:500px">
<thead>
<tr bgcolor="#aaaaaa">
<td>Title</td>
<td>body</td>
</tr>
</thead>
<tbody>
<tr parser-repeat="blog_entries">
<td>{title}</td>
<td>{body}</td>
</tr>
</table>
</table>
    </body>
</html>


แหล่งข้อมูลอ้างอิง

CodeIgniter - Template Parser Class
https://www.codeigniter.com/userguide3/libraries/parser.html

15 PHP regular expressions for web developers
https://www.catswhocode.com/blog/15-php-regular-expressions-for-web-developers

PHP โค้ดตัวอย่างการหา UNIQUE KEY จากคำสั่ง SQL CREATE TABLE

โดย SONGCHAI SAETERN

หลังจากที่ได้ทดสอบโปรเจ็กต์ PHP CI MANIA 

มาสักระยะก็พบว่า จะเกิด error message เมื่อมีการบันทึกข้อมูลซ้ำกันในฟิลด์ที่เป็น UNIQUE KEY คือฟิลด์ที่จะไม่ให้ข้อมูลซ้ำกัน เช่น ฟิลด์รหัสสินค้า หรือบาร์โค้ดสินค้า ฟิลด์เก็บ Username ของผู้ใช้งานเป็นต้น

ทีนี้ถ้าเราไม่อยากให้เวลาใช้งานแล้วเด้ง Error Message จากฐานข้อมูลว่า Duplicate entry '' for key 'unique_id' เราก็ทำการตรวจสอบทุกครั้งก่อนบันทึกซะเลยว่าข้อมูลที่เป็น UNIQUE KEY ซ้ำกันไหม แล้วก็ส่ง Alert สวยๆอย่าง SweetAlert.JS.Org ขึ้นมาแสดงแทน


สำหรับวิธีเขียนโค้ดเพื่อตรวจสอบ UNIQUE KEY ของตารางแต่ละตารางนั้น มีตัวอย่างดังต่อไปนี้


PHP Code

<?php
    //string from => SHOW CREATE TABLE tb_members;
    $sql ="CREATE TABLE `tb_members` (
      `userid` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(30) DEFAULT NULL COMMENT 'ชื่อผู้ใช้งาน',
      `password` varchar(50) DEFAULT NULL COMMENT 'รหัสผ่าน',
      `level` int(1) DEFAULT '1' COMMENT 'สิทธิ์การใช้งาน 9=admin',
      `email` varchar(100) DEFAULT NULL COMMENT 'อีเมล',
      `tel_number` varchar(30) DEFAULT NULL COMMENT 'เบอร์โทรศัพท์',
      `line_id` varchar(50) DEFAULT NULL COMMENT 'ไอดี Line',
      `fullname` varchar(50) DEFAULT NULL COMMENT 'ชื่อ',
      `lastname` varchar(50) DEFAULT NULL COMMENT 'นามสกุล',
      `department` varchar(70) DEFAULT NULL COMMENT 'สังกัด',
      `create_date` datetime DEFAULT NULL,
      `modify_date` datetime DEFAULT NULL,
      `Column13` int(11) DEFAULT NULL,
      `Column14` int(11) DEFAULT NULL,
      PRIMARY KEY (`userid`),
      UNIQUE KEY `email` (`email`),
      UNIQUE KEY `username` (`username`),
      UNIQUE KEY `Column13_Column14` (`Column13`,`Column14`)
    ) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8";
    preg_match_all('/UNIQUE KEY `(.*?)`\\)/', $sql, $matches );
    echo '<pre>', print_r($matches), '</pre>';
    echo '<hr/>';
    $field_key = array();
    $all_key = $matches[1];
    foreach($all_key as $string){
        $arr = explode('(', $string);
        $new_string = trim(str_replace("`", "", $arr[1]));
        $field_key[] = explode(',', $new_string);
    }
    echo '<pre>', print_r($field_key), '</pre>';
?>

คำอธิบาย

 SHOW CREATE TABLE tb_members;

เป็นคำสั่งสำหรับแสดง SQL สำหรับสร้างตาราง tb_members ซึ่งจะได้ข้อมูลของฟิลด์ที่เป็น UNIQUE KEY ทั้งหมดออกมาด้วย ซึ่งตัวอย่างนี้ได้คัดลองมาจากคิวรี่เก็บไว้ในตัวแปร $sql โดยตรงเพื่อทดสอบ


preg_match_all('/UNIQUE KEY `(.*?)`\\)/', $sql, $matches ); 
echo '<pre>', print_r($matches), '</pre>';

คำสั่งด้านบนนี้ จะช่วยแยกฟิลด์ที่เป็น UNIQUE KEY ออกมาจากคำสั่ง SQL CREATE
ผลลัพธ์เมื่อลอง print_r() ออกมาดู



foreach($all_key as $string){
$arr = explode('(', $string);
$new_string = trim(str_replace("`", "", $arr[1]));
$field_key[] = explode(',', $new_string);
}

หลังจะได้อาร์เรย์สองชุด คือ [0] กับ [1] เราจะนำอาร์เรย์ชุดที่ 2 นั่นก็คือ $matches[1] ไปทำการวนลูป foreach() เพื่อทำการแยก วงเล็บด้านท้ายออก และนำชื่อฟิลด์จากอาร์เรย์ส่วนท้ายไปใช้ โดยจะตัดเครื่องหมาย ` ออกไปจากชื่อฟิลด์ด้วย จะเหลือแค่เครื่องหมาย , คั่นกลางชื่อฟิลด์ ก็ทำการ explode() อีกครั้งก็จะได้อาร์เรย์ที่เป็นชื่อฟิลด์เก็บไว้ใน $field_key เพื่อใช้งานต่อไป

ใช้คำสั่ง print_r($field_key) เพื่อดูผลลัพธ์ที่ได้

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


SQL keys, MUL vs PRI vs UNI
https://stackoverflow.com/questions/5317889/sql-keys-mul-vs-pri-vs-uni

Starts and Ends with PHP Regex
https://stackoverflow.com/questions/10895260/starts-and-ends-with-php-regex?rq=1

To match or not to match with regular expressions in PHP
https://phpenthusiast.com/blog/php-regular-expressions

Only get text within brackets?!?
https://www.sitepoint.com/community/t/only-get-text-within-brackets/59549



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

JavaScript สุ่ม จับทีมแข่งขันแบบแข่ง 2 ครั้ง กับต่างโรงเรียน (โรงเรียนชื่อเดียวกันไม่ให้คู่กัน)

โดย SONGCHAI SAETERN

โจทย์คือ โรงเรียนชื่อเดียวกันไม่ให้คู่กัน



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

<html>
<head>
<meta charset="UTF-8">
    <title>JavaScript Random</title>
  <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.3.1/jquery.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
    <h2>ตารางการแข่งขัน (หนึ่งคนแข่ง 2 รอบ)</h2>
    <table class="table table-bordered">
        <thead>
          <tr>
            <th>ชื่อโรงเรียน</th>
            <th>ตัวแทนเข้าแข่งขัน</th>
            <th></th>
            <th>ตัวแทนเข้าแข่งขัน</th>
            <th>ชื่อโรงเรียน</th>
            <th>CHECK School</th>
          </tr>
        </thead>
        <tbody id="demo">
       
        </tbody>
    </table>
    <script>
    var lists = [
                    {"name" : "1. นายอุดม", "school" : "โรงเรียนอนุบาลสกลนคร"},
                    {"name" : "2. นายคงฤทธิ์", "school" : "โรงเรียนบ้านท่าเรือ"},
                    {"name" : "3. นายสุวิทย์", "school" : "โรงเรียนบ้านทุ่งโพธิ์"},
                    {"name" : "4. นายพงษ์พันธ์", "school" : "โรงเรียนบ้านท่าเรือ"},
                    {"name" : "5. นายวิสุทธิ์", "school" : "โรงเรียนอนุบาลสกลนคร"},
                    {"name" : "6. นายเกษม", "school" : "โรงเรียนอนุบาลบ้านแพง"},
                    {"name" : "7. นายสว่าง", "school" : "โรงเรียนอนุบาลบ้านแพง"},
                    {"name" : "8. นายบุญมี", "school" : "โรงเรียนอนุบาลศรีวิไล"},
                    {"name" : "9. นายวรนาถ", "school" : "โรงเรียนบ้านทุ่งโพธิ์"},
                    {"name" : "10. นายยุทธนา", "school" : "โรงเรียนอนุบาลศรีวิไล"},
                ];

    var items = lists;
   
    //array_count_values();
    var map_counts = items.reduce(function (p, item) {
        var key = item.school
        p[key] = (p[key] || 0) + 1;
        return p;
    },{});


    var prev_num=0;
    var check_equal_team = true;
    $.each(map_counts, function( index, value ) {
        if(prev_num > 0 && value != prev_num){
            check_equal_team = false;
        }
        prev_num = value;
    });
   
    //sort by Highest Count
    var arr1 = items.sort(function (team1, team2) {
        return map_counts[team1.school] < map_counts[team2.school];
    });
   
    //เปลี่ยนเป็นเช็คว่า โรงเรียนเท่ากัน
    if (check_equal_team == false) {
        alert("การจับคู่แข่งขัน จะต้องเป็นเลขคู่. ข้อมูลที่ได้มีเพียง " + names.length + " รายชื่อ.");
    }else{

        var arr1 = items.slice(); // copy array
        arr1.sort(function() { return 0.5 - Math.random();});// shuffle arrays

        var arr2 = items.slice(); // copy array again
        arr2.sort(function() { return 0.5 - Math.random();});// shuffle arrays
       
        var over_head = arr1.length;
       
        var i, compare_loop = 0;
        var obj1, obj2 = {};
        var new_tr, html, name1, name2, school1, school2 = '';
        while (arr1.length) //จนกว่าจะ pop() หมดทุกตัว
        {
            compare_loop = 0;
            obj2 = {};
            //max is first
            while (arr2.length) //จนกว่าจะ splice() หมดทุกตัว ||  break
            {
                var check = (arr1[0].school == arr2[0].school);
                if(check == false){
                    obj2 = arr2.shift();// get first and shift them
                    break;
                }else{
                    arr2.push(arr2.shift());//push to last
                }
               
                compare_loop++;//ป้องกันกรณีหาคู่ไม่ได้ เนื่องจากมีเศษ
                if(compare_loop > over_head){
                    break;
                }
            }
           
            obj1 = arr1.shift();
           
            name1 = obj1.name;
            name2 = obj2.name;
            school1 = obj1.school;
            school2 = obj2.school;

            html = document.getElementById('demo').innerHTML;
            new_tr = '<tr>';
            new_tr += '<td>'+ school1 +'</td><td>' + name1 + '</td>';
            new_tr += '<td class="text-center"> <=> </td>';
            new_tr += '<td>' + name2 + '</td><td>'+ school2 +'</td>';
            new_tr += '<td>'+ (check == false ? 'ไม่ซ้ำ' : 'ซ้ำ') +'</td>';
            new_tr += '</tr>';
            document.getElementById('demo').innerHTML = html + new_tr;
        }//while
    }//if
    </script>
</body>
</html>

ข้อเสนอแนะ

จากผลลัพธ์ที่ได้มานั้น จะเห็นว่า 1 คนจะแข่งอยู่ด้วยกัน 2 ครั้ง แต่ถ้าหากต้องการให้แข่งครั้งเดียว ก็สามารถทำได้โดยการตัด ชื่อที่จับคู่เรียบร้อยแล้ว ทั้งด้านซ้ายและด้านขวา โดยการค้นหาด้วยชื่อและโรงเรียน จากนั้นก็สั่ง .slice() ออกไปตามตำแหน่งที่พบ

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

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

วิธีแก้ก็จะใช้วิธี จับสลากแล้วตัดคนที่ได้ออกไปจากการสุ่มไปเลย


ที่มา :

php สุ่มจับคู่จากชื่อโรงเรียน โดยโรงเรียนชื่อเดียวกันไม่ให้คู่กัน
http://www.thaicreate.com/php/forum/131941.html

แหล่งอ้างอิงฟังก์ชั่นต่างๆ :

Javascript: randomly pair items from array without repeats
https://stackoverflow.com/questions/21295162/javascript-randomly-pair-items-from-array-without-repeats

array_count_values for JavaScript instead
https://stackoverflow.com/questions/5217136/array-count-values-for-javascript-instead



sort by number of occurrence(count) in Javascript array
https://stackoverflow.com/questions/22010520/sort-by-number-of-occurrencecount-in-javascript-array

Print JSON parsed object?
https://stackoverflow.com/questions/4933217/print-json-parsed-object



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

SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data

โดย SONGCHAI SAETERN

วิธีแก้ปัญหา SyntaxError: JSON.parse: unexpected non-whitespace character

ปัญหาที่เกิดขึ้นคือ เมื่อมีการ submit ผ่าน iframe และให้ PHP คืนค่าเป็น JSON

$json = json_encode(array(
                                    "is_successful" => FALSE,
                                    "message" => $message
));
echo $json;
ผลที่ได้คือ หน้าเว็บค้าง ไม่มีอะไรตอบสนองหรือทำงานต่อจากนั้นเลย และเมื่อเปิด console ขึ้นมาดูก็พบข้อความแจ้ง error ดังนี้

SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data at line 1 column 921 of the JSON data
ทั้งที่ค่าคืนกลับมาใน iframe ก็ปกติดี



โค้ดในส่วนของการดึงข้อมูลใน iframe หลังจาก Submit เรียบร้อยมีดังนี้
var c = 0;
$("#post_iframe").on('load',function() {
c++;
if(c==1){
iframeContents = this.contentWindow.document.body.innerHTML;
var json_string = iframeContents.toString();
if(json_string != ""){
var results = jQuery.parseJSON( json_string ); if(results.is_successful){
//SUCCESS
}else{
//FALSE
} }else{
alert('การดำเนินการล้มเหลว กรุณาลองใหม่อีกครั้ง');
}
}
});

และจุดที่เกิด error ก็คือ

var results = jQuery.parseJSON( json_string );

ก็ลองค้นจนเจอกระทู้หนึ่งแนะนำว่าให้แปลงโค้ดก่อน
var decoded = $("<div/>").html(encodedStr).text();

ก็จะได้โค้ดดังนี้
var c = 0;
$("#post_iframe").on('load',function() {
c++;
if(c==1){
iframeContents = this.contentWindow.document.body.innerHTML;
var json_string = iframeContents.toString();
if(json_string != ""){
var json_string = $("<div/>").html(json_string).text();
var results = jQuery.parseJSON( json_string ); if(results.is_successful){
//SUCCESS
}else{
//FALSE
} }else{
alert('การดำเนินการล้มเหลว กรุณาลองใหม่อีกครั้ง');
}
}
});

เป็นอันเสร็จพิธี!!


แต่เดี๋ยวก่อน ถ้าท่านยังเจอปัญหานี้อยู่
JSON.parse: expected property name or '}'

ปัญหาจะเกี่ยวกับ Double / Single Quotes ให้ลองแก้ตามนี้ดู



Change:JSON.parse("{'wrongQuotes': 5}")


To:

JSON.parse('{"rightQuotes": 5}')





อ้างอิง

How to decode HTML entities using jQuery?
https://stackoverflow.com/questions/1147359/how-to-decode-html-entities-using-jquery

JSON.parse: expected property name or '}'
https://stackoverflow.com/questions/8013582/json-parse-expected-property-name-or

PHP CI MANIA : PHP CodeIgniter Generator โปรแกรมช่วยสร้างโค้ด ลดเวลาการเขียนโปรแกรม

โดย SONGCHAI SAETERN


ราคาแต่ละเวอร์ชั่น   (ราคา 1,500 .- / 2,990.- / 3,990 .-)

       จะเริ่มที่เวอร์ชั่น 0.1 ถึง 0.9 โดยความสามารถเริ่มต้นคือ
       1) หน้าแสดงรายการทั้งหมด จะสามารถค้นหา และแบ่งหน้าได้อัตโนมัติ
       2) หน้าเพิ่มข้อมูล จะสามารถกำหนดจำนวน INPUT ที่ต้องการป้อนข้อมูลได้
       3) หน้าแสดงรายละเอียดเรคอร์ด สามารถคลิกเลือกรายการที่ต้องการดูข้อมูลทั้งหมดได้
       4) หน้าแก้ไขข้อมูล สามารถคลิกแก้ไขข้อมูลในแต่ละเรคอร์ดจากหน้าแสดงรายการได้
       5) การลบข้อมูล สามารถคลิกลบรายการในแต่ละเรคอร์ดได้
       6) มีระบบ Login และบริหารจัดการข้อมูลสมาชิกได้

       นอกจากนี้ระบบยังรองรับส่วนของการ JOIN TABLE อื่นๆได้อีกด้วย เช่น ฟิลด์ที่เก็บรหัสสมาชิก จะสามารถ JOIN ไปยังตารางสมาชิก เพื่อดึงชื่อมาแสดงผลได้ด้วย



       และความสามารถอีกอย่างที่จำเป็นอย่างยิ่งนั่นก็คือ รองรับการเชื่อมโยงกับตารางรายการ หรือการสร้างโค้ดสำหรับโครงสร้างฐานข้อมูลแบบ Master & Detail ได้อีกด้วย

       ในระหว่างนี้จะมีการปล่อย Patch ให้อัพเดตเรื่อยๆ จนกว่าจะเสถียร (ในระหว่างที่ปล่อยเวอรชั่นใหม่จะมีการปล่อย Patch ย่อยให้แต่ละเวอร์ชั่นเพื่อแก้บั๊กและปรับปรุงให้ใช้งานง่ายขึ้น ซึ่งจะไม่มีค่าใช้จ่ายในการอัพ Patch ไม่ว่าจะแก้บั๊กหรือฟีเจอร์ใหม่ก็ตาม) และแนวทางพัฒนาในเวอร์ชั่นต่อๆไปมีดังนี้


สั่งซื้อได้แล้ววันนี้ที่เว็บไซต์
http://fastcoding.phpcodemania.com