WordPressのアーカイブの記事一覧をAjaxを使って追加形式でロードしてみる
WordPressのワンオフテーマで、Ajaxを使ってボタンを押したら次の記事一覧を表示させたいというような仕様を相談された。
タイトルの通り、久し振りにWordPress関係の記事になります。
あ、Ajaxのロードなんてプラグイン使えば余裕でしょ(草)となしで。
※いちいちテーマに直接書くのが面倒くさくなったので、大凡の動きを踏襲し、新たにコードを作成してプラグインを作りました。興味のある方はこちらからどうぞ。
ただ、一応のところプラグインを使わずにAjaxでの記事一覧を読み込みをしたい人向けにここのコードは残しておきます。
俺も最初、相談された内容ならそういうプラグインってあるんじゃね?とか思いながら適当に探してみた。
・・・が、この手のAjaxのWordPressプラグインって意外とヒットしてこない。
探し方が悪いのか、はたまた、公式プラグインの数が限られているからなのか・・・。
まぁ、記事一覧の表示形式ってテーマごとに変わってくるから、この手のものは公式プラグインとして作りづらいのかな、とは思った。
ただ、WordPressのAjaxによる記事一覧出しはわざわざプラグインを入れるほどのもんでもない。
基本的なテーマの作り方と、少しのJavaScriptとjQueryの知識があれば、割とかんたんに実装できる。
そうしてフォームから送信されたデータをAjax使ってコマネチは既にやったことあるけど、この手のことはまだノータッチだったから、試しに作ってみっか!ということであまり時間と労力を書けずに原型を作成してみた。
たたき台として作っているので色々不十分だから、そこら辺はあとで加筆・・・するかもしれないし、しないかもしれない。
で、とりあえず今回やること。
- 記事一覧の下にmoreボタンを設置。押したときに動作させる。
- 初回表示の記事一覧の下に、次の記事一覧ページに相当する記事のデータを読んでくる。
- 2ページ以降にアクセスされた場合は、1ページめ+2ページめの記事一覧をガッツリ出す。
これらを行うものとして、コードは以下となる。
functions.php
function theme_replace_paged_number(){ $num = get_query_var('paged') == 0 ? 1 : get_query_var('paged'); return $num; } function theme_ajax_load_posts(){ theme_ajax_load_posts_add_posts(); die(); } add_action('wp_ajax_theme_ajax_load_posts', 'theme_ajax_load_posts'); add_action('wp_ajax_nopriv_theme_ajax_load_posts', 'theme_ajax_load_posts'); function theme_ajax_load_posts_add_posts(){ $hold_query = json_decode(stripslashes($_POST['hold_query']), true); $request_paged = $_POST['request_paged']; $args = array( 'posts_per_page' => get_option('posts_per_page'), 'paged' => $request_paged, ); $args = array_merge($hold_query, $args); query_posts($args); // 記事一覧のコンテントのループ if ( have_posts() ) : while ( have_posts() ) : the_post(); get_template_part( 'template-parts/post/content', get_post_format() ); endwhile; else : get_template_part( 'template-parts/post/content', 'none' ); endif; wp_reset_query(); } function theme_ajax_load_posts_direct_access(){ global $wp_query; $args = array( 'posts_per_page' => get_option('posts_per_page') * theme_replace_paged_number(), ); $args = array_merge($wp_query->query, $args); query_posts($args); // 記事一覧のコンテントのループ if ( have_posts() ) : while ( have_posts() ) : the_post(); get_template_part( 'template-parts/post/content', get_post_format() ); endwhile; else : get_template_part( 'template-parts/post/content', 'none' ); endif; wp_reset_query(); } function theme_ajax_load_posts_script(){ ?> <script type="text/javascript"> var theme_ajax_load_vars = { url: '<?php echo admin_url('admin-ajax.php'); ?>', next_paged: <?php echo theme_replace_paged_number() + 1; ?>, max_page: <?php global $wp_query; echo $wp_query->max_num_pages; ?>, query_json: '<?php global $wp_query; echo json_encode($wp_query->query); ?>' }; </script> <?php }
theme_ajax_load_posts.js
(function($){ $.fn.ajaxLoadPosts = function(options){ var option = { appendOf: '#element', }; var config = $.extend(option, options); var inc = $(this); var repush = true; var ajax_load_posts = (function(){ return{ init: function(){ this.clip(inc); }, clip: function(button){ button.bind('click', function(){ if(repush){ // ボタンにロード中を入れる場合、ここで開始時のクラスの付与除去行う ajax_load_posts.post(); } return false; }); }, post: function(){ if(theme_ajax_load_vars.next_paged <= theme_ajax_load_vars.max_page){ repush = false; $.ajax({ type: 'POST', url: theme_ajax_load_vars.url, data: { action: 'theme_ajax_load_posts', hold_query: theme_ajax_load_vars.query_json, request_paged: theme_ajax_load_vars.next_paged }, success: function(result){ $(config.appendOf).append(result); // たぶんこのへんでブラウザURL書き換え // ボタンにロード中を入れた場合、ここで終了時の付与除去行う theme_ajax_load_vars.next_paged = theme_ajax_load_vars.next_paged + 1 // 加算処理より、ここらへんでボタンを消す条件 repush = true; } }); } } }; })(); ajax_load_posts.init(); return(this); }; })(jQuery);
header.php
<head> ~~~ <?php wp_head(); ?> <!--↓wp_enqueue_scriptsにフックすべきだけど面倒くさいので直書き--> <script type="text/javascript" src="jquery.js"></script> <!--↓wp_enqueue_scriptsにフックすべきだけど面倒(ry--> <script type="text/javascript" src="theme_ajax_load_posts.js"></script> <!--↓wp_headにフックすべきでも面d(ry--> <?php theme_ajax_load_posts_script(); ?> <!--↓たぶんどこでもOK--> <script type="text/javascript"> (function($){ $(document).ready(function(){ $('#load-posts').ajaxLoadPosts({appendOf: '#main'}); }); })(jQuery); </script> ~~~ </head>
index.php、archive.php
<main id="main" class="site-main" role="main"> <?php custom_ajax_load_posts_direct_access(); ?> </main> <a href="#" id="load-posts"><?php _e('More posts'); ?></a>
仕組みとしてはまんまソースコードだけど、
- functions.phpのtheme_replace_paged_numberは、単なるページ番号ゼロの振替え。
- functions.phpのtheme_ajax_load_posts_direct_accessでは、直接アクセスがあった際の出力。
wp_queryがスタックしているクエリデータを元に、記事一覧を1ページめの内容から当該のページまでクエリ内容に持たせて表示させる。
(書くのが面倒だったのでquery_postsでコピペのwhileを回しているが、get_posts()とかでやったほうがいいかもしれない。気にならないなら別に問題なし) - functions.phpのtheme_ajax_load_postsでは、次ページめに相当する記事一覧の追加表示を行っている。
Ajaxから送信するPOSTデータを「$hold_query→現在のクエリをJSON化したもの」「$hold_query→追加表示するページ番号」にそれぞれ取得して、それをもとに追加表示する記事のクエリを再生成して当該ページ分の記事一覧を表示させる。 - theme_ajax_load_posts.jsの内容は主にAjaxでの非同期通信の処理内容やボタンの挙動。
よくある記述方式をクロージャー化している。「init→イベントのバインド呼び出し」「clip→ボタンのイベントリスナーのバインド」「post→イベント発火された際のAjax処理」。 - functions.phpのtheme_ajax_load_posts_scriptは、JSのグローバル変数theme_ajax_load_varsを出力しているだけ。
Ajaxで追加表示させる部分に関して、このtheme_ajax_load_varsで「url→ajaxのURL」「next_paged→次回ページ番号」「max_page→最大ページ数」「query_json→クエリ情報のJSON」を元に進行状態を管理する。(正直、ajax_load_posts.jsのクロージャーにキックして、その中の変数保持で管理してもいいかもしれない) - header.phpは関連ファイルの読み込みと、Ajaxの記事一覧表示の宣言をしている。それだけ。(書いてある内容の通り、可能ならばできるかぎり全てフックした方がいい。)
$(‘#load-posts’).ajaxLoadPosts({appendOf: ‘#main’});で、ボタンと、記事一覧の出力先を指定する。 - index.php、archive.phpでif(have_posts()): while ~endwhile; endif;に相当する部分を<?php custom_ajax_load_posts_direct_access(); ?>に挿げ替え。この際、親のDOMには必ずajaxLoadPostsで指定したappendOfのIDを指定しておくこと。
<a href=”#” id=”load-posts”><?php _e(‘More posts’); ?></a>でボタンを↑の箇所以外の適当な場所に設置。
正直、Railsの箸休めに短時間でかなりやっつけ気味に作ったため、他必要な内容についてはコメントに書いた。
特にスクリプト周りは砂糖のように甘い。
こんなクソ記事書いてる暇あったらもうちょっとべっこう飴程度にまでは煮詰めろよとか突っ込まないこと。
俺は今、Railsをやることを強いていられているんだ!(自分から自分に)
まぁ、半端な有様を補足すると、
- 記事一覧がロードされて追加表示された際のアニメーションとかは、CSSアニメーション定義しとけば多分おk。
- URLの書き換えはhistory.replaceStateでも使えばいいじゃない。これだけ書いてとりあえず満足したので入れてない。
- ロード中ならその表記を行う+ロード終了後ボタンを再帰させる、全ての記事表示済ならボタン消す、かんたんなので割愛。
以上の有様なので、コメントに合わせて適宜追記する必要あり。
まぁ、だいたいこんな感じでいけるよーって程度のサンプルコードだとでも思ってもらえるとこれ幸い。