ファイル構造 (テーマフォルダ直下)
- functions.php
- js/
- core.js
- page-example.php
固定ページ(スラッグはexample)のテンプレート「page-example.php」では次のようにボタンを設置している
<?php
$paged = max( 1, get_query_var('paged') );
$args = array(
'post_type' => 'post',
'paged' => $paged
);
$post_query = new WP_Query( $args )
if ( $post_query->have_posts() ) {
while ($post_query->have_posts() ) {
$post_query->the_post();
printf(
'<a href="%s">%s</a>',
esc_url( get_the_permalink() ),
esc_html( get_the_title() ),
);
}
}
?>
<a href="#!" id="loadPostsButton">記事を読み込む</a>
<?php
wp_reset_postdata();
ではこのページにJavaScriptファイル「js/core.js」を登録しよう。functions.phpに次のコードを追記。
<?php
...
add_action( 'wp_enqueue_scripts', 'core_js_file' );
function core_js_file() {
wp_enqueue_script( 'core_js', get_theme_file_uri( '/js/core.js'), array(), rand( 1000 ,9999 ) , true );
}
...
バージョン番号をランダムすることでキャッシュ回避をしている荒業。それはさておいて、これでcore.jsが読み込まれるようになった。ではそのcore.jsにAjaxするための準備をコーディング。
let paged = 1;
document.getElementById('loadPostsButton').addEventListener( 'click', ( event ) => {
event.preventDefault();
paged++;
// Ajax処理…
} );
ボタンをクリックすると`paged`がインクリメントしてAjax処理される。pagedの初期値が1なのは、今表示している投稿が1ページ目だから。クリックとともにpagedが2になり、2ページ目を要求するメッセージを送れる寸法。
もし、access-control(CORS関連)で弾かれる場合はwatchなどでプロキシサーバーを使っていないかチェック!localhostを別のサーバーでプロキシして表示している場合(その逆も)は一度WordPressのURLをチェックしよう。
WordPressとAjaxさせるための記事を探すと、ほぼすべての記事でWordPress同梱のjQueryを利用した$.ajax()の例ばかり紹介されていた。たしかにこれが一番確実であるのだが、jQueryを使わずともAjaxできて良いのではないかと思い、今回この記録を執筆した次第。
Ajax処理の部分には`XMLHttpRequest()`を使う方法と`fetch()`を使う方法がある。まずは`XMLHttpRequest()`を使う方法から。
const data = new URLSearchParams();
data.append( 'action', 'load_more_posts' ); // WordPressにある関数名(呼び出し先)
data.append( 'nonce', ajax_obj.nonce ); // 呼び出し先に送りたいデータ1
data.append( 'paged', paged ); // 呼び出し先に送りたいデータ2
data.append( 'blah', 'blah blah ...' ); // 呼び出し先に送りたいデータ3 ...
const xhr = new XMLHttpRequest();
xhr.open( 'POST', `${ ajax_obj.url }?${ data.toString() }`, true );
xhr.onreadystatechange = () => {
if ( xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200 ) {
console.log( xhr.response ); // WordPressからの返信メッセージ(文字列)
// または console.log( xhr.responseText );
}
}
xhr.send();
この中にはまだ定義していない変数がひとつある。`ajax_obj`だ。これはあるJSONデータを格納したグローバル変数なのだが、この説明はのちほど…。ここでは`URLSearchParams()`をご紹介。このAPIの.append()を使うことで例えば次のような文字列を得ることができる。
const data = new URLSearchParams();
data.append( 's', 'potato' );
data.append( 'p', 'tomato' );
data.toString(); // → "s=potato&p=tomato"
ご覧のようにHTTPのGETメソッド、URLのクエリ文字列をかんたんに生成編集できるAPIである。なのでやっていることはクエリ文字列をつくっているだけである。
通信はPOSTメソッドで。WordPress(サーバー)側がHTTPレスポンスコード200(通信成功)を返してくれれば`xhr`オブジェクトに返信結果が入っている。ただ、まだWordPress側で返信スクリプトを書いていないのでもちろん何も返って来ず、エラーになるがWeb InspectorのNetworkタブなどできちんとデータ送信されていることを確認できると思われる。
このXMLHttpRequest()を使う方法だとWordPress側で`$_GET`か`$_REQUEST`を使いJavaScriptから送られたデータを読める。例えば$_GET['blah']で文字列"blah blah ..."を扱える。
さて、次はfetch()を使った方法。
const data = new URLSearchParams();
data.append( 'action', 'load_more_posts' );
data.append( 'paged', paged );
data.append( 'nonce', ajax_obj.nonce );
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: data.toString(),
}
const getPosts = async () => {
const response = await fetch( ajax_obj.url, options ); // 戻り値はPromiseオブジェクト
const jsonData = await response.json(); // PromiseオブジェクトをJSONへ変換
console.log( jsonData ); // WordPressからの返信メッセージ(JSON)
};
getPosts();
fetch()はPromiseを返すので次のように.then()を使っても書ける。
const data = new URLSearchParams();
data.append( 'action', 'load_more_posts' );
data.append( 'paged', paged );
data.append( 'nonce', ajax_obj.nonce );
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: data.toString(),
}
fetch( ajax_obj.url, options )
.then( ( response ) => { // 返ってきたresponseはPromiseオブジェクト
if ( !response.ok ) {
throw new Error( `Ajax response: ${ response }` );
}
return response.json(); // PromiseオブジェクトをJSONに変換し、次の.then()へパス
} )
.then( ( jsonData ) => { // 成功!
console.log( jsonData ); // WordPressからの返信メッセージ(JSON)
} )
.catch( ( error ) => { // 失敗!
console.error( error );
} )
.finally( () => {
// 成功、失敗、関係なく、最後に実行させたい処理 (オプショナル)
} );
ちなみに、なぜJSONでなくURLSearchParams()を使ったクエリ文字列で送るのかと言われれば、WordPressがJSONを嫌がるからだ。ヘッダーのContent-Typeを"application/json"にしてbodyに`JSON.stringify({'s': 'potato'})`をセットすると400 Bad Requestエラーが出る。色々試した結果、クエリ文字列で送れば大丈夫だと判った次第。
で、わたしは2番目のfetch()とawait/asyncを使う方法で書いた。するとJSファイルの全体像は次のように書ける。
let paged = 1;
const getPosts = async ( event ) => {
event.preventDefault();
paged++;
const path = ajax_obj.url;
const nonce = ajax_obj.nonce;
const data = new URLSearchParams();
data.append( 'action', 'load_more_posts' );
data.append( 'paged', paged );
data.append( 'nonce', nonce );
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: data.toString(),
}
try {
const response = await fetch( path, options );
const data = await response.json();
console.log( data );
}
catch ( error ) {
console.error( error );
}
};
document.getElementById('loadPostsButton').addEventListener( 'click', getPosts );
さて、`ajax_obj`という変数が何なのかご紹介。この変数名ももちろん任意である。functions.phpに次を追記。
add_action( 'wp_enqueue_scripts', 'core_js_localize_script' );
function core_js_localize_script ( $hook ) {
$obj = array(
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'my_nonce' ), // nonce生成時の名前(要メモ!)
);
wp_localize_script( 'core_js', 'ajax_obj', $obj );
}
wp_enqueue_scriptsにてjs/core.jsを読み込んだときの名前"core_js"をwp_localize_scriptの第一引数に、ついでJavaScriptで参照したい変数名、ここでは"ajax_obj"を(グローバル変数になるのでユニークな名前に!)、最後にajax_objに代入したいJSONデータをPHPの配列形式で渡す。こうすると'core_js'が読み込まれているページに指定したデータを持つJSONが代入されたグローバル変数'ajax_obj'が自動生成される。
JavaScriptではこのデータを参照していたのだ。ここで'url'にはWordPressがAjax処理するためのPHPファイルのフルパスが、'nonce'にはセッションごとにユニークな文字列が入る(この文字列を使ってセッションを判別する仕組み)。
さて本題。WordPress側でAjaxリクエストを処理するコードをfunctions.phpに追記しよう!
add_action( 'wp_ajax_load_more_posts', 'load_more_posts' ); // ログインユーザー向け
add_action( 'wp_ajax_nopriv_load_more_posts', 'load_more_posts' ); // 一般ユーザー向け
function load_more_posts() {
check_ajax_referer( 'my_nonce', 'nonce' );
wp_send_json( array(
'message1' => 'Hi!',
'message2' => 'Hello',
'message3' => 'blah blah ...',
) );
}
2つのアクションを使う。ひとつは管理画面にログイン中のユーザー向け、もうひとつはログインしていない、つまりは一般ユーザー向けのAjaxを受け付けるアクションだ。`wp_ajax_関数名`と`wp_ajax_nopriv_関数名`アクションの2つ。そしてアクションコールバックでは`check_ajax_referer`を使ってnonceをチェックし、意図したセッションからのリクエストか否かをチェックしている。nonceが食い違えばfalseとなる。12時間以内に生成されたnonceなら1が、24時間以内に生成されたものなら2を返す関数だ。`check_ajax_referer`の第一引数にはnonce生成時の名前(wp_create_nonce()の第一引数)、第2引数にはJavaScriptが送ったnonceのキー($_REQUEST['キー'])を渡す。キー名を"_ajax_nonce"にすれば第2引数を省くことができる。
例:
const data = new URLSearchParams();
data.append( 'action', 'load_more_posts' );
data.append( '_ajax_nonce', ajax_obj.nonce ); // ←ここ
`wp_send_json()`に連想配列を渡せばそれをJSONとしてAjaxリクエスト先へ返してくれる。wp_send_json()を使う代わりに文字列を`echo`しても良い。echoならば1つの文字列だけを返すシンプルなものにできる。ただその際はきちんと`wp_die()`などで処理を終わらせるようにすること。wp_send_json()は内部でwp_die()かdie()を呼んでいるので書く必要は無い(書くに越したことはないが…)。
例:
function load_more_posts() {
check_ajax_referer( 'my_nonce', 'nonce' );
echo 'Hello JavaScript';
wp_die();
}
echoの場合、返ってくるメッセージはJSONでなくただの文字列なのでfetch()の場合はresponse.text()で受けよう。
例:
const response = await fetch( ajax_obj.url, options );
const textData = await response.text(); // ←ここ
console.log( textData ); // 文字列メッセージ
これで通信完了。「記事を読み込む」ボタンをクリックすればコンソール上にWordPressからの応答(JSONデータ)が表示されるはず。いかがだろう。うまく行かないかたはコメント欄で議論しよう。