Algoliaのeコマース向けのフィルタリング実装例 – その2

Algoliaのドキュメントに Solutions という章があって、実装ガイド的になっておりまして、そちらを参考に、昨日はeコマース向けのフィルタリング機能の Auto-selected facetsDynamic faces positioningAlgoliaの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に投稿いただければ幸いです。

コメント

タイトルとURLをコピーしました