クライアントサイドで絞り込み検索を実現するItemsJS(更新中)
記事
スクラップが溜まったところで記事を作成しました。今後、ソートや他ライブラリとの連携についても書いて行く予定です。
facet-search
ItemsJSをフォークして作られたfacet-searchというライブラリもあります。
ItemsJSには一部のデータが読み込めない不具合があります(条件不明)。これは利用しているFastBitSetというライブラリが悪さをしているようです。facet-searchはこれをBitSetというライブラリに置き換えることで不具合を修正しています。またTypeScriptによる書き直しや機能追加もされていますが、一方で全文検索を外部のライブラリに置き換える機能が削除されるなど、完全な上位互換というわけではありません。
ItemsJS | facet-search | |
---|---|---|
絞り込み検索 | ○ | ○ |
実装 | JavaScript | TypeScript |
外部の全文検索ライブラリ | ○ | × |
ドキュメント | 少ない | 皆無 |
ちなみに、ItemsJSではプロパティにper_page
とスネークケースが使われているところがあり、これがfacet-searchではよりJavaScriptっぽくperPage
とキャメルケースに修正されています。個人的にはこの点はfacet-searchのほうが好きです。
ItemsJS
ItemsJSはJavaScriptのオブジェクトの絞り込みやファセット検索をクライアントサイドで実現するライブラリです。
小さくない需要に応えているライブラリなのですが、公式のドキュメントの分量が少なく、日本語の情報もほとんどないので、Zennにまとめることにしました。
サンプル
ItemsJS、Fuse.js、Svelteを利用してデータを検索するサンプルアプリを公開しました。
GitHubにサンプルコードを公開しています。
参考になりましたら幸いです。
ファセット検索とは
ファセット検索については以下をご参照ください。
シマウマ用語集: ファセット検索(ファセットナビゲーション)
Google ウェブマスター向け公式ブログ: ファセットナビゲーションのベストプラクティスと5つのワーストプラクティス導入
他のライブラリと同様、npm install itemsjs
でインストールします。
公式のドキュメントではrequire文が用いられていますが、以下のようにimport文で読み込むことが可能です。
import itemsjs from 'itemsjs';
import data from './data.json'; // 検索対象となる配列
const configuration = {
// 設定、この部分は後述します
}
const items = itemsjs(data, configuration);
サンプルデータ
まずは英語だけのシンプルなデータから始めましょう。
[
{
"name": "Apple",
"price": 100,
"quantity": 10,
"tags": ["fruit", "red"]
},
{
"name": "Apple Watch",
"price": 50000,
"quantity": 5,
"tags": ["gadget", "silver"]
},
{
"name": "Cabbage",
"price": 200,
"quantity": 20,
"tags": ["vegetable", "green"]
},
{
"name": "Tomato",
"price": 300,
"quantity": 15,
"tags": ["vegetable", "red"]
}
]
最小限の検索
search
メソッドの引数にquery: 'query'
を与えるだけで、いちおう検索が可能です。
const result = items.search({
query: 'apple'
});
console.log(JSON.stringify(result, null, 2));
結果は以下のようになります。
{
"pagination": {
"per_page": 12,
"page": 1,
"total": 2
},
"timings": {
"total": 1,
"facets": 1,
"search": 0,
"sorting": 0
},
"data": {
"items": [
{
"name": "Apple",
"price": 100,
"quantity": 10,
"tags": ["fruit", "red"],
"_id": 1
},
{
"name": "Apple Watch",
"price": 50000,
"quantity": 5,
"tags": ["gadget", "silver"],
"_id": 2
}
],
"aggregations": {}
}
}
なぜ1ページあたりの表示件数が12なのか、空の"aggregations"とは何なのか、など疑問は尽きませんが、とりあえず"apple"
を含む"Apple"
と"Apple Watch"
がヒットしていることがわかります。
aggregationの利用
aggregation[1]を利用することで、タグやグループによる絞り込みが可能になります。
どのフィールドをaggregationとして扱うかをconfiguration
の中で指定する必要があります。
const configuration = {
"aggregations": {
"tags": {
"title": "Tags",
"field": "tags"
}
}
}
aggregationによる絞り込みを行うときは、filters
に以下のように指定します(複数のaggregationを一度に指定することも可能です)。
const result = items.search({
query: "apple",
filters: {
tags: ['fruit']
},
});
console.log(JSON.stringify(result, null, 2));
これを実行すると結果は以下のようになります。
{
// 省略
"data": {
"items": [
{
"name": "Apple",
"price": 100,
"quantity": 10,
"tags": [
"fruit",
"red"
],
"_id": 1
}
],
"aggregations": {
"tags": {
"name": "tags",
"title": "Tags",
"position": 1,
"buckets": [
{
"key": "fruit",
"doc_count": 1,
"selected": true
},
{
"key": "red",
"doc_count": 1,
"selected": false
},
{
"key": "gadget",
"doc_count": 0,
"selected": false
},
{
"key": "green",
"doc_count": 0,
"selected": false
},
{
"key": "silver",
"doc_count": 0,
"selected": false
},
{
"key": "vegetable",
"doc_count": 0,
"selected": false
}
]
}
}
}
}
"apple"
を含む2項目のうち、"fruit"
タグを持つ"Apple"
のみがヒットしていることがわかります。
また、"aggregations"
以下に、検索結果のうち各タグに当てはまるものの件数が表示されています。
-
aggregationという英単語には日本語で「集合、集計」といった訳語が当てられていますが、ここでは英語のまま使用することにします。 ↩︎