Trong các bài trước, chúng ta đã làm quen với ô Search tổng quát. Tuy nhiên, khi dữ liệu trở nên phức tạp, người dùng thường có nhu cầu lọc chính xác theo từng trường thông tin (ví dụ: chỉ xem sản phẩm "Hết hàng" hoặc thuộc loại "Điện tử").
Hôm nay, mình sẽ hướng dẫn các bạn cách tạo bộ lọc dạng Popup Dropdown ngay trên Header của DataTable
Chúng ta sẽ sử dụng Icon tam giác và Popup Menu của Bootstrap
graph LR
subgraph "Thẻ TH (Table Header Cell)"
Title[Text: Category] --- Icon[Icon: bi-caret-down-fill]
end
Icon -.-> Menu[Dropdown Menu - Absolute Position]
subgraph "Dropdown Menu Items"
Option1[Tất cả]
Option2[Giá trị 1]
Option3[Giá trị 2]
end
Menu --- Option1
Menu --- Option2
Menu --- Option3
Cài đặt giao diện CSS
Tham khảo: https://getbootstrap.com/docs/4.0/components/dropdowns/Cấu Trúc HTML Dropdown
<div class="dropdown d-inline-block filter-container ms-2">
<!-- Icon filter (người dùng click vào đây) -->
<i class="bi bi-caret-down-fill filter-icon"
data-bs-toggle="dropdown"
aria-expanded="false"
title="Filter by Category">
</i>
<!-- Dropdown menu chứa các option -->
<ul class="dropdown-menu shadow-lg" id="filter-category">
<li><a class="dropdown-item filter-option" href="#" data-value="">All Category</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item filter-option" href="#" data-value="Fruit">Fruit</a></li>
<li><a class="dropdown-item filter-option" href="#" data-value="Dairy">Dairy</a></li>
</ul>
</div>Giải thích các class:
dropdown- Container của dropdown (Bootstrap)d-inline-block- Hiển thị inline để không chiếm full widthfilter-container- Class custom để styling và ngăn chặn event propagationms-2- Margin-start từ Bootstrap (margin left = 8px)filter-icon- Class custom cho icondropdown-menu- Menu dropdown từ Bootstrapshadow-lg- Drop shadow lớn (Bootstrap utility)dropdown-item- Item trong menu (Bootstrap)`filter-option- Class custom để handle click eventdropdown-divider- Dòng ngăn cách (Bootstrap)
Filter Icon Styling
/* Container chứa tiêu đề và icon */
.filter-container {
display: flex;
align-items: center;
justify-content: space-between;
cursor: default;
}
/* Icon tam giác nhỏ */
.filter-icon {
font-size: 0.7rem;
cursor: pointer;
margin-left: 8px;
color: #e0e0e0; /* Mờ khi chưa active */
transition: 0.3s;
}
.filter-icon:hover, .filter-active {
color: #ffc107 !important; /* Vàng rực khi đang có filter */
}
/* Menu popup */
.dropdown-menu {
max-height: 300px;
overflow-y: auto;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
Triển khai JavaScript (The Logic)
Chúng ta sẽ sử dụng sự kiện initComplete để inject HTML vào Header sau khi table đã khởi tạo xong.initComplete: function () {
this.api().columns().every(function () {
var column = this;
var header = $(column.header());
// Chỉ áp dụng lọc cho một số cột nhất định (ví dụ index 2 và 6)
if (header.text() === 'Category' || header.text() === 'Status') {
// Tạo cấu trúc Dropdown
var $filterWrapper = $(`
<div class="dropdown d-inline-block shadow-none">
<i class="bi bi-caret-down-fill filter-icon" data-bs-toggle="dropdown" aria-haspopup="true"></i>
<ul class="dropdown-menu">
<li><a class="dropdown-item filter-option" href="#" data-value="">(Tất cả)</a></li>
<li><hr class="dropdown-divider"></li>
</ul>
</div>
`);
// Chèn vào cạnh Text của Header
header.wrapInner('<span class="column-title"></span>').append($filterWrapper);
// Lấy dữ liệu Unique để đổ vào menu
column.data().unique().sort().each(function (d) {
if (d) {
$filterWrapper.find('.dropdown-menu').append(
`<li><a class="dropdown-item filter-option" href="#" data-value="${d}">${d}</a></li>`
);
}
});
// Xử lý sự kiện Filter
$filterWrapper.find('.filter-option').on('click', function (e) {
e.preventDefault();
var val = $(this).data('value');
// Đổi màu icon để báo hiệu đang filter
var icon = $filterWrapper.find('.filter-icon');
val !== "" ? icon.addClass('filter-active') : icon.removeClass('filter-active');
// Search chính xác (Exact Match) bằng Regex
column.search(val ? '^' + $.fn.dataTable.util.escapeRegex(val) + '$' : '', true, false).draw();
});
// Quan trọng: Ngăn chặn Sort khi click vào Icon/Menu
$filterWrapper.on('click', function (e) {
e.stopPropagation();
});
}
});
}
Exact Match (^...$$): Khi lọc "Fruit", nếu không dùng Regex, DataTable có thể hiển thị cả "Fruit Juice". Việc bọc giá trị trong ^ (bắt đầu) và $ (kết thúc) giúp lọc chính xác 100%.
Lưu ý khi dùng với Server-side Processing
Khi bạn bật serverSide: true, dữ liệu trả về mỗi lần chỉ có 10 dòng. Do đó,column.data().unique() sẽ chỉ hiện 10 giá trị đó.
Trong trường hợp này, hãy tạo một API riêng ở Backend trả về danh mục (Categories/Statuses) đầy đủ, sau đó dùng Ajax để đổ vào menu filter thay vì quét dữ liệu tại chỗ.
Source code: DataTable-demo
Nhận xét
Đăng nhận xét