Category Archive jQuery

jQuery: Ajax Cache

ต้องการเก็บข้อมูลส่วนที่ใช้บ่อยๆ ไว้ในฝั่ง user (browser) นั่นละ จะได้ลดการ query แต่ข้อมูลมันมีการเปลี่ยนแปลงด้วย ที่เก็บข้อมูลระยะยาวได้ในฝั่ง browser กลับออกแบบให้ localStorage เก็บข้อมูลถาวรโดยไม่มีการ expires เหมือน cookie ทำให้ต้องเขียน code จัดการเพิ่ม

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>Ajax: localStorage</title>
</head>

<body>
    <h1>localStorage</h1>
    <div class="col-sm-12" id="datasA"></div>
    <div class="col-sm-12" id="resultA"></div>
    <script src="../vendor/components/jquery/jquery.min.js"></script>
    <script>
        $(function() {

            function getDatas() {
                let cacheKey = 'memories';

                if (cacheKey in localStorage) {
                    let datas = JSON.parse(localStorage.getItem(cacheKey));

                    // if expired
                    if (datas['expires'] < Date.now()) {
                        localStorage.removeItem(cacheKey);

                        getDatas()
                    } else {
                        setDatas(datas);
                    }
                } else {
                    $.ajax({
                        "dataType": "json",
                        "success": function(datas, textStatus, jqXHR) {
                            let today = new Date();

                            datas['expires'] = today.setDate(today.getDate() + 7) // expires in next 7 days

                            setDatas(datas);

                            localStorage.setItem(cacheKey, JSON.stringify(datas));
                        },
                        "url": "http://localhost/phunsanit/snippets/PHP/json.json_encode.php",
                    });
                }
            }

            function setDatas(datas) {
                // display json as text
                $('#datasA').text(JSON.stringify(datas));

                // your code here
                let html = '';
                $.each(datas.datas, function(index, value) {
                    html += '<br>' + index + ' = ' + value;
                });
                $('#resultA').html(html);

            }

            // call
            getDatas();

        });
    </script>
</body>

</html>

ข้อมูลที่ call ผ่าน ajax จะโดนเก็บใน localStorage แต่เวลาใช้ก็จะเอามาเทียบ expires ก่อน ถ้ายังไม่หมดอายุก็เอามาใช้ ถ้าหมดอายุไปแล้วก็ call ใหม่ ลองดัดแปลงดูให้เหมาะกับงานดูครับ

javascript: string to JSON.parse()

หลังจากใช้ MySQL: return json format กันเป็นแล้วเรามาลองใช้กับ joget แทนที่จะต้องไปเขียน beanshell ให้ยุ่งยาก (พลาดง่ายๆ อีกตะหาก) ดูจริงๆ แล้วสามารถใช้กับหน้าเว็บทั้งหมดไม่ใช่แค่ joGet เพราะมันคือ javascript พื้นฐาน

เปิดฟอร์มที่ต้องการมา

  1. สร้าง section ขึ้นมา โดยกำหนด Load Binder เป็น JDBC Binder และ SQL SELECT Query ใส่ query ที่ต้องการ เช่น
    SELECT
        CONCAT('[',
                GROUP_CONCAT(JSON_OBJECT('label', name, 'value', appId)),
                ']') AS optionsList
    FROM
        jwdb.app_app
    ORDER BY name ASC
  2. ลาก Hidden Field มาตั้ง id เป็น optionsList ใน section ที่สร้างไว้
  3. เขียน javascript ดึงค่ามาจาก string json ที่เก็บไว้ใน input ชื่อ optionsList โดยใช้ JSON.parse() เผื่อความปลอดภัย ควรใช้ JSON.parse() ใน try catch เพราะว่าถ้า json ที่ได้ผิดปกติจะสามารถควบคุมได้ ลาก Custom HTML มาแล้วใส่ code
    <table class="table table-striped" id="tableA">
        <thead>
            <tr>
                <th scope="col" style="width:20px;">#</th>
                <th scope="col">Name</th>
                <th scope="col">Value</th>
            </tr>
        </thead>
        <tbody>
        </tbody>
    </table>
    <script>
        $(document).ready(function () {
    
            let html = new Array();
            let no = 0;
            let optionsList = $('#optionsList');
            try {
                let temp = JSON.parse(optionsList.val());
    
                if (temp.length > 0) {
                    $.each(temp, function (index, value) {
                        no++;
                        html.push('<tr><th scope="row">' + no + '</th><td>' + value.label + '</td><td>' + value.value + '</td><tr>');
                    });
                }
                if (html.length == 0) {
                    html = '<tr><th scope="row">-</th><td>-</td><td>-</td><tr>';
                } else {
                    html = html.join("\n");
                }
            } catch (e) {
                console.log(e.message);
                html = '<h1>Error!!</h1>';
            }
            $('#tableA > tbody').html(html);
    
        });
    </script>

สามารถ copy form ตัวอย่างได้จาก json

{
    "className": "org.joget.apps.form.model.Form",
    "properties": {
        "noPermissionMessage": "",
        "loadBinder": {
            "className": "org.joget.apps.form.lib.WorkflowFormBinder",
            "properties": {}
        },
        "name": "JSON Parse",
        "description": "",
        "postProcessorRunOn": "both",
        "permission": {
            "className": "",
            "properties": {}
        },
        "id": "JSONParse",
        "postProcessor": {
            "className": "",
            "properties": {}
        },
        "storeBinder": {
            "className": "org.joget.apps.form.lib.WorkflowFormBinder",
            "properties": {}
        },
        "tableName": "prototypes"
    },
    "elements": [
        {
            "elements": [
                {
                    "elements": [
                        {
                            "className": "org.joget.apps.form.lib.HiddenField",
                            "properties": {
                                "useDefaultWhenEmpty": "",
                                "workflowVariable": "",
                                "id": "optionsList",
                                "value": ""
                            }
                        }
                    ],
                    "className": "org.joget.apps.form.model.Column",
                    "properties": {
                        "width": "100%"
                    }
                }
            ],
            "className": "org.joget.apps.form.model.Section",
            "properties": {
                "readonly": "",
                "loadBinder": {
                    "className": "org.joget.plugin.enterprise.JdbcLoadBinder",
                    "properties": {
                        "jdbcDatasource": "default",
                        "sql": "SELECT \n    CONCAT('[',\n            GROUP_CONCAT(JSON_OBJECT('label', name, 'value', appId)),\n            ']') AS optionsList\nFROM\n    jwdb.app_app\nORDER BY name ASC"
                    }
                },
                "permissionReadonly": "",
                "permission": {
                    "className": "",
                    "properties": {}
                },
                "comment": "",
                "id": "section1",
                "label": "",
                "storeBinder": {
                    "className": "",
                    "properties": {}
                },
                "readonlyLabel": ""
            }
        },
        {
            "elements": [
                {
                    "elements": [
                        {
                            "className": "org.joget.apps.form.lib.CustomHTML",
                            "properties": {
                                "autoPopulate": "",
                                "id": "field2",
                                "label": "",
                                "value": "<table class=\"table table-striped\" id=\"tableA\">\n    <thead>\n        <tr>\n            <th scope=\"col\" style=\"width:20px;\">#</th>\n            <th scope=\"col\">Name</th>\n            <th scope=\"col\">Value</th>\n        </tr>\n    </thead>\n    <tbody>\n    </tbody>\n</table>\n<script>\n    $(document).ready(function () {\n\n        let html = new Array();\n        let no = 0;\n        let optionsList = $('#optionsList');\n        try {\n            let temp = JSON.parse(optionsList.val());\n\n            if (temp.length > 0) {\n                $.each(temp, function (index, value) {\n                    no++;\n                    html.push('<tr><th scope=\"row\">' + no + '</th><td>' + value.label + '</td><td>' + value.value + '</td><tr>');\n                });\n            }\n            if (html.length == 0) {\n                html = '<tr><th scope=\"row\">-</th><td>-</td><td>-</td><tr>';\n            } else {\n                html = html.join(\"\\n\");\n            }\n        } catch (e) {\n            console.log(e.message);\n            html = '<h1>Error!!</h1>';\n        }\n        $('#tableA > tbody').html(html);\n\n    });\n</script>"
                            }
                        }
                    ],
                    "className": "org.joget.apps.form.model.Column",
                    "properties": {
                        "width": "100%"
                    }
                }
            ],
            "className": "org.joget.apps.form.model.Section",
            "properties": {
                "readonly": "",
                "loadBinder": {
                    "className": "",
                    "properties": {}
                },
                "permissionReadonly": "",
                "permission": {
                    "className": "",
                    "properties": {}
                },
                "comment": "",
                "id": "section2",
                "label": "Options List",
                "storeBinder": {
                    "className": "",
                    "properties": {}
                },
                "readonlyLabel": ""
            }
        }
    ]
}

jQuery: เปลี่ยนลำดับ event

โดยปกติ event ของ jQuery จะทำงานตามลำดับที่มันโดน bind เอาไว้ ถ้าต้องการให้ทำบาง event ทำก่อน จะบอกว่า “ขอลุงก่อน” ไม่ได้ ต้องเขียนให้ bind ก่อน แต่ถ้าเป็น cms หรือเป็น code ที่โดยระบบใส่ให้เองก็ต้องใช้มาตรา 44 หรือลูกเล่นกันนิดหนึ่ง

คำถามนี้มีคนถามและตอบไว้ที่ jQuery event handlers always execute in order they were bound – any way around this? [duplicate] และเขียนตัวอย่างไว้ให้แล้ว http://jsfiddle.net/x8Na8/2/ งานก็เลยทำง่ายเลย ctrl+c ctrl+v เค้ามาแต่งให้หล่อหน่อยก็เอาไปใช้ได้เลย ขอขอบคุณคร๊าบบ

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>jQuery: Events Queue</title>
    <link href="../vendor/twbs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css">
</head>

<body>
    <h1>พร้อมเพย์ (PromptPay)</h1>
    <form action="values.php" class="form-horizontal" id="formA" method="post">
        <div class="form-group">
            <label class="col-sm-2 control-label">โอนเงินด้วย</label>
            <div class="col-sm-10">
                <div class="radio">
                    <label>
                        <input checked name="way" type="radio" value="socialid"> หมายเลขบัตรประชาชน
                    </label>
                </div>
                <div class="radio">
                    <label>
                        <input name="way" type="radio" value="phone"> หมายเลขโทรศัพท์
                    </label>
                </div>
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-2 control-label" for="socialidI">หมายเลขบัตรประชาชน</label>
            <div class="col-sm-10">
                <input class="form-control" id="socialidI" maxlength="13" name="socialid" placeholder="หมายเลขบัตรประชาชน" type="number">
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-2 control-label" for="phoneI">หมายเลขโทรศัพท์</label>
            <div class="col-sm-10">
                <input class="form-control" id="phoneI" maxlength="11" name="phone" placeholder="หมายเลขโทรศัพท์" type="tel">
            </div>
        </div>
        <div class="form-group">
            <label for="amount" class="col-sm-2 control-label">จำนวนเงิน</label>
            <div class="col-sm-10">
                <div class="input-group">
                    <input class="form-control" id="amount" max="5000" min="0" name="amount" placeholder="จำนวนเงิน" type="number">
                    <div class="input-group-addon">&#3647;</div>
                </div>
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-10 text-right">
                <button type="submit" class="btn btn-success">โอนเงิน</button>
            </div>
        </div>
    </form>
    <div class="col-sm-12" id="resultA"></div>
    <script src="../vendor/components/jquery/jquery.min.js"></script>
    <script>
        $.fn.bindFirst = function (name, fn) {
            // bind as you normally would
            // don't want to miss out on any jQuery magic
            this.on(name, fn);

            // Thanks to a comment by @Martin, adding support for
            // namespaced events too.
            this.each(function () {
                var handlers = $._data(this, 'events')[name.split('.')[0]];
                // take out the handler we just inserted from the end
                var handler = handlers.pop();
                // move it at the beginning
                handlers.splice(0, 0, handler);
            });
        };
    </script>
    <script>
        $(function () {

            $('#formA').submit(function () {
                alert('1');
                return true;
            });

            $('#formA').submit(function () {
                alert('2');
                return true;
            });

            $('#formA').submit(function () {
                alert('3');
                return true;
            });

            $('#formA').bindFirst('submit', function () { alert('4'); });

        });
    </script>

</body>

</html>

Bootstrap: dynamic tabs hash

เพราะว่า dynamic tabs ของ Bootstrap มันจะไม่เลือก tabs จาก hash หรือเครื่องหมาย # ใน url ถ้าต้องการให้แสดงแท็บที่ต้องการจาก links ต้องเขียน code เพิ่มเติมอีกเล็กน้อย

<!doctype html>
<html>

<head>
<meta charset="utf-8">
<title>Bootstrap: Dynamic Tabs With Hash</title>
<link href="../vendor/twbs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css">
</head>

<body>
<div class="container">
<h2>Dynamic Tabs With Hash</h2>

<input id="tabindex" name="tabindex" type="text">

<ul class="nav nav-tabs">

<li class="nav-item">
<a class="active nav-link show" data-toggle="tab" href="#menu1">Menu 1</a>
</li>

<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#menu2">Menu 2</a>
</li>

<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#menu3">Menu 3</a>
</li>
</ul>

<div class="tab-content">

<div id="menu1" class="active fade in show tab-pane">
<h3>Menu 1</h3>
<p>นาย</p>
</div>

<div id="menu2" class="fade tab-pane">
<h3>Menu 2</h3>
<p>พิชญ์</p>
</div>

<div id="menu3" class="fade tab-pane">
<h3>Menu 3</h3>
<p>พันธุ์สนิท</p>
</div>

</div>

</div>

<script src="../vendor/components/jquery/jquery.min.js"></script>
<script src="../vendor/twbs/bootstrap/dist/js/bootstrap.min.js"></script>
<script>
$(document).ready(function () {

let hash = window.location.hash.substr(1);
let tabindex = $('#tabindex');
if (hash != tabindex.val()) {
$('a[href="#' + hash).click();
tabindex.val(hash);
}

});
</script>

</body>

</html>

เมื่อเปิดหน้าเว็บมาจาก url ที่ใส่ # ของแท็บที่ต้องการมา เช่น http://localhost/snippets/Bootstrap/tabs.dynamic.hash.html#menu2 แท็บก็จะเปิดหน้าที่ถูกต้องตามที่ต้องการ แทนที่จะดูจาก class active เพียงอย่าเดียว

Bootstrap: Dynamic Tabs

สามารถสร้างเนื้อหาที่เป็นรูปแบบ tabs หรือที่เรียกว่า dynamic tabs ได้โดยใช้แค่ jQuery และ Bootstrap

ตัวอย่าง

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>Bootstrap: Dynamic Tabs</title>
    <link href="../vendor/twbs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css">
</head>

<body>
    <div class="container">
        <h2>Bootstrap: Dynamic Tabs</h2>

        <ul class="nav nav-tabs">

            <li class="nav-item">
                <a class="active nav-link show" data-toggle="tab" href="#menu1">Menu 1</a>
            </li>

            <li class="nav-item">
                <a class="nav-link" data-toggle="tab" href="#menu2">Menu 2</a>
            </li>

            <li class="nav-item">
                <a class="nav-link" data-toggle="tab" href="#menu3">Menu 3</a>
            </li>
        </ul>

        <div class="tab-content">

            <div id="menu1" class="active fade in show tab-pane">
                <h3>Menu 1</h3>
                <p>นาย</p>
            </div>

            <div id="menu2" class="fade tab-pane">
                <h3>Menu 2</h3>
                <p>พิชญ์</p>
            </div>

            <div id="menu3" class="fade tab-pane">
                <h3>Menu 3</h3>
                <p>พันธุ์สนิท</p>
            </div>

        </div>

    </div>

    <script src="../vendor/components/jquery/jquery.min.js"></script>
    <script src="../vendor/twbs/bootstrap/dist/js/bootstrap.min.js"></script>

</body>

</html>

โดยพื้นฐานคือใส่ data-toggle=”tab” ใน tag a ที่ลิงค์ไปยัง hash ที่เป็น id ของ div ที่มี class tab-pane ที่อยู่ใน div class tab-content ก็จะทำงานได้แล้วโดยไม่ต้องกำหนดสคริปเพิ่มเติมอีก แต่ถ้าหากต้องการเลือกแท็บจากลิงค์ให้อ่าน Bootstrap: dynamic tabs hash เพิ่มเติม

ขึ้นบรรทัดใหม่ใน textarea

ข้อมูลถูกเก็บไว้เป็น string ยาวๆ โดยแบ่งหัวข้อย่อยโดยใช้ @ นำหน้า ต้องการให้แสดงใน textarea โดยขึ้นบรรทัดใหม่ที่ละหัวข้อจะได้อ่านง่ายหน่อย

การเขียนให้ javascript จัดรูปแบบให้เขียนได้ในบรรทัดเดียว

string.split('\n').join('').split('@').join('\n@').replace(/^\s/, '');

ทดลอง หรือเขียนดูเอง ถ้า textarea id เท่ากับ detail จะเขียน code ง่ายๆได้เป็น

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>jQuery: textarea newline</title>
    <link href="../vendor/twbs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css">
</head>

<body>
    <h1>Textarea newline</h1>
    <form action="values.php" class="form-horizontal" id="formA" method="post">
        <div class="form-group">
            <label for="detail">Example textarea</label>
            <textarea class="form-control" id="detail" rows="10"></textarea>
        </div>
    </form>
    <div class="col-sm-12" id="resultA"></div>
    <script src="../vendor/components/jquery/jquery.min.js"></script>
    <script>
        $(document).ready(function () {
            let textarea = $('#detail');

            function format() {
                let temp = textarea.val().split('\n').join('').split('@').join('\n@').replace(/^\s/, '');
                textarea.val(temp);
            }

            format();
            textarea.on('change', function () {
                format();
            });
        });
    </script>
</body>

</html>

jQuery.Calx2:binding

เพราะว่าตารางที่ต้องนำมาแสดงค่าการคำนวณมาจากระบบอื่นและใช้ในจุดอื่นๆ ด้วยดังนั้นการเพิ่มแอติบิว data-cell และ data-formula มันไม่ค่อยจะเหมาะนัก ลองใช้ .data แล้วกลับพบว่าสคริปมันไม่ทำงานเหมือน jQuery.Calx2: ใช้สูตร Excel ใน เว็บ ซะงั้น ลองแก้ปัญหาดูจนได้ code

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>jQuery Calx: dynamic</title>
    <link href="../vendor/twbs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" rel="stylesheet" type="text/css">
</head>

<body>
    <table class="table table-bordered table-hover table-striped" id="sheetA" width="100%">
        <thead>
            <tr>
                <td width="58">No.</td>
                <td width="145">First Name</td>
                <td width="126">Last Name</td>
                <td width="135">Relationship to policyholder / main insured</td>
                <td width="191">Employee name<br /> (main insured)</td>
                <td width="100">Period<br /> Start Date<br /> (dd/mm/yy)
                </td>
                <td width="105">Effetive Date<br /> (dd/mm/yy)
                </td>
                <td width="101">Period<br /> End Date<br /> (dd/mm/yy)
                </td>
                <td width="109">DOB</td>
                <td width="51">Age</td>
                <td width="113">ACF - Vital<br /> Annual Premium</td>
                <td width="113">Annually Premium included AGA</td>
                <td width="113">Annual Premium<br /> after 10%<br /> Gr. Discntd</td>
                <td width="107">Prorated Premium<br /> (USD)
                </td>
                <td width="74">Duration Days</td>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td>Tianyi</td>
                <td>Deng</td>
                <td>Employee</td>
                <td>-</td>
                <td>15-May-17</td>
                <td>20-Oct-17</td>
                <td>14-May-18</td>
                <td>24-May-1989</td>
                <td>27</td>
                <td>1,022.00</td>
                <td>ccc</td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
        </tbody>
        <tfoot>
            <tr>
                <td colspan="8"></td>
                <td colspan="5">Total Premium to be Refund(USD)</td>
                <td>(622.19)</td>
                <td></td>
            </tr>
        </tfoot>
    </table>
    <script src="../vendor/components/jquery/jquery.min.js"></script>
    <script src="jquery-calx-2.2.7.min.js"></script>
    <script>
        $(document).ready(function() {

            let sheetA = $('#sheetA');

            let row = $('tr:eq(1)', sheetA);
            $('td:eq(10)', row).attr({
                "data-cell": "K24"
            });
            $('td:eq(11)', row).attr({
                "data-cell": "L24",
                "data-formula": "K24+197"
            });
            $('td:eq(12)', row).attr({
                "data-cell": "M24",
                "data-formula": "L24-(L24*0.1)"
            });
            $('td:eq(13)', row).attr({
                "data-cell": "N24",
                "data-formula": "-(M24*O24)/365"
            });
            $('td:eq(14)', row).attr({
                "data-cell": "O24",
                "data-formula": "H24-G24+1"
            });

            row = $('tr:eq(2)');
            $('td:eq(14)', row).attr({
                "data-cell": "O25",
                "data-formula": "N27"
            });

            sheetA.calx();
        });
    </script>
</body>

</html>

การใช้ .attr() แทน .data() ที่เป็น function โดยตรงกลับทำงานได้ซะงั้น บางครั้ง programmer ก็ต้องอ้อมโลกบ้าง

jQuery.Calx2: ใช้สูตร Excel ใน เว็บ

แรกเริ่มเดิมที่ user จะให้ไฟล์ excel ตัวอย่างการคำนวณมาให้ sa แล้ว sa ก็ส่งสูตรตัวนี้มาให้ทาง programmer เปลี่ยนเป็น javascript พอมีการแก้สูตรตัวนี้กระบวนการก็เริ่มอีกครั้งหนึ่ง ทั้งๆ ที่บางครั้งมีการเปลี่ยนแค่สูตรเดียวเท่านั้น แต่เพราะการเขียนด้วย javascript ที่ซับซ้อนกว่า ทำให้ต้องใช้เวลาในการแก้ไขมากกว่าที่ควรจะเป็น

จะดีกว่ามั๋ย ถ้าหากว่า เราสามารถใช้ excel formula ได้ในหน้าเว็บเลย โดยไม่ต้องมาแปลงเป็นภาษาอื่นและ sa หรือใครก็ตามสามารถมาแก้มันได้เอง จึงเป็นที่มาของ xsanisty/jquery-calx ที่จะช่วยใช้การทำงานเร็วขึ้นมาก

การใช้งานง่ายๆ เพียงแค่ใส่ data 2 ตัวคือ data-cell=” { cell reference } ” และ data-formula=” {สูตร excel ที่ต้องการ} “กับการเรียกใช้อีกเล็กน้อย

<script>
    $(document).ready(function() {
        $('#sheetA').calx();
    });
</script>

เท่านั้น

ตัวอย่างการใช้งาน

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>jQuery Calx: basic</title>
    <link href="../vendor/twbs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" rel="stylesheet" type="text/css">
</head>

<body>
    <table class="table table-bordered table-hover table-striped" id="sheetA" width="100%">
        <thead>
            <tr>
                <td width="58">No.</td>
                <td width="145">First Name</td>
                <td width="126">Last Name</td>
                <td width="135">Relationship to policyholder / main insured</td>
                <td width="191">Employee name<br /> (main insured)</td>
                <td width="100">Period<br /> Start Date<br /> (dd/mm/yy)
                </td>
                <td width="105">Effetive Date<br /> (dd/mm/yy)
                </td>
                <td width="101">Period<br /> End Date<br /> (dd/mm/yy)
                </td>
                <td width="109">DOB</td>
                <td width="51">Age</td>
                <td width="113">ACF - Vital<br /> Annual Premium</td>
                <td width="113">Annually Premium included AGA</td>
                <td width="113">Annual Premium<br /> after 10%<br /> Gr. Discntd</td>
                <td width="107">Prorated Premium<br /> (USD)
                </td>
                <td width="74">Duration Days</td>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td>Tianyi</td>
                <td>Deng</td>
                <td>Employee</td>
                <td>-</td>
                <td>15-May-17</td>
                <td>20-Oct-17</td>
                <td>14-May-18</td>
                <td>24-May-1989</td>
                <td>27</td>
                <td data-cell="K24">1,022.00</td>
                <td data-cell="L24" data-formula="K24+197"></td>
                <td data-cell="M24" data-formula="L24-(L24*0.1)"></td>
                <td data-cell="N24" data-formula="-(M24*O24)/365"></td>
                <td data-cell="O24" data-formula="H24-G24+1"></td>
            </tr>
        </tbody>
        <tfoot>
            <tr>
                <td colspan="8"></td>
                <td colspan="5">Total Premium to be Refund(USD)</td>
                <td>(622.19)</td>
                <td data-cell="O25" data-formula="N27"></td>
            </tr>
        </tfoot>
    </table>
    <script src="../vendor/components/jquery/jquery.min.js"></script>
    <script src="jquery-calx-2.2.7.min.js"></script>
    <script>
        $(document).ready(function() {
            $('#sheetA').calx();
        });
    </script>
</body>

</html>

เพียงเท่านี้ เราก็สามารถจะคำนวณค่าต่างๆ ได้เหมืือนใช้ excel ได้แล้วโดยไม่ต้องแก้สูตรให้ยุ่งยากเลย

DataTable: แสดงเลขที่รายการ

เมื่อมีข้อมูลจำนวนมาก โดยที่ไม่ได้แสดง primary key หรือข้อมูลมาจากการ join ข้อมูลหลายๆ ตาราง การอ้างอิงแถวในตารางก็จะทำไม่สะดวก เดิมจะใช้วิธี query โดยใช้ :rownum หรือ row_number ให้แสดงแถวของข้อมูลตาม database ที่ใช้ แต่ข้อเสียก็คือ มันจะเพิ่มภาระให้ฐานข้อมูล ซึ่งจริงๆแล้วสามารถผลักภาระส่วนนี้ไปให้ทางฝั่ง client ได้

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="author" content="Pitt Phunsanit">
    <title>DataTables: row number</title>
    <link href="../vendor/twbs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css">
    <link href="../vendor/twbs/bootstrap/dist/css/bootstrap-theme.min.css" rel="stylesheet" type="text/css">
    <link href="../vendor/datatables/datatables/media/css/dataTables.bootstrap.min.css" rel="stylesheet" type="text/css">
</head>

<body>
    <table class="table table-bordered table-hover table-striped" id="tableA" width="100%"></table>
    <script src="../vendor/components/jquery/jquery.min.js"></script>
    <script src="../vendor/datatables/datatables/media/js/jquery.dataTables.min.js"></script>
    <script src="../vendor/datatables/datatables/media/js/dataTables.bootstrap.min.js"></script>
    <script src="row_number.js"></script>
</body>

</html>

$(function() {

    tableA = $('#tableA');

    datatable = tableA.DataTable({
        "ajax": {
            "data": function(parameters) {},
            "method": "POST",
            "url": "data.json.php",
        },
        "columns": [{
                "orderable": false,
                "render": function render(data, type, row, meta) {
                    var row_number = (parseInt(meta.settings._iDisplayStart) + parseInt(meta.row) + 1);
                    return String(row_number).replace(/(\d)(?=(\d{3}))/g, '$1,');;
                },
                "searchable": false,
                "targets": 0,
                "title": "No.",
            },
            {
                "orderable": false,
                "render": function(data, type, row, meta) {
                    return parseInt(meta.row) + parseInt(meta.settings._iDisplayStart) + 1;
                },
                "title": 'No.',
                "width": "10px",
            },
            {
                "orderable": false,
                "render": function(data, type, row, meta) {
                    return '<input type="checkbox" value="' + row.DISTRICT_CODE + '">';
                },
                "title": '<input class="checkAll" type="checkbox">',
                "width": "10px",
            },
            {
                "orderable": false,
                "render": function(data, type, row, meta) {
                    if (row.enable == '1') {
                        return '<span class="glyphicon glyphicon-ok"></span>';
                    } else {
                        return '<span class="glyphicon glyphicon-remove"></span>';
                    }
                },
                "title": "Enable",
                "width": "10px",
            }, {
                "data": "DISTRICT_CODE",
                "title": "District Code",
                "width": "90px",
            }, {
                "data": "DISTRICT_NAME",
                "title": "District Name",
            }, {
                "data": "PROVINCE_NAME",
                "title": "Province Name",
            }
        ],
        /* default sort */
        "order": [
            [3, "asc"],
            [4, "asc"],
        ],
        "processing": true,
        "serverSide": true,
        "stateSave": true,
    });

    $('.checkAll', tableA).click(function() {
        $('input:checkbox', tableA).not(this).prop('checked', this.checked);
    });

});

<?php
/* https://datatables.net/manual/server-side */

$output = [
    'data' => [],
    'debug' => [
        'length' => $_REQUEST['length'],
        'post' => $_REQUEST,
        'sqlCount' => '',
        'sqlResult' => '',
        'start' => $_REQUEST['start'],
    ],
    'draw' => $_REQUEST['draw'],
    'recordsFiltered' => $_REQUEST['length'],
    'recordsTotal' => 0,
];

$dns = new PDO('mysql:host=localhost;dbname=snippets', 'root', '', [
    //PDO::ATTR_EMULATE_PREPARES => false,
    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
]);

$condition = [];
$from = ' FROM district AS d LEFT JOIN province AS p ON d.PROVINCE_ID = p.PROVINCE_ID';
$parameters = [];
$where = '';

if (isset($_REQUEST['filters']) || isset($_REQUEST['search']['value'])) {

    if (isset($_REQUEST['search']['value'])) {
        if ($_REQUEST['search']['value'] != '') {

            $parameter = ':d_DISTRICT_NAME';

            $parameters[$parameter] = '%' . $_REQUEST['search']['value'] . '%';
            array_push($condition, 'd.DISTRICT_NAME LIKE ' . $parameter);
        }
    }

    if (isset($_REQUEST['filters'])) {
        foreach ($_REQUEST['filters'] as $tableAlias => $filter) {
            foreach ($filter as $field => $value) {
                if ($value != '') {
                    $parameter = ':' . $tableAlias . '_' . $field;

                    $parameters[$parameter] = '%' . $value . '%';
                    array_push($condition, $tableAlias . '.' . $field . ' LIKE ' . $parameter);
                }
            }
        }
    }

}

if (isset($_REQUEST['geo_id']) && $_REQUEST['geo_id'] != '') {
    $parameter = ':d_geo_id';

    $parameters[$parameter] = $_REQUEST['geo_id'];
    array_push($condition, 'd.GEO_ID = ' . $parameter);
}

if (count($parameters)) {
    $where = ' WHERE ' . implode("\n\t AND ", $condition);
}

if (isset($_REQUEST['order']) && isset($_REQUEST['order'][0])) {
    $columns = [
        0 => 'DISTRICT_NAME',
        3 => 'DISTRICT_CODE',
        4 => 'DISTRICT_NAME',
        5 => 'PROVINCE_NAME',
    ];

    $order = ' ORDER BY ' . $columns[$_REQUEST['order'][0]['column']] . ' ' . strtoupper($_REQUEST['order'][0]['dir']);
} else {
    $order = ' ORDER BY DISTRICT_NAME ASC';
}

$output['debug']['parameters'] = $parameters;

/* Total records, before filtering */
$sql = 'SELECT COUNT(d.DISTRICT_ID)' . $from;
try {
    $output['debug']['sqlCount'] = $sql;
    $stmt = $dns->prepare($sql);
    $stmt->execute($parameters);
    $output['recordsTotal'] = (int) $stmt->fetchColumn(0);
} catch (PDOException $e) {
    exit($e->getMessage());
}

/* Total records, after filtering */
$sql = 'SELECT COUNT(d.DISTRICT_ID)' . $from . $where;
try {
    $output['debug']['sqlCount'] = $sql;
    $stmt = $dns->prepare($sql);
    $stmt->execute($parameters);
    $output['recordsFiltered'] = (int) $stmt->fetchColumn(0);
} catch (PDOException $e) {
    exit($e->getMessage());
}

/* data */
if ($output['recordsTotal'] > 0) {
    $sql = 'SELECT d.enable, d.DISTRICT_ID, d.DISTRICT_CODE, d.DISTRICT_NAME, p.PROVINCE_NAME' . $from . $where . $order . " LIMIT " . $_REQUEST['start'] . ", " . $_REQUEST['length'] . ";";

    try {
        $output['debug']['sqlResult'] = $sql;
        $stmt = $dns->prepare($sql);
        $stmt->execute($parameters);
        $output['data'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (PDOException $e) {
        exit($e->getMessage());
    }
}

/* unset debug for security */
unset($output['debug']);

header('Content-type: application/json; charset=utf-8');
echo json_encode($output);

slick: responsive และ ความกว้าง

เจอปัญหาว่า slick เวลาวางต่อไปอีก block ต่อจากตัวอื่น มันจะตกลงมา หรือแสดงไม่ครบแล้วแต่กรณี เพราะว่า block มันจะยาวเท่าหน้า windows เลย ไม่ใช่ container หรือ div ที่ห่อมันเอาไว้เหมือน slideshow ตัวอื่นๆ

ลองหลายวิธีจนพบว่าวิธีของคุณ kirana Slick Carousel Center Padding Demo นั้นดีที่สุด

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>kenwheeler.slick: responsive</title>
    <link href="../vendor/components/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
    <link href="../vendor/kenwheeler/slick/slick/slick.css" rel="stylesheet" type="text/css" />
    <link href="../vendor/kenwheeler/slick/slick/slick-theme.css" rel="stylesheet" type="text/css" />
    <link href="../vendor/twbs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css">
    <link href="theme.css" rel="stylesheet" type="text/css" />
    <style>
        .slickContainer {
            background: #1f6d38;
            color: #333;
            margin: 0 auto;
            padding: 40px;
            width: 80%;
        }
    </style>
</head>

<body>

    <div class="container">
        <div class="row">
            <div class="col-1 col-sm-2 col-md-3 col-lg-4 col-xl-5">
                <h1>sidebar</h1>
            </div>
            <div class="col-11 col-sm-10 col-md-9 col-lg-8 col-xl-7">
                <div class="slickContainer">
                    <div id="slideshowA">
                        <div><b>Acrobat</b><img alt="Acrobat" src="http://www.avatarsdb.com/avatars/acrobat.gif">
                            <p>แสดง acrobat.gifจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Cat Rain</b><img alt="Cat Rain" src="http://www.avatarsdb.com/avatars/cat_rain.gif">
                            <p>แสดง cat_rain.gifจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Dota Windranger</b><img alt="Dota Windranger" src="http://www.avatarsdb.com/avatars/dota_windranger.jpg">
                            <p>แสดง dota_windranger.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Fighting Funny</b><img alt="Fighting Funny" src="http://www.avatarsdb.com/avatars/fighting_funny.gif">
                            <p>แสดง fighting_funny.gifจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Fire 01</b><img alt="Fire 01" src="http://www.avatarsdb.com/avatars/fire_01.gif">
                            <p>แสดง fire_01.gifจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>German Shepherd Puppy</b><img alt="German Shepherd Puppy" src="http://www.avatarsdb.com/avatars/german_shepherd_puppy.jpg">
                            <p>แสดง german_shepherd_puppy.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Girl With Cigarette</b><img alt="Girl With Cigarette" src="http://www.avatarsdb.com/avatars/girl_with_cigarette.jpg">
                            <p>แสดง girl_with_cigarette.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Hidden Cat</b><img alt="Hidden Cat" src="http://www.avatarsdb.com/avatars/hidden_cat.jpg">
                            <p>แสดง hidden_cat.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Im Fabulous</b><img alt="Im Fabulous" src="http://www.avatarsdb.com/avatars/im_fabulous.jpg">
                            <p>แสดง im_fabulous.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>One Direction</b><img alt="One Direction" src="http://www.avatarsdb.com/avatars/One_Direction.jpg">
                            <p>แสดง One_Direction.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Panda Kiss</b><img alt="Panda Kiss" src="http://www.avatarsdb.com/avatars/panda_kiss.gif">
                            <p>แสดง panda_kiss.gifจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>PC User</b><img alt="PC User" src="http://www.avatarsdb.com/avatars/pc_user.gif">
                            <p>แสดง pc_user.gifจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Riri Queen</b><img alt="Riri Queen" src="http://www.avatarsdb.com/avatars/riri_queen.gif">
                            <p>แสดง riri_queen.gifจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Tennessee</b><img alt="Tennessee" src="http://www.avatarsdb.com/avatars/tennessee.jpg">
                            <p>แสดง tennessee.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Tuxedo M</b><img alt="Tuxedo M" src="http://www.avatarsdb.com/avatars/Tuxedo_m.jpg">
                            <p>แสดง Tuxedo_m.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Tuxedo Mask</b><img alt="Tuxedo Mask" src="http://www.avatarsdb.com/avatars/tuxedo_mask.jpg">
                            <p>แสดง tuxedo_mask.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Ugly Face</b><img alt="Ugly Face" src="http://www.avatarsdb.com/avatars/ugly_face.gif">
                            <p>แสดง ugly_face.gifจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Waifu</b><img alt="Waifu" src="http://www.avatarsdb.com/avatars/waifu.jpg">
                            <p>แสดง waifu.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Wolf In The Snow</b><img alt="Wolf In The Snow" src="http://www.avatarsdb.com/avatars/wolf_in_the_snow.jpg">
                            <p>แสดง wolf_in_the_snow.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                        <div><b>Xerxes Break Kevin</b><img alt="Xerxes Break Kevin" src="http://www.avatarsdb.com/avatars/xerxes_break_kevin.jpg">
                            <p>แสดง xerxes_break_kevin.jpgจาก http://www.avatarsdb.com</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script src="../vendor/components/jquery/jquery.min.js"></script>
    <script src="../vendor/kenwheeler/slick/slick/slick.min.js"></script>
    <script>
        $(function() {
            $('#slideshowA').slick({
    "autoplay": true,
    "autoplaySpeed": 3000,
    "centerMode": true,
    "infinite": true,
    "responsive": [{
            "breakpoint": 1140,
            "settings": {
                "dots": true,
                "infinite": true,
                "slidesToScroll": 4,
                "slidesToShow": 4
            }
        }, {
            "breakpoint": 960,
            "settings": {

                "dots": true,
                "infinite": true,
                "slidesToScroll": 3,
                "slidesToShow": 3
            }
        }, {
            "breakpoint": 720,
            "settings": {
                "dots": false,
                "infinite": false,
                "slidesToScroll": 2,
                "slidesToShow": 2
            }
        },
        {
            "breakpoint": 540,
            "settings": "unslick"
        }
    ],
    "speed": 300,
    "variableWidth": true,
    "zIndex": 2
});

});
    </script>
</body>

</html>

เมื่อสร้าง container ครอบตัว slide show และร่วมกับคำสั่ง responsive จะเห็นได้ว่าสามารถควบคุมการแสดงผลได้ดียิ่งขึ้น