Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in /home/kpkyvkzp/public_html/unskilled.site/wp-content/plugins/all-in-one-seo-pack/modules/aioseop_opengraph.php on line 825
wordpressでajax実践編[ページネーションをajaxで行ってみる] | Unskilled?
2015/05/14 18:00:24

wordpressでajax実践編[ページネーションをajaxで行ってみる]

目次(クリックするとジャンプします)
  • 1:概要
  • 1.1:はじめに
  • 1.2:概念図
  • 1.3:デモ・すべてのコード
  • 2:リクエストデータを用意する
  • 2.1:リクエストデータ一覧
  • 2.1.1:「flag」
  • 2.1.2:「nonce」
  • 2.1.3:「is_single」「category」「tag」「search」
  • 2.1.4:「looplength」
  • 2.1.5:「registered」
  • 3:PHPからjavascriptへ変数を渡す
  • 3.1:PHPからjavascriptへ変数を渡す方法-その1
  • 3.1.1:自力でやる
  • 3.1.2:自力での変数出力
  • 3.2:PHPからjavascriptへ変数を渡す方法-その2
  • 3.2.1:関数をつかう
  • 3.2.2:wp_enqueue_script()
  • 3.2.3:wp_localize_script()
  • 3.2.4:関数での出力
  • 4:ネイティブjavascriptでのajax
  • 4.1:リクエストデータをjsonにする
  • 4.1.1:JSON.stringify
  • 4.2:オブジェクトの生成とイベントハンドラ(開始地点)のバインド ネイティブ版
  • 4.2.1:XMLHttpRequest
  • 4.2.2:イベントハンドラのバインド
  • 4.3:ajax通信の初期化・開始 ネイティブ版
  • 4.3.1:openメソッド
  • 4.3.2:setRequestHeaderメソッド
  • 4.3.3:sendメソッド
  • 4.4:イベントハンドラ ネイティブ版
  • 4.4.1:イベントの種類
  • 4.4.2:onloadstartイベント
  • 4.4.3:onloadendイベント
  • 4.4.4:onloadイベント
  • 4.4.5:onreadystatechangeイベント
  • 4.4.6:onerrorイベント
  • 4.4.7:ontimeoutイベント
  • 4.4.8:onabortイベント
  • 4.5:スクロール位置でajaxを発生させる仕組み
  • 4.5.1:elementObj() DOMオブジェクトまとめ
  • 4.5.2:bodyScrollSenser() スクロール取得
  • 4.5.3:allLoopHeight() 要素の高さ計算
  • 4.5.4:processPoint() ajax発動位置算出
  • 4.5.5:scrollSenser() スクロール検知
  • 5:jQueryでのajax
  • 5.1:グローバルイベントハンドラのバインド jQuery版
  • 5.2:XMLHttpRequestオブジェクト jQuery版
  • 5.3:ajax通信の初期化・開始 jQuery版
  • 5.3.1:type url data
  • 5.4:コールバック関数 jQuery版
  • 5.4.1:done() 成功時のコールバック
  • 5.4.2:always() 終了時のコールバック
  • 5.4.3:fail() 失敗時のコールバック
  • 5.4.4:その他のコールバック
  • 6:PHPファイルの処理
  • 6.1:wordpress関数を使えるようにする
  • 6.2:リクエストデータを受け取る
  • 6.3:nonceチェック
  • 6.4:表示数の定義
  • 6.5:wp_queryオブジェクト生成用のパラメータ
  • 6.5.1:記事表示数
  • 6.5.2:記事参照位置のオフセット
  • 6.5.3:記事リストのソート
  • 6.5.4:条件分岐によるパラメータ追加
  • 6.6:wp_queryオブジェクトの生成と残り個数の確認
  • 6.6.1:wp_queryオブジェクト
  • 6.6.2:照会された記事数のカウント
  • 6.6.3:残存記事数の確認
  • 6.7:htmlデータを作る
  • 6.7.1:htmlデータの生成
  • 6.8:レスポンスデータの整理・送信
  • 6.8.1:レスポンスデータをjson化
  • 6.8.2:レスポンスヘッダー
  • 6.8.3:データ送信
  • 7:補足説明
  • 8:まとめ

概要

はじめに

今回の題材として、wordpressのページネーションをajaxで行ってみたいと思います。

スクロールが表示物の無くなる寸前くらいになった時にajax通信を開始して、新たに記事リストを追加表示していくという感じを想定しています。

つまりスクロールしていれば勝手にすべての(クエリに合致した)記事が読み込まれていくということです。

どちらかといえば、モバイルの利便性に重点を置いた実装かと思います。

この記事の前に書いたwordpressで使ってみるajaxの基本も合わせて読んでいただけるとより網羅的になるかと思います。

また今回は前回の記事のようにwordpressが用意してくれている経路(admin-ajax.phpにリクエストを送る方法)を使うのではなく、独自ファイルを用意してajax通信を試みようと思います。

さらに前回と同じように、ネイティブjavascriptとjQueryのどちらともでコードを書いてみましました。@MINOのコードは下手くそこの上ないですが、一応ネイティブとjQueryを対比することが可能です。

jQueryで書くにしてもネイティブの書き方を知っておくことでより理解が深まるのではないかと思います。

ネイティブでajaxを書いているサイトがほとんど見つからないので、もしかしたら有意義かもしれないです。

概念図

まずは今回の概念を図にまとめてみましました。

ajax

あまり見やすい図とは言えないですが、今回のajaxがどんな風に成り立っているかがわかってもらえたら幸いです。

本記事ではコードとその説明で進めていきたいと思いますが、この図も合わせて参照してもらうと、より把握しやすいのではないかと思います。

デモ・すべてのコード

今回もデモページを用意してみましました。条件分岐の都合上、別ドメインサイトではありますが、動作の確認をしてもらえるかと思います。

デモはネイティブで書かれた方jsファイルを使っています。

デモページ

またコード全体を記事面に載せるには長くうざったらしい感じがするし、ダウンロードもできるのでGitHubに置かせてもらうことにしました。下手なコードをさらすのは勇気がいますが、煮るなり焼くなり好きにしてもらいたいです。

デモコード(GitHub)

リクエストデータを用意する

まずはリクエストデータ関係に関して説明していきたいです。今回の目的として記事リストをajaxによって得たいと言うことなので、その処理を進める為に、いくつかデータが必要になる。

対象となるファイルは[index.php]です。

ajaxによるページネーションをどのように行うかで用意すべきデータも変わるのだと思いますが、とりあえず今回は以下のようなリクエストデータが必要になりました。

リクエストデータ一覧

今回用意するデータ 意味
flag ページ読み込みのフラグ
nonce セキュリティチェック用
is_single シングルページの判定
category カテゴリIDの取得とフラグ
tag タグIDの取得とフラグ
search 検索ワードの取得とフラグ
looplength ループの個数
registered 読み込み中画像(くるくる)の表示フラグ

「flag」

ajaxで記事リストをレスポンスしてもらうとき、どうしても残り個数を判定しなくてはなりません。表示するものがないのにajax通信を何度も発生させてしまうことになるからです。

そこでPHP側では常に表示すべき記事数の残りをカウントし、一回に表示する個数以下しか残っていない(今回の通信ですべて表示しつくす)場合は、出し尽くしフラグとしてfalseをレスポンスしています。

このflagはそのflaseを受け取るためのjavascript側のオブジェクトプロパティです。

これによってjavascript側でajax通信を発生させるかの判断が行えるようになる。

「nonce」

nonceについては、すこし説明しておきたいです。nonceはセキュリティです。サーバから発行したハッシュ値(このハッシュ値をnonceという)をリクエストに含めて返しサーバがチェックすることで、その通信が正規なものかどうかを確認する仕組みとなっています。

nonceは主にCSRFを防ぐための手段として使われる。掲示板に勝手に書き込まれたり、ネットショッピングを勝手にされたりというのはCSRFの典型的な被害ですが、こういった攻撃を無効化できます。

wordpressではこのnonceを出力する関数、チェックする関数が準備されています。

nonceは外部から推測、もしくは容易に生成できるようなハッシュ値では意味がないので、実質的に暗号生成と同じです。

そのため堅牢なハッシュ値が必要なのですが、wordpressは内部に保持している暗号生成用のハッシュ値や時刻など複数の値を用いてnonceを生成していますので、自前で作るより安心かと思います。

PHPでのwordpress関数を使ったnonce生成は以下の通り。

$nonce = wp_create_nonce();

生成も簡単だし、後述するがチェックもとても簡単なので、ぜひ盛り込んでおくことをお勧めします。

「is_single」「category」「tag」「search」

今回の例ではindexページだけでカテゴリーアーカイブもタグアーカイブも検索結果表示も兼ねる形となっていますので、これらの条件分けが必須となる。

さらに重要なのが、条件分岐にあわせてタグID、カテゴリID、検索文言を取得するようにしています。

このデータをリクエストに用いることで、PHP側でwp_queryオブジェクトを生成する際のクエリとして使い、条件分岐に合わせたレスポンスを返す事が可能となる。

もちろんアーカイブテンプレートなどを使っても構わないですが、ファイルを統合できるので、@MINOはこの方法が意外と好きです。

念の為言っておくとwordpressにはほかに日付アーカイブ、投稿者アーカイブ、カスタム投稿アーカイブ、カスタムタクソノミーアーカイブというページ分岐が存在するので、しっかりした対応をするなら、これらも分岐条件に入れる必要はある。

ただ、投稿者が1人でカスタム投稿もカスタムタクソノミーも使っていない場合はそもそもそれらは用意する必要(日付アーカイブは別として)がないです。

「looplength」

このデータは「すでに表示されている記事リストの個数」です。単純ではありますが、ページに表示されているループ要素の外殻、本例のcssセレクタで言えば「.index-loop」の個数をカウントしています。

この個数はPHP側でクエリを発行する際に「オフセット」として使うことになる。

詳しくはPHPの該当処理の箇所で説明したいと思います。

「registered」

registeredもフラグです。

ajaxにはローディングタイムラグが生じる場合がある。スパッと通信が終わってくれればいいのですが、ユーザの回線状況やサーバの状態などでどうしても通信に時間が掛かってしまうこともある。

その際、「今通信中ですよ」と知らせる為にローディング中画像、通称「くるくる画像」を表示させることが多いです。

くるくる画像はユーザに今何が起こっているかを端的に説明できます。

読み込み中であることをわかってもらうことでユーザの離脱率を下げることができるという意見もある。

これも後述になってしまいますが、ajaxの発動条件を作る際に気を付けないと、ajaxスタートイベントが何度も起きてしまう場合がある。その際にくるくる画像のON・OFFを適切に行う為、表示条件をフラグ化しておくと楽になるのではないかと思います。

registeredはその為に使用します。

リクエストデータは最終的にjson形式としてまとめて使おうと思います。

PHPからjavascriptへ変数を渡す

図を確認してもらえればわかる通り、リクエストデータの「nonce」「is_single」「category」「tag」「search」「flag」に格納される値はPHP側で出力されている値です。

そこでPHPからjavascriptに値を渡す必要が出てきます。

やり方としては2つの方法が考えられる。

PHPからjavascriptへ変数を渡す方法-その1

自力でやる

自力の場合、スクリプトタグに挟まれたjavascriptをPHPからecho等で出力することで受け渡しができます。

こんな感じで記述するとjavascriptにデータを受け渡す事が可能。


$nonce = wp_create_nonce();
if(is_single()){
    $is_single = true;
}else{
    $is_single = false;
}
echo <<<AAA
        var nonce = '{$nonce}';
        var is_single = '{$is_single}';
AAA;

自力での変数出力

上記の例では以下のように変数が出力されることになる。

handmead

変数でグローバルに何個も出すと、名前空間が汚れるのが嫌だと言う場合は、オブジェクトでまとめてしまうのがスマートな方法かもしれないです。オブジェクトであれば名前汚しは一個で済みます。

PHPからjavascriptへ変数を渡す方法-その2

関数をつかう

実はwordpressではPHPからjavascriptへの変数受け渡しを関数として実装しています。wordpressを使っているのならこの関数を使うのがもっともスマートでしょう。今回の例でもこの方法を用いています。

その関数と言うのはwp_localize_script()です。この関数はwp_register_script()かwp_enqueue_script()と一緒に使う必要がある。例としては以下です。


//スクリプトを登録する
wp_enqueue_script( 'ajax', get_template_directory_uri(). '/js/ajaxnaitive.js', array('jquery') , '1.0' , false );
//登録したスクリプトに'AjaxData'というオブジェクトでデータを作成する
//これらはwp_head()の前に記述する必要がある
wp_localize_script( 
        'ajax', 
        'AjaxData',
        array( 
            flag => 'true',
            registered => 'false',
            nonce => $nonce, 
            category => $categoryId, 
            tag => $tag,
            search => $search,
            is_single => $is_single
        ) 
);
wp_head();

wp_enqueue_script()

コメントの通り、wp_enqueue_scriptでスクリプトを登録します。

第一引数はハンドル名で、のちにwp_localize_scriptで指定することになる。

第二引数はパスで「絶対ハードコーディングするなよ」(絶対に押すなよ的なフリではない)とcodexに書かれていますので、get_template_directory_uri等をつかって取得するようにしましょう。ちなみに空白のスクリプトファイルでも問題ないです。

第三引数は先に読み込む必要のあるスクリプトを指定できます。 配列なので複数指定が可能です。jQueryなどのライブラリを先に読み込みたいときに便利です。

第四引数はwp_footer()でスクリプトを読み込むようにするかどうか。デフォルトはfalseなので、スクリプトはwp_head()で読み込まれるようになる。footerで読み込む必要がある様だったらtrueにしましょう。もちろんその場合はwp_footer()の前に関数を書かなければなりません。

昨今ではページの見える範囲のレンダリングをブロッキングするスクリプト読み込みなどは嫌厭される傾向がありますので、問題がない場合はfooterで読み込んだ方がいいのかもしれないです。

ちなみにここで指定したスプリクトファイルは別途スクリプトタグで読み込ませる必要はないです。

wp_localize_script()

この関数でPHPからjavascriptにデータを渡す。第一引数はハンドル名。wp_enqueue_scriptで指定したものです。

第二引数は生成するオブジェクトの名前で任意で構わないですが、グローバルにでるので重複してはいけないです。

なるべくグローバルを汚さないようオブジェクトで変数を格納するようにしているのだと思います。これであれば汚れる名前は一個だけで済みます。

第三引数はjavascriptに渡したいデータです。連想配列で指定することによって、キーがプロパティ名となってデータが格納されるという寸法です。

関数での出力

例にあげた記述でヘッダ部分に以下のようにスクリプトが出力されるはずです。ご覧のとおりグローバルオブジェクト’AjaxData’のプロパティとして、指定したデータが格納されていることが判る。

wplocalizescript02

javascript側からこのオブジェクトにアクセスするには、いつも通りオブジェクトを扱うように以下の記述でOKです。

AjaxData.nonce

自力でやるにしても関数を使うにしてもどのみち、グローバルにすくなくとも一つはオブジェクトを出すことに変わりはないので、やりやすい方法で構わないと思います。

ネイティブjavascriptでのajax

javascript側は最初にネイティブjavascriptから説明していきたいと思います。

対象となるファイルは[ajaxnaitive.js]です。

リクエストデータをjsonにする

jsonとはjavascriptのオブジェクトを元に制定されたデータ形式です。

リクエストを行う際は出来る限り1回ですべてのデータを送れるのが良いです。

と言うのも、通信にはあたりまえだが時間が掛かる。リクエスト・レスポンスの回数ごとに時間が増大していくことになる。

だからできるだけ、変数一個一個ではなく、オブジェクトの形などでリクエストするのが望ましいです。

ただjavascriptオブジェクトをそのままの形でリクエストには使うことはできないです。

そこでデータはjson形式にしてしまうのが便利です。

少し語弊がありますが、jsonは通信用のオブジェクトと捉えてもらうといいかもしれないです。

JSON.stringify

json形式データを作るにはjavascriptのJSON.stringifyを使う。以下が例です。

//------------------------------------
//リクエストデータをjsonにする関数
//------------------------------------
function ajaxRequestsJson(){
    var loopLength = elementObj().indexLoop.length;
    var requestJson = JSON.stringify({
        //グローバルに出したオブジェクトから受け取る
        nonce : AjaxData.nonce,
        category : AjaxData.category,
        tag : AjaxData.tag,
        search : AjaxData.search,
        looplength : loopLength
    });
    return requestJson;
}

例では関数宣言の形にしてるが、もちろん単体で使用しても構わないです。使い方は簡単で、引数にオブジェクトを突っ込むだけ。これでrequestJsonがjson形式データになる。

今回はjQueryでもネイティブでもjson変換は同じコードで行っています。

実のところjsonはPHPなどと連携する場合、とても奥が深いのですが、それの説明を含めるととんでもなく長くなるのでよしておきたい(決して知識がないわけじゃないんだからねっ)

オブジェクトの生成とイベントハンドラ(開始地点)のバインド ネイティブ版

XMLHttpRequest

少し説明が前後したが、順番の上ではXMLHttpRequestオブジェクト作成を一番先に行っています。

ajaxではXMLHttpRequestオブジェクトを使うことになる。ブラウザで多少の実装差異がありますが、基本的には標準化されていますので、古いブラウザでなければ問題なく標準実装されているはずです。

ネイティブでのオブジェクト生成は以下の通り。

var Request = new XMLHttpRequest();

今回IE6などのレガシーブラウザの対応方法は省かせてもらおうと思います。もうIE6~8の対応意味はほとんど消失しているでしょう。もし対応の必要がある人は、IE6においてはActiveXについても調べる必要が出てきます。

一応IE7以降ではXMLHttpRequestが実装されている(IE7の時点ではXMLHttpRequestが標準化されている訳ではないようだ)ので今回の記事でも通信は可能かもしれない(未検証)。

イベントハンドラのバインド

ajaxの発動条件となるイベントにイベントハンドラをバインドしています。つまりここがajax処理の起点になっているという訳です。

window.addEventListener( "load" ,  function(){ scrollSenser( Request ); } , false );
window.addEventListener( "scroll" , function(){ scrollSenser( Request ); } , false );

ロードが完了した際とスクロールが発生した際に、後述するがajax通信を行う為のscrollSenser()という自作関数が呼ばれるようになっています。

ajax通信の初期化・開始 ネイティブ版

リクエストデータが用意できたら今度は通信の準備です。

ネイティブでは通信はスリーステップで行う。

今回はsendAjaxという関数にしてみましました。この関数はXMLHttpRequestオブジェクトを引数にしています。

//------------------------------------ 
//ajax実行関数
//------------------------------------ 
function sendAjax( request ){
    request.open( "POST" , "http://pecodrive.heteml.jp/ajaxtest/wp-content/themes/ajaxtest/ajaxcontent.php" , true );
    request.setRequestHeader( "content-type" , "application/json; charset=UTF-8" );
    request.send( ajaxRequestsJson() );
}

openメソッド

まず通信の初期化を行う。これにはXMLHttpRequestオブジェクトのopenメソッドを使う。

request.open( "POST" , "http://pecodrive.heteml.jp/pe/wp-content/themes/test/ajaxcontent.php" , true );

第一引数にはHTTPメソッドの指定です。基本的にPOSTとGETしか使わないです。POSTはリクエストにデータが伴う場合、GETはリクエストにデータを伴わない場合に使うことになる。

GETでもクエリの形でURIにデータを忍ばすことは出来るが、jsonなどの便利な形式が使えないし、クエリデータを整形するのは意外と骨が折れるので、データがあるなら無理せずPOSTにした方がよいかと思います。

第二引数は通信先のパスです。wordpressが用意してくれている経路では、admin-ajax.phpのパスを指定することになりますが、任意のファイルで行う際は用意したajax通信用のPHPファイルを指定します。 今回はajaxcontent.phpというファイルになる。

第三引数は同期通信を望む場合はflaseにし、非同期通信を望む場合はtrueにします。 ajaxは非同期通信である事が重要なので、falseにする機会は滅多にないはずです。デフォルトではtrueなので省略しても構わないです。

setRequestHeaderメソッド

このメソッドでリクエストにヘッダを与えることができます。 ヘッダによってサーバに対して「このデータはjsonで、UTF-8がんす」と教えることができます。

request.setRequestHeader( "content-type" , "application/json; charset=UTF-8" );

コンテンツタイプと言うのはデータの形式の事で、MIMEタイプと同義です。

今回はjson形式でリクエストを行うので、application/jsonを指定することになる。

その他にも以下の様なコンテンツタイプが存在しています。

コンテンツタイプ 意味
text/plain プレーンテキストを送る場合
text/html HTM文書を送る場合
application/xml Xml形式データを送る場合
application/x-www-form-urlencoded フォームデータを送る場合

chrasetはデフォルトでUTF-8になる。だが念の為、明記しておいた方がいいでしょう。文字化けに泣く羽目になるかもしれないです。

もちろん用途によっては他のキャラセットを使う必要があるかもしれないですが、@MINOはあまり詳しくないので説明が能わないです。

sendメソッド

このメソッドで実際に送信が始まる。先に説明したリクエストデータをjsonにする関数を引数に取っていますが、これはjsonデータを指定していることと同義です。

request.send( ajaxRequestsJson() );

これで拵えたリクエストデータがサーバに届くことになる。

イベントハンドラ ネイティブ版

ajax通信のレスポンスは通常XMLHttpRequestオブジェクトのイベントにてハンドリングすることになる。

ネイティブとjQueryではこのあたりが大分違う印象を受けるかと思いますが、jQueryでも内部で処理しているのはこれらのイベントなので、ネイティブで書く予定がなくても、知っておくといいかもしれないです。

基本的にこれらのイベントハンドリングがajaxの中心的な部位になります。

イベントの種類

XMLHttpRequestオブジェクトは通信開始、通信中、通信ステータス変化、通信成功、通信失敗、通信中断、通信完了を捕まえることが出来るようになっています。

イベントがどのようなタイミングで発火するかは図も参照してもらうとわかりやすいかもしれないです。

イベント種類 発火条件
onreadystatechange readystateが変化
onloadstart 通信開始
onprogress 通信中
onloadend 通信完了(成否無関係)
onload 通信成功
onerror 通信エラー
onabort 通信中断
ontimeout 通信タイムアウト

onloadstartイベント

このイベントはajaxがスタートした場合に発火します。 ajaxがスタートした際に行いたい処理はこのイベントにバインドするのがよいです。

例ではこのイベントに、くるくる画像の付与と画面を少し白くする処理を与えています。

//------------------------------------
//通信開始イベントハンドラ
//----------------------------------
Request.onloadstart = function(e){
    console.log("スタートしたで");
    var obj = elementObj();
    if( AjaxData.registered !== true){
        //画面を白っぽくして…
        obj.responseArea0.style.opacity = "0.5";
        //くるくるを追加
        var element = document.createElement( "div" ); 
        element.id = "loading"; 
        element.innerHTML = "<img src='https://pecodrive.heteml.jp/ajaxtest/wp-content/themes/ajaxtest/img/ajax-loader3.gif' alt="" />
        obj.body.appendChild( element );
        AjaxData.registered = true;
    }
};

下記のコードで記事挿入親要素のopacityを0.5にして、画面を白っぽい感じにしています。

obj.responseArea0.style.opacity = "0.5";

また下記のコードでbodyにくるくる画像付け加えています。

var element = document.createElement( "div" ); 
element.id = "loading"; 
element.innerHTML = "<img src='https://pecodrive.heteml.jp/ajaxtest/wp-content/themes/ajaxtest/img/ajax-loader3.gif' alt="" />"; 
obj.body.appendChild( element );

ネイティブで要素を加えるのは結構面倒です。エレメントを登録しなくてはならないからです。jQueryのようにすぐにappendするわけにはいかないのです。

今回の例ではloadingというidをもつdivを作り、その中にくるくる画像のimgタグを突っ込んでいます。

ちなみにinnerHTMLは標準化されているプロパティではない(いわゆるDOM0というやつ)なので、本来は使うべきではないのだろうが、ほとんどのブラウザに実装されており、現実的には楽に要素を付け加えられるので使われている現状がある。

これらの処理は

if( AjaxData.registered !== true)

によって条件分岐しています。registeredは先に説明したとおり、くるくる画像の付与フラグです。付与されていない場合のみくるくる画像が付与されるようにしてある。

今回はスクロールによってajaxが発生するようにしているのですが、スクロールの状態によってはonloadstartが何度も発火してしまう場合があり、

<div id="loading"></div>

が重複して出力されてしまう可能性がある。それを避ける為に、くるくる画像を付与したらtrueにし、外れたらfalseし分岐させています。

onloadendイベント

このイベントはajaxが終了した場合に発火します。 成功したか失敗したかは関係なく発火するので、終了をキャッチするならこのイベントを使うのがいいでしょう。

//------------------------------------
//通信終了イベントハンドラ
//------------------------------------
Request.onloadend = function(e){
    console.log("終わったで");
    var body = document.querySelector( "body" );
    var responseArea = document.querySelector( "#responseArea" );
    //成功したら画面を戻して…
    responseArea.style.opacity = "1.0";
    //くるくるを外す
    var loading = document.getElementById("loading");
    body.removeChild( loading );
    //くるくるフラグをセット
    AjaxData.registered = false;
};

下記のコードでonloadstartイベントハンドラで0.5にした記事挿入親要素のopacityを1.0戻し復帰させています。

obj.responseArea0.style.opacity = "1.0";

また下記のコードでくるくる画像を取り除いています。

var loading = document.getElementById("loading");
obj.body.removeChild( loading );

最後にフラグをセットして重複出力を防いでいます。

AjaxData.registered = false;

onloadイベント

このイベントはajaxが成功した場合に発火します。 その為、通常はレスポンス処理のイベントハンドラをバインドする事になると思います。

//------------------------------------
//通信成功イベントハンドラ
//------------------------------------
Request.onload = function(e){
   //jsonデータをパース
   var responceData = JSON.parse(Request.response);
   //レスポンスされたフラグをグローバルにセット
   AjaxData.flag = responceData.flag;
   //追加エレメントを準備
   var element = document.createElement( "div" ); 
   element.innerHTML = responceData.html; 
   document.querySelector( "#responseArea" ).appendChild( element );
};

レスポンスはXMLHttpRequestオブジェクトのresponseに格納されることになっています。

var responceData = JSON.parse( Request.response );

今回はレスポンスもjsonなので、JSON.parseにてパースを行っています。これによってデータはjavascriptのオブジェクトに変換されることになる。

そしてレスポンスデータにはhtmlとflagというプロパティを持たせていますので、記事ループhtmlはhtmlプロパティを参照することで得られる。

ネイティブでは要素追加を登録する必要がありますので、div要素を作り、そこにレスポンスhtmlを突っ込んでいます。

element.innerHTML = responceData.html; 
document.querySelector( "#responseArea" ).appendChild( element );

onreadystatechangeイベント

このイベントはXMLHttpRequestオブジェクトのreadyStateプロパティが変化した場合に発火します。 readyStateは数字で全部で5つの状態が存在しています。

//------------------------------------
//通信中イベントハンドラ
//------------------------------------
Request.onreadystatechange = function(e){
    console.log("今" + Request.readyState + "やで");
};
番号 意味
0 リクエストが初期化されていない
1 サーバとの接続確立
2 (サーバが)リクエストを受信した
3 (サーバの)リクエストの処理中
4 レスポンスの準備完了

onreadystatechangイベントを追うことでajax通信状態を細かく把握でき、また細かいコントロールが可能になる。今回の例ではあまりこのイベントには関係する処理がないので、単にreadyStateを出力するconsole.logだけを置いていますが、ajaxを複雑にコントロールしたいならばこのイベントは必須です。

onerrorイベント

このイベントは通信が失敗した場合に発火します。 先ほど説明したonloadイベントとは排他的で、どちらかのイベントしか起こらないことが保障されています。

//------------------------------------
//通信失敗イベントハンドラ
//------------------------------------
Request.onerror = function(e){
   console.log("エラーやで");
};

ajax失敗時の処理は通常このイベントにイベントハンドラをアタッチして行われる。

今回の例ではエラー処理を何も行っていないので(!!)これもconsole.logを置いてコメント出力するだけにしてある。

普通であれば通信のやり直しなどの処理をバインドすることになるかと思います。

ontimeoutイベント

このイベントは通信がタイムアウトした場合に発火します。

//------------------------------------
//通信タイムアウトイベントハンドラ
//------------------------------------
Request.ontimeout = function(e){
   console.log("タイムアウトやで");
};

timeout処理も何も行っていないので(!!!!)console.logを置いてコメント出力するだけにしてある。

ちなみにtimeoutのデフォルト値はブラウザの実装に依るのではないかと思う(W3Cに記述が見当たらなかった)。

ネイティブでtimeout値をセットするには以下の様にtimeoutプロパティで設定を行う。

var Request = new XMLHttpRequest();;
request.open( "POST" , "http://pecodrive.heteml.jp/pe/wp-content/themes/test/ajaxcontent.php" , true );
request.setRequestHeader( "content-type" , "application/json; charset=UTF-8" );
Request.timeout = 2000;
request.send( ajaxRequestsJson() );

指定値の単位はミリ秒なので、1000で1秒です。おそらく0にするとエラーになるはずなので、0以上の値を指定する必要がある。

timeoutの設定はopenメソッドとsendメソッドの間で行う必要がある。

onabortイベント

このイベントは通信を中断した場合に発火します。

//------------------------------------
//通信中断イベント
//------------------------------------
Request.onabort = function(e){
   console.log("中断したで");
};

今回は処理を実装していないですが、XMLHttpRequestオブジェクトのabort()メソッドでajaxを中断することが可能です。

onabortはabort()で中断されたときに発火される。

スクロール位置でajaxを発生させる仕組み

今回はスクロールの状態に対してajaxを発生させるようにしています。 その処理を行っているのが以下の自作関数です。

  • elementObj()
  • bodyScrollSenser()
  • processPoint()
  • allLoopHeight()
  • scrollsenser()

ちなみにこの箇所もネイティブ、jQueryでほぼ共通です。(違う箇所についてはjQuery版の際に説明したい)

elementObj() DOMオブジェクトまとめ

DOMオブジェクトを取得する関数として定義。いろいろな場所でエレメントオブジェクトを使うのでまとめた。

//------------------------------------
//エレメントオブジェクトまとめ関数
//------------------------------------
function elementObj(){
    console.log("エレメントオブジェクトまとめ関数");
    var body = document.querySelectorAll( "body" )[0];
    var html = document.querySelectorAll( "html" )[0];
    var indexLoop = document.querySelectorAll( ".index-loop" );
    var indexLoop0 = document.querySelectorAll( ".index-loop" )[0];
    var responseArea = document.querySelectorAll( "#responseArea" );
    var responseArea0 = document.querySelectorAll( "#responseArea" )[0];
    var single = document.querySelectorAll( "#single" );
    var windowHeight = $( window ).height();
    return {
        "body" : body,
        "html" : html,
        "indexLoop" : indexLoop,
        "indexLoop0" : indexLoop0,
        "responseArea" : responseArea,
        "responseArea0" : responseArea0,
        "single" : single,
        "windowHeight" : windowHeight

    };
}

シングルトンにしていないのは、querySelectorAllを使っているからです。その都度値を取得する必要がありますので、関数の形にしてある。

なんだか無駄な感じもしなくもないですが、まあいいかと思っています。

ちなみにネイティブの方がjQueryでDOMオブジェクトを得るよりも、相当速く(上手くいくと10倍程度)動作します。

bodyScrollSenser() スクロール取得

windowスクロールを取得する関数。

//------------------------------------
//スクロール取得
//------------------------------------
function bodyScrollSenser(){
    var obj = elementObj();
    var bodyScroll;
    //スクロールの取得(一応クロスブラウザ対応)
    if( obj.body.scrollTop === 0 || obj.body.scrollTop == false ){
        bodyScroll = obj.html.scrollTop;
    }else{
        bodyScroll = obj.body.scrollTop;
    }
    return bodyScroll;
}

特に説明する箇所はないですが、しいて言うなら一応クロスブラウザ対応にしてある。

chromはbodyオブジェクトからスクロール値を得ることができるが、FirefoxとIEはhtmlオブジェクトからスクロール値を得る。

この関数はスクロール発生時に実行されるので、bodyかhtmlのどちらかでスクロール値が取れているはずです。

なので条件分岐することで、bodyで取得するか、htmlで取得するかを分けています。

allLoopHeight() 要素の高さ計算

すでに表示されている記事リストの要素のoffsetHeight(最外殻高さ)の合計を計算します。

//------------------------------------
//要素の高さ計算関数
//------------------------------------
function allLoopHeight(){
    var obj = elementObj();
    var allLoopHeight = 0;
    //.index-loop全部の高さを合計
    for( var i = 0 ; i < obj.indexLoop.length ; i++ ){
        allLoopHeight = allLoopHeight + obj.indexLoop[i].offsetHeight;
    }
    return allLoopHeight;
}

次のprocessPoint()での値として使う。

processPoint() ajax発動位置算出

ajaxを発生させる基準を計算する関数。

//------------------------------------
//ajax発動位置算出関数
//------------------------------------
function processPoint(){
    var obj = elementObj();
    //index-loopは最小1列、最大では何列になるかがわからないので、横幅を計測して列数分endpointを縮小する
    if(!obj.indexLoop0){
        return { "bodyPoint" : 0 , "endPoint" : 0 };
    }else{
        var ratio = Math.floor( obj.responseArea0.offsetWidth  /  obj.indexLoop0.offsetWidth );
        var endPoint = ( allLoopHeight() / ratio );
        var bodyPoint = bodyScrollSenser() +  obj.windowHeight + ( obj.windowHeight * 0.1 );
        return { "bodyPoint" : bodyPoint , "endPoint" : endPoint };
    }
}

今回のデモでは一応レスポンシブなつくりにしています。PC、スマートフォン、タブレットなどで見てもらうと、記事リストの列数がかわるを確認してもらえるはずです。

そのため記事リストが何列になるかはデバイスに依るので、記事リストがapendされる親要素(#responseArea)のoffsetwidth(最外殻幅)を記事リストの外殻要素(.index-Loop)で割って比(ratio)を出しています。

記事リストの外殻要素のoffsetHeight(最外殻高さ)を合計するallLoopHeight()を先ほどのratioで割ってやれば、記事リストが切れる位置が判るので、それをendPointとしています。

またスクロール値にwindowのheightを足すことで、画面下辺のスクロール位置を得ることが出来き、これをbodyPointとしています。

すなわち、endPointよりもbodyPointが同じか大きくなった場合にajaxを発生させるという事です。

bodyPointの計算に

( obj.windowHeight * 0.1 )

を足しているのは、記事が切れる少し前の位置でajaxが発生するようにするマージンです。

scrollSenser() スクロール検知

実質的なメイン関数。ajaxを発生させるかの条件分岐を行う。

//------------------------------------
//スクロール検知関数
//------------------------------------
function scrollSenser( request ){
    console.log("スクロール検知関数");
    var flag = AjaxData.flag;
    var obj = elementObj();
    var point = processPoint();
    //初回の動作
    if( obj.indexLoop.length === 0 && AjaxData.is_single === "false" ){
        console.log("初回やで");
        sendAjax( request );
    //二回目以降の動作
    }else if( obj.indexLoop.length > 1 ){
        //フラグチェック
        if( flag === false ){
            console.log("全部出し尽くしたで");
        //フラグがtrueだったらajaxスタートのスクロール検知の処理を続行
        }else if( flag === true ){
            //スクロールが基準値に達したらajax発動
            console.log("point.endPoint" + point.endPoint);
            console.log("point.bodyPoint" + point.bodyPoint);
            if( point.endPoint < point.bodyPoint){
                //通信中はajaxをスタートさせないようにする
                if( request.readyState === 0 /*初期化前状態*/ || request.readyState === 4 /*通信終了状態*/){ 
                    sendAjax( request );
                }
            }
        }
    }
}

表示されている記事リストが無い場合(つまり初回の場合)には一度ajaxを発生させるようにしています。

if( obj.indexLoop.length === 0 && AjaxData.is_single === "false" ){
        console.log("初回やで");
        sendAjax( request );

二回目以降は、PHP側からもたらされたflagにて判断を行う。flagがtrueであれば記事はまだ残存しているということになるし、flagがfalseであればもう記事はないので、ajaxを発生させても仕方ないから、なにも処理しないです。

if( flag === false ){
            console.log("全部出し尽くしたで");

さらにflagがtrueの場合に、今度はスクロールのチェックを行う。先に説明したprocessPoint()から受け取ったendPointとbodyPointを比べて、bodypointがendpointよりも大きくなったら、処理が進むようになっています。

}else if( flag === true ){
            //スクロールが基準値に達したらajax発動
            console.log("point.endPoint" + point.endPoint);
            console.log("point.bodyPoint" + point.bodyPoint);
            if( point.endPoint < point.bodyPoint){

そして最後に、XMLHttpRequestオブジェクトのreadyStateを確認しています。今回の例ではオブジェクトを一つ(現実的には上書きしている)だけ使っていますので、前の通信の状態がreadyStateに残っています。

 if( request.readyState === 0 /*初期化前状態*/ || request.readyState === 4 /*通信終了状態*/){ 
     sendAjax( request );
 }

もし通信が始まっていないならreadyStateは0になる。

通信が始まっていればreadyStateは1~3のどれかの値を取ることになる。

通信が終わっていればreadyStateは4になる。

その為、readyStateが4か0の時には通信をスタートさせてもいいということになる。

以上の条件をすべてクリアしている場合に、sendAjax()がよばれ、ajaxが発生することになる。

jQueryでのajax

長くてうんざりかもしれないですが、こんどはjQuery版の説明していきたいと思います。

対象となるファイルは[ajax.js]です。

グローバルイベントハンドラのバインド jQuery版

jQueryではonメソッドをつかってloadとscrollイベントが発生した場合にscrollsenser()が呼ばれるようにしてある。

ネイティブと同じくここが起点となる。

//------------------------------------
//ajax実行
//------------------------------------
$( window ).on( "load scroll" , function(){
    scrollsenser( );
    atachcss(obj1);
});

XMLHttpRequestオブジェクト jQuery版

jQueryでは$.ajax()メソッドがjqXHRというオブジェクトを返すことになる。このオブジェクトはXMLHttpRequestのスーパーセット(拡張版みたいな意味)なのでほぼXMLHttpRequestとして扱うことができます。

//------------------------------------
//jqXHRオブジェクトをつくる関数
//------------------------------------
function jqueryAjax(){
    var jqXHR = $.ajax({
        type: "POST",
        url: "http://pecodrive.heteml.jp/pe/wp-content/themes/test/ajaxcontent.php",
        data: ajaxRequestsJson()
    }).done( function( responceData ){
        doneProcess( responceData );//成功時
    }).always( function(){
        alwaysProcess();//完了時
    }).fail(function(){
        alwaysProcess();//失敗時
    });

    return jqXHR;
}

この例ではjqXHRにXMLHttpRequestのスーパーセットが入ることになる。

他の部分はそれぞれの項目で後述します。

ajax通信の初期化・開始 jQuery版

jQuery版ではajax初期化・開始に.ajaxメソッドをつかう。

$.ajax({
        type: "POST",
        url: "http://pecodrive.heteml.jp/pe/wp-content/themes/test/ajaxcontent.php",
        data: ajaxRequestsJson()
})

type url data

これらはネイティブ版のopenまたはsendで指定しているデータと同じです。このように.ajaxメソッドに設定値を与える事で、通信の初期化・開始が行える。

ネイティブ版ではsetRequestHeaderメソッドでヘッダをセットしていましたが、jQueryではリクエストデータの形式を勝手に判断してくれて、適切なヘッダを付与してくれる。もちろん自分で付与することも可能でその為のメソッドもありますが、今回は特に必要ないので詳しい説明は省かしてもらった。

先に説明したとおりajaxメソッドはXMLHttpRequestのスーパーセットオブジェクトを吐く。設定値を与えオブジェクトを構築するまでをこの関数で行うことになる。

コールバック関数 jQuery版

jQueryでは、イベントにイベントハンドラをバインドするといった方法ではなく、jQueryが用意しているメソッドを使うことになる。

先に説明したとおりに、$.ajaxはXMLHttpRequestのスーパーセットであるjqXHRを返す。

このjqXHRはPromiseインターフェースが持っているメソッドとプロパティを持っています。

Promiseに関して(さらに言えばDeferredに関して)は本記事の範疇を超してしまうので、詳しくは別記事にしたいと思います。

簡単に説明すると、jqXHRは処理が成功した、処理が失敗した、処理が終わった、処理中などといった状態をこちらからトラッキングせずに、自分から訴え出ることのできる子と言ったらわかりやすいだろうか?

つまるところPromiseやDeferredオブジェクトを使うとブロッキングを避けることができるので、柔軟かつ応答性の良い処理が行えるのです。

その為、従来のsuccess、error、complete等を使った書き方よりも優れていると言える(はず)。

done() 成功時のコールバック

ajaxが成功した場合に実行されるコールバック。

.done( function( responceData ){
        doneProcess( responceData );//成功時

ネイティブでいうとonloadに相当します。

このコールバックにはdoneProcess()という自作関数を当てています。

//------------------------------------
//通信成功時処理関数
//------------------------------------
function doneProcess( responceData ){
    //レスポンスされたフラグをグローバルにセット
    AjaxData.flag = responceData.flag;
    $( "#responseArea" ).append( responceData.html );
}

やっている事はネイティブのonloadイベントハンドラとほとんど変わりはないです。

jsonからjvascriptオブジェクトへの変換処理がないのは、jQueryがその辺の処理を勝手にやってくれるからです。

always() 終了時のコールバック

ajaxが終了した場合に実行されるコールバック。

.always( function(){
        alwaysProcess();//完了時

ネイティブでいうとonloadendに相当します。

これも成功時と同じように、alwaysProcess()という自作関数を与えて、ネイティブとほとんど変わらない処理をしています。

//------------------------------------
//通信完了時処理関数
//------------------------------------
function alwaysProcess(){
    console.log( "終わったで" );
    //成功したら画面を戻して…
    $( "#responseArea" ).css( "opacity" , "1" );
    //くるくるを外す
    $( "#loading img" ).remove();
    //くるくるフラグをセット
    AjaxData.registered = false;
}

fail() 失敗時のコールバック

ajaxが失敗した場合に実行されるコールバック。

.fail(function(){
        alwaysProcess();//失敗時

ネイティブでいうとonerrorに相当します。

特に意味のある処理をアタッチしていない(!!!)

//------------------------------------
//通信失敗時処理関数
//------------------------------------
function failProcess(){
    console.log("エラーやで");
}

その他のコールバック

ネイティブの方に存在した、onloadstart、onreadystatechange、ontimeout、onabort、onprogressに相当するものがないじゃないかと思われたかもしれないです。

onloadstartに関してはstratProcess()という関数がコールバックにあたるが、これは単にscrollsenser()のjqueryAjax()の実行に後に読んでいるだけです。

//------------------------------------
//通信開始時処理関数
//------------------------------------
function stratProcess(){
    console.log( "はじまったで" );
    if( AjaxData.registered !== true){
        //画面を白っぽくして…
        $( "#responseArea" ).css( "opacity" , "0.5" );
        //くるくるを追加
        $( "body" ).append( "<div id="loading"><img src="https://pecodrive.heteml.jp/ajaxtest/wp-content/themes/ajaxtest/img/ajax-loader3.gif" alt="" /></div>");
        //くるくるフラグをセット
        AjaxData.registered = true;
    }
}

というのもonloadstartが何に当たるのかがよくわからなかったからです。勉強不足もいいところですが、特に困らなかったのでこれでいいかという感じです。

その他のイベントに関しても、jQueryではラッピングされていますので、何が何に当たるかがあまり判らなかった。

少なくともonprogressに関してはprogress()が、onreadystatechangeに関してはstate()が対応するかと思います。

今回の処理ではそれほど困る事もないのでまあいいかといった感じです。

PHPファイルの処理

さてPHP側です。

何回か説明しているとおり、wordpressにはあらかじめajax経路が存在しています。/wp-admin/admin-ajax.phpにリクエストする方法です。

しかし今回はwordpressがマネージしてくれる経路でajaxを行わないです。その為に自作のPHPを拵えることになる。本例ではファイル名をajaxcontent.phpとしました。

このajaxcontent.phpはwordpressとは何にも関係ないので、このファイル上でwordpress関数を使うことは能わないです。

wordpress関数を使えるようにする

その為、関数を使えるように関数定義ファイルを読み込む必要がある。

//wordpress関数を使えるようにするためにwp-load.phpを読み込む
require_once( dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) . '/wp-load.php' );

wordpressは実に便利にできており、公開範囲直下に存在するwp-load.phpを読み込むことで、芋づる式に各種クラス・関数定義ファイルを読み込むことができます。

上記の例ではマジック変数__FILE__とdirname( )でパスを取得しています。dirnameを4つも重ねているのは、ajaxcontent.phpをテーマフォルダに入れているからです。テーマフォルダから見た場合、wp-load.phpは4階層上に存在しているのでdirnameを4つ重ねているのです。

リクエストデータを受け取る

$requests = json_decode( file_get_contents( "php://input" ) , true );

php://inputはリクエストのbody部分から生データを取得することができます。

file_get_contentsメソッドでphp://inputを指定することで、javascriptによってサーバに送られたPOSTのリクエストデータを得ることができます。

リクエストデータはjsonなので、PHPで使えるようにjson_decodeメソッドでデコードする必要がある。

json_decodeの第二引数はデコード形式の指定で、trueにすると連想配列となる。falseにするとデータ形式に合わせた解析がおこなわれ、適切なPHPの型に変換される。おおよそオブジェクトに変換されることになるかと思います。

nonceチェック

通信のセキュリティを高める為に、nonceの発行を行ったが、ここでnonceが正規なものかをチェックします。

//nonceチェック nonceが不正ならここでdie
if( ! wp_verify_nonce( $requests["nonce"] ) ){
            die( "不正やで?" );
}

チェックは非常に簡単で、wp_verify_nonceで行える。/wp-admin/admin-ajax.phpでのajaxの場合、check_ajax_refererを使うのがもっとも楽ですが、この関数はadmin-ajax.phpを経由することを前提としていますので、今回のような独自ルートの場合はwp_verify_nonceを使うのが良いと思います。

$requestsにはリクエストデータが入っていますので、今回の場合などは$requests[“nonce”]でnonce値を引っ張ることができます。

コードでは、もしnonceが不正であれば、即座にdieで通信を終えることになる。もちろんdieだけでなく、高度な処理を書くこともできるので、各位の必要に応じて実装して構わないです。

表示数の定義

ここで表示数の定義を行っています。

//表示数の定義
$posts_per_page = get_option('posts_per_page');

wordpress管理画面の表示設定にある「1ページに表示する最大投稿数」を引っ張て来ています。

こうすることによって、管理画面上で表示数を変えることができるので少し楽です。

「1ページに表示する最大投稿数」を10にしている場合、ajax1回につき10記事分のリストがレスポンスされることになる。

wp_queryオブジェクト生成用のパラメータ

ここではwp_queryオブジェクトを生成するためのパラメータを拵えています。

//$wp_query用のクエリを拵える
$args = array(  
    'posts_per_page'   => $posts_per_page,
    'offset'           => $requests["looplength"],
    'orderby'          => 'post_date',
    'order'            => 'DESC',
    'post_type'        => 'post',
    'post_status'      => 'publish'
);

//カテゴリーアーカイブの場合
if( $requests["category"] ){
    $args = $args + array(  
        'cat' => $requests["category"]
    );
}

//タグーアーカイブの場合
if( $requests["tag"] ){
    $args = $args + array(  
        'tag_id' => $requests["tag"]
    );
}

//検索の場合
if($requests["s"]){
    $args = $args + array(  
        's' => $requests["s"]
    );
}

記事表示数

先に説明したget_option(‘posts_per_page’)で取得した値を設定しています。

'posts_per_page' => $posts_per_page

「1ページに表示する最大投稿数」を10にすれば、10記事づつレスポンスされることになる。

記事参照位置のオフセット

ここで重要なのは以下の箇所です。

'offset'=> $requests["looplength"]

wp_queryコンストラクタが取るパラメータに「offset」と言うものがある。これは指定した分検索位置をずらすというパラメータです。

今回はリストはクラス要素の「.index-loop」で構成しています。またこの「.index-loop」の個数をカウントしてすでに表示されている個数を判断しています。

たとえば30記事分のリストがすでに表示されている場合にajax通信を行うと、次は31記事目からレスポンスしてもらいたいはずです。

しかし「offset」を設定しておかないと、照会されるのは1記事目からになってしまう。

31記事目からにするためには、既に表示されている個数をoffsetする必要があるのです。

そのため、リクエストデータに含めたlooplength(すでに表示されている.index-loopの個数)をoffsetに当てているのです。

これで、毎回の通信で1記事目からのレスポンスにならずに済みます。

記事リストのソート

'orderby' => 'post_date'

orderbyが並べ替えをする基準です。今回は日付の降順で並べていますが、他にも多数指定できるパラメータがある。またこれらは複数指定が可能です。

‘none’, ‘name’, ‘author’, ‘date’, ‘title’, ‘modified’,’menu_order’, ‘parent’, ‘ID’, ‘rand’, ‘comment_count’

orderが並べ替え方法です。

'order' => 'DESC',

‘DESC’は降順で、昇順の場合は’ASC’を指定します。

条件分岐によるパラメータ追加

ここではis_home、is_category、is_tag、is_search()ではなく、リクエストデータの有無で条件分岐をしています。リクエストデータにカテゴリIDがあればそれをクエリ条件に加える、タグIDがあればそれをクエリ条件に加える、検索キーワードがあればそれをクエリ条件に加える、といった処理を行っています。

//カテゴリーアーカイブの場合
if( $requests["category"] ){
    $args = $args + array(  
        'cat' => $requests["category"]
    );
}
//タグーアーカイブの場合
if( $requests["tag"] ){
    $args = $args + array(  
        'tag_id' => $requests["tag"]
    );
}
//検索の場合
if($requests["s"]){
    $args = $args + array(  
        's' => $requests["s"]
    );
}

基本のパラメータ配列にこれらがプラスされ、状況にあったレスポンスを得られるようになっています。

wp_queryオブジェクトの生成と残り個数の確認

wp_queryオブジェクト

拵えたパラメータ配列をwp_queryコンストラクタに与えてwp_queryオブジェクト生成。

//$wp_queryオブジェクトを作成
$wp_query = new WP_Query( $args );

照会された記事数のカウント

そして、照会された記事数をカウントしています。記事は$wp_queryのpostsプロパティに配列の形で格納されていますので、この配列の個数をcountメソッドで数えることで。照会された記事数がわかる。

//クエリに合致した投稿数をカウント
$post_count = count( $wp_query->posts );

残存記事数の確認

その記事数が$posts_per_pageよりも少ない場合、次にリクエストされてもレスポンスする記事が無い(つまり今回で最後)ということなので、falseを$flagに入れています。

もし記事数が$posts_per_pageより多ければ、$flag = trueとなる。

$flagは最終的にレスポンスデータとしてjavascriptに返されるので、javascript側ではこのフラグ値でajax通信を行うかどうか判断しています。

//もし残存投稿数が$posts_per_page以下ならfalseを返してajaxを止める
if($post_count < $posts_per_page ){
    $flag = false;
}else{
    $flag = true;
}

htmlデータを作る

htmlデータの生成

//疑似ループにてloop部品を仕立てる
while (have_posts()){
    the_post();
    $permalink = get_the_permalink();
    $time = get_the_time('Y/m/d G:i');
    $title = get_the_title();
    $output = preg_match_all('/<img.+src=[\'"]([^\'"]+)[\'"].*>/i', $post->post_content, $matches);
    $cat = get_the_category();
    $catname = $cat[0]->cat_name;
    $catid = $cat[0]->cat_ID;
    $caturl = get_category_link($catid);
    $img = $matches [1] [0];
    $tags = get_the_tags();
    $retags = array_values((array)$tags);
    $tagname = $retags[0]->name;
    $tagid = $retags[0]->term_id;
    $tagurl = get_tag_link($tagid);
    if(empty($img)){ 
        $img = get_template_directory_uri()."/img/noimage.png";
    }
    $img_tag = "<img class=\"img\" src=" . $img . ">";
    $loophtml = <<< EOF
        <div class="index-loop">
            {$img_tag}
            <span class="time">
                {$time}
            </span>
            <span class="cat">
                カテゴリー
                <a href="{$caturl}">
                    {$catname}
                </a>
            </span>
            <span class="tag">
                タグ 
                <a href="{$tagurl}">
                    {$tagname}
                </a>
            </span>        
            <span class="title">
                タイトル
                <a href="{$permalink}">
                    {$title}
                </a>
            </span>
        </div>
EOF;
    //どんどん付け足していく
    $tmp = $tmp . $loophtml;
}

テンプレートファイルと同じようにhave_posts()やthe_post()を使うと楽です。

the_post()を使うと記事はループ毎に$postに格納されるのでハンドリングがたやすくなる。

$postに格納してしまえば、get_the_permalink()、get_the_time()、get_the_title()などを使うことができます。

$permalink = get_the_permalink();
$time = get_the_time('Y/m/d G:i');
$title = get_the_title();

ループ毎に1個のdiv.index-loopができるので、それをどんどん足していってひと塊のhtmlデータしています。

$tmp = $tmp . $loophtml;

以下は記事で使われている画像を取得するためのコード

$output = preg_match_all('/<img.+src=[\'"]([^\'"]+)[\'"].*>/i', $post->post_content, $matches);
$img = $matches [1] [0];
if(empty($img)){ 
   $img = get_template_directory_uri()."/img/noimage.png";
}
$img_tag = "<img class=""img"" src=" . $img . " alt="" />";

レスポンスデータの整理・送信

レスポンスデータをjson化

作ったhtmlデータと記事残存フラグをjsonデータにします。

今回は配列にしてから、json_encodeにてjson形式データに変換しています。これはjvascript側でリクエストデータを生成した時と同じような処理です。

ちなみに配列じゃなけらばならないわけではなく、オブジェクトでもOKです。

//レスポンスデータをjson形式に整形
$data = array(
    "html" => $tmp,
    "flag" => $flag,
);
$jsonData = json_encode( $data );

レスポンスヘッダー

javascript側でリクエスト時にリクエストヘッダが必要だったように、ブラウザにレスポンスを返す際もレスポンスヘッダが必要になる。

//レスポンスヘッダーをセット
header( "Content-Type: application/json; X-Content-Type-Options: nosniff; charset=utf-8" );

レスポンスデータはjsonなのでコンテンツタイプにはapplication/jsonを指定します。

X-Content-Type-Options: nosniff;と言うのは、データの自動解析をオフにするパラメータです。これを付けておくと、データが誤解析される事を意図するXSS攻撃を防ぐことができます。

wordpressが用意してくれる経路でのajaxでは、レスポンス時にこのX-Content-Type-Options: nosniff;を自動的に不可してくれるが、今回は独自経路なので、明示的に書く必要がある。

セキュリティーなので忘れずにつけておきたいです。

データ送信

最後にデータをechoして、dieで終了。

//レスポンス送信
echo  $jsonData;
//おわり
die();

補足説明

コードをダウンロードしてもらえばわかるかと思いますが、jsファイルには、紹介していないコードが含まれています。

これはレスポンシブを行う為の自作関数なので、チラ見してもらえれば十分です。

@MINOはメディアクエリを使って分岐させるのとても嫌いなので、javascript側から分岐させるようにしています。

今回ブレークポイントは三つ、320、1200、9999にしてある。すなわち320px以下、321px以上~1200px、1201px~9999pxでレスポンシブが発動します。

レスポンス追従しながらでもajax発動するので、ぜひ確認してもらえたらと思います。

まとめ

ざっと3万字の記事になってしまいましました。もっと整理して記事を書ければいいのですが、申し訳ない気持ちでいっぱいです。

今回の記事でajaxの端緒でもお伝えできていれば幸いです。

PHPとjavascriptを交互に書いていると、いつの間にかjavascriptでvar_dumpで書いてみたり、$を付けて見たりします。 PHP側でconsole.logを書いて、なんでエラーなんだと悩むこと度々。

こういうことがあると、サーバ側もクライアント側も言語を統一したくなる気持ちもわかる。

しかし人気は落ちてきているとはいってもjQueryには助けられることが多いです。ネイティブで書くのは本当に骨が折れる。

最近ではTypescriptが便利だなと思いつつある。