flexible SearchとMTを組み合わせて使ってみた

MovableTypeで検索ページを実現したかったのですが、DataAPIでは実現できなかったので[flexible Search](https://github.com/tinybeans/jq-plugin-flexibleSearch)を利用して実現したときのメモ。

# 仕様（実現したいこと）

### 記事
- MovableTypeで登録
- カテゴリは4つ
- カスタムフィールド2つ（所属、団体名）

### 検索ページ
- カテゴリ、カスタムフィールド、キーワードで検索
- 結果を一覧に表示、ページングあり
- 一覧は全カテゴリを含むページと、各カテゴリごとの計5ページ

# 実装

基本的には[GitHubのページ](https://github.com/tinybeans/jq-plugin-flexibleSearch)に載っているので、こちらを参照してもらえれば実現できます。


## 検索用JSONファイルの出力
検索対象のJSONファイルの出力が必要になります。これはMovableTypeで行います。
カスタムテンプレートを以下のような感じで作成します。
速度を少しでも早くしたい場合は、カテゴリごとに出力した方が良いです。

``` xml:entries.json
<mt:Unless name="compress" regex_replace="/^\s*\n/gm","">
<mt:SetVar name="items">
<mt:Entries include_blogs="1" lastn="0">
<mt:SetVarBlock name="item{date}"><$mt:EntryDate format="%Y.%m.%d"$></mt:SetVarBlock>
<mt:SetVarBlock name="item{title}"><mt:EntryTitle></mt:SetVarBlock>
<mt:SetVarBlock name="item{url}"><mt:EntryPermalink></mt:SetVarBlock>
<mt:SetVarBlock name="item{body}"><mt:EntryBody remove_html="1" regex_replace="/\n|\t| |　/g",""></mt:SetVarBlock>
<mt:SetVarBlock name="item{excerpt}"><$mt:EntryExcerpt$></mt:SetVarBlock>
<mt:SetVarBlock name="item{more}"><mt:EntryMore remove_html="1" regex_replace="/\n|\t| |　/g",""></mt:SetVarBlock>
<mt:SetVarBlock name="item{tag}">,<mt:EntryTags glue=","><mt:TagName regex_replace="/ |　/g","%20"></mt:EntryTags>,</mt:SetVarBlock>
<mt:SetVarBlock name="item{category}"><mt:EntryCategories glue=","><mt:CategoryLabel></mt:EntryCategories></mt:SetVarBlock>
<mt:SetVarBlock name="item{group}"><$mt:CFGroup$></mt:SetVarBlock>
<mt:SetVarBlock name="item{circle}"><$mt:CFCircle$></mt:SetVarBlock>
<mt:SetVarBlock name="items" function="push"><mt:var name="item" to_json="1"></mt:SetVarBlock>
</mt:Entries>
{"items":[
<mt:Loop name="items" glue=","><mt:Var name="__value__"></mt:Loop>
]}
</mt:Unless>
```

- `<mt:SetVarBlock name="item{date}">`で指定している`item{date}`の`date`の部分は後々使用するので、わかりやすい名前にしておいた方がよいです
- `<$mt:CFGroup$>`、`<$mt:CFCircle$>`はカスタムフィールドです


## flexibleSearchの設定

今回オプションに設定したのは以下です。

## 検索用JSONファイルパス（searchDataPath）
MovableTypeで出力するパスです。

```flexibleSearch-cate.js
    searchDataPath: '/data/entries.json',
```

## フォームHTML（searchFormHTML）
検索フォーム部分のHTML。

```flexibleSearch-cate.js

searchFormHTML: [
         '<div>\r\n',
           '<form action="index.html" method="get">\r\n',
            '<div>\r\n',
              '<label for="group">所属</label>\r\n',
              '<select name="g" id="group">\r\n',
                '<option value="" selected="selected">選択してください</option>\r\n',
                '<option value="1">所属1</option>\r\n',
              '</select>\r\n',
            '</div>\r\n',

            '<div>\r\n',
              '<label for="circle">団体名</label>\r\n',
              '<select name="c" id="circle">\r\n',
                '<option value="" selected="selected">選択してください</option>\r\n',
              '</select>\r\n',
            '</div>\r\n',
            '<fieldset>\r\n',
              '<legend>カテゴリ</legend>\r\n',
              '<label><input type="checkbox" name="cat[]" id="category1" value="1"><span id="category1_label">カテゴリ1</span></label>\r\n',
              '<label><input type="checkbox" name="cat[]" id="category2" value="2"><span id="category2_label">カテゴリ2</span></label>\r\n',
              '<label><input type="checkbox" name="cat[]" id="category3" value="3"><span id="category3_label">カテゴリ3</span></label>\r\n',
              '<label><input type="checkbox" name="cat[]" id="category4" value="4"><span id="category4_label">カテゴリ4</span></label>\r\n',
            '</fieldset>\r\n',
            '<div>\r\n',
              '<label for="keyword">キーワード</label>\r\n',
              '<input type="text" name="search" id="keyword" value="">\r\n',
            '</div>\r\n',

            '<div>\r\n',
              '<input type="submit" id="submitButton" value="検索">\r\n',
            '</div>\r\n',
            '<input type="hidden" name="offset" value="0">\r\n',
            '<input type="hidden" name="limit" value="6">\r\n',
          '</form>\r\n',
        '</div>\r\n',
        ].join(""),
```

- `<input type="hidden" name="offset" value="0">`は必ず必要
- `<input type="hidden" name="limit" value="6">`も必ず必要。valueは環境に合わせて。
- `<input type="text" name="search" id="keyword" value="">`も必ず必要。`name=search`が大事。
- 末尾に改行(\r\n)を入れているのは見た目ではなく、このdivに「display: inline-block;」を指定している場合、改行を入れないとレイアウトが崩れるためです。

## 検索結果一覧（resultItemTmpl）
検索結果の一覧部分。

```flexibleSearch-cate.js
        resultBlockId: "news-list",
        resultItemTmpl: [,
    '{{#items}}',
      '<li>',
        '<h3>{{group}}：{{circle}}</h3>',
        '<h4><a href="{{url}}">{{title}}</a> ({{date}})</h4>',
        '<p>{{&excerpt}}</p>',
      '</li>',
    '{{/items}}',
        ].join(""),
```

- `resultBlockId`は検索結果を挿入する要素のIDを指定します。jqueryでいうappend対象。
- `resultItemTmpl`は一覧部分。この辺はMT書いてる人ならなんとなくわかるはず。使われているurl、image等の名前は出力したjsonファイルの`item{url}`に合わせて指定します。

## ページタイトル（resultMetaTitleTmpl）
ページタイトルが自動的に変更されるのでそれを修正。

```flexibleSearch-cate.js
        resultMetaTitleTmpl: [ 
        "{{metaTitle}}"
        ].join(""),
```

## ページング（paginateTmpl）
```flexibleSearch-cate.js
        paginateTmpl: [
        '<div class="paging fs-paginate" id="fs-paginate">',
          '<ul>',
          '{{#showTurnPage}}',
          '{{#exceptFirst}}',
             '<li class="fs-prev"><a class="fs-prev-link fs-turn-page-link" href="#" title="{{prevPageText}}">{{&prevPageText}}</a></li>',
          '{{/exceptFirst}}',
          '{{/showTurnPage}}',
        
          '{{#page}}',
          '{{#checkRange}}',
             '<li class="{{current}}"><a class="fs-page-link {{currentLink}}" href="#" title="{{pageNumber}}">{{pageNumber}}</a></li>',
          '{{/checkRange}}',
          '{{/page}}',
          
          '{{#showTurnPage}}',
          '{{#exceptLast}}',
            '<li class="fs-next"><a class="fs-next-link fs-turn-page-link" href="#" title="{{nextPageText}}">{{&nextPageText}}</a></li>',
          '{{/exceptLast}}',
          '{{/showTurnPage}}',
          '</ul>',
        '</div>'
        ].join(""),
        paginateCount: 6,
        showTurnPage: true,
        prevPageText: 'Prev',
        nextPageText: 'Next',
        maxPageCount: 10,
```

- `{{#exceptFirst}}`が「Prev」、`{{#exceptLast}}`が「Next」の部分
- hrefは#でOK。flexibleSearchがよしなにしてくれる
- 自分だけなのかもしれないが、classとtitleを設定してあげないと正しくページングが動いてくれなかった
- `maxPageCount`はありがたい

## 独自の検索（customSearch）
flexibleSearchではキーワード検索は行えるが、カテゴリやカスタムフィールドの検索までは行えないので、その辺はこのエリアで独自に実装する必要がある。
`return false;`にすると結果から除外される。

```flexibleSearch-cate.js
        customSearch: function(item, paramObj){
            var group = $('#group').val();
            var circle = $('#circle').val();
            var groupText = $("#group option:selected").text();
            var circleText = $("#circle option:selected").text();
            
            // Group
            if (group != '') {
                if (item.group != groupText) {
                    return false;
                }
                else {
                    // Circle
                    if (circle != '') {
                        if (item.circle != circleText) {
                            return false;
                        }
                    }
                }
            }
            
            // Category Check
            var arrCategory = new Array();
            var bCategory = false;
            for (var i = 1; i <= 4; i++) {
                if ($('#category' + i).prop('checked')) {
                    arrCategory.push($('#category' + i + '_label')[0].innerText);
                    bCategory = true;
                }
            }
            
            if (bCategory) {
                var myCategory = item.category.split(',');
                var bCategoryMatch = false;

                for (var j = 0; j < myCategory.length; j++) {
                    if ($.inArray(myCategory[j], arrCategory) >= 0) {
                        // 対象
                        bCategoryMatch = true;
                        break;
                    }
                }

                if (!bCategoryMatch) {
                    // 対象外
                    return false;
                }
            }

            return true;
        },
```

## ページングHTMLの変更（modifyPaginateHTML）
あまりないかもしれませんが、今回は表示中のページ番号はクリックできないようなHTMLだったので、変更する必要がありました。そういう場合はこのエリアで変更できます。

```flexibleSearch-cate.js
        modifyPaginateHTML: function(html){
            // current change
            var searchStartStr = '<li class="fs-current">';
            var searchEndStr = '</li>';
            var start = html.indexOf(searchStartStr);
            if (start != -1) {
                var end = html.indexOf(searchEndStr, start);
                start = parseInt(start) + searchStartStr.length;
                
                var before = html.substring(start, end);
                var title = $(before).attr('title');
                html = html.replace(before, '<em>' + title + '</em>');
            }
            
            return html;
        },
```

## その他
- 初期表示で検索結果を表示したかったのですが、うまいこといきませんでした。なので、初期表示は独自でjqueryで実装しました。
- ページングのリンクは、クリックされたあとにflexibleSearchで自動で計算されるようです
- 検索対象データ（JSONファイル）が0件の場合、customSearchは動かないので、customSearch内で何か（例えばjqueryの関数の実行など）を行っている場合、実行されません。その場合、resultCompleteだと動くのでこちらを利用しましょう。

# MovableTypeのDataAPIを断念した理由

- 検索速度が遅い
- キーワード検索したときに、記事のカテゴリが取得できない
- 1回のリクエストで複数カテゴリを指定して記事を取得できない

実現はできるが、遅いというのが気になった。



# 参考
[flexible Search](https://github.com/tinybeans/jq-plugin-flexibleSearch)
便利なライブラリありがとうございます。