Algoliaのドキュメントに Solutions という章があって、実装ガイド的になっておりまして、そちらを参考に、昨日はeコマース向けのフィルタリング機能の Auto-selected facets と Dynamic faces positioning を Algoliaのeコマース向けのフィルタリング実装例 – その1 でご紹介しました。
本日は Recommended filters と Visual facets をやっていきたいと思います。
Recommended filters
選択したファセットに基づくレコメンデーションフィルターを表示することで、ユーザーが望むものに素早くアクセス可能に。
あまり長いファセットはユーザーに良い印象を与えないこともあり、選択されたファセットを元に、更に絞り込みを行うためにこちらはどうですか?といった条件を提案します。
実装ガイド
何かカテゴリが1つ選択されたら、他のフィルターの選択も促すような処理を以下の3ステップで行います。1. Rules を作る 2. レコメンドフィルター用のHTMLを表示する 3. レコメンドフィルターをJavaScriptで取得する。
Ruleの作成
Conditionの Filters で categorlies.lvl0 is 食品
を定義。
ConsequenceはReturn Custom Dataで以下のようにJSONを設定します。
今回作ったJSONの全容は👇
{
"filters": [
{
"name": "水・ソフトドリンク",
"type": "disjunctive",
"filter": {
"attribute": "categories.lvl0",
"value": "水・ソフトドリンク"
},
"clear": "true"
},
{
"name": "Price3000円以下",
"type": "numeric",
"filter": {
"attribute": "price",
"operator": "<=",
"value": 3000
},
"clear": "false"
}
]
}
JavaScriptの実装
InstantSearch.jsでhits、refinement、rangeSliderといったwidgetを使って検索できるところまで作ってあるものとして、
Ruleで設定したFilter用に queryRuleContext widgetを使います。
instantsearch.widgets.queryRuleContext({
trackedFilters: {
categories: values => values
}
})
クライアント側で受け取ったCustomDataをハンドリングして画面描画を行うためのコード。additional-categoriesというdivをHTML側で用意して、そこに取得したデータを元にしてボタンを配置します。
instantsearch.widgets.queryRuleCustomData({
container: '#additional-categories',
transformItems: function(items){
if(items.length > 0){
let transformedFilters = items[0].filters.map(function(item){
if(typeof item.filter === 'object'){
item.filter = JSON.stringify(item.filter);
}
return item;
});
return [{filters: transformedFilters}];
}
else {
return items;
}
},
templates: {
default: `
{{#items}}
{{#filters}}
<button class="additional-filter" data-filter="{{filter}}" data-filter-type="{{type}}", data-clear-filters="{{clear}}">{{name}}</button>
{{/filters}}
{{/items}}
`
}
})
最後に、レコメンドされたフィルターがクリックされた場合の検索結果をリファインするためのコード。
document.addEventListener('click', function(event){
if(event.target.classList.contains('additional-filter')){
let helper = search.helper;
let data = event.target.dataset;
let filter = JSON.parse(data.filter);
if(data.clearFilters == 'true'){
helper.clearRefinements();
}
if(data.filterType === 'disjunctive'){
helper.addDisjunctiveFacetRefinement(filter.attribute, filter.value);
}
if(data.filterType === 'numeric'){
helper.removeNumericRefinement(filter.attribute);
helper.addNumericRefinement(filter.attribute, filter.operator, filter.value);
}
helper.search();
}
});
そんな感じで、カテゴリーで「食品」を選択すると、「水・ソフトドリンク」と「Price 3000円以下」の レコメンドフィルターが表示されるようになります。後から気が付きましたが、フィルターの置き換えなのか、フィルターの追加なのか、といったところはボタンを見て判別できた方が良いですね。(今回は「水・ソフトドリンク」は置き換えで、「Price 3000円以下」はフィルタの追加になります。)
Visual facets
カラーパレットや画像などの視覚的なフィルターを使うことでユーザー体験を高める
実装ガイド
実装の方法は2つあります。1つ目はレコードに直接画像のURLやカラーコードを埋め込む。今回は || をセパレーターに。
レコードの例
{
"name": "Black T-shirt",
"color": "#000000||black".
"availableIn": "https://source.unsplash.com/100x100/?paris||Paris"
}
インデックスの設定で attributesForFaceting (ダッシュボードから設定することももちろんできます)
index.setSettings({
attributesForFaceting: [
'color',
'availableIn'
]
});
InstantSearch.jsの refinementList ウィジェットで以下のように || でSplitして background-color や imgタグのsrc属性を指定
// 色のファセット
search.addWidget(
instantsearch.widgets.refinementList({
container: '#facets',
attribute: 'color',
templates: {
item: `
<input type="checkbox" id="{{label.split("||")[1]}}" {{#isRefined}}checked{{/isRefined}}/>
<label for="{{label.split("||")[1]}}" class="{{#isRefined}}isRefined{{/isRefined}}">
{{label.split("||")[1]}}
<span class="color" style="background-color: {{label.split("||")[0]}}"></span>
</label>`
}
})
);
// 画像のファセット
search.addWidget(
instantsearch.widgets.refinementList({
container: '#facets',
attribute: 'availableIn',
templates: {
item: `
<span class="location-pair" style="{{#isRefined}}font-weight: bold{{/isRefined}}">
<img class="location-image" src="{{label.split(||)[0]}}""
<span class="facet-value">{{label.split("||")[1]}} ({{count}})</span>
<span>`
}
})
);
2つ目の実装方法はフロントエンドで色や画像のURLをやりくりするようなもの
const getHexaCodeFromColor = color => color ? color : 'transparent';
const getLocationImage = location => location ? `${location.toLowerCase()}.jpeg` : "";
こちらも InstantSearch.js の refinementList で background-color や imgタグのsrc属性をやりくり
// 色のファセット
search.addWidget(
instantsearch.widgets.refinementList({
container: '#facets',
attribute: 'color',
transformItems(items) {
return items.map(item => ({
...item,
hexaCode: getHexaCodeFromColor(item.label)
}));
},
templates: {
item: `
<input type="checkbox" id="{{label}}" {{#isRefined}}checked{{/isRefined}}/>
<label for="{{label}}" class="{{#isRefined}}isRefined{{/isRefined}}">
{{label}}
<span class="color" style="background-color: {{hexaCode}}"></span>
</label>`
}
})
);
// 画像のファセット
search.addWidget(
instantsearch.widgets.refinementList({
container: '#facets',
attribute: 'availableIn',
transformItems(items) {
return items.map(item => ({
...item,
image: getLocationImage(item.label)
}));
},
templates: {
item: `<span class="location-pair" style="{{#isRefined}}font-weight: bold{{/isRefined}}">
<img class="location-image" src="assets/images/locations/{{image}}">
<span class="facet-value">{{label}} ({{count}})</span>
<span>`
}
})
);
このように実装を行うと↓のように色や画像で絞り込みを行うことができるようになります。
ということで、以下の4つのフィルタリングの実装例をご紹介してきましたが、いかがだったでしょうか?
- Auto-selected facets
- Dynamic facets positioning
- Recommended filters
- Visual facets
簡単だと思われた方もいらっしゃるかもしれませんし、プログラムを書かなければいかないのはハードルが高すぎると思われた方もいらっしゃるかもしれません。
もし、何かこちらのようなことをAlgoliaを使って実装した、もしくは、導入しようと思ったけどできなかったといったようなフィードバックがございましたら #AlgoliaJP ハッシュタグでTwitterに投稿いただければ幸いです。
コメント