เลือกจังหวัด,อำเภอ,ตำบล ด้วย auto complete

เมื่อจะต้องทำฟอร์มกรอกข้อมูลที่เป็นลำดับขั้นหรือมีข้อมูลที่เป็นหน่วยย่อยของกันและกัน ผู้เขียนหลายคนมักเลือกที่จะใช้ select box, drop box โดยเลือกจากหน่วยใหญ่ไปหน่วยย่อยหรือเล็ก. ยกตัวอย่างเช่น เลือกจังหวัด แล้วจึงเลือกอำเภอ(เขต) แล้วจึงเลือกตำบล(แขวง) เป็นต้น. กระบวนการดังกล่าวก็เป็นขั้นตอนที่คลาสสิกและดูจะเป็นมาตรฐานสำหรับฟอร์มทั่วไป แต่ในแง่มุมของผู้ใช้การต้องคลิกๆเลือกๆบ่อยๆอาจไม่ค่อยสะดวกนัก ดังนั้นถ้าหากเราประยุกต์ใช้ auto complete เป็นการค้นหาชื่อจังหวัด, อำเภอ, ตำบล แล้วแสดงให้ผู้ใช้เลือกได้ภายในคลิกเดียว ย่อมจะสะดวกมากกว่า.

สำหรับการทำงานของ auto complete นั้นจะต้องใช้ JavaScript สำหรับเว็บเบราว์เซอร์ เพื่อรองรับการสั่งการจากผู้ใช้และส่งงานไปยังฝั่ง server เพื่อค้นหา. ในส่วนของ auto complete นั้น บทความนี้จะใช้โค้ดของ autocomplete.js ซึ่งปรับแต่งได้พอสมควรและมีการกำหนดค่าต่างๆครบความต้องการพื้นฐานทั่วๆไป.

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

สำหรับฐานข้อมูลจังหวัดนั้น ขอให้ดาวน์โหลดจาก GitHub ( https://github.com/parsilver/thailand-provinces ) ซึ่งแม้จะไม่ใช่ข้อมูลที่ 100% แต่ก็เพียงพอต่อการนำมาทดลอง. เมื่อดาวน์โหลดมาแล้ว ทำการสร้างฐานข้อมูลพร้อมทั้ง import ให้เรียบร้อย.

<?php

$dbName = 'my-database-name';
$dbHost = 'localhost';
$dbUsername = '';
$dbPassword = '';

.config.php

<?php

require __DIR__ . '/.config.php';


$dsn = 'mysql:dbname=' . $dbName . ';host=' . $dbHost . ';charset=utf8mb4';
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_STRINGIFY_FETCHES => true,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
];
$dbh = new PDO($dsn, $dbUsername, $dbPassword, $options);
unset($options);


header('Content-Type: application/json');

$search = trim(filter_input(INPUT_GET, 'search'));


// start search in DB. ===========================
// search provinces. -----------------------------
$sql = 'SELECT * FROM `provinces` WHERE `name_th` LIKE :search ORDER BY `name_th` ASC';
$bindValues = [
    ':search' => '%' . $search . '%',
];
$Sth = $dbh->prepare($sql);
foreach ($bindValues as $placeholder => $value) {
    $Sth->bindValue($placeholder, $value);
}// endforeach;
unset($bindValues, $placeholder, $value, $sql);
$Sth->execute();
$provincesResult = $Sth->fetchAll();
$Sth->closeCursor();
// end search provinces. -------------------------

// search amphurs. -------------------------------
$sql = 'SELECT *,
    `amphures`.`id` AS `amphure_id`,
    `amphures`.`name_th` AS `amphure_name_th`,
    `provinces`.`name_th` AS `province_name_th`
    FROM `amphures` 
    INNER JOIN `provinces` ON `amphures`.`province_id` = `provinces`.`id`
    WHERE `amphures`.`name_th` LIKE :search
    ORDER BY `amphure_name_th` ASC';
$bindValues = [
    ':search' => '%' . $search . '%',
];
$Sth = $dbh->prepare($sql);
foreach ($bindValues as $placeholder => $value) {
    $Sth->bindValue($placeholder, $value);
}// endforeach;
unset($bindValues, $placeholder, $value, $sql);
$Sth->execute();
$amphursResult = $Sth->fetchAll();
$Sth->closeCursor();
// end search amphurs. ---------------------------

// search districts. -----------------------------
$sql = 'SELECT *,
    `amphures`.`province_id` AS `province_id`,
    `districts`.`amphure_id` AS `amphure_id`,
    `districts`.`id` AS `district_id`,
    `districts`.`name_th` AS `district_name_th`,
    `amphures`.`name_th` AS `amphure_name_th`,
    `provinces`.`name_th` AS `province_name_th`
    FROM `districts` 
    INNER JOIN `amphures` ON `districts`.`amphure_id` = `amphures`.`id`
    INNER JOIN `provinces` ON `amphures`.`province_id` = `provinces`.`id`
    WHERE (
        `districts`.`name_th` LIKE :search
        OR `districts`.`zip_code` LIKE :search
    )
    ORDER BY `district_name_th` ASC';
$bindValues = [
    ':search' => '%' . $search . '%',
];
$Sth = $dbh->prepare($sql);
foreach ($bindValues as $placeholder => $value) {
    $Sth->bindValue($placeholder, $value);
}// endforeach;
unset($bindValues, $placeholder, $value, $sql);
$Sth->execute();
$districtsResult = $Sth->fetchAll();
$Sth->closeCursor();
// end search districts. -------------------------
unset($dbh, $Sth);


// prepare response data. ========================
$output = [];
$i = 0;

// set province results for response. ------------
foreach ($provincesResult as $pvrow) {
    $output[$i]['province_id'] = $pvrow->id;
    $output[$i]['province_name_th'] = $pvrow->name_th;
    $output[$i]['display'] = '<strong>จ.</strong>' . $pvrow->name_th;
    $i++;
}// endforeach;
unset($pvrow);
unset($provincesResult);

// set amphur results for response. ---------------
foreach ($amphursResult as $arow) {
    $output[$i]['province_id'] = $arow->province_id;
    $output[$i]['amphure_id'] = $arow->amphure_id;
    $output[$i]['province_name_th'] = $arow->province_name_th;
    $output[$i]['amphure_name_th'] = $arow->amphure_name_th;
    $output[$i]['display'] = '<strong>จ.</strong>' . $arow->province_name_th 
        . ' &gt; <strong>อ.</strong>' . $arow->amphure_name_th;
    $i++;
}// endforeach;
unset($arow);
unset($amphursResult);

// set district results for response. ------------
foreach ($districtsResult as $drow) {
    $output[$i]['province_id'] = $drow->province_id;
    $output[$i]['amphure_id'] = $drow->amphure_id;
    $output[$i]['district_id'] = $drow->district_id;
    $output[$i]['district_name_th'] = $drow->district_name_th;
    $output[$i]['amphure_name_th'] = $drow->amphure_name_th;
    $output[$i]['province_name_th'] = $drow->province_name_th;
    $output[$i]['zip_code'] = $drow->zip_code;
    $output[$i]['display'] = '<strong>จ.</strong>' . $drow->province_name_th 
        . ' &gt; <strong>อ.</strong>' . $drow->amphure_name_th
        . ' &gt; <strong>ต.</strong>' . $drow->district_name_th
        . ' (' . $drow->zip_code . ')';
    $i++;
}// endforeach;
unset($drow);
unset($districtsResult);
unset($i);


echo json_encode($output);
exit();

search.php สำหรับทำหน้าที่รับคำค้นหา แล้วค้นหาในฐานข้อมูล.

<!DOCTYPE html>
<html lang="th">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Test autocomplete.js</title>

        <link rel="stylesheet" href="assets/css/autoComplete.02.css">
    </head>
    <body>
        <h1><a href="https://tarekraafat.github.io/autoComplete.js" target="autocomplete-js-website">Autocomplete.js</a></h1>
        <h2>AJAX provinces</h2>
        <form id="form" method="post">
            <input id="autoComplete" placeholder="ค้นหา">
            <hr>
            <div style="margin: 20px 0;">
                <h3>Selected</h3>
                <div style="margin: 0 0 10px">
                    ตำบล<br>
                    ID: <input id="district_id">
                    Name: <input id="district_name">
                </div>
                <div style="margin: 0 0 10px">
                    อำเภอ<br>
                    ID: <input id="amphure_id">
                    Name: <input id="amphure_name">
                </div>
                <div style="margin: 0 0 10px">
                    จังหวัด<br>
                    ID: <input id="province_id">
                    Name: <input id="province_name">
                </div>
                <div style="margin: 0 0 10px">
                    รหัสไปรษณีย์<br>
                    <input id="zip_code">
                </div>
            </div>
            <hr>
            <div style="margin: 20px 0;">
                <button type="submit">Submit</button>
            </div>
        </form>

        <script src="assets/js/autoComplete.min.js"></script>
        <script>
            document.addEventListener('DOMContentLoaded', (event) => {
                // reset input prevent Firefox cache.
                document.getElementById('form').reset();

                const config = {
                    data: {
                        src: async (query) => {
                            try {
                                // Fetch Data from external Source
                                const source = await fetch('./search.php?search=' + encodeURIComponent(query));
                                // Data should be an array of `Objects` or `Strings`
                                const data = await source.json();

                                return data;
                            } catch (error) {
                                return error;
                            }
                        },
                        keys: [
                            'province_name_th', 
                            'amphure_name_th',
                            'district_name_th',
                            'zip_code',
                        ],
                    },
                    resultsList: {
                        element: (list, data) => {
                            if (!data.results.length) {
                                // if there is no result.
                                // Create "No Results" message list element
                                const message = document.createElement('li');
                                message.setAttribute('class', 'no_result');
                                message.setAttribute('onclick', 'return false;');
                                // Add message text content
                                message.innerHTML = `<span onclick="return false;">ไม่พบคำค้นหา "${data.query}"</span>`;
                                // Add message list element to the list
                                list.appendChild(message);
                            }
                        },
                        maxResults: 1000,
                        noResults: true,
                    },
                    resultItem: {
                        element: (item, data) => {
                            item.innerHTML = data?.value?.display;
                        },
                        highlight: true,
                    },
                    events: {
                        input: {
                            selection: (event) => {
                                if (!event?.detail?.selection?.value) {
                                    // if not found, not select = not show undefined.
                                    return ;
                                }
                                // set display to autocomplete input box.
                                const selection = (event.detail.selection.value?.display ?? '');
                                const txt = document.createElement('textarea');// for decode html like `&gt;` will becomes `>`
                                txt.innerHTML = selection.replace(/(<([^>]+)>)/gi, "");// strip html
                                autoCompleteJS.input.value = txt.value;

                                // set sub values.
                                document.getElementById('district_id').value = (event.detail.selection.value?.district_id ?? '');
                                document.getElementById('district_name').value = (event.detail.selection.value?.district_name_th ?? '');
                                document.getElementById('amphure_id').value = (event.detail.selection.value?.amphure_id ?? '');
                                document.getElementById('amphure_name').value = (event.detail.selection.value?.amphure_name_th ?? '');
                                document.getElementById('province_id').value = (event.detail.selection.value?.province_id ?? '');
                                document.getElementById('province_name').value = (event.detail.selection.value?.province_name_th ?? '');
                                document.getElementById('zip_code').value = (event.detail.selection.value?.zip_code ?? '');
                            }
                        }
                    },
                };

                const autoCompleteJS = new autoComplete(config);
            });
        </script>
    </body>
</html>

form.html สำหรับทำหน้าที่แสดงฟอร์มและ auto complete. สำหรับไฟล์ CSS, JS นั้น ผู้อ่านสามารถดาวน์โหลดได้โดยตรงจากเว็บ autoComplete.js ได้เลยครับ.

จากโค้ดตัวอย่างทั้งหมดด้านบนนั้น เมื่อทำการเปิดหน้า form.html ผ่าน web server ที่รองรับ PHP แล้วทำการกรอกคำค้นหา เช่น "เชียง" (โดยไม่ต้องมีเครื่องหมาย ") ตัวโปรแกรมจะค้นทุกอย่าง ทั้งชื่อตำบล, อำเภอ, จังหวัด ที่มีคำว่า"เชียง"มาแสดง และเมื่อผู้อ่านเลือกรายการใด ข้อมูลย่อยๆจะถูกส่งไปยังฟอร์มต่างๆ เช่น ID ของอำเภอ เป็นต้น.

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *

คุณอาจใช้แท็กHTMLและแอททริบิวต์เหล่านี้: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>