あなたなら何を絞り込む?絞り込み検索に挑戦しよう!

マークアップ

2016.10.07(Fri)

こんにちは、アカウントセールス部のフロントエンドエンジニア、土樋(つちとい)と申します。初めてコラムを担当します。

唐突ですが、みなさんネットショッピングで欲しいものを探す時にどうやって目的の商品を見つけていますか?
最初から買うものが決まっている場合は商品名で検索するかもしれませんし、買うものが曖昧な場合はカテゴリーや価格帯等の条件で商品を徐々に絞り込んで探していくと思います。
こういった「絞り込み検索機能」はショッピングサイトには必ずありますが、通常のサイトにも導入出来たら便利ではないでしょうか。

そこで、今回は簡易的な絞り込み機能を実装する方法を紹介したいと思います。
色々な手法があると思いますが、今回はJavaScriptを使った方法です。

こんなページを作ってみましょう

今回作るページはこんな感じになります。

201610071204_1.png

DEMO

このページでは、複数の動物を下の条件で絞り込んで表示検索出来るようにしています。

  • 平均寿命 … セレクトボックスで4パターンから選択
  • 属性 … チェックボックスで10種類の中から選択(複数選択可)
  • キーワード … テキストボックスに自由に入力(スペースで区切って複数入力可)

何だかカメラ目線の動物が多いのは気にしないでください…。

必要なライブラリ

以下ライブラリを使います。

  • jQuery
  • Underscore.js

「jQuery」は言わずもがなですが、「Underscore.js」は馴染みがない方もいるかもしれません。
簡単に言いますとJavaScriptの配列操作を強力にサポートするライブラリです。
Underscoreという名前のとおり、命令が「_(アンダースコア)」から始まるのが特徴です。

詳しくはこちらのサイト等をご参考ください。

http://gihyo.jp/dev/serial/01/underscorejs/0001

実装してみましょう

HTML(index.html)

<div class="filterArea">    
    <div class="filter_life">
        <h2>平均寿命</h2>
        <select name="life">
            <option value="">指定なし</option>
            <option value="1">~1</option>
            <option value="2">2~20</option>
            <option value="3">31~50</option>
            <option value="4">51~</option>
        </select>
    </div>            
    <div class="filter_tag">
        <h2>属性</h2>
        <input type="checkbox" name="tag" value="哺乳類" id="chk01"><label for="chk01">哺乳類</label>
        <input type="checkbox" name="tag" value="鳥類" id="chk02"><label for="chk02">鳥類</label>
        <input type="checkbox" name="tag" value="魚類" id="chk03"><label for="chk03">魚類</label>
        <input type="checkbox" name="tag" value="昆虫類" id="chk04"><label for="chk04">昆虫類</label>
        <input type="checkbox" name="tag" value="かわいい" id="chk05"><label for="chk05">かわいい</label>
        <input type="checkbox" name="tag" value="かっこいい" id="chk06"><label for="chk06">かっこいい</label>
        <input type="checkbox" name="tag" value="ほのぼの" id="chk07"><label for="chk07">ほのぼの</label>
        <input type="checkbox" name="tag" value="こわい" id="chk08"><label for="chk08">こわい</label>
        <input type="checkbox" name="tag" value="うごかない" id="chk09"><label for="chk09">うごかない</label>
        <input type="checkbox" name="tag" value="だれなん?" id="chk10"><label for="chk10">だれなん?</label>
    </div>            
    <div class="filter_keyword">
        <h2>キーワード</h2>
        <input type="text" name="filter_free" size="40" />&nbsp;<button type='button' name='free_submit'>検索</button>
    </div>                
</div>

<div class="resultArea">
    <div class="productCntArea"></div>
    <div class="productArea"></div>    
</div>

HTML部分は大きく分けて検索機能のfilterAreaクラスと結果を出力するresultAreaクラスに分けています。
filterAreaクラスの内部に平均寿命(セレクトボックス)、属性(チェックボックス)、キーワード(テキストボックス)があります。
これらを操作したときをトリガーとして、resultAreaクラス内に検索結果が出力される仕組みです。

JavaScript(js/script.js)

JavaScript部分は少し長いので、一部を抜粋して説明します。

動物の情報を登録

var allList = [
    {
        id: "id001",
        life: 14,
        title: "猫",
        tag: ["哺乳類", "かわいい", "かっこいい"],
        description: "猫様は何をしても許されます。猫様にあやかりましょう。目指せ閲覧数アップ。"
    }, 
    {
        id: "id002",
        life: 36,
        title: "ハシビロコウ",
        tag: ["鳥類", "かっこいい", "こわい", "うごかない"],
        description: "(以下Wikipediaより)ペリカン目ハシビロコウ科の鳥類の一種である。 本種のみでハシビロコウ科を形成する(1属1種)。"
    },

    (中略)
];    

配列allListに全ての動物に関する情報をオブジェクト形式で格納します。

各動物はid、life、title、tag、descriptionを持つことにします。
これらの値を使って絞り込み検索を行います。

イベントの登録(init関数)

function init() {
    $(".filter_life select").on("change", onFilterChange);
    $(".filter_tag input").on("change", onFilterChange);
    $(".filter_keyword button").on("click", onFilterChange);

    refleshHtml(allList);
}

検索機能を操作した時をトリガーにするようにイベントを登録します。
イベントが発生したらonFilterChange関数を実行するようにしておきます。

refleshHtml関数は、配列を受け取ってリストを出力するための関数です。
最初は全ての動物を表示するため、引数にallListを指定してリストを出力します。

function onFilterChange(e) {

    var filterFncs = [];
    var result = [];

    filterFncs.push(
        function(list) {
            return filterByLife(list, $('.filter_life select').val());
        }
    );

    filterFncs.push(
        function(list) {
            return filterByTag(list, $('.filter_tag input:checked'));
        }
    );

    filterFncs.push(
        function(list) {
            return filterByKeyword(list, _.escape($('.filter_keyword input').val()));
        }
    );

    result = _.reduce(filterFncs, function(list, fnc) {
        return fnc(list);
    }, allList);

    refleshHtml(result);    
}	

ここはちょっとややこしいかもです。。。

まず配列filterFncsの中に3つの関数を格納(push)します。

各関数は配列と絞り込みの値(selectやinputの値)を引数として受け取ると、各自の絞り込みルールに従ってフィルタリングを行い、結果を配列として返します。

filterFncsに関数の登録が済んだら、真打「_.reduce」登場です。
Underscore.jsの_.reduceメソッドを使って3つの関数を連続で実行していきましょう。

_.reduceメソッドは各関数を実行した際の返り値を次のループ処理に渡します。
これにより、1つ目の関数で絞り込まれた配列を2つ目の関数に渡し、2つ目の関数で絞り込まれた配列をさらに次の関数に渡し、最終的にすべての絞り込み条件をクリアした配列だけが生き残ります。

これすごい便利だと思いません?(興奮している人約1名。。。)

そして、最終的に生き残った配列をrefleshHtmlで出力しています。

絞り込みのルールと書きましたが、具体的には以下filterByLife関数、filterByTag関数、filterByKeyword関数でルールを定義しています。

平均寿命で絞り込み(filterByLife関数)

function filterByLife(list, value) {

    if (value == "") {
        return list;
    }

    return _.filter(list, function(item) {
        switch (value) {
            case '1':
                return item.life <= 1;
            case '2':
                return 1 < item.life && item.life <= 20;
            case '3':
                return 20 < item.life && item.life <= 50;
            case '4':
                return 50 < item.life;
        }
    });
}	

引数として、配列listとセレクトボックスの値valueを受け取ります。
セレクトボックスの値が空の場合は何もせず関数から抜けます。

セレクトボックスに値が入っている場合は、Underscore.jsの_.filterメソッド使って条件に一致するものだけを配列として返すようにします。
他の2つの関数(filterByTag、filterByKeyword)も同じように_.filterメソッドを使用しています。

この関数では、配列list内のオブジェクトのプロパティlifeの値が下のような条件と比較して、マッチした要素だけを配列として返します。

  • value値が1の時
    →オブジェクトのプロパティlifeの値が1以下か
  • value値が2の時
    →オブジェクトのプロパティlifeの値が2以上20以下か
  • value値が3の時
    →オブジェクトのプロパティlifeの値が21以上50以下か
  • value値が4の時
    →オブジェクトのプロパティlifeの値が51以上か

属性で絞り込み(filterByTag関数)

function filterByTag(list, value) {
    
    if (value.length == 0) {
        return list;
    }

    return _.filter(list, function(item) {
        
        var isMatch = false;

        _.each(value, function(chkItem, i) {
            
            _.each(item.tag, function(tagItem, i) {
                if (tagItem === $(chkItem).val()) {
                    isMatch = true;
                }
            });
            
        });

        return isMatch;
    });
}
	

引数として、配列listとチェックボックスの値valueを受け取ります。 チェックボックスは値を配列として受け取るため、受け取った配列の個数が0の場合(なんのチェックもついていない場合)はそのまま関数を抜けます。 受け取った配列に値がある場合(チェックボックスのどれかをチェックしている場合)は、この配列valueの中の値と、配列listの中のオブジェクトのプロパティtagを総当たりで比較して、マッチした要素だけを配列として返します。

キーワードで絞り込み(filterByKeyword関数)

function filterByKeyword(list, value) {

    if (value == "") {
        return list;
    }

    var freeAry = []; 
    var val = value.replace(/ /g, " ");
    searchAry = val.split(" ");

    return _.filter(list, function(item) {

        var isMatch = false;

        _.each(searchAry, function(data, i) {
            if (item.title.indexOf(data) != -1 || item.description.indexOf(data) != -1) {
                isMatch = true;
            }
        });

        return isMatch;

    });

}   

引数として、配列listとテキストボックスの値valueを受け取ります。
テキストボックスに値がない場合はなにもせず関数を抜けます。

テキストボックスに値が入っている場合は、値をスペースで分割して配列searchAryに格納します。

キーワード検索はtitleとdescriptionにマッチするかどうかを判定するので、配列listの中のオブジェクトのプロパティtitleもしくはdescriptionの値と配列searchAryを総当たりで比較して、マッチした要素だけを配列として返します。

まとめ

ちょっと長くなってしまいましたね、すみません。
こんな感じでサイトの中に絞り込み機能をつけると、いつものWebがなんだか一味違う感じになるのではないでしょうか?
是非あなたのサイトにも実装してください。

Let's 絞り込みLIFE!