iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 27
0
自我挑戰組

WordPress 客製化從 0 開始系列 第 27

Day 27 WordPress 客製化:用 AJAX 製作無限捲動

大家好,我是 Eric。

昨天我們介紹用過勾點 (hook) 的機制,製作 Contact Form 7 的擴充功能。今天我們將進一步介紹透過 WordPress 內建的方法,製作 AJAX 的無限捲動 (infinite scroll) 功能。

有關 WordPress AJAX 的作法,除了這篇文章外,也可以參考社群夥伴阿峻的〈開發 AJAX 功能的正確流程〉一文。

WordPress AJAX 勾點

要透過 WordPress 製作 AJAX 端點的方法有 wp_ajax_{動作函式}wp_ajax_nopriv_{動作函式} 兩種,作法如下:

/* 讓已登入使用可以存取 AJAX */
add_action( 'wp_ajax_eric_load_more', 'eric_load_more' ); 
/* 讓未登入使用可以存取 AJAX */
add_action( 'wp_ajax_nopriv_eric_load_more', 'eric_load_more' );
function eric_load_more() {
    /* 回傳錯誤 */
    wp_send_json_error( array(
        'code'    => 500, // 錯誤代碼
        'data'    => '',
        'msg'     => '',
        ) 
    );

    /* 回傳正確資料 */
    wp_send_json_success( array(
        'code'    => 200,
        'data'    => '{{回傳資料}}',
        )
    );
}

實作 AJAX 無限捲動

了解文章列表的 HTML 架構

在開始製作 AJAX 端點之前,因為我們會將查詢後的結果附加在現有文章列表下,因此了解文章列表的結構,對於後續拼裝 HTML 很重要。我們一樣以 Twenty Twenty 為例,我們可以從 template-parts/content.phptemplate-parts/entry-header.php 找到它的架構。

製作 AJAX 的基本端點

add_action( 'wp_ajax_eric_load_more', 'eric_load_more' ); 
add_action( 'wp_ajax_nopriv_eric_load_more', 'eric_load_more' );
function eric_load_more() {
    $nonce           = $_POST['nonce'];
    $max_num_pages   = $_POST['max_num_pages'];
	$current_page    = $_POST['current_page'];
	$found_posts     = $_POST['found_posts'];
	$post_type       = $_POST['post_type']; //可以擴展自訂內容類型
    $category        = $_POST['category']; //用於文章時可以支援同分類文章查詢
    
    /* 如果無法通過 nonce 驗證,則顯示系統錯誤 */
    if ( ! wp_verify_nonce ( $nonce, 'ajax-nonce' ) ) {
        wp_send_json_error( array(
            'code'    => 500, // 錯誤代碼
            'data'    => '',
            'msg'     => 'Nonce 驗證失敗',
            )
        );
    }
    
    /* 如果沒有回傳必要的參數,則顯示錯誤 */
    if ( ! isset( $max_num_pages ) || $max_num_pages == '' ||
         ! isset( $current_page )  || $current_page  == '' ||
         ! isset( $found_posts )   || $found_posts   == '' ||
         ! isset( $post_type )     || $post_type     == '' ) {
         wp_send_json_error( array(
            'code'    => 500, // 錯誤代碼
            'data'    => '',
            'msg'     => '未回傳必要的參數',
            )
        );
    }
    
    /* 開始製作自訂迴圈 */
    $args = array(
        'post_type'      => $post_type,
        'paged'          => intval( $current_page ) + 1,
        'category_name'  => $category,
        'post_status'    => 'publish',
    );
    
    $the_query = new WP_Query( $args );
    
    $output = '';
    
    while( $the_query -> have_posts() ){
        $output .= '<hr class="post-separator styled-separator is-style-wide section-inner" aria-hidden="true" />'; //因為 Twenty Twenty 會加入分隔線,所以先接上
        $the_query -> the_post();

        /* 參考 template-parts/content.php */
        $output .= '<article class="' . implode( ' ', get_post_class() ) . '" id="post-' . get_the_ID() . '">';

        /* 參考 template-parts/header.php */
        $output .= '<header class="entry-header has-text-align-center">';
        $output .= '<div class="entry-header-inner section-inner medium">';
        $show_categories = apply_filters( 'twentytwenty_show_categories_in_entry_header', true );
        if ( true === $show_categories && has_category() ) {
            $output .= '<div class="entry-categories">';
            $output .= '<span class="screen-reader-text">' . __( 'Categories', 'twentytwenty' ) . '</span>';
            $output .= '<div class="entry-categories-inner">';
			$output .= get_the_category_list( ' ' );
			$output .= '</div><!-- .entry-categories-inner -->';
            $output .= '</div><!-- .entry-categories -->';
        }
        $output .= '<h2 class="entry-title heading-size-1"><a href="' . esc_url( get_permalink() ) . '">' . get_the_title() . '</a></h2>';
        $output .= twentytwenty_get_post_meta( get_the_ID(), 'single-top' );
        $output .= '</div><!-- .entry-header-inner -->';
        $output .= '</header>';
        $is_thin = is_page_template( 'templates/template-full-width.php' ) ? '' : 'thin';
        $output .= '<div class="post-inner ' . $is_thin . '">';
        $output .= '<div class="entry-content">';
        $output .= get_the_content( __( 'Continue reading', 'twentytwenty' ) );
        $output .= '</div><!-- .entry-content -->';
        $output .= '</div><!-- .post-inner -->';
        $output .= '<div class="section-inner">';
        $output .= twentytwenty_get_post_meta( get_the_ID(), 'single-bottom' );
        $output .= '</div><!-- .section-inner -->';
        $output .= '</article><!-- .post -->';
    }
    
    wp_reset_postdata();
    wp_send_json_success( array(
        'code' => 200, 
        'data' => $output
        )
    );
}

引入 JavaScript 與 wp_localize_script

在了解 AJAX 的勾點後,我們要透過 jQuery 來存取這個 AJAX 端點。首先,我們先在子佈景主題中,建立 assets/js 的資料夾,並在其中新增 infinite-scroll.js 這個檔案。接著在子佈景主題的 functions.php 或 Code Snippets 中,插入以下程式碼,引用 infinite-scroll.js 這個檔案:

add_action( 'wp_enqueue_scripts', 'eric_load_more_scripts' );
function eric_load_more_scripts() {
    wp_enqueue_script( 'load-more', get_stylesheet_directory_uri() . '/assets/js/infinite-scroll.js', array( 'jquery' ) );
    /* 用 wp_localize_script 將 JavaScript 檔案中需要用到的物件傳送過去 */
    /* wp_localize_script( '處理常式 handler 名稱', 'JavaScript 物件名稱', array(
     *   '物件屬性'    => '屬性值',
     *  '物件屬性2'   => '屬性值',
     *  ) ); 
     */
    wp_localize_script( 'load-more', 'inf', array(
        /* WordPress AJAX 的統一端點是 admin-ajax.php */
        'ajaxurl'    => admin_url( 'admin-ajax.php' );
        'nonce'      => wp_create_nonce( 'ajax-nonce' ); //這個 nonce 的名稱要與 eric_load_more() 中一致
        'msg'        => array(
            'query_end'      => '查詢完畢',
        );
    ) );
}

用 jQuery 呼叫 AJAX

假設我們要透過連結來自訂「繼續載入」的按鈕,我們可以在子佈景主題中的 index.php 中加入:

if ( is_home() ) { ?>
    <a href="#" class="button load-more">繼續載入</a>
<?php }

接著,我們在 infinite-scroll.js 這個檔案中加入以下的 JavaScript 程式碼:

(function($){
    $( document ).ready( function(){
        $( 'a.load-more' ).click( function(e){
            e.preventDefault();
            $( this ).attr( 'disabled' , true);
            var that = $( this );
            if ( inf.posts.max_num_pages == inf.posts.current_page ) {
                $( this ).text( inf.msg.query_end );
                $( this ).unbind( 'click' );
                return;
            }
            
            var data = {
                'action'       : 'eric_load_more', //記得檢查 action 有沒有下對
                'nonce'        : inf.nonce,
                'max_num_pages': inf.posts.max_num_pages,
                'current_page' : inf.posts.current_page,
                'found_posts'  : inf.posts.found_posts,
                'post_type'    : inf.posts.post_type,
                'category'     : inf.posts.category,
            };

            $.post( inf.ajaxurl, data, function( res ) {
                if ( res.success ) {
                    $( '#site-content' ).append( res.data.data );
                    inf.posts.current_page += 1;
                    that.attr('disabled', false);
                } else {
                    alert('Oops! Sorry error occurred!');
                }
            }).fail(function (xhr, status, error) {
                alert('Oops! Sorry error occurred! Internet issue.');
            });
        });
    });
}(jQuery));

定義 inf.posts

看到這裡,可能會注意到 inf 這個物件下還有 posts 這個屬性,因此我們需要再額外定義這個屬性。我們再回到 index.php,加入以下程式碼:

/* index.php Line 20*/
<main id="site-content" role="main">
    <?php if ( is_home() || is_category() : ?>
    <script>
		if ( typeof(inf) === undefined ){
			var inf = {};	
		}

		<?php
		$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
		$postsparam = array(
			'post_type'        => get_post_type(),
			'category'         => $wp_query -> get_queried_object() -> slug,
			'max_num_pages'    => $wp_query -> max_num_pages,
			'found_posts'      => $wp_query -> found_posts,
			'current_page'     => $paged,
		);
		?>
		inf.posts = <?php echo json_encode( $postsparam ) ?>;
	</script>
    <?php endif; ?>
    <?php

結語

今天介紹了如何透過 AJAX 在 Twenty Twenty 的子佈景主題文章列表頁中,加入了無限捲動的功能。
明天,我們會再介紹如何將自己的佈景主題加上多語系支援,讓佈景主題或外掛的字串可以供其他人翻譯。


上一篇
Day 26 WordPress 客製化實務:替 Contact Form 7 加上地址欄位
下一篇
Day 28 幫 WordPress 佈景主題與外掛加上多國語系支援
系列文
WordPress 客製化從 0 開始30

尚未有邦友留言

立即登入留言