abe

WordPressで「最近見た記事」をスクラッチで実装する

プラグインを使わずに、最近見た記事を表示してみます。

要件としては、文字通りサイト閲覧者が表示した・訪問した記事をいくつか記録し、その記事情報を表示する、といったところでしょうか。
対象の閲覧者は非ログイン・ログインどちらもということにします。

設計する上で

見た記事を保存するストレージが必要です。
これはユーザごとに必要です。

また、記事のデータは編集されることがあると想定します。
何らかの不都合があって記事を非公開にした場合があると仮定しておきます。
その場合、記事データをユーザ側のストレージに保存しておくとサーバ側とのデータの差異が出ます。
差異があった場合にどのようにするか考えておきます。

記事データをストレージに保存する場合と、記事を特定する情報をストレージに保存する場合を考えます。
前述の通り、記事を非公開にした場合に記事データをストレージに保存しているとデータの不整合とみなすことができます。
これが許容されるかは、そのケース次第ですので、場合に応じて検討してください。

以上のことを踏まえた設計

データはWebStorageAPIを使って、LocalStorageに保存します。
ここには記事IDのみを保存しておくことにします。
何件かの記事を表示するので、配列型での保存になりますがLocalStorageでは1つのキーに対して値は文字列型での保存になりますので、JSONを文字列化して保存することにします。

記事IDのみを保存しているため、レンダリングに必要な情報はサーバ側から取得します。
WP REST API v2を有効にして、XHRで取得することとします。
一度の操作(最近見た記事の表示)で記事は複数個存在しうるので、1つ1つの記事を取得せず、一回のXHRで取得できるよう、カスタムエンドポイントを作成します。
こうしておくことで、Advanced Custom Fieldsで登録した情報もレスポンスに返しやすくなります。

カスタムエンドポイントは、GETリクエストを受け付けることとします。
記事IDの配列を受け取る上で、 ?array[1]=1&array[2]=2 のようなURLはテストの際面倒そうなので、カンマ区切り値として送信することにします。
処理は増えますが、デバッグの楽さがあるのでこういう設計にしてます。

使用技術

jQueryもUnderscore.jsもWordPressに組み込まれているので、 wp_enqueue_script('undescore'); のような形ですぐに利用できます。
WP REST APIはプラグインをインストールし、有効化してください(そこの細かい解説は下記が詳しいです)。

WordPress REST APIで投稿の取得から新規投稿を行う

実際の処理フロー

記事を見たとき

LocalStorageにアクセス
 ↓
データの有無の判定、なければ作成
 ↓
すでにデータに記事が含まれていたら順番を入れ替える
データに記事がなければ追加する
 ↓
配列長をチェックし、一定長以上であれば切り落とす

最近見た記事を表示するとき

LocalStorageにアクセス
 ↓
データ有無の判定、なければ「ありません」の表示へ
 ↓
データが正しいものであるか判定、異常値の場合「ありません」の表示へ
 ↓
正常値でXHR、エラー判定の場合「ありません」の表示へ
 ↓
レスポンスがあればレンダリング、なければ「ありません」の表示へ

実装

記事

記事に追加するJavaScriptファイルはこのようなかんじ
enqueueする時にjQueryとUnderscore.jsに依存を明記します。
Underscore.jsでやっているのは生JavaScriptで書いても大した量ではないので、ここでは依存を切って書き直しても大丈夫です。

bodyタグで data-postid="<?php the_ID(); ?>" としておくのを忘れないようにしてください。

最近見た記事モジュール

テンプレート

ユーザが目にする状態は3つあります。

  • ローディング中
  • 最近見た記事がn個存在
  • 記事がない(エラーもこの場合に含む)

まずはこのモックを作っておきます。
ローディングやエラー表示はクラス名の制御やDOMの差し替えなど、どちらでも構いません。
今回は見た目は省きますが、マークアップは次の節を参考にしてみてください。

WordPressフロント側

テンプレートパーツとして実装します。
記事ページにも設置されることを想定して、 is_single() を使って依存を場合分けしています。

ここのポイントは type="text/template" にしたHTMLっぽい何かです。
Underscore.jsのtemplateメソッドを使って処理するので、このような形でテンプレート化しています。
こうすることでJavaScriptファイルの見通しが立ちやすくなり、HTMLを書く場所をまとめられます。
templateメソッドの細かいことは前述の公式ドキュメントを参考にしてください。簡単に言うと <%- %> に変数が入ります。

WordPress REST API側

ここも細かいことは省きますが、今回はざっくりこんなかんじです。
functions.php に追記する形で十分かと思います。

エンドポイントを /v1/posts/{ids} としたかったのですが、カンマ区切り値の正規表現を書く時間がもったいなかったのでやめました。
IDの正当性チェックはintegerでチェックしたほうがいいかもしれませんが、少なくとも数値型かはチェックしておきます。
レスポンス整形は結構自由に行なえます。

ここでのポイントは、レスポンスに「リクエストされたidの配列」と「実際にクエリを発行したidの配列」を含んでいることです。
あとで使いますので、ここは実装しておいてください。

また、注意点としてはDBへのクエリは投げたIDの配列の並び順ではないということです。
ここでその並び順に加工してもよいのですが、クライアント側で簡単に整形できますのでこのまま返却してしまいましょう。

デバッグはターミナルでcurlを叩いたり、Postmanを使うのがよいでしょう。
本当はテストコードを書いたほうがいいのですが、今回は省略します。

JavaScript

エラー処理は少し省略していますが、概ねこれで動くのではないでしょうか。

ローディング表現はクラス操作で擬似要素をだしたり消したりする想定です。
PHPのフロント側で is_single() でクラス名をつけていたところなんかは今見ている記事が最近見た記事に出てこないようにするためでしたが、必要なければ削って大丈夫です。

結構コメントを書いていますが、ポイントは _.indexBy() でしょうか。
WordPress側で並び替えなかった記事ですが、一緒に返却したクエリを発行した記事ID配列を使って簡単に並び替えることができました。

さて、記事IDはLocalStorageに最大で10個保存されているのに表示数は5個としています。
先述した「見ている記事を最近見た記事に表示しない」というバッファと、非表示になってしまった記事があったときに歯抜けを防ぐための安全係数です。
このあたりは必要に応じて定数(ES2015ではないので、本当は変数ですが)を変更してみてください。
もちろん、記事数が増えれば増えるほどデータ量などは増えていきますので、極端な量を指定するのはやめておいたほうが良いかと思います。

さいごに

どうですか?意外と簡単だったのではないでしょうか。

ユーザの入力が可能なので、そのあたりはもう少しキツめに見ておくと良いかもしれません。
WebAPIは他のエンドポイントの兼ね合いがありますので、エンドポイントはもう少しいい感じにしておきたいですね。
画面のロード後に処理が走りますので、画面表示した際すこしスクロールのガタつきなどが出るはずです。そのあたりは実際に触って確認していきましょう。

お問い合わせはこちら