Học Wordpress

Tự code chức năng phân trang bằng AJAX

209

Kỹ thuật AJAX (Asynchronous JavaScript and XML) bây giờ đã quá phổ biến trên website rồi, nó sẽ giúp việc truyền gửi dữ liệu từ backend ra frontend mà không cần tải lại trang. Hàng ngày bạn có thể sẽ thấy rất nhiều ví dụ về việc sử dụng AJAX, như việc bấm nút like hoặc gửi comment trên Facebook cũng đều là AJAX cả.

Trong bài viết này, mình sẽ hướng dẫn bạn cách làm quen với kỹ thuật AJAX áp dụng vào WordPress với một ví dụ phổ biến nhất là làm phân trang bằng AJAX. Từ ví dụ này, bạn có thể hiểu cách AJAX hoạt động trong WordPress để gửi dữ liệu về front-end.

Tải source

Sử dụng theme TwentyFifteen

Trong ví dụ này, mình sẽ sử dụng theme TwentyFifteen để các bạn dễ làm theo. Nhưng bạn vẫn có thể làm ở các theme khác vì chỉ cần tìm ra cái vùng chọn của liên kết chuyển trang mà thôi, nếu bạn custom lại từ một theme khác thì nên tạo child theme.

ajax-pagination-twentyfifteen

Tạo thư mục plugin

Bây giờ bạn hãy vào thư mục /wp-content/plugins/ và tạo thêm một thư mục với tên là ajax-pagination. Trong thư mục ajax-pagination, bạn tạo ra một file tên là plugin.php với nội dung sau:

<?php
	/*
	Plugin Name: AJAX Pagination
	Description: Phân trang website bằng AJAX
	Version: 1.0
	Author: Thach Pham
	Author URI: https://thachpham.com
	*/

Từ bây giờ, mọi code bằng PHP bạn sẽ đều viết trong file plugin.php này. Sau đó bạn có thể vào Plugins -> Installed Plugins rồi kích hoạt plugin này lên.

Nhúng file Javascript vào theme

Khi làm việc với AJAX chúng ta sẽ cần phải viết thêm một số đoạn Javascript nên sẽ cần tạo một file .js trong plugin rồi viết code cho nó tự nhúng vào theme. Bây giờ bạn hãy tạo một file tên ajax-pagination.js, sau đó viết thêm code này vào file plugin.php để chèn file này vào theme đang sử dụng tự động thông qua hàm wp_enqueue_script rồi móc vào hook wp_enqueue_scripts để nó thực thi.

/*
 @ ajax_pagination_scripts()
 @ Nhúng file ajax-pagination.js vào theme
 */
 add_action( ‘wp_enqueue_scripts’, ‘ajax_pagination_scripts’ );
 function ajax_pagination_scripts() {

/*
* Chèn file ajax-pagination.js vào frontend
*/
wp_enqueue_script( ‘ajax-pagination-script’, plugins_url( ‘/ajax-pagination.js’, __FILE__ ),
array( ‘jquery’ )
);
}


Về cách hoạt động của hook wp_enqueue_scripts và hàm wp_enqueue_script ra sao thì mình sẽ giải thích ở một bài khác. Nhưng trước hết bây giờ bạn có thể hiểu rằng với đoạn trên, nó sẽ tự động chèn một file ajax-pagination.js trong thư mục plugin hiện tại của nó nhờ hàm plugins_url(), cái array('jquery') có nghĩa là cho WordPress tự hiểu được script này sẽ sử dụng jQuery nhằm tự động chèn vào theme và luôn load sau jQuery để bị lỗi.

Nếu bạn cần chèn nhiều file Javascript khác nhau thì chỉ cần viết nhiều đoạn wp_enqueue_script() trong một hàm nào đó rồi móc vào hook wp_enqueue_scripts là được.

Bây giờ bạn hãy ra ngoài website kiểm tra qua mã nguồn HTML ngoài frontend của nó, file ajax-pagination.js đã được gọi ra thế này.

ajax-pagination-finish-addscript

Tìm class phân trang và viết event cho AJAX

Event cho AJAX nghĩa là một hành động nào đó mà chúng ta muốn dựa vào đó để kích hoạt việc gửi truy vấn, ở đây chúng ta sẽ sử dụng event click để gửi truy vấn khi click vào một vị trí nào đó. Và vị trí đó chúng ta sẽ xác định là các đường link bên trong thanh phân trang.

Bây giờ bạn hãy tìm đến thanh phân trang, ấn chuột phải vào đường link và chọn Inspect Element (hoặc Firebug) để xem vùng chọn khu vực đó, nó như thế này.

ajax-pagination-twentyfifteen-pagination

Bạn hãy để ý như trong hình, thanh phân trang sẽ được nằm trong một vùng chọn với class .nav-links. Sau đó các liên kết bên trong sẽ có class là .page-numbers. Riêng liên kết cho nút next thì sẽ có thêm class .next. Vậy nên để tạo ra event chính xác, chúng ta sẽ thiết lập một event click dựa trên thẻ a trong vùng chọn .nav-links (viết là .nav-links a).

Trước tiên hãy viết đoạn này vào file ajax-pagination.js để thiết lập event và thử nghiệm nó.

// chắc chắn là đang sử dụng chế độ No Conflict của jQuery, sử dụng jQuery() thay vì $()
jQuery.noConflict();

// load sau khi website được tải xong
jQuery( document ).ready( function($) {

 

$(document).on ( ‘click’, ‘.nav-links a’, function( event ) {
event.preventDefault();
// kiểm tra event click
alert( “Bạn vừa click vào phân trang đấy!” );
} ) // end event

 

} );


Và bây giờ hãy thử click vào phân trang đi xem có thấy cái bảng alert, nếu thấy thì thành công và xóa đoạn alert() trong code đi.

Thử nghiệm event

Thử nghiệm event

Sử dụng AJAX trong WordPress

Mặc định WordPress đã tích hợp sẵn các phương thức xử lý dữ liệu bằng AJAX, nên chúng ta có sử dụng thì chỉ cần gọi nó ra mà thôi. Để gọi nó ra, chúng ta sẽ tiến hành gọi file admin-ajax.php có trong WordPress ra với code sau (đặt bên trong hàm ajax_pagination_scripts mà ta đã khai báo ở trên):

 	/*
 	 * Gọi AJAX trong WordPress
 	 */
 	global $wp_query;
 	wp_localize_script( ‘ajax-pagination-script’, ‘ajax_object’, array(

// Các phương thức sẽ sử dụng
‘ajax_url’ => admin_url( ‘admin-ajax.php’ ),
‘query_vars’ => json_encode( $wp_query->query )

 

));


Hàm wp_localize_script() sẽ có tác dụng dò tìm và sử dụng một script nào đó đã có trong mã nguồn WordPress vì file admin-ajax.php đã có sẵn rồi nên không dùng wp_enqueue_script() như ở trên nữa. Trong đó, ajax_object là đối tượng mà chút nữa chúng ta sẽ sử dụng trong file .js để xác định đối tượng AJAX. Còn global $wp_query; là ta sẽ đưa đối tượng $wp_query ra toàn cục để có thể sử dụng trong file của plugin nhằm lấy dữ liệu của post.

Sau khi chèn xong, bạn kiểm tra source thì sẽ thấy một đoạn script được in ra như sau:

[html]
<script type=’text/javascript’>
/* <![CDATA[ */
var ajaxobject = {“ajax_url”:”http://domain/wp-admin/admin-ajax.php”};
/* ]]> */
</script>
[/html]

Bây giờ việc còn lại mà bạn cần làm ở bước này đó là viết Javascript để khai báo AJAX.

// chắc chắn là đang sử dụng chế độ No Conflict của jQuery, sử dụng jQuery() thay vì $()
jQuery.noConflict();

// load sau khi website được tải xong
jQuery( document ).ready( function($) {

 

$(document).on ( ‘click’, ‘.nav-links a’, function( event ) {
event.preventDefault();

 

// AJAX
$.ajax({
url: ajax_object.ajax_url,
type: ‘post’,
data: { action: ‘ajax_pagination_data’ },
success: function( ketqua ) {

 

// Thử nghiệm
var ketqua = “AJAX working”;
alert( ketqua );

 

}

 

})
} ) // end event

 

} );


Từ đoạn 10 đến đoạn 23 là những code mà chúng ta cần viết bổ sung vào file ajax-pagination.js, mình giải thích như sau:

  • $.ajax() – Đây là một hàm đặc biệt trong jQuery để xử lý AJAX đơn giản và gọn gàng hơn.
    • url: Đường dẫn của file mà các dữ liệu sẽ được gửi vào khi thực thi AJAX từ event click. ajax_object.ajax_url có nghĩa là đường dẫn của file admin-ajax.php mà ta đã khai báo ở file plugin.php.
    • type: loại phương thức gửi dữ liệu, sử dụng POST hay GET gì tùy bạn nhưng nếu chỉ là nhận dữ liệu thôi thì mình khuyến khích bạn dùng POST.
    • data: khai báo đối tượng chứa dữ liệu trả về sau khi gửi request từ AJAX. ajax_pagination_data nghĩa là cái hàm trả dữ liệu về, chút nữa chúng ta sẽ viết nó sau.
    • success: hành động sau khi AJAX truyền dữ liệu thành công, tạm ở ở đây sẽ kiểm tra bằng cách in một đoạn chữ ra để thử nghiệm. Nếu nó trả về được thì thành công.

Bây giờ lưu lại và hãy thử click vào cái link phân trang xem biến ketqua có trả về không, nếu có thì thành công.

ajax-pagination-test-ajax

Tạo hàm trả về dữ liệu từ WordPress

Như mình nói ở trên, tham số data là sẽ chứa đối tượng trả kết quả về, nên bây giờ chúng ta sẽ xây dựng đối tượng ajax_pagination_data để trả về cho user khi họ truyền dữ liệu về. Trước tiên, bạn sẽ cần sửa lại file ajax-pagination.js thành như sau:

// chắc chắn là đang sử dụng chế độ No Conflict của jQuery, sử dụng jQuery() thay vì $()
jQuery.noConflict();

// load sau khi website được tải xong
jQuery( document ).ready( function($) {

 

/*
@ hàm xác định số trang sẽ được tải
@ bằng cách bóc tách số trong chuỗi dữ liệu
*/
function set_page( element ) {
element.find(‘span’).remove();
return parseInt( element.html() );
}

 

$(document).on ( ‘click’, ‘.nav-links a’, function( event ) {
event.preventDefault();
page = set_page( $(this).clone() );

 

// AJAX
$.ajax({
url: ajax_object.ajax_url,
type: ‘post’,
data: {
action: ‘ajax_pagination_data’,
query_vars: ajax_object.query_vars,
page: page
},
beforeSend: function() {
/*
@ Tạo các hiệu ứng trước khi request gửi đi
*/
$( ‘#main’ ).find( ‘article’ ).remove();
$( ‘#main nav’ ).remove();
$( ‘#main’ ).scrollTop(0);
$( ‘#main’ ).append( ‘<div id=”loading”>Đang lấy dữ liệu bài viết</div>’ );
},
success: function( ketqua ) {
/*
@ Xóa nút loading
@ và khôi phục lại dữ liệu trả về
*/
$( ‘#main’ ).find( ‘#loading’ ).remove();
$( ‘#main’ ).append( ketqua );
console.log(page);

 

}

 

})
} ) // end event

 

} );


Ở đoạn code mới, chúng ta có thêm một hàm mới mang tên set_page() trong Javascript. Hàm này sẽ có tác dụng tìm kiếm tất cả nội dung trong thẻ span của tài liệu. Sau đó tiến hành dùng hàm parseInt() trong Javascript để tách số trong chuỗi dữ liệu tìm thấy. Mục đích của nó là để chúng ta lấy được con số của trang trong các đoạn text trên website, ví dụ trên thanh pagination sẽ có các số 1,2,3,4,…nó sẽ lấy các số này để làm giá trị của tham số page nhằm giúp query của WordPress hiểu được sẽ cần lấy dữ liệu của trang nào.

Chúng ta sẽ khai báo thêm tham số query_varspage vốn sẽ được dùng trong việc gửi truy vấn trong WordPress để lấy danh sách post. Tham số query_vars được xem là một phương thức của đối tượng ajax_object mà chúng ta đã khai báo ở file plugin.php. Còn tham số page là nó sẽ sử dụng giá trị trả về của hàm set_page() ta tạo ở trên.

Kế tiếp là tham số beforeSend để thêm cái gì đó trước khi dữ liệu của truy vấn gửi đi. Chúng ta sẽ dùng để xóa các post (thẻ aritlce trong #main của TwentyFifteen), xóa thẻ khung pagination đang có (thẻ nav có class pagination trong #main của TwentyFifteen) và cuối cùng là thêm vào dữ liệu nhận được vào trong #main. Và quan trọng là tự động nhảy lên đầu website với phương thức scrollTop và in một đoạn nội dung cho người dùng biết là dữ liệu đang được tải.

Cuối cùng là tham số success sẽ trả kết quả về sau khi gửi truy vấn AJAX đến WordPress vì tham số ketqua WordPress tự hiểu được đó là hàm hứng dữ liệu trả về. Đồng thời xóa cái chữ Đang lấy dữ liệu bài viết kia đi.

Mình cũng xin lưu ý như sau:

  • #main – vùng chọn của khung hiển thị danh sách các post.
  • article – vùng chọn riêng lẻ của post, bạn có thể dùng class .post cũng được.
  • nav.pagination – vùng chọn cho khung phân trang.

Và tiếp theo là tạo hàm để trả dữ liệu về cho AJAX với hook là tên action wp_ajax_ajax_pagination_data, trong đó ajax_pagination là tên action mà ta đã khai báo ở ajax-pagination.js, còn wp_ajax là tiền tố bắt buộc cho hook khi làm việc với AJAX trong WordPress.

/*
 @ Hàm chứa dữ liệu trả về
 */
add_action( ‘wp_ajax_nopriv_ajax_pagination_data’, ‘set_ajax_pagination_data’ );
add_action( ‘wp_ajax_ajax_pagination_data’, ‘set_ajax_pagination_data’ );
function set_ajax_pagination_data() {

$query_vars = json_decode( stripslashes( $_POST[‘query_vars’] ), true );

 

$query_vars[‘paged’] = $_POST[‘page’];

 

$posts = new WP_Query( $query_vars );
$GLOBALS[‘wp_query’] = $posts;

 

if( ! $posts->have_posts() ) {
get_template_part( ‘content’, ‘none’ );
}
else {
while ( $posts->have_posts() ) {
$posts->the_post();
get_template_part( ‘content’, get_post_format() );
}
}

 

the_posts_pagination( array(
‘mid_size’ => 5,
‘prev_text’ => __( ‘Previous page’, ‘twentyfifteen’ ),
‘next_text’ => __( ‘Next page’, ‘twentyfifteen’ ),
‘before_page_number’ => ‘<span class=”meta-nav screen-reader-text”>’ . __( ‘Page’, ‘twentyfifteen’ ) . ‘ </span>’,
) );

 

die();

 

}


Ở đoạn trên, đầu tiên là chúng ta chuyển dữ liệu ở biến $_POST['query_vars'] được trả về bởi AJAX vì mặc định hàm $.ajax() của jQuery sẽ trả dữ liệu về là kiểu JSON và lưu vào biến $query_vars.

Kế tiếp, chúng ta đưa giá trị của $_POST['page'] khi được AJAX gửi về vào biến $query_vars['paged'] để thiết lập phân trang cho query.

Cuối cùng là ta tạo một đối tượng $posts từ biến $query_vars ở trên và gọi template content.php ra để hiển thị nội dung. Trong theme TwentyFifteen thì file này sẽ chứa các đoạn code hiển thị từng thành phần của một post và vòng lặp cứ thế lặp đi lặp lại. Đồng thời chèn thêm một thanh phân trang mới với hàm the_posts_pagination() và kết thúc quá trình cho đến khi nó đã hoàn tất việc lặp.

Bây giờ bạn có thể lưu lại và ra ngoài thử click vào liên kết chuyển trang ở các con số trong phần phân trang để xem nó có hoạt động hay không, nếu bạn làm từ đầu bài tới giờ thì nó sẽ hoạt động được.

Cũng nên lưu ý là kể từ WordPress 4.1 trở đi thì hàm the_posts_pagination() sẽ hiển thị thanh phân trang dạng số rồi.

Vấn đề gặp phải trong tutorial này

Thực ra tutorial này mình đang gặp phải một vấn đề là nếu bạn nhấp vào liên kết dạng số trên phân trang thì nó sẽ làm việc được, nhưng nếu bạn click vào nút Next hay đại loại như vậy thì nó sẽ trả về trang chủ. Bởi vì theo tutorial này thì dữ liệu từng trang của WordPress sẽ được lấy nhờ vào biến $query_vars['paged'] mà nó hứng được từ dữ liệu mà jQuery gửi về bằng AJAX, trong khi đó ở file Javascript mình đã xác định rằng số trang sẽ được tìm bằng cách dùng hàm parseInt() trong Javascript để tách số trên thẻ <span>, vốn là liên kết của từng trang trong phân trang. Nhưng nếu thẻ <span> đó có nội dung là chữ Next thì dĩ nhiên nó sẽ trả về giá trị là “NaH”, tương ứng với việc không có dữ liệu kiểu số nào, thành ra sẽ không hoạt động được.

Còn cách làm phân trang kiểu Next, Prev thì hiện nay có rất nhiều tutorial nên bạn có thể tìm trên Google.

Chúc các bạn thành công!