PowerCMS Xの案件において、「検索フォームで検索条件を指定していくと条件に合致する件数がいくつあるかをリアルタイムに表示する」という要件がありました。これはPowerCMS Xのダイナミック出力機能と複数の条件を指定可能なウェブサイト内検索を利用することで容易に実装が可能です。
テンプレート
PowerCMS XにはPowerCMS 5のようなData API機能は現在の所ありませんが、出力パスをobject_count.json
のように拡張子をJSONにするとContent-Typeがapplication/json
で送信されるような仕組みになっており、DataAPIのような感覚で扱うことができます。
よって、object_count.json
のテンプレートにJSONのような文字列を出力するコードを書くことで、リクエスト内容に応じたオブジェクトの件数を返すことができるようになります。この時、mt:entries
やmt:schools
(私が独自に定義したモデル)等のタグを使用しても良いのですが、mt:objectloop
を使用することでどのモデルにも対応したテンプレートにすることができ、object_count.json
を汎用的に利用することが可能となります。検索条件のパラメータは自動で処理されるため、テンプレートへの記述は不要です。
<mt:var name="request._filter" setvar="model" /><mt:var name="request.workspace_id" setvar="workspace_id" />
<mt:objectloop model="$model" workspace_id="$workspace_id">
<mt:If name="__first__"><mt:var name="object_count" setvar="result_count" /></mt:If>
</mt:objectloop>
{ "totalResult": <mt:If name="result_count"><mt:var name="result_count"><mt:Else>0</mt:If> }
※見やすいように改行・スペースを入れていますが、実際は1行で書いています。
JavaScript
フォームのselect要素やinput要素の状態が変化した時に、条件にマッチする件数を取得するgetCount関数を用意しました。
フォームの内容を$('#school_search').serialize().split('&')
で取得して処理を行っているのは、PowerCMS Xの検索仕様に合わせたデータを送信するためです。select要素やinput要素には_filter_value_カラム名[]
が指定してあり、これらに値が設定されている時に検索条件を指定する_filter_cond_カラム名[]
や_filter_and_or_[カラム名]
を追加して送信する仕組みです。
const formElem = document.getElementById('school_search');
const formSlectedItems = formElem.querySelectorAll('.Search__formItem.-select');
function getCount() {
const dataArray = $('#school_search').serialize().split('&');
const dataArrayForPost = [];
const existKeys = [];
dataArray.forEach(function (data) {
if (/^_filter_value.*\=$/.test(data)) {
// 送信しない
} else if (/^_filter_value.*\=.*/.test(data)) {
const condKey = data.replace(/^_filter_value(.*)\=.*$/, '$1');
dataArrayForPost.push(
data,
'_filter_cond' + condKey + '=eq'
);
if (existKeys.indexOf(condKey) === -1) {
dataArrayForPost.push(
'_filter_and_or' + condKey.replace('%5B%5D', '') + '=OR'
);
existKeys.push(condKey);
}
} else {
dataArrayForPost.push(data);
}
});
fetch('/search/object_count.json?' + dataArrayForPost.join('&'))
.then(function (response) {
return response.json();
})
.then(function (json) {
const targetElem = document.getElementById('school_search_result_count');
targetElem.textContent = json.totalResult;
})
}
formSlectedItems.forEach(function (formItem) {
formItem.addEventListener('change', getCount);
});
formElem.addEventListener('reset', function () {
window.setTimeout(getCount, 200);
});
getCount();
余談ですが、ほぼバニラJSで書いているのですが、フォームのデータを取得する部分のみjQueryに依存しています。ここを上手く書き換えたいですが、ほとんどの構築案件でjQueryが利用されており書き換えてもあまり利がないのが現状でしょうか。
表示速度
データは学校モデルに広島県の公立学校805件を登録しています。学校種別(小・中・高)のカラムは数値(小=1・中=2・高= 3)で格納しています。また、市町村カラムは市町村コードを数値で格納していますが、この市町村コードはデータ登録時に市町村モデルの内容をダイアログ表示し該当する市町村を選択するようになっています。よって検索時は市町村名で市町村モデル検索して該当するIDを取得し、さらに学校モデルの市町村カラムを検索するようになります。サーバーはさくらのVPSにnginx + php-fpmで構築しています。
上記のような条件で検索条件を変化させてリクエストに要した時間を確認したところTTFB(接続が確立した後最初の1バイトが到着するまでの時間)は200ms〜300msとなっており、ストレスを感じることなく検索マッチ数の更新ができています。