Chuyển đến nội dung chính

Hướng dẫn DataTable cơ bản

DataTable là gì

DataTables là một plug-in mã nguồn mở cho thư viện jQuery giúp nâng cao khả năng tương tác cho các bảng HTML thông thường. Nó cung cấp các tính năng nâng cao như phân trang, tìm kiếm tức thì và sắp xếp dữ liệu chỉ với một dòng mã JavaScript

Chuẩn bị môi trường

Để sử dụng DataTables, bạn cần tích hợp thư viện vào project. Bạn có thể sử dụng CDN để nhanh chóng bắt đầu.

Thêm vào file _Layout.cshtml hoặc trực tiếp vào View:

<link rel="stylesheet" href="https://cdn.datatables.net/2.3.7/css/dataTables.dataTables.min.css" />
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.datatables.net/2.3.7/js/dataTables.min.js"></script>

Cài đặt cơ bản

Giả sử bạn có table HTML với id = 'productTable' như sau:
<table id="productTable" class="display">
    <thead>
        <tr>
            <th>Name</th>
            <th>Price</th>
            <th>Category</th>
            <th>Stock</th>
            <th>Rating</th>
            <th>Status</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Apple</td>
            <td>$10</td>
            <td>Fruit</td>
            <td>150</td>
            <td>4.5</td>
            <td><span class="badge bg-success">In Stock</span></td>
        </tr>
        <tr>
            <td>Milk</td>
            <td>$20</td>
            <td>Dairy</td>
            <td>80</td>
            <td>4.8</td>
            <td><span class="badge bg-success">In Stock</span></td>
        </tr>
        <tr>
            <td>Orange</td>
            <td>$8</td>
            <td>Fruit</td>
            <td>200</td>
            <td>4.3</td>
            <td><span class="badge bg-success">In Stock</span></td>
        </tr>
        <tr>
            <td>Cheese</td>
            <td>$15</td>
            <td>Dairy</td>
            <td>45</td>
            <td>4.6</td>
            <td><span class="badge bg-warning">Low Stock</span></td>
        </tr>
        <tr>
            <td>Banana</td>
            <td>$5</td>
            <td>Fruit</td>
            <td>300</td>
            <td>4.4</td>
            <td><span class="badge bg-success">In Stock</span></td>
        </tr>
        <tr>
            <td>Yogurt</td>
            <td>$12</td>
            <td>Dairy</td>
            <td>120</td>
            <td>4.7</td>
            <td><span class="badge bg-success">In Stock</span></td>
        </tr>
        <tr>
            <td>Carrot</td>
            <td>$6</td>
            <td>Vegetable</td>
            <td>250</td>
            <td>4.2</td>
            <td><span class="badge bg-success">In Stock</span></td>
        </tr>
        <tr>
            <td>Butter</td>
            <td>$18</td>
            <td>Dairy</td>
            <td>60</td>
            <td>4.9</td>
            <td><span class="badge bg-success">In Stock</span></td>
        </tr>
        <tr>
            <td>Broccoli</td>
            <td>$7</td>
            <td>Vegetable</td>
            <td>100</td>
            <td>4.1</td>
            <td><span class="badge bg-danger">Out of Stock</span></td>
        </tr>
        <tr>
            <td>Cream</td>
            <td>$25</td>
            <td>Dairy</td>
            <td>35</td>
            <td>4.8</td>
            <td><span class="badge bg-warning">Low Stock</span></td>
        </tr>
        <tr>
            <td>Grapes</td>
            <td>$12</td>
            <td>Fruit</td>
            <td>180</td>
            <td>4.6</td>
            <td><span class="badge bg-success">In Stock</span></td>
        </tr>
        <tr>
            <td>Spinach</td>
            <td>$4</td>
            <td>Vegetable</td>
            <td>220</td>
            <td>4.3</td>
            <td><span class="badge bg-success">In Stock</span></td>
        </tr>
        <tr>
            <td>Cheese Spread</td>
            <td>$22</td>
            <td>Dairy</td>
            <td>50</td>
            <td>4.5</td>
            <td><span class="badge bg-warning">Low Stock</span></td>
        </tr>
        <tr>
            <td>Watermelon</td>
            <td>$15</td>
            <td>Fruit</td>
            <td>90</td>
            <td>4.7</td>
            <td><span class="badge bg-success">In Stock</span></td>
        </tr>
        <tr>
            <td>Tomato</td>
            <td>$5</td>
            <td>Vegetable</td>
            <td>310</td>
            <td>4.2</td>
            <td><span class="badge bg-success">In Stock</span></td>
        </tr>
    </tbody>
</table>
Việc kích hoạt DataTables chỉ tốn đúng 1 dòng code JavaScript.
$(document).ready(function () {
     $('#productTable').DataTable();
});

Load dữ liệu động với Ajax

Với các ứng dụng thực tế, việc tách biệt giao diện và dữ liệu thông qua Ajax sẽ giúp trang web tải nhanh hơn và trải nghiệm người dùng mượt mà hơn.

DataTables yêu cầu dữ liệu trả về thường nằm trong một object có chứa mảng (mặc định là thuộc tính data).

[HttpGet]
public IActionResult GetProducts()
{
    var products = new[] {
        new { id = 1, name = "Apple", category = "Fruit", price = 10m, stock = 150, ... },
        // ... các sản phẩm khác
    };

    // Phải bọc trong object { data = ... }
    return Json(new { data = products });
}
Chỉ định nghĩa Header cho table:
<table id="ajaxDataTable" class="display compact">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Category</th>
            <th>Price</th>
            <th>Stock</th>
            <th>Rating</th>
            <th>Status</th>
            <th>Date Added</th>
            <th>Supplier</th>
        </tr>
    </thead>
    <tbody>
    </tbody>
</table>
Load data:
$(document).ready(function () {
    var table = null;

    // Function to get status badge HTML
    function getStatusBadge(status) {
        var badgeClass = '';
        if (status === 'In Stock') {
            badgeClass = 'bg-success';
        } else if (status === 'Low Stock') {
            badgeClass = 'bg-warning';
        } else if (status === 'Out of Stock') {
            badgeClass = 'bg-danger';
        }
        return '<span class="badge ' + badgeClass + '">' + status + '</span>';
    }

    // Function to load data via Ajax
    function loadData() {
        $('#loadingIndicator').show();

        $.ajax({
            url: '/Home/GetProducts',
            type: 'GET',
            dataType: 'json',
            success: function (response) {
                if (table) {
                    table.destroy();
                }

                // Clear tbody
                $('#ajaxDataTable tbody').empty();

                // Populate table with data
                $.each(response.data, function (index, product) {
                    var row = $('<tr>').append(
                        $('<td>').text(product.id),
                        $('<td>').text(product.name),
                        $('<td>').text(product.category),
                        $('<td>').text('$' + product.price),
                        $('<td>').text(product.stock),
                        $('<td>').text(product.rating),
                        $('<td>').html(getStatusBadge(product.status)),
                        $('<td>').text(product.dateAdded),
                        $('<td>').text(product.supplier)
                    );
                    $('#ajaxDataTable tbody').append(row);
                });

                // Reinitialize DataTable
                table = $('#ajaxDataTable').DataTable({
                    paging: true,
                    pageLength: 10,
                    lengthChange: true,
                    searching: true,
                    ordering: true,
                    info: true,
                    responsive: false
                });

                $('#loadingIndicator').hide();
            },
            error: function (xhr, status, error) {
                console.error('Error loading data:', error);
                $('#loadingIndicator').hide();
                alert('Error loading data from server. Please try again.');
            }
        });
    }

    // Load data on page load
    loadData();

    // Reload button click handler
    $('#reloadBtn').click(function () {
        loadData();
    });
});

Tối ưu hóa với Server-side Processing

Chúng ta sẽ nâng cấp lên Server-side Processing: Client chỉ yêu cầu 10 dòng, Server chỉ trả về đúng 10 dòng đó

Cơ chế hoạt động

Khi bật chế độ serverSide: true, mỗi khi bạn chuyển trang, tìm kiếm hoặc sắp xếp, DataTables sẽ gửi một request kèm theo các tham số:

  • draw: Số thứ tự request (để tránh xử lý nhầm dữ liệu cũ).
  • start: Vị trí dòng bắt đầu (ví dụ: trang 2, mỗi trang 10 dòng thì start = 10).
  • length: Số lượng dòng cần lấy.
  • search[value]: Từ khóa tìm kiếm.

 Chúng ta xây dựng Search model:

public class DataTableRequest
{
    public int Draw { get; set; }
    public int Start { get; set; }
    public int Length { get; set; }
    public Search Search { get; set; } = new Search();
    public List<Order> Order { get; set; } = new List<Order>();
}
public class Search {
    public string Value { get; set; }
}
Hàm Get Product:
[HttpPost]
public IActionResult GetProductsServerSide([FromForm] DataTableRequest request)
{
    // Sample data source
    var allProducts = new List<ProductDto>
    {
        new ProductDto { Id = 1, Name = "Apple", Category = "Fruit", Price = 10m, Stock = 150, Rating = 4.5, Status = "In Stock", DateAdded = "2024-01-15", Supplier = "Fresh Farms Inc" },
        new ProductDto { Id = 2, Name = "Milk", Category = "Dairy", Price = 20m, Stock = 80, Rating = 4.8, Status = "In Stock", DateAdded = "2024-01-14", Supplier = "Dairy Corp" },
        //...
        new ProductDto { Id = 15, Name = "Potatoes", Category = "Vegetable", Price = 8m, Stock = 400, Rating = 4.5, Status = "In Stock", DateAdded = "2024-01-01", Supplier = "Root Vegetables Inc" },
        new ProductDto { Id = 16, Name = "Watermelon", Category = "Fruit", Price = 15m, Stock = 90, Rating = 4.7, Status = "In Stock", DateAdded = "2023-12-31", Supplier = "Melon Farms" },
        new ProductDto { Id = 17, Name = "Broccoli Sprouts", Category = "Vegetable", Price = 9m, Stock = 60, Rating = 4.2, Status = "In Stock", DateAdded = "2023-12-30", Supplier = "Sprout Factory" },
        new ProductDto { Id = 18, Name = "Cheddar Cheese", Category = "Dairy", Price = 22m, Stock = 50, Rating = 4.8, Status = "In Stock", DateAdded = "2023-12-29", Supplier = "Cheese Masters" },
        new ProductDto { Id = 19, Name = "Strawberry", Category = "Fruit", Price = 14m, Stock = 110, Rating = 4.6, Status = "In Stock", DateAdded = "2023-12-28", Supplier = "Berry Bliss" },
        new ProductDto { Id = 20, Name = "Kale", Category = "Vegetable", Price = 5m, Stock = 180, Rating = 4.3, Status = "In Stock", DateAdded = "2023-12-27", Supplier = "Green Leafy Co" }
    };

    var query = allProducts.AsQueryable();
    var totalRecords = allProducts.Count;

    // Search logic
    if (!string.IsNullOrEmpty(request.Search?.Value))
    {
        var searchValue = request.Search.Value.ToLower();
        query = query.Where(x => x.Name.ToLower().Contains(searchValue)
                              || x.Category.ToLower().Contains(searchValue)
                              || x.Supplier.ToLower().Contains(searchValue));
    }

    var filteredRecords = query.Count();

    // Sorting logic
    if (request.Order != null && request.Order.Count > 0)
    {
        var orderColumn = request.Order[0].Column;
        var orderDir = request.Order[0].Dir.ToLower() == "desc" ? 1 : 0;

        query = orderColumn switch
        {
            0 => orderDir == 1 ? query.OrderByDescending(x => x.Id) : query.OrderBy(x => x.Id),
            1 => orderDir == 1 ? query.OrderByDescending(x => x.Name) : query.OrderBy(x => x.Name),
            2 => orderDir == 1 ? query.OrderByDescending(x => x.Category) : query.OrderBy(x => x.Category),
            3 => orderDir == 1 ? query.OrderByDescending(x => x.Price) : query.OrderBy(x => x.Price),
            4 => orderDir == 1 ? query.OrderByDescending(x => x.Stock) : query.OrderBy(x => x.Stock),
            5 => orderDir == 1 ? query.OrderByDescending(x => x.Rating) : query.OrderBy(x => x.Rating),
            _ => query.OrderBy(x => x.Id)
        };
    }

    // Pagination
    var data = query.Skip(request.Start).Take(request.Length).ToList();

    var response = new DataTableResponse<ProductDto>
    {
        Draw = request.Draw,
        RecordsTotal = totalRecords,
        RecordsFiltered = filteredRecords,
        Data = data
    };

    return Json(response);
}
Update hàm lấy data từ FrontEnd, chú ý property: "serverSide": true
$(document).ready(function () {
    var table = $('#serverSideTable').DataTable({
        "processing": true,
        "serverSide": true,
        "ajax": {
            "url": "/Home/GetProductsServerSide",
            "type": "POST",
            "error": function (xhr, error, thrown) {
                console.error('Error:', error);
            }
        },
        "columns": [
            { "data": "id" },
            { "data": "name" },
            { "data": "category" },
            { "data": "price", "render": function (data) { return '$' + data.toFixed(2); } },
            { "data": "stock" },
            { "data": "rating" },
            { 
                "data": "status",
                "render": function (data) {
                    var badgeClass = '';
                    if (data === 'In Stock') {
                        badgeClass = 'bg-success';
                    } else if (data === 'Low Stock') {
                        badgeClass = 'bg-warning';
                    } else if (data === 'Out of Stock') {
                        badgeClass = 'bg-danger';
                    }
                    return '<span class="badge ' + badgeClass + '">' + data + '</span>';
                }
            },
            { "data": "dateAdded" },
            { "data": "supplier" }
        ],
        "pageLength": 10,
        "lengthChange": true,
        "searching": true,
        "ordering": true,
        "info": true,
        "paging": true,
        "language": {
            "processing": "Processing...",
            "paginate": {
                "first": "First",
                "last": "Last",
                "next": "Next",
                "previous": "Previous"
            }
        }
    });
});

 

Nhận xét

Bài đăng phổ biến từ blog này

[ASP.NET MVC] Authentication và Authorize

Một trong những vấn đề bảo mật cơ bản nhất là đảm bảo những người dùng hợp lệ truy cập vào hệ thống. ASP.NET đưa ra 2 khái niệm: Authentication và Authorize Authentication xác nhận bạn là ai. Ví dụ: Bạn có thể đăng nhập vào hệ thống bằng username và password hoặc bằng ssh. Authorization xác nhận những gì bạn có thể làm. Ví dụ: Bạn được phép truy cập vào website, đăng thông tin lên diễn đàn nhưng bạn không được phép truy cập vào trang mod và admin.

ASP.NET MVC: Cơ bản về Validation

Validation (chứng thực) là một tính năng quan trọng trong ASP.NET MVC và được phát triển trong một thời gian dài. Validation vắng mặt trong phiên bản đầu tiên của asp.net mvc và thật khó để tích hợp 1 framework validation của một bên thứ 3 vì không có khả năng mở rộng. ASP.NET MVC2 đã hỗ trợ framework validation do Microsoft phát triển, tên là Data Annotations. Và trong phiên bản 3, framework validation đã hỗ trợ tốt hơn việc xác thực phía máy khách, và đây là một xu hướng của việc phát triển ứng dụng web ngày nay.

Tổng hợp một số kiến thức lập trình về Amibroker

Giới thiệu về Amibroker Amibroker theo developer Tomasz Janeczko được xây dựng dựa trên ngôn ngữ C. Vì vậy bộ code Amibroker Formula Language sử dụng có syntax khá tương đồng với C, ví dụ như câu lệnh #include để import hay cách gói các object, hàm trong các block {} và kết thúc câu lệnh bằng dấu “;”. AFL trong Amibroker là ngôn ngữ xử lý mảng (an array processing language). Nó hoạt động dựa trên các mảng (các dòng/vector) số liệu, khá giống với cách hoạt động của spreadsheet trên excel.