WordPress ページングはやめてAjaxローディングにする

Ajaxを使う方法はこちらが最新です: WordPressでAjaxを使う方法の解説

ページングはやめて、Ajaxによる非同期ローディングにしてみましょう。

最初は5件の記事が表示されています。
「もっと見る」ボタンをクリックするとAjaxにより次の5件が非同期で読み込まれます。
以後、記事がなくなるまで同じ動作です。

テンプレート

<section id="content">

	<?php query_posts('posts_per_page=5'); ?>
	<?php if (have_posts()) : ?>
		<?php while (have_posts()) : the_post(); ?>
			<article>
				<h2><a href="<?php the_permalink() ?>"><?php the_title(); ?></a></h2>
				<div class="excerpt"><?php the_excerpt(); ?></div>
			</article>
		<?php endwhile; ?>
	<?php else : ?>
		Not Found.
	<?php endif; ?>
	<?php wp_reset_query(); ?>
	
	<div id="more_disp"><a href="#">もっと表示</a></div>
	
</section>

基本的なWordPressループに、「もっと表示」ボタンを設置します。

query_posts()で、最初に表示しておく件数を指定しておきましょう。
指定しない場合は、管理画面 > 設定 > 表示設定 の「1ページに表示する最大投稿数」が反映されます。

PHPファイル

Ajaxで通信するPHPファイルを作成します。
テーマフォルダの中に more-disp.php を作ります。
テーマフォルダ名は「ajax_loading」とします。

> テーマフォルダ/more-disp.php

<?php

require_once("../../../wp-config.php");

$now_post_num = $_POST['now_post_num'];
$get_post_num = $_POST['get_post_num'];

$sql = "SELECT
		$wpdb->posts.ID, 
		$wpdb->posts.post_title, 
		$wpdb->posts.post_excerpt 
	FROM 
		$wpdb->posts  
	WHERE 
		$wpdb->posts.post_type = 'post' AND $wpdb->posts.post_status = 'publish' 
	ORDER BY 
		$wpdb->posts.post_date DESC 
	LIMIT $now_post_num, $get_post_num";
		
$results = $wpdb->get_results($sql);

$html = "";

foreach ($results as $result) {
	$html .= '<article>';
	$html .= '<h2><a href="'.get_permalink($result->ID).'">'.apply_filters('the_title', $result->post_title).'</a></h2>';
	$html .= '<div class="excerpt">'.apply_filters('the_excerpt', $result->post_excerpt).'</div>';
	$html .= '</article>';
}
echo $html;

?>

wp-config.phpの読み込み

データベースの接続設定が記述されている wp-config.php を読み込むことで、グローバル変数 $wpdb が使えるようになります。
テーマフォルダからの相対パスで記述します。

関数リファレンス/wpdb Class
wpdb クラスのメソッドは、直接呼び出さず、必ずグローバル変数 $wpdb を使いましょう。

SQLの発行

必要なデータを取得する為のSQLを発行します。
タイトル(posts.post_title)と抜粋(posts.post_excerpt)以外に必要なデータがあれば適宜追加します。
ID(posts.ID)は、JavaScriptに返すHTMLを組み立てるのに使います。

データを組み立てる

必要なデータを組み立ててJavaScriptに返すHTMLを組み立てます。
get_permalink($result->ID)を使っていることからもわかる通り、get系のテンプレートタグを用いればどんなデータでも取得できます。

フィルターを通す

テンプレートで抜粋を表示するテンプレートタグは the_excerpt() です。
これは posts.post_excerpt という生のデータが the_excerpt フィルターを通ることを意味します。
よって、the_excerpt フィルターを通して返します。

JavaScript

var now_post_num = 5; // 現在表示されている数
var get_post_num = 5; // 一度に取得する数

$(function() {
	$("#more_disp a").live("click", function() {
		
		$("#more_disp").html('<img class="ajax_loading" src="http://localhost/wordpress/wp-content/themes/ajax_loading/images/ajax_loader.gif" />');
		
		$.ajax({
			type: 'post',
			url: 'http://localhost/wordpress/wp-content/themes/ajax_loading/more-disp.php',
			data: {
				'now_post_num': now_post_num,
				'get_post_num': get_post_num
			},
			success: function(data) {
				now_post_num = now_post_num + get_post_num;
				$("#content").append(data);
				$("#more_disp").remove();
				$("#content").append('<div id="more_disp"><a href="#">もっと表示</a></div>');
			}
		});
		return false;
	});
});

事前にjQueryを読み込んでおきます。

ローディング画像

ajax_loader.gif は、Ajax通信中のローディング画像です。
なくても動作しますが、あった方がいいですね。

POSTするPHPファイルのパス

JavaScriptではWordPressのテンプレートタグは使えないので、絶対パスで記述します。

あとがき

サイトや記事の内容にもよりますが、ページングを設置してもクリックされる可能性はかなり低いと思います。
特にスマホで閲覧されることを考えると、ページ遷移を伴うページングはユーザビリティが低いと言わざるを得ません。
ということで、ページングをやめてAjaxによる非同期ローディングにするのは、非常に意味のあることだと思います。

読み込むデータがなくなったら「もっと表示」を非表示にする

※2013/10/11追記 あとでちゃんとコード整理します。。

> script.js

var now_post_num = 5; // 現在表示されている数
var get_post_num = 5; // 一度に取得する数

$(function() {
	$("#more_disp a").live("click", function() {
		
		$("#more_disp").html('<img class="ajax_loading" src="http://localhost/wordpress/wp-content/themes/ajax_loading/images/ajax_loader.gif" />');
		
		$.ajax({
			type: 'post',
			url: 'http://localhost/wordpress/wp-content/themes/ajax_loading/more-disp.php',
			data: {
				'now_post_num': now_post_num,
				'get_post_num': get_post_num
			},
			success: function(data) {
				now_post_num = now_post_num + get_post_num;
				data = JSON.parse(data);
				$("#content").append(data['html']);
				$("#more_disp").remove();
				if (!data['noDataFlg']) {
					$("#content").append('<div id="more_disp"><a href="#">もっと表示</a></div>');
				}
			}
		});
		return false;
	});
});

・戻り値をJSONで受け取る。
・第一引数は最後まで読み込んだかどうかフラグ、第二引数はHTMLデータ。

> more-disp.php

require_once("../../../wp-config.php");

$now_post_num = $_POST['now_post_num'];
$get_post_num = $_POST['get_post_num'];

$next_now_post_num = $now_post_num + $get_post_num;
$next_get_post_num = $get_post_num + $get_post_num; 

$sql = "SELECT
		$wpdb->posts.ID, 
		$wpdb->posts.post_title, 
		$wpdb->posts.post_excerpt 
	FROM 
		$wpdb->posts  
	WHERE 
		$wpdb->posts.post_type = 'post' AND $wpdb->posts.post_status = 'publish' 
	ORDER BY 
		$wpdb->posts.post_date DESC 
	LIMIT $now_post_num, $get_post_num";
		
$results = $wpdb->get_results($sql);

$sql = "SELECT
		$wpdb->posts.ID, 
		$wpdb->posts.post_title, 
		$wpdb->posts.post_content 
	FROM 
		$wpdb->posts  
	WHERE 
		$wpdb->posts.post_type = 'post' AND $wpdb->posts.post_status = 'publish' 
	ORDER BY 
		$wpdb->posts.post_date DESC 
	LIMIT $next_now_post_num, $next_get_post_num";

$next_results = $wpdb->get_results($sql);

$noDataFlg = 0;
if ( count($results) < $get_post_num || !count($next_results) ) {
	$noDataFlg = 1;
}

$html = "";

foreach ($results as $result) {
	$html .= '<article>';
	$html .= '<h2><a href="'.get_permalink($result->ID).'">'.apply_filters('the_title', $result->post_title).'</a></h2>';
	$html .= '<div class="excerpt">'.apply_filters('the_excerpt', $result->post_excerpt).'</div>';
	$html .= '</article>';
}

$returnObj = array();
$returnObj = array(
	'noDataFlg' => $noDataFlg,
	'html' => $html,
);
$returnObj = json_encode($returnObj);

echo $returnObj;

・いま取得したデータ数が取得件数より少ない、もしくは、次に取得されるデータ数がゼロ(ちょうどいま最後まで取得)の場合はフラグを立てる。
・第一引数をフラグ、第二引数をHTMLデータとしてJSON形式で戻す。

  • Tohaku Moon

    先ほど質問したものです。
    続け様で申し訳ないのですが、もうひとつ質問させてください。
    先ほどお話した状態で、カテゴリによる検索を行うと、該当件数にかかわらず10件(最初に設定した件数)が表示されてしまいます。

    こちらの解決策も合わせてご教授いただけないでしょうか?

    ムリをお願いして申し訳ありませんが、どうかよろしくお願いします。

    • hijiriworld

      > Tohoku Monn さん
      “先ほどの質問”とは、どれを指していますか?

  • Tohaku Moon

    > hijiriworldさん
    失礼しました。コードを書いたせいで1件目の質問が投稿されていなかったようです。

    当方初めてのWPカスタマイズに取り組んでいる初心者です。
    この記事の機能を実装したく、試してみたのですが、追加の記事が表示されません。

    wordpress3.4.1で、子ブログ2つ(A/B)あるマルチサイトで構築しています。
    サイトのindexに、子ブログAの新着10件を表示し、ボタンクリックで10件づつ追加表示したいと考えています。

    テンプレートに——————————————

    ←子ブログAのID


    ——————————————
    と表記しました。
    引っ張りたい項目がカスタムフィールドなど複雑なので、とりあえずテーマフォルダ/more-disp.phpの$htmlにはタイトルのみで試しました。

    MOREボタンを押すとloading画像が出た後、もう一度MOREボタンがでるのですが、追加の記事を表示することができません。

    マルチサイトであることが原因なのか、$htmlのj内容がテンプレートと一致していないのが原因なのか、それとも他に原因があるのか。
    そのあたりの見当がつかなく悩んでいたため、質問させていただきました。

    原因と解決策にお心当たりがありましたらアドバイスをいただけないでしょうか?

    また2つ目の質問、検索結果の表示についても、同じくAjaxローディングにしたいと考えています。

    よろしくお願いいたします。

    • hijiriworld

      $sites = $wpdb->get_results(“SELECT * FROM wp_blogs”);
      foreach ($sites as $site) {
      switch_to_blog($site->blog_id);

  • Tohaku Moon

    ご回答ありがとうございます!
    早速more-disp.phpの20行目を
    $wpdb->get_results(“SELECT * FROM wp_blogs WHERE …”);
    に書き換え、
     $html .=
    の行を上下に1行づつ追加して
    $html .= ”;
    $html .= ”;
    ではさむ形にしたのですが、うまく行きませんでした…
    私の解釈が間違っているのでしょうか?

    知識不足で申し訳ないのですが、ご教授お願いいたします。

  • Tohaku Moon

    長々書き込みまして申し訳ありません
    分不相応なことは辞めてマルチサイトをあきらめました。
    トップページでこちらの機能を使わせていただきます。
    ありがとうございました。

  • mo

    大変参考になる記事をありがとうございます。

    Ajaxローディングの動作自体は上手くいったのですが
    「もっと表示」ボタンを押した後、下部に表示される記事部分
    (more-disp.php で取得し表示する部分)
    が、文字化けてしまいます。

    対処法がございましたらご教示くださいませんでしょうか。
    よろしくお願い申し上げます。

  • mo

    ↑1件質問させていただいた者です。また基本的な部分で申し訳ございません

    SQLの発行について、
    >
    タイトル(posts.post_title)と抜粋(posts.post_excerpt)以外に必要なデータがあれば適宜追加します。

    記事内の最初の画像を呼び出して表示したいのですが、
    $wpdb->
    部分、どのように追加したらよろしいでしょうか。

    初歩的な部分かもしれませんが、お教えいただけますと幸いです。
    よろしくお願い申し上げます。

    • > moさん

      記事内の最初の画像を取得する方法は以下のようになります。まず、SELECTでpost_contentを取得しておくのが前提。foreachの中で以下のように記述すれば$first_imgに記事内の最初の画像のパスが取得できます。$content_images = preg_match_all(‘//i’, $result->post_content, $matches);$first_img = $matches[1][0];

      • mo

        hijiri様
        お返事をありがとうございます!
        ご丁寧にお教えいただいたのに、再び初歩的な質問で大変申し訳ございません、
        様々やってみたのですがforeachの中の書き方がわからず画像が表示できません、
        きっと初歩的な間違いだとは思うのですが、ご教示いただけませんでしょうか…

        foreach ($results as $result){$html = mb_convert_encoding($html, ‘HTML-ENTITIES’, ‘ASCII, JIS, UTF-8, EUC-JP, SJIS’);
            $html .= ”;    $html .= ‘ID).'”>’.apply_filters(‘the_title’, $result->post_title).’‘;
        $content_images = preg_match_all(‘//i’, $result->post_content, $matches);    $html .= ‘<img src="$first_img = $matches[1][0];’;
            $html .= ”.apply_filters(‘the_excerpt’, $result->post_excerpt).”;    $html .= ”;}
        まことに恐縮ですが、お時間あるときにお願い申し上げます。

  • dico1225

    あとがき が素晴らしいですね。

    機能を追加する時には、人を納得させるだけの理由が必要ですし、
    何も考えずに他のサイトを模倣した機能追加は無意味どころか目的に反する場合もありますからね。

    • > dico1225さん
      コメントありがとうございます。
      なにごとも”Because”で答えられないと説得力がないですよね。
      今後もより意識させていただきます。

  • sakanaction

    Ajaxでページローディングを探していたらこのページに辿り着きました。動作も上手くいっており感謝しております。
    SQLの発行という部分で、記事のサムネイルや時間、カテゴリーの表示などを試みたのですが知識不足で上手く出来ませんでした…。もし差し付けありませんでしたら、教えて頂けないでしょうか?

    • > sakanaction さん
       テンプレートタグが使えるのでどんなデータでも取得できます。
       例えばアイキャッチ画像(サムネイル)が取得したければ、$thumb = get_the_post_thumbnail($result->ID); として、$html .= $thumb; とすればOKです。
       カテゴリーやその他データも同様です。

      • sakanaction

        返信ありがとうございます。ご指摘頂いた通りに以下の様にやってみたのですが、上手く表示されませんでした…。何か間違っている箇所があるでしょうか?いきなりの質問攻めで申し訳ないです。基本的に上のmore-disp.phpと同じでご指摘の様に追加しただけです。

        ID);

        $sql = “SELECT

        $wpdb->posts.ID,

        $wpdb->posts.post_title,

        $wpdb->posts.post_excerpt

        FROM

        $wpdb->posts

        WHERE

        $wpdb->posts.post_type = ‘post’ AND $wpdb->posts.post_status = ‘publish’

        ORDER BY

        $wpdb->posts.post_date DESC

        LIMIT $now_post_num, $get_post_num”;

        $results = $wpdb->get_results($sql);

        $html = “”;

        foreach ($results as $result) {

        $html .= ”;

        $html .= $thumb;

        $html .= ‘ID).'”>’.apply_filters(‘the_title’, $result->post_title).’‘;

        $html .= ”.apply_filters(‘the_excerpt’, $result->post_excerpt).”;

        $html .= ”;
        }
        echo $html;

        ?>

        • sakanaction

          先ほど投稿した関数が上手く表示されなかったのですが、この記事内にあるmore-disp.phpに$thumb = get_the_post_thumbnail($result->ID);と$html .= $thumb;をそのまま同じ様に入れただけです。

  • よしだ

    はじめまして、素晴らしい方法の紹介ありがとうございます。
    おかげさまで無事動いております。
    さて、質問させていただいてよろしいでしょうか。
    記事がなくなったとき、「もっと表示」を消したいのですが、可能でしょうか?
    方法をご教授いただけると幸いです。
    宜しくお願いいたします。

    • hijiriworld

      >よしださん
      取り急ぎ、データがなくなった時に「もっと表示」を消すコードを追記しました。
      本来ここまでやっておくべきなので、あとでちゃんとコードを整理して再掲しますね。

  • うえこ

    初めまして、一年前以上の記事ですが質問させて下さい。
    こちらは、タグテンプレートは利用可能ですが
    基本的にSQLの「posts」テーブル内の情報しか取り出せませんよね?
    「users」「terms」の情報も取り出したいのですがどうすれば宜しいでしょうか。
    お手数おかけ致しますが、ご教授頂けると助かります。宜しくお願い致します。

    • hijiriworld

      postsテーブル以外のデータも取得できます。
      usersテーブルであれば$wpdb->usersとなります。termsテーブルについても同様です。

      参考: 関数リファレンス/wpdb Class

  • Pingback: Wordpressの記事をAjaxで追加読み込みしたときの話 | koukiTips()

  • DISQUSでうまく投稿できないようなのでこちらで

    more-disp.phpにはSQLインジェクション脆弱性が存在するように見受けられます。

    POSTから渡ってくる値をエスケープするか、プリペアドステートメントを使用するべきです。

    • akjra

      誰もこのまま使えと言ってないし慈善事業じゃないんだから自分でそれくらい修正しろよカス

  • mechi

    上記を参考に使用させていただいているのですが、
    一度しか読み込みができず困っております。
    【今回の場合】
    最初3件表示されており、もっと見るをクリックすると6件表示される。
    次にもっと見るをクリックしても6件表示のまま。

    どのように対処すればよろしいでしょうか?

    ご回答のほどよろしくお願いします。

    ↓以下javascriptのコード↓

    var now_post_num = 3; // 現在表示されている数

    var get_post_num = 3; // 一度に取得する数

    $(function() {

    $(“#more_disp a”).on(“click”, function() {

    $.ajax({

    type: ‘post’,

    url: ‘http://localhost/wordpress/wp-content/themes/ajax-loading/more-disp.php’,

    data: {

    ‘now_post_num’: now_post_num,

    ‘get_post_num’: get_post_num

    },

    success: function(data) {

    now_post_num = now_post_num + get_post_num;

    $(“#content”).append(data);

    $(“#more_disp”).remove();

    $(“#content”).append(‘もっと見る‘);

    }

    });

    return false;

    });

    });

    • Dansyaku

      私も同じとこでつまづいたのですが、ajaxで追加した要素へのイベント登録をしたら解決できました。
      こちらを参考にhttp://qiita.com/fukamiiiiinmin/items/427c41e0feb2311f9621

  • shotarion

    とても参考になる記事をありがとうございます。

    固定ページの親ページに子ページ一覧を表示させ、それをAjaxローディングしたいと思っているのですが、上記コードの編集箇所をご教授いただければ幸いです。

    よろしくお願いいたします。