AjaxでWordPressのコンテンツを遷移せず表示させる

WordPressのコンテンツをtwitterやgoogle画像検索のようにあらかじめ一定数のコンテンツを表示させておき「もっと見る」リンクをクリックで画面遷移無くコンテンツを出力させたいと思います。

INDEX

  1. 仕様・デモ
  2. WordPressでAjaxを使う

    1. /wp-admin/admin-ajax.phpを使う
    2. /wp-admin/admin-ajax.phpを使ったデモ
  3. 表示用の固定ページ作成
  4. functions.php周り

    1. JavaScript登録
    2. 全投稿数を取得しクッキーに保存させる
    3. 「もっと見る」クリック時のAjax処理登録
  5. 課題等
  6. 参考サイト

仕様・デモ

いくつか課題はありますが、とりあえずの仕様としては以下の通り。

  • 投稿のタイトル及びリンクをあらかじめ一定数表示(今回は10件)
  • 「もっと見る」をクリックする度にAjaxでコンテンツを取得し10件ずつ追加
  • 固定ページ「AjaxでWordPressのコンテンツを遷移無く表示サンプル(page-wp-content-ajax-viewer.php)」を作成しそこに出力
  • すべてのコンテンツを表示し終えたら「もっと見る」リンクは無効化にする
  • テーマはTwenty Elevenをベースに

実際のデモは以下よりご覧ください。
AjaxでWordPressのコンテンツを遷移無く表示デモ

WordPressでAjaxを扱う

実際の処理部分の説明の前にWordPressでAjaxを扱う方法を。

/wp-admin/admin-ajax.phpを使う

AJAX in Plugins – WordPress Codex 日本語版が本家マニュアルだけど複雑で理解できなかったのでgoogle先生に頼りました。
分かりやすかったのは次のサイト。
Wp Ajax – WordPress Hook to Handle Ajax Request — W4dev.com
仕組みとしては次のようなイメージ(自己解釈)。

  • JavaScriptで/wp-admin/admin-ajax.phpへリスエストを送信することで任意のPHP関数を呼び出し、そこから送り返された情報をもとにJavaScriptで結果を出力する。
  • 任意のPHP関数はアクションフック「wp_ajax_アクション名」「wp_ajax_nopriv_アクション名」で呼び出すことができ、これらアクションフックを実行するには/wp-admin/admin-ajax.phpリクエスト時のactionパラメータに「アクション名」を渡しおく。
    「wp_ajax_アクション名」「wp_ajax_nopriv_アクション名」はログインユーザか否かで、公開部分でAjax処理を行いたい場合はともにフックする必要がある。

    add_action( 'wp_ajax_my_func', 'my_func' );
    add_action( 'wp_ajax_nopriv_my_func', 'my_func' );
    
    function my_func() {
    	// JavaScriptに値を返す処理
    }
  • my_funcにはJavaScript側に渡す情報を出力する処理を。

/wp-admin/admin-ajax.phpを使ったデモ

/wp-admin/admin-ajax.phpを使ってAjax処理を行うデモ

ソースは以下の通り。

page-wp-ajax-demo.php

// ヘッダとか省略
<div class="entry-content">
	<a href="#" id="show-json">JSON出力</a>
	<div id="json-data"></div>
</div><!-- .entry-content -->
// フッタとか省略

ヘッダ、フッタ領域は省略しています。
jQueryのclick()イベント用のアンカーとJSONデータ出力用のdivを用意。

functions.php

// JavaScript登録
add_action( 'wp_enqueue_scripts', 'sh_add_scripts' );

function sh_add_scripts() {
	if ( is_page( 'wp-ajax-demo' ) ) {
		wp_enqueue_script( 'jquery' );
	}
}

// admin-ajax.phpへリクエストを送信し返ってきた情報をもとにページ情報を出力
add_action( 'wp_head', 'sh_show_json' );

function sh_show_json() {
	if ( is_page( 'wp-ajax-demo' ) ) {
		?>
		<script>
			//<![CDATA[
			ajaxurl = '<?php echo admin_url( 'admin-ajax.php' ); ?>';
			jQuery(function($){
				$('#show-json').one('click', function(){
					jQuery.ajax({
						type: 'POST',
						url: ajaxurl,
						data: {
							"action": "sh_get_json"
						},
						success: function(data){
							var json_str = JSON.stringify(data);
							$('#json-data').append(json_str);
						},
						error: function(){
							alert('error');
						}
					});
					$(this).css({
						'pointerEvents': 'none',
						'color': '#ccc'
					});
					return false;
				});
				return false;
			});
			//]]>
		</script>
		<?php
	} // endif
}

// json出力
add_action( 'wp_ajax_sh_get_json', 'sh_get_json' );
add_action( 'wp_ajax_nopriv_sh_get_json', 'sh_get_json' );

function sh_get_json() {
	$charset = get_bloginfo( 'charset' );
	$array = array( 'foo' => 'bar', 'hoge' => 'fuga' );
	$json = json_encode( $array );
	nocache_headers();
	header( "Content-Type: application/json; charset=$charset" );
	echo $json;
	die();
}

jQuery.ajax()を使用するので#01~08でjQueryの登録。
デモページスラッグ名がwp-ajax-demoなのでis_page( ‘wp-ajax-demo’ )でデモページのみ読み込まれるように。

#10~47でデモページにJavaScriptを出力。処理内容は以下の通り。

  • jQuery.ajax()のリクエスト先URLを設定(#18)
  • 「JSON出力」アンカー(#show-json)クリックイベント(#20)
  • jQuery.ajax()実行。(#21~)
    urlは”/wp-admin/wp-ajax.php”、dataキー(サーバに送信するデータ)のactionパラメータとして”sh_get_json”を渡す。後述のsh_get_json()が実行されJSONオブジェクトを返す。
    通信成功時には渡されたJSONオブジェクトをJSON文字列に変換し出力。(#27~30)

#49~61でJSON出力処理。

  • /wp-admin/admin-ajax.php?action=sh_get_jsonへのリクエストでアクションフック”wp_ajax_sh_get_json”、”wp_ajax_nopriv_sh_get_json”がフックされ、sh_get_json()が実行される。(#51,52)
  • sh_get_json()では適当な配列$arrayを作成し、json_encode()で配列をJSON形式にして出力。
  • JSONを出力したままだとエラーが発生するのでdie()でプログラムを終了させる。(#60)
  • 出力されたJSONが先のsh_show_json()のjQuery.ajax()のsuccess時の引数”data”として渡され適宜処理される。

というわけで/wp-admin/admin-ajax.phpについて一応の理解はできたところで本題に戻って実際の処理を行なっていきます。

表示用の固定ページ作成

まず固定ページ「AjaxでWordPressのコンテンツを遷移無く表示サンプル(page-wp-content-ajax-viewer.php)」を作成して投稿の最新10件を表示します。

page-wp-content-ajax-viewer.php

<?php
/**
 * AjaxでWordPressのコンテンツを遷移無く表示デモ
 *
 * @package WordPress
 * @subpackage Twenty_Eleven
 * @since Twenty Eleven 1.0
 */
get_header();
?>
<div id="primary">
	<div id="content" role="main">
		<article id="wp-content-ajax-viewer" <?php post_class(); ?>>
			<header class="entry-header">
				<h1 class="entry-title"><?php the_title(); ?></h1>
			</header><!-- .entry-header -->
			<div class="entry-content">
				<?php
				$args = array(
					'posts_per_page' => 5
				);
				$my_posts = get_posts( $args );
				if ( $my_posts ):
					$output = '';
					$output .= '<ul id="content-list">';
					foreach ( $my_posts as $post ):
						$output .= '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
					endforeach;
					$output .= '</ul>';
					echo $output;
				endif;
				?>
				<a id="show-more" href="#">もっと見る</a>
			</div><!-- .entry-content -->
		</article>
	</div><!-- #content -->
</div><!-- #primary -->

<?php get_footer(); ?>
	

get_posts()で最新10件を取得し、リスト形式でタイトル・パーマリンクを出力しています。

functions.php周り

JavaScript登録

jQuery.ajax()を使用するのでjQueryを、全投稿数保持の為にjquery.cookieプラグインを利用するので登録しておきます。

functions.php

// JavaScript登録
add_action( 'wp_enqueue_scripts', 'sh_add_scripts' );

function sh_add_scripts() {
	if ( is_page( 'wp-content-ajax-viewer' ) ) {
		wp_enqueue_script( 'jquery' );
		wp_enqueue_script( 'jquery-cookie', get_template_directory_uri() . '/js/jquery.cookie.js', array( 'jquery' ), true );
	}
}

全投稿数を取得しクッキーに保存させる

投稿を全て表示し終えたら「もっと見る」アンカーを無効化する、を実現するために全投稿の件数が必要になります。
ページ読み込み時に全投稿数をカウントしてクッキーに保存しておきます。

2012/10/17修正
wp_headにフックさせるとhtmlを出力した後setcookie()が実行され”Warning: Cannot modify header information – headers already sent by”が発生するのでinitにフックするよう修正しています。
PHP: setcookie – Manual

2012/10/17更に修正
initにフックさせるとsh_get_query_count()関数内のis_page()が効かないのでwpフックに修正。

functions.php

// 全投稿数をカウントしクッキーに保存
add_action( 'wp', 'sh_get_query_count' );

function sh_get_query_count() {
	if ( is_page( 'wp-content-ajax-viewer' ) ) {
		$args = array( 'posts_per_page' => -1 );
		$my_posts = get_posts( $args );
		$query_count = count( $my_posts );
		setcookie( 'count', $query_count, time() + 3600 );
	}
}

「もっと見る」クリック時のajax処理登録

functions.php

// admin-ajax.phpへリクエストを送信し返ってきた情報をもとにページ情報を出力
add_action( 'wp_head', 'sh_add_ajax_script' );

function sh_add_ajax_script() {
	if ( is_page( 'wp-content-ajax-viewer' ) ) {
		?>
		<script>
			//<![CDATA[
			ajaxurl = '<?php echo admin_url( 'admin-ajax.php' ); ?>';
			jQuery(function($){
				$('#show-more').click(function(){
					var paged = $('#content-list li').length;
					jQuery.ajax({
						type: 'POST',
						url: ajaxurl,
						data: {
							"action": "sh_show_more",
							"paged": paged
						},
						success: function(data){
							var html = generateHtml(data);
							var target = $('#content-list');
							$(target).append(html);
							if ( $('#content-list li').length == $.cookie('count')) {
								$('#show-more').css({
									'pointerEvents': 'none',
									'color': '#ccc'
								});
							}
						},
						error: function(){
							alert('error');
						}
					});
					return false;
				});
				function generateHtml(data){
					var dataLength = data.length;
					var li = [];
					for ( i = 0; i < dataLength; i++ ){
						li.push('<li><a href="' + data[i].url + '">' + data[i].title + '</a></li>');
					}
					li = li.join("");
					return li;
				}
			})
			//]]>
		</script>
		<?php
	} // endif
}

// json出力
add_action( 'wp_ajax_sh_show_more', 'sh_show_more' );
add_action( 'wp_ajax_nopriv_sh_show_more', 'sh_show_more' );

function sh_show_more() {
	global $post;
	$charset = get_bloginfo( 'charset' );
	$paged = $_POST['paged'];
	$json = array( );
	$args = array(
		'posts_per_page' => 10,
		'offset' => $paged
	);

	$my_posts = get_posts( $args );
	foreach ( $my_posts as $key => $post ) :
		$json[$key]['title'] = get_the_title();
		$json[$key]['url'] = get_permalink();
	endforeach;
	
	$json = json_encode( $json );
	nocache_headers();
	header( "Content-Type: application/json; charset=$charset" );
	echo $json;
	die();
}

基本は/wp-admin/admin-ajax.phpを使ったデモと同じなのでそちらを参照。

大まかな流れとしては以下の通り。

  1. wp_headアクションフックでsh_add_ajax_script()を実行し(#02)、「AjaxでWordPressのコンテンツを遷移無く表示デモ」ページのみJavaScriptを出力(#05)
  2. 「もっと見る」アンカー(#show-more)クリックイベント(#11)
  3. 現在表示されている投稿件数を取得。(#12)
  4. jQuery.ajax()実行。(#13~)
    今回はdataキー(サーバに送信するデータ)にactionパラメータ他にpagedパラメータを登録。pagedパラメータは#12で取得した現在表示されている投稿件数の値。
  5. /wp-admin/admin-ajax.php?action=sh_show_moreへのリクエストでアクションフック”wp_ajax_sh_show_more”、”wp_ajax_nopriv_sh_show_more”がフックされ、sh_show_more()が実行される。(#54,55)
  6. PHP関数側で$pagedにjQuery.ajax()より送信されたpagedの値を代入。(#60)
  7. get_posts()用のパラメータを設定。posts_per_page(表示件数)は10件。offset(取得開始位置)$pagedの値を。
    他パラメータは今回はデフォルトで。画像で同様の処理を行いたいなら’post_type’ => ‘attachment’とかを指定。
  8. get_posts()で投稿を取得してきて(#67)、foreachで配列$jsonにタイトルとパーマリンクを代入。(#68~71)
  9. 先のデモ同様JSONを出力してdie()でプログラムを終了させる。(#73~77)
  10. jQuery.ajax()にJSONオブジェクトが返されsuccess時の処理実行。(#20~)
    出力するhtmlを生成し(#21及び#37~45)、指定した要素へ出力。(#22,23)
  11. もし投稿件数がcookieに保存されている全投稿数と同じになれば「もっと見る」アンカー無効化処理。(#24~29)

ちなみにjQuery.ajax()でsuccess時に返ってくるJSONオブジェクトは以下の様な形式。(JSON文字列化及び整形済み)

[
    {
        "title": "Mac OS X Lion環境NetBeans IDEにプログラミング用フォントRicty導入", 
        "url": "http://show-web.jp/2012/01/31/mac-os-x-lion%E7%92%B0%E5%A2%83netbeans-ide%E3%81%AB%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E7%94%A8%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88ricty%E5%B0%8E%E5%85%A5/"
    }, 
    {
        "title": "WordCrab Fukui 2012に参加してきました", 
        "url": "http://show-web.jp/2012/01/31/wordcrab-fukui-2012%E3%81%AB%E5%8F%82%E5%8A%A0%E3%81%97%E3%81%A6%E3%81%8D%E3%81%BE%E3%81%97%E3%81%9F/"
    }, 
    {
        "title": "Windows7でSCSS+Compass(導入編)", 
        "url": "http://show-web.jp/2012/01/17/windows7%E3%81%A7scss-compass%EF%BC%88%E5%B0%8E%E5%85%A5%E7%B7%A8%EF%BC%89/"
    }, 
    {
        "title": "Retinaディスプレイ時に読み込む画像を切り替えるjQueryその2", 
        "url": "http://show-web.jp/2012/01/12/retina%E3%83%87%E3%82%A3%E3%82%B9%E3%83%97%E3%83%AC%E3%82%A4%E6%99%82%E3%81%AB%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%82%80%E7%94%BB%E5%83%8F%E3%82%92%E5%88%87%E3%82%8A%E6%9B%BF%E3%81%88%E3%82%8Bjquery-2/"
    }, 
    {
        "title": "Retinaディスプレイ時に読み込む画像を切り替えるjQuery", 
        "url": "http://show-web.jp/2011/12/31/retina%E3%83%87%E3%82%A3%E3%82%B9%E3%83%97%E3%83%AC%E3%82%A4%E6%99%82%E3%81%AB%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%82%80%E7%94%BB%E5%83%8F%E3%82%92%E5%88%87%E3%82%8A%E6%9B%BF%E3%81%88%E3%82%8Bjquery/"
    }, 
    {
        "title": "第1回WordPressお茶会@WordBench香川を開催しました", 
        "url": "http://show-web.jp/2011/11/21/%E7%AC%AC1%E5%9B%9Ewordpress%E3%81%8A%E8%8C%B6%E4%BC%9A%EF%BC%A0wordbench%E9%A6%99%E5%B7%9D%E3%82%92%E9%96%8B%E5%82%AC%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F/"
    }, 
    {
        "title": "第1回WordPressお茶会 &#8211; WordBench香川 &#8211; WordPressのインストール", 
        "url": "http://show-web.jp/2011/11/16/%E7%AC%AC1%E5%9B%9Ewordpress%E3%81%8A%E8%8C%B6%E4%BC%9A-wordbench%E9%A6%99%E5%B7%9D-wordpress%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB/"
    }, 
    {
        "title": "第1回WordPressお茶会 &#8211; WordBench香川 &#8211; PHP環境設定", 
        "url": "http://show-web.jp/2011/11/10/%E7%AC%AC1%E5%9B%9Ewordpress%E3%81%8A%E8%8C%B6%E4%BC%9A-wordbench%E9%A6%99%E5%B7%9D-php%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A/"
    }, 
    {
        "title": "第1回WordPressお茶会 &#8211; WordBench香川 &#8211; MAMPの環境設定(外部からのアクセス制限設定)", 
        "url": "http://show-web.jp/2011/11/05/%E7%AC%AC1%E5%9B%9Ewordpress%E3%81%8A%E8%8C%B6%E4%BC%9A-wordbench%E9%A6%99%E5%B7%9D-mamp%E3%81%AE%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A%EF%BC%88%E5%A4%96%E9%83%A8%E3%81%8B%E3%82%89%E3%81%AE/"
    }, 
    {
        "title": "第1回WordPressお茶会 &#8211; WordBench香川 &#8211; XAMPPの環境設定(ドキュメントルートの確認)", 
        "url": "http://show-web.jp/2011/11/05/%E7%AC%AC1%E5%9B%9Ewordpress%E3%81%8A%E8%8C%B6%E4%BC%9A-wordbench%E9%A6%99%E5%B7%9D-xampp%E3%81%AE%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A%EF%BC%88%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3/"
    }
]

課題等

以上でタイトル通りの処理は実現できましたがいくつか課題が。

  • ソースが全体的に冗長的。(get_posts()の条件を変更したい場合、例えば出力件数等を変更したい場合、page-wp-content-ajax-viewer.phpとfunctions.phpともに変更する必要がある)
    Class化とかである程度対応出来そう?
  • 拡張性が無い。(出力内容を変更したい場合ソースを結構変更する必要があるので面倒)
    これもClass化で対応出来そう?
  • セキュリティ的にどうか。(まだそこまで検証出来てません)
  • プラグイン化して、WP-PageNaviプラグインみたいにindex.phpやarchive.phpの所定の箇所に関数を書くと、「もっと読む」クリックで投稿が出力されたら面白いかなと。(既にあるか?)

参考サイト

以下サイトを参考にさせて頂きました。