KnockoutJSでリストの絞り込みとテキストのハイライトを行う

# 動機

リストの絞り込みのサンプルはあったし、テキストをハイライトするサンプルも見つかった。
しかし両方をこなしているサンプルが見つからなかったから作ってみた。

# 前提

* データ取得は別なのでデータとなる配列はべた書き
* 絞り込み時には大文字小文字を区別しない

# コード
## HTML
```lang:index.html
<div class="content">
    <label>検索:
        <input type="text" class="input_highlight" data-bind="value: searchBrand, valueUpdate: 'afterkeydown'">
    </label>

    <ul data-bind="highlightForeach: {data: filteredBrands, search: searchBrand}">
        <li>
            <a data-bind="attr: {href: url}">
                <span data-bind="html: name"></span>
                <span data-bind="html: alias"></span>
            </a>
        </li>
    </ul>
</div>
```

## JavaScript
```lang:main.js
ko.bindingHandlers.highlightForeach = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        ko.bindingHandlers.foreach.init(element, function () { return []; }, allBindings, viewModel, bindingContext);
    },

    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        var options = valueAccessor();
        var search = ko.utils.unwrapObservable(options.search);

        var highlight = ko.utils.unwrapObservable(options.data).map(function(value, index) {
            var name, alias;
            if (search.length > 0) {
                name = value['name'].replace(new RegExp(search, 'gi'),  '<span class="yellow">' + '$&' + '</span>');
                alias = value['alias'].replace(new RegExp(search, 'gi'),  '<span class="yellow">' + '$&' + '</span>');
            } else {
                name = value['name'];
                alias = value['alias'];
            }

            return { url: value['url'], name: name, alias: alias};
        });

        ko.bindingHandlers.foreach.update(element, function() { return highlight; }, allBindings, viewModel, bindingContext);
    }
};

var vm = function() {
    var self = this;
    self.searchBrand =  ko.observable('');
    self.brands = ko.observableArray([
        {url: "http://www.sony.jp/", name: "ソニーモバイルコミュニケーションズ", alias: "SONY MC"},
        {url: "http://www.samsung.com/jp/", name: "サムスン電子", alias: "Samsung"},
        {url: "http://www.htc.com/jp/", name: "エイチティーシー", alias: "HTC"},
        {url: "http://www.lg.com/jp", name: "LGエレクトロニクス", alias: "LG Electronics"},
        {url: "http://www.asus.com/jp/", name: "アスース", alias: "ASUSTek"},
        {url: "http://www.huawei.com/jp/", name: "ファーウェイ", alias: "Huawei"},
        {url: "http://www.motorola.co.jp/", name: "モトローラ・モビリティ", alias: "Motorola Mobility"},
        {url: "http://www.sharp.co.jp/", name: "シャープ", alias: "SHARP"},
        {url: "http://jp.fujitsu.com/", name: "富士通", alias: "FUJITSU"},
        {url: "http://www.kyocera.co.jp/", name: "京セラ", alias: "KYOCERA"},
        {url: "https://www.apple.com/jp/", name: "アップル", alias: "Apple"},
        {url: "http://www.nokia.com/global/", name: "ノキア", alias: "Nokia"}
    ]);

    self.filteredBrands = ko.computed(function() {
        var filter = self.searchBrand().toLowerCase();
        if (!filter) {
            return self.brands();
        } else {
            return ko.utils.arrayFilter(self.brands(), function(brand) {
                if (brand['name'].toLowerCase().indexOf(filter) !== -1) {
                    return true;
                } else if (brand['alias'].toLowerCase().indexOf(filter) !== -1) {
                    return true;
                }

                return false;
            });
        }
    });
};
ko.applyBindings(new vm());
```

## 簡単な解説
HTMLもJavaScriptも大きく2つに分けられる。
`filteredBrands`の部分と`hightlightForeach`の部分である。

`self.filteredBrands`では`data-bind="value: searchBrand”`としているsearchBrandの値を見て、空であれば元々の値を、そうでなければ`ko.utils.arrayFilter`を使ってフィルタリングを行っている。
`ko.utils.arrayFilter`で`true`が返った要素のみ`ko.computed`によって表示が行われる。

`ko.bindingHandlers.highlightForeach`ではKnockoutJSにあるforeachを拡張する形で新たなハンドラを定義している。
`update`で`searchBrand`が更新された際に`update`が呼ばれ、`highlight`という値でfoeachでループを回す前に値
を正規表現で置き換えしている。



## 動作例

JSFiddleを使って動かせるようにしてみた。
http://jsfiddle.net/nana4gonta/t85ywshv/

# 参考資料
* [KnockoutJSでAngularJSのfilter機能実装 - Qiita](http://qiita.com/y-mori/items/72dfc491ef715445d838)
* [Fun with Highlighting in KnockoutJS - Knock Me Out](http://www.knockmeout.net/2011/06/fun-with-highlighting-in-knockoutjs.html)
* [Knockout custom binding handlers](http://blog.csainty.com/2013/11/knockout-custom-binding-handlers.html)