Bài 12: Search ajax Autocomplete có phân trang

DOWNLOAD

Đây là bài thứ 12 trong loạt serie ajax toàn tập. Nếu như bạn đã đọc đến bài này thì tôi tin rằng bạn đã nắm vững được ajax jquery rồi, nên để nâng cao hơn xíu hôm nay tôi sẽ hướng dẫn làm chức năng search autocomplete ajax có phân trang.

Search autocomplete là gì? Nếu bạn chưa biết nó là gì thì tôi xin trích dẫn thế này, chắc hẳn bạn đã từng vào một số website có chức năng search và khi bạn nhập nội dung vào nó tự động lấy nội dung và hiển thị cho bạn mà không cần phải reload trang. Đây là chức năng rất thân thiện với người dùng nên nếu áp dụng vào website thì tuyệt cú mèo. 

Kết quả của bài này ta có form như hình sau:

search autocomplete

1. Xây dựng database

Trước tiên bạn cần tạo một database tên test và chạy nội dung sql bên dưới để tạo bảng và thêm một số data.

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";

CREATE TABLE IF NOT EXISTS `member` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(30) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=30 ;

INSERT INTO `member` (`id`, `username`, `email`) VALUES
(1, 'thehalfheart', 'thehalfheart@gmail.com'),
(2, 'freetuts', 'freetuts.net@gmail.com'),
(3, 'kingston', 'kingston@gmail.com'),
(4, 'cafeviet', 'cafeviet@gmail.com'),
(5, 'emailer', 'emailer@gmail.com'),
(6, 'domain', 'domain@gmail.com'),
(7, 'root', 'root@gmail.com'),
(8, 'admin', 'admin@gmail.com'),
(9, 'supper', 'supper@gmail.com'),
(10, 'hoconline', 'hoconline@gmail.com'),
(11, 'codon', 'codon@gmail.com'),
(12, 'thesky', 'thesky@gmail.com'),
(13, 'khoanguyen', 'khoanguyen@gmail.com'),
(14, 'trinhut', 'trinhut@gmail.com'),
(15, 'sanhot', 'sanhot@gmail.com'),
(16, 'nammoitetden', 'nammoitetden@gmail.com'),
(17, 'nhatkydoitoi', 'nhatkydoitoi@gmail.com'),
(18, 'freetutsonline', 'freetutsonline@gmail.com'),
(19, 'sinhthoi', 'sinhthoi@gmail.com'),
(20, 'xatoiradioco', 'xatoiradioco@gmail.com'),
(21, 'emailaothu1', 'emailaothu1@gmail.com'),
(22, 'emailaothu2', 'emailaothu2@gmail.com'),
(23, 'emailaothu3', 'emailaothu3@gmail.com'),
(24, 'emailaothu4', 'emailaothu4@gmail.com'),
(25, 'emailaothu5', 'emailaothu5@gmail.com'),
(26, 'emailaothu6', 'emailaothu6@gmail.com'),
(27, 'emailaothu7', 'emailaothu7@gmail.com'),
(28, 'emailaothu8', 'emailaothu8@gmail.com'),
(29, 'emailaothu9', 'emailaothu9@gmail.com');

2. Xây dựng thư viện phân trang

Vì trong bài search autocomplete ajax này tôi có phân trang nên bắt buộc phải xây dựng lớp phân trang rồi :D. Bạn tạo file pagination.php và copy nội dung bên dưới vào:

class Pagination {

    protected $_config = array(
        'current_page'  => 1, // Trang hiện tại
        'total_record'  => 1, // Tổng số record
        'total_page'    => 1, // Tổng số trang
        'limit'         => 10, // limit
        'start'         => 0, // start
        'link_full'     => '', // Link full có dạng như sau: domain/com/page/{page}
        'link_first'    => '', // Link trang đầu tiên
        'range'         => 9, // Số button trang bạn muốn hiển thị
        'min'           => 0, // Tham số min
        'max'           => 0  // tham số max, min và max là 2 tham số private
    );
    
    function init($config = array()) 
    {
        foreach ($config as $key => $val) {
            if (isset($this->_config[$key])) {
                $this->_config[$key] = $val;
            }
        }
        
        if ($this->_config['limit'] < 0) {
            $this->_config['limit'] = 0;
        }

        $this->_config['total_page'] = ceil($this->_config['total_record'] / $this->_config['limit']);

        if (!$this->_config['total_page']) {
            $this->_config['total_page'] = 1;
        }

        if ($this->_config['current_page'] < 1) {
            $this->_config['current_page'] = 1;
        }

        if ($this->_config['current_page'] > $this->_config['total_page']) {
            $this->_config['current_page'] = $this->_config['total_page'];
        }

        $this->_config['start'] = ($this->_config['current_page'] - 1) * $this->_config['limit'];

        $middle = ceil($this->_config['range'] / 2);

        if ($this->_config['total_page'] < $this->_config['range']) {
            $this->_config['min'] = 1;
            $this->_config['max'] = $this->_config['total_page'];
        }
        else {
            $this->_config['min'] = $this->_config['current_page'] - $middle + 1;

            $this->_config['max'] = $this->_config['current_page'] + $middle - 1;

            if ($this->_config['min'] < 1) {
                $this->_config['min'] = 1;
                $this->_config['max'] = $this->_config['range'];
            }

            else if ($this->_config['max'] > $this->_config['total_page']) {
                $this->_config['max'] = $this->_config['total_page'];
                $this->_config['min'] = $this->_config['total_page'] - $this->_config['range'] + 1;
            }
        }
    }
    
    function get_config($key){
        return $this->_config[$key];
    }
    
    private function __link($page) {
        if ($page <= 1 && $this->_config['link_first']) {
            return $this->_config['link_first'];
        }
        return str_replace('{page}', $page, $this->_config['link_full']);
    }

    function html() {
        $p = '';
        if ($this->_config['total_record'] > $this->_config['limit']) {
            $p = '<ul>';

            // Nút prev và first
            if ($this->_config['current_page'] > 1) {
                $p .= '<li><a href="' . $this->__link('1') . '" title="1">First</a></li>';
                $p .= '<li><a href="' . $this->__link($this->_config['current_page'] - 1) . '" title="'.($this->_config['current_page'] - 1).'">Prev</a></li>';
            }

            // lặp trong khoảng cách giữa min và max để hiển thị các nút
            for ($i = $this->_config['min']; $i <= $this->_config['max']; $i++) {
                // Trang hiện tại
                if ($this->_config['current_page'] == $i) {
                    $p .= '<li><span>' . $i . '</span></li>';
                } else {
                    $p .= '<li><a href="' . $this->__link($i) . '" title="'.$i.'">' . $i . '</a></li>';
                }
            }

            // Nút last và next
            if ($this->_config['current_page'] < $this->_config['total_page']) {
                $p .= '<li><a href="' . $this->__link($this->_config['current_page'] + 1) . '" title="'.($this->_config['current_page'] + 1).'">Next</a></li>';
                $p .= '<li><a href="' . $this->__link($this->_config['total_page']) . '" title="'.$this->_config['total_page'].'">Last</a></li>';
            }
            $p .= '</ul>';
        }
        return $p;
    }
}

Nếu bạn chưa biết cách viết thư viện phân trang thì có thể tham khảo hai bài sau:

3. Xây dựng model xử lý database

Để cho tiện lợi trong quá trình xử lý database để search autocomplete thì tôi sẽ xây dựng một file chuyên dùng để lấy danh sách và truy vấn theo tham số search truyền vào. Bạn tạo file database.php và copy nội dung bên dưới vào.

// Khai báo biến toàn cục kết nối
global $conn;
 
// Hàm kết nối database
function connect(){
    global $conn;
    $conn = mysqli_connect('localhost', 'root', 'vertrigo', 'test') or die ('{error:"bad_request"}');
}
 
// Hàm đóng kết nối
function disconnect(){
    global $conn;
    if ($conn){
        mysqli_close($conn);
    }
}
 
// Hàm đếm tổng số thành viên
function count_all_member($username = '')
{
    global $conn;
    
    if ($username){
        $query = mysqli_query($conn, 'select count(*) as total from member where username like \'%'.mysql_escape_string($username).'%\'');
    }
    else{
        $query = mysqli_query($conn, 'select count(*) as total from member');
    }
    
    if ($query){
        $row = mysqli_fetch_array($query, MYSQLI_ASSOC);
        return $row['total'];
    }
    return 0;
}
 
// Lấy danh sách thành viên
function get_all_member($username = '', $limit = 10, $start = 0)
{
    global $conn;
    
    if ($username){
        $sql = 'select * from member  where username like \'%'.mysql_escape_string($username).'%\' limit '.(int)$start . ','.(int)$limit;
    }
    else{
        $sql = 'select * from member limit '.(int)$start . ','.(int)$limit;
    }
    
    $query = mysqli_query($conn, $sql);
     
    $result = array();
     
    if ($query)
    {
        while ($row = mysqli_fetch_array($query, MYSQLI_ASSOC)){
            $result[] = $row;
        }
    }
     
    return $result;
}

Trong file tôi đã comment rất kỹ rồi nên ko giải thích gì thêm

4. Xây dựng hàm lấy danh sách search autocomplete và trả về chuỗi JSON

Bạn tạo file get_data.php và copy nội dung sau:

// Require Lib
require "database.php";
require "pagination.php";

// Lấy thong tin lọc
$username = isset($_GET['username']) ? $_GET['username'] : '';

// Lấy trang hiện tại
$page = isset($_GET['page']) ? $_GET['page'] : 1;

// Khởi tạo đối tượng Pagination mới
$pagination = new Pagination();

// Kết nối db
connect();

// Cấu hình thông số phân trang
$pagination->init(array(
    'current_page'  => $page,
    'total_record'  => count_all_member($username),
    'link_full'     => 'get_data.php?page={page}&username='.$username,
    'link_first'    => 'get_data.php'
));

// Lấy limit và Start
$limit = $pagination->get_config('limit');
$start = $pagination->get_config('start');

// Lấy danh sách người dùng
$data = get_all_member($username, $limit, $start);

// Ngắt kết nối
disconnect();

// Trả kết quả cho client
die (json_encode(array(
    'data' => $data,
    'paging' => $pagination->html()
)));

Các bạn lưu ý là tôi trả về kết quả là chuỗi JSON nên ở file xử lý ajax ta phải truyền dataType là JSON nhé.

5. Xây dựng trang hiển thị danh sách và search autocomplete

Bạn tạo file index.php và copy nội dung sau:

<!DOCTYPE html>
<html>
    <head>
        <title>Tìm kiếm theo ajax</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script language="javascript" src="http://code.jquery.com/jquery-2.0.0.min.js"></script>
        <style>
            html{width:900px; margin:0px auto}
            input{padding: 5px}
            li{float:left; margin: 3px; border: solid 1px gray;}
            a{padding: 5px;}
            span{display:inline-block; padding: 0px 3px; background: blue; color:white}
            li{list-style: none; padding: 0px 5px}
        </style>
        <script language="javascript">
            $(document).ready(function()
            {
                // Biến lưu trữ thông số tìm kiếm
                var data = {
                    username : '', // tên đăng nhập
                    page : 1 // trang cần đến, dùng trong trường hợp có phân trang
                };
                
                // Khi nhập dữ liệu thì gọi ajax
                $('#q').keyup(function(){   
                    data.username = $(this).val();
                    search();
                });
                
                // Hàm xử lý khi click vào phân trang
                $('#content').on('click', 'a', function (){
                   data.page = $(this).attr('title');
                   search();
                   return false;
                });
                
                // Hàm gửi ajax
                function search()
                {
                    $.ajax({
                        url : 'get_data.php',
                        data : data,
                        type : 'get',
                        dataType : 'json',
                        success : function (result)
                        {
                            // Nếu dữ liệu trả về đúng thì mới xử lý
                            if (result.hasOwnProperty('data') && result.hasOwnProperty('paging'))
                            {
                                var html = '<table border="1" cellspacing="0" cellpadding="5">';
                                    html += '<tr style="font-weight:bold">';
                                        html += '<td>Username</td>';
                                        html += '<td>Email</td>';
                                    html += '</tr>';
                                    
                                    // Lặp qua từng record và gán html
                                    $.each(result['data'], function (index, item){
                                        html += '<tr>';
                                            html += '<td>'+item.username+'</td>';
                                            html += '<td>'+item.email+'</td>';
                                        html += '</tr>';
                                    });

                                html += '</table>';
                                
                                // Thêm html paging
                                html += result['paging'];
                                
                                // Gán kết quả vào div#content
                                $('#content').html(html);
                            }
                        }
                    });
                }
            });
        </script>
    </head>
    <body>
        <input type="text" id="q" value="" placeholder="Nhập nội dung muốn tìm kiếm" size="50"/>
        <div id="content" style="margin-top: 20px;"></div>
    </body>
</html>

Trong file này thẻ div#content dùng để gán data vào, và tôi đã sử dụng hàm html() jquery để gán.

Trong đoạn ajax tôi có định nghĩa một hàm search dùng để gửi ajax và tôi cũng có gán 2 sự kiện khi keyUp vào ô search và khi click vào phân trang. Trong phần comment tôi đã trình bày rõ ràng rồi nên tôi cũng ko nói gì thêm nữa nhé.

6. Lời kết

Trong bài này quan trọng nhất là đoạn code xử lý jquery ajax search autocomplete, các bạn cố gắng gõ và làm theo nhé, copy nhưng cũng phải hiểu thì mới học tốt được. Còn một vấn đề tôi không trình bày, đó là khi ta nhập từ search nó gửi rất nhiều request, mỗi ký tự một request nên không được hay lắm, để giải quyết vấn đề này ta sẽ xây dựng hệ thống timer, nghĩa là khi người dùng dừng không gõ chừng 0.5 giây ta mới gửi, như vậy sẽ giảm được số request gửi lên server. Vấn đề này ta sẽ học ở bài tiếp they delay keyup event.

Hãy để lại link bài viết gốc khi chia sẻ bài viết này, mình sẽ report DMCA với những website lấy nội dung mà không để nguồn hoặc copy bài với số lượng lớn.

Nguồn: freetuts.net

Profile photo of adminTheHalfHeart

TheHalfHeart

Có sở thích viết tuts nên đã từng tham gia viết ở một số diễn đàn, đến năm 2014 mới có điều kiện sáng lập ra freetuts.net. Sinh năm 90 và có 1 vợ 2 con, thích ca hát và lập trình.

ĐĂNG BÌNH LUẬN: Đăng câu hỏi trên Group Facebook để được hỗ trợ nhanh nhất.