Vấn đề cần giải quyết: Group bài viết theo tiêu đề trong WordPress
Trong quá trình làm web WordPress, đôi khi bạn gặp yêu cầu hết sức đặc thù: cần hiển thị danh sách bài viết hoặc sự kiện được nhóm (group) theo một tiêu đề cụ thể, ví dụ như ngày tháng. Yêu cầu này tưởng đơn giản nhưng nếu dùng plugin hoặc code PHP trên server lại đòi hỏi nhiều công sức, đặc biệt khi bạn không có quyền truy cập trực tiếp vào theme files.
Đây là bài toán thực tế mà mình gặp phải từ một khách hàng. Ở mục “Sự kiện tuyển sinh” trên website, khách hàng muốn hiển thị danh sách sự kiện, trong đó các sự kiện cùng ngày được gom lại chung một nhóm. Ngày tháng chỉ cần hiển thị một lần duy nhất ở đầu nhóm, các sự kiện phía dưới không cần lặp lại ngày tháng nữa.

Hình trên là giao diện trước khi group. Các sự kiện hiển thị bình thường, ngày tháng lặp lại cho từng sự kiện. Mục tiêu là biến giao diện này thành dạng grouped như hình dưới đây:

Giải pháp bằng Javascript (jQuery)
Vì không thể sửa trực tiếp template PHP (hoặc không muốn vì lý do bảo trì), mình chọn giải pháp dùng Javascript chạy trên client-side. Cách tiếp cận này có ưu điểm: không cần đụng đến server code, dễ deploy qua Custom HTML widget hoặc Custom JS field trong theme builder.
Nguyên lý hoạt động
Ý tưởng rất đơn giản: duyệt qua tất cả các phần tử chứa tiêu đề (ngày tháng), so sánh với phần tử ngay trước nó. Nếu nội dung trùng nhau, nghĩa là đây là phần tử lặp lại, ta sẽ add một CSS class để ẩn nó đi. Chỉ giữ lại phần tử đầu tiên của mỗi nhóm.
Code Javascript (jQuery)
// Khởi tạo biến để lưu giá trị phần tử trước đó
var nextelm='', elm=null;
// Duyệt qua từng phần tử có class .event_date
jQuery('.event_date').each(function( index ) {
// Lấy nội dung text của phần tử ngày tháng
// Cấu trúc DOM có thể khác nhau tuỳ theme, anh em
// inspect element để xác định đúng đường dẫn
var event_date = jQuery(this).children().eq(0)
.children().eq(0)
.children().eq(0).text();
// Nếu ngày tháng này trùng với phần tử trước đó
if (event_date == nextelm) {
// Add class mustRemove vào phần tử bị trùng
jQuery(this).children().eq(0)
.children().eq(0)
.addClass('mustRemove');
}
// Cập nhật giá trị cho vòng lặp tiếp theo
nextelm = event_date;
elm = jQuery(this).children().eq(0).children().eq(0);
});
Một vài điểm cần lưu ý về đoạn code trên:
1. Cấu trúc DOM: Dòng jQuery(this).children().eq(0).children().eq(0).children().eq(0).text() phụ thuộc vào cấu trúc HTML của theme bạn đang dùng. Đây là cách duyệt DOM theo chỉ số index. Anh em cần inspect element (F12) trên trình duyệt để xác định đúng đường dẫn đến phần tử chứa text ngày tháng, sau đó chỉnh sửa số .children().eq(N) cho phù hợp.
2. Giá trị text: Phương thức .text() lấy toàn bộ text bao gồm cả khoảng trắng. Nếu có vấn đề về khoảng trắng thừa, hãy dùng $.trim(event_date) để loại bỏ khoảng trắng ở hai đầu trước khi so sánh.
CSS để ẩn phần tử trùng
.mustRemove {
display: none !important;
}
Đơn giản vậy thôi. Class mustRemove sẽ ẩn tất cả các phần tử ngày tháng bị trùng, chỉ giữ lại phần tử đầu tiên của mỗi nhóm. Trong quá trình development, bạn có thể thêm background: yellow; để highlight test, sau khi xác nhận đúng thì bỏ đi.
Hướng dẫn triển khai thực tế
Cách 1: Thêm qua Custom HTML Widget: Nếu bạn dùng theme có hỗ trợ Custom HTML widget, chỉ cần paste đoạn code Javascript (bọc trong thẻ <script>) vào widget ở khu vực muốn hiển thị list sự kiện. Đừng quên add CSS class mustRemove vào Appearance > Customize > Additional CSS.
Cách 2: Thêm vào functions.php: Dùng hook wp_footer để enqueue script vào footer của trang:
// Group by title bằng JS - add vào functions.php
function custom_group_by_script() {
// Chỉ load script khi cần thiết
if (is_page('su-kien-tuyen-sinh')) {
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
var nextelm = '';
$('.event_date').each(function( index ) {
var event_date = $.trim(
$(this).find('.date-text').text()
);
if (event_date == nextelm) {
$(this).find('.date-text')
.addClass('mustRemove');
}
nextelm = event_date;
});
});
</script>
<style>
.mustRemove { display: none !important; }
</style>
<?php
}
}
add_action('wp_footer', 'custom_group_by_script');
Cách 3: Dùng Elementor Custom HTML widget: Nếu bạn dùng Elementor page builder, kéo widget Custom HTML vào trang, paste đoạn code JS vào. Cách này phù hợp nhất khi bạn muốn kiểm soát chính xác trang nào cần script chạy.
Giải pháp PHP thay thế (server-side)
Nếu bạn có quyền chỉnh sửa template PHP, giải pháp server-side sẽ tốt hơn vì không phụ thuộc vào Javascript. Dưới đây là cách tiếp cận bằng PHP:
$args = array(
'post_type' => 'event',
'posts_per_page' => -1,
'meta_key' => 'event_date',
'orderby' => 'meta_value',
'order' => 'DESC'
);
$events = new WP_Query($args);
$current_date = '';
if ($events->have_posts()) :
while ($events->have_posts()) : $events->the_post();
$event_date = get_post_meta(
get_the_ID(), 'event_date', true
);
// Chỉ hiển thị ngày khi đổi nhóm
if ($event_date != $current_date) {
echo '<h3 class="date-group">'
. esc_html($event_date) . '</h3>';
$current_date = $event_date;
}
// Hiển thị nội dung sự kiện
echo '<div class="event-item">';
the_title();
echo '</div>';
endwhile;
endif;
wp_reset_postdata();
Giải pháp PHP này trực tiếp query data từ database và render HTML đúng cấu trúc group ngay từ đầu. Ưu điểm là nhanh, không cần Javascript, SEO-friendly hơn vì nội dung đã có sẵn trong HTML source. Nhược điểm là cần quyền truy cập file theme.
Lưu ý về hiệu năng và SEO
Hiệu năng: Đoạn Javascript chạy trên client nên không ảnh hưởng đến hiệu năng server. Tuy nhiên, với danh sách rất dài (hàng trăm item), việc duyệt DOM nhiều lần có thể gây chậm trang. Trong trường hợp đó, nên chuyển sang giải pháp PHP hoặc dùng document.querySelectorAll() thay vì jQuery để giảm overhead.
SEO: Vì Javascript chỉ ẩn phần tử bằng CSS (display: none) chứ không xóa khỏi DOM, các bộ máy tìm kiếm vẫn đọc được nội dung ngày tháng bị trùng. Điều này có thể gây duplicate content issue. Nếu SEO là ưu tiên, hãy dùng giải pháp PHP để render đúng cấu trúc ngay từ đầu.
Responsive: Đảm bảo đoạn Javascript hoạt động đúng trên cả desktop và mobile. Test trên nhiều trình duyệt và kích thước màn hình khác nhau, vì cấu trúc DOM có thể thay đổi khi theme responsive thay đổi layout.
Kết luận
Mẹo group by WP title bằng Javascript này là một giải pháp nhanh, gọn, phù hợp khi bạn không thể (hoặc không muốn) đụng vào server code. Dù không phải là cách tối ưu nhất về mặt kiến trúc, nhưng trong thực tế nó giải quyết được vấn đề ngay lập tức với ít công sức nhất.
Tuy nhiên, nếu dự án cần lâu dài và bền vững, mình khuyên anh em nên dành thời gian implement giải pháp PHP server-side. Nó sạch hơn, nhanh hơn, tốt hơn cho SEO và dễ maintain hơn. Đôi khi cái nhanh chưa chắc đã là cái tốt nhất.
Xem thêm: Cấu hình bộ search chỉ search tiêu đề không dùng plugin
Mình viết bài này cũng như bao bài khác trên blog – chủ yếu để ghi lại những lúc vấp váp với WordPress, plugin, hosting… Nên chắc chắn không phải dân làm web chuyên nghiệp. Nếu bạn đang cần một website đàng hoàng, có thể liên hệ TaigaWeb – mình quen từ lâu, họ xử lý gọn hơn mình nhiều.

