AngularJSのFilterでAND検索を実装してみる

AngularJSのFilterを自作して、 **AND検索（スペース区切りで複数ワード検索）** を実装してみる。

#まず、既存のFilterを用いた絞り込みをつくる

##Service
各種メソッドを実装する。
今回は、フィルタをかける対象データを作成する getData() を生成する。

```js:service.js
/**
 * コンストラクタ
 */
function SearchService() {
}
 
/**
 * データを返す
 */
SearchService.prototype.getData = function () {
    return [
        {
            id: 1,
            name: "空条承太郎",
            kana: "クウジョウジョウタロウ",
            note: "オラオラ"
        },
        {
            id: 2,
            name: "東方仗助",
            kana: "ヒガシカタジョウスケ",
            note: "変な髪型"
        },
        {
            id: 3,
            name: "荒木飛呂彦",
            kana: "アラキヒロヒコ",
            note: "歳を取らない"
        },
        {
            id: 4,
            name: "虹村億泰",
            kana: "ニジムラオクヤス",
            note: "東方仗助が岸辺露伴を困らせた時わたしは焼身自殺します"
        },
        {
            id: 5,
            name: "岸辺露伴",
            kana: "キシベロハン",
            note: "岸部露伴は動かない"
        },
        {
            id: 6,
            name: "広瀬康一",
            kana: "ヒロセコウイチ",
            note: "エコーズ"
        }
    ];
};
```

##Controller
htmlにデータを渡す機能を担う。

```js:controller.js
function SearchController($scope, searchService) {
    $scope.users = searchService.getData();
}
```

##moduleに登録
作成したServiceとControllerをmoduleに登録する。
moduleは、Webアプリケーションでいうインスタンス化やアプリケーションの起動などを行うMain部分。
moduleの第一引数には、後述のng-app属性に指定している文字列を指定する。

```js:main.js
angular.module("SearchApp", [])
    .service("searchService", SearchService)
    .controller("searchController", SearchController);
```

##データ表示部分
ng-appに任意のアプリケーション名を指定。 
これをしていしないとangularアプリケーションは動かないので注意。

```html:index.html(head)
// 忘れずに指定
<html ng-app="SearchApp">

// 関連ファイルを読み込む
 <script type="text/javascript" src="js/service/service.js"></script>
    <script type="text/javascript" src="js/controller/controller.js"></script>
    <script type="text/javascript" src="js/main.js"></script>
```

```html:index.html(body)

// 文字入力部分
<input type="text" ng-model="keyword" placeholder="Keyword" />

// 表示部分
<div ng-controller="SearchController">

    <table class="data-table">
        <thead>
        <tr>
            <th>ID</th>
            <th>氏名</th>
            <th>フリガナ</th>
            <th>備考</th>
        </tr>
        </thead>
        <tbody>
        <tr ng-repeat="user in users | filter:keyword">
            <td>{{user.id}}</td>
            <td>{{user.name}}</td>
            <td>{{user.kana}}</td>
            <td>{{user.note}}</td>
        </tr>
        </tbody>
    </table>
<div>
```

ここまでのデモは[こちら](http://jsfiddle.net/tanaka_yutaro/swN9f/2/)

#AND検索をつくる

##オブジェクト探索
入力された文字列を解析するメソッドを追加する。
オブジェクト内に指定したキーワードに一致するものがあるかどうか判定する。

```js:service.js
/**
 * オブジェクト内の値がキーワードと部分一致するかどうか判定
 * @param obj 検索対象オブジェクト
 * @param keyword キーワード配列
 * @returns {boolean}
 */
SearchService.prototype.keywordJudge = function (obj, keyword) {
    var self = this;
 
    if (angular.isArray(obj)) {
        // 配列の場合
        // 格納されている要素を順番にチェックし、ひとつでも部分一致した場合trueを返す
        return obj.some(function (child) {
            return self.keywordJudge(child, keyword);
        });
    } else if (angular.isObject(obj)) {
        // オブジェクトの場合
        // 子要素を順番にチェックし、ひとつでも部分一致した場合trueを返す
        var properties = Object.getOwnPropertyNames(obj);
        return properties.some(function (property) {
            var child = obj[property];
            return self.keywordJudge(child, keyword);
        });
    } else if (obj != null) {
        // オブジェクト、配列以外で、値がある場合
        // 文字列に変換し、部分一致した場合trueを返す
        return angular.toJson(obj).search(keyword) != -1;
    }
    // nullまたはundefinedの場合
    return false;
};
```

##CustomFilterをつくる

```js:ANDSearchFilter.js
/**
 * AND検索用のFilter
 * @param searchService
 * @returns {Function} Filterをかけたデータの配列
 */
function ANDSearchFilter(searchService) {
    return function (list, searchQuery) {
 
        if (searchQuery) {
            // 全角スペースを半角スペースに置換
            var query = searchQuery.replace(/　/g, " ");
        }
 
        // 検索フォームに文字が入力されている場合
        if (query) {
            // 検索対象ワードの配列を作成
            var queryWordArray = query.split(" ");
 
            var filteredList = [];
 
            list.forEach(function (obj) {
                // 検索キーワードでオブジェクトそれぞれを探索
                var isMatch = !queryWordArray.some(function (keyword) {
                    return !searchService.keywordJudge(obj, keyword);
                });
 
                // 検索キーワードがAND一致した場合、一覧に表示する配列に格納
                if (isMatch) {
                    filteredList.push(obj);
                }
            });
            return filteredList;
        }
        return list;
    };
}
```

##Filterを登録
作成したフィルタを登録。

```html:index.html(head)
<script type="text/javascript" src="js/filter/andSearchFilter.js"></script>
```

```js:main.js
angular.module("SearchApp", [])
    .service("searchService", SearchService)
    .filter("andSearchFilter", ANDSearchFilter)
    .controller("searchController", SearchController);
```

##データ表示部分を修正
元々 filter としていた部分を andSearchFilter に置き換える。

```html:index.html
<tr ng-repeat="user in users | andSearchFilter:keyword">
```

#まとめ

- [Source - Github](https://github.com/TanakaYutaro/AngularJSCustomFilter)
- [既存のフィルタデモ](http://jsfiddle.net/tanaka_yutaro/swN9f/2/)
- [AND検索のデモ](http://jsfiddle.net/tanaka_yutaro/Lbj7c/2/)