Angular UI Grid とUI Bootstrap を使ってGridのタブ対応 + フィルタ機能付き をやってみる

こんにちは。サーバーサイド開発から急遽クライアント側の開発をすることになりました。
未知の世界で苦労しているので、忘れない為にもこつこつと記録に残していきたいと思います。

ということで今回はタブ付きのGridを実現させてみたいと思います。

こんなの↓↓　です。
※Plunkerだとうまくいかなかったので画像で我慢してください。。。

![001.png](https://qiita-image-store.s3.amazonaws.com/0/53473/53dbba6d-7739-19b4-b8ee-628364eb2434.png)

![002.png](https://qiita-image-store.s3.amazonaws.com/0/53473/77e47434-3792-95a0-35a3-c806a22e804d.png)

フィルタも・・・！
![003.png](https://qiita-image-store.s3.amazonaws.com/0/53473/4a27cc23-49ef-6c9f-1a20-f58ae106c2af.png)


なお、本記事はWindows環境での手順なので、他の環境については良い感じに読み替えて頂ければと思います :laughing: 

# 事前準備

まず、事前に必要なものたちは以下の通り。

* Node.js (typescript, bowerをインストールする為にnpmを使います)
* Typescript (今回はTypescriptで書きます)
* tsd (型定義ファイルをインストールするのに使います)
* Bower (angularJSもろもろをインストールするのに使います)

まずはこいつらを準備しましょう。

## Node.js

既に導入済みの場合は飛ばしてください。
特別なことは特にないので、[ココ](https://nodejs.org/en/)からDLしてインストールしましょう。
exeファイルを実行した後、ウィザードに沿ってサクッといけます。
ちなみに今回使用したのは`v5.4.1 Stable`です。

## Typescript

今回はTypescriptを使いたいので、npmでインストールします。
Typescript使わない方は無視してください。後ほどES5でコンパイルしたJSソースも展開します。
Node.jsをインストールすればnpmも自動で入ってますので、[GitBash](https://git-for-windows.github.io/)やらで以下のコマンドを実行します。

```
$ npm install typescript -g
```

## tsd

Typescriptを使用するにあたって必要な```型定義ファイル```を管理する為のツールです。
これもTypescriptを使わない方は無視してください。
インストールはnpmで行います。

```
$ npm install tsd -g
```

## Bower

Bowerはクライアントサイド用のパッケージマネージャーです。
npmはサーバーサイド用として、それぞれ使い分けるのが一般的なようですが、
今回はそれに則ってangularjsやui-grid等のツールはBowerでインストールします。

```
$ npm install bower -g
```

Bowerとかnpmとかについては[この記事](http://qiita.com/megane42/items/2ab6ffd866c3f2fda066)が無茶苦茶わかりやすいです。

# 今回のプロジェクト構成(最終形)

今回はファイル数も多くないですし、<del>めんどうなので</del>男らしくフォルダ分割等をせずにいきたいと思います。

```
ui-grid-sample
　┣ bower_components         // Bowerでインストールされたパッケージたち
　┣ typings                  // tsdでインストールされた型定義ファイルたち
　┣ app.css
　┣ app.js                   // コンパイル(ts->js)時に1つのjsとしてまとめています
　┣ app.ts
　┣ bower.json               // Bowerの設定ファイル
　┣ DataService.ts           // データ処理担当クラス
　┣ index.html
　┣ MainController.ts        // コントローラクラス
　┣ tsconfig.json            // tsc(Typescriptのコンパイラ)の設定ファイル
　┗ tsd.json                 // tsdの設定ファイル
```
ルート直下に全部おきます。
tsファイルをコンパイルしたjsファイルも同じ場所におきました。

# もろもろの説明

コードの説明だけを少し…。各種設定ファイルは今回は割愛させて頂きます。

## コード一式と使い方

[こちら](https://github.com/nakamu-mu/grid-ui-sample)に置きました。
※ところどころアクセス修飾子があったりなかったり、型注釈が抜けてたりしますが、ご容赦下さい…

Cloneしたら以下のコマンドを実行して依存パッケージと型定義ファイルをインストールしてください。

※以下GitBash想定

* プロジェクトルートに移動

```
$ cd {ui-grid-sampleの場所}
```

* Bowerで依存パッケージのインストール

```
$ bower install
```

上記コマンドを実行することで```bower.json```に記述された依存パッケージがインストールされます。
(bower_componentディレクトリが作成されます)

* tsdで型定義ファイルのインストール

```
$ tsd install
```

上記コマンドを実行することで```tsd.json```に記述された依存パッケージがインストールされます。
(typeingsディレクトリが作成されます)

この時点で```index.html```をFirefoxで開くと、以下のようなgridが出るんじゃないかなーと思います。
Chrome等ではそのままでは実行できないのでご注意ください。
どうしてもChrome等で実行したい場合はgulpでgulp-webserverプラグインを利用し、webserverタスクを作成すると良いでしょう。

## index.htmlを基点に、コードを少し説明

```index.html
<!DOCTYPE html>
<html ng-app="app">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>ui-grid + tab sample</title>
    <link rel="stylesheet" href="bower_components/angular-ui-grid/ui-grid.css" type="text/css">
    <link rel="stylesheet" href="bower_components/angular-bootstrap/ui-bootstrap-csp.css" type="text/css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
    <link rel="stylesheet" href="app.css" type="text/css">
    <script src="bower_components/angular/angular.js"></script>
    <script src="bower_components/angular-ui-grid/ui-grid.js"></script>
    <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
    <!-- <script src="./DataService.js"></script>
    <script src="./MainController.js"></script> -->
    <script src="./app.js"></script>
  </head>
  <body>
    <div ng-controller="MainController as mainController">
      <input ng-model="mainController.filterValue" ng-change="mainController.update()" placeholder="名前検索"/>
      <br/><br/>
      <uib-tabset>
        <uib-tab ng-repeat="tab in mainController.tabs" heading="{{tab.title}}" select="mainController.setSelectedTab(tab.title)"></uib-tab>
      </uib-tabset>
      <div id="grid" ui-grid="mainController.gridOptions" class="grid"></div>
    </div>
  </body>
</html>
```

angularの基本的なところは省略します。
body部分を少し。

```index.html
<input ng-model="mainController.filterValue" ng-change="mainController.update()" placeholder="名前検索"/>
```

この部分は単純な入力フィールドです。役割はGridのフィルタです。
```MainController```クラスの```filterValue```と紐付けてあり、
入力がある度に同クラスの```update()```を呼ぶようにしています。

```MainController.ts
    update(): void {
      this.gridApi.grid.refresh();
    }
```

この```update()```ではui-gridの```refresh()```をコールし、gridのリフレッシュをしています。
そして```refresh()```がコールされると```gridOptions.onRegisterApi```で登録されている処理を実行します。

```MainController.ts
      onRegisterApi: (gridApi) => {
        this.gridApi = gridApi;
        this.gridApi.grid.registerRowsProcessor((renderableRows:any) => {
          var matcher = new RegExp(this.filterValue);
          renderableRows.forEach(function(row: any) {
            var match = false;
            ['name'].forEach(function(field){
              if (row.entity[field].match(matcher)){
                match = true;
              }
            });
            if (!match){
              row.visible = false;
            }
          });
          return renderableRows;
        }, 200 );
      },
```

この部分がソレです。
読んで頂ければわかるのですが、gridの各行について```name```フィールドの値が```filterValue```の値にマッチしているかチェックし、
マッチしていれば```visible = true```、マッチしなければ```visible = false```にして
gridに表示させる行、させない行の設定を行っています。

次にタブですが

```index.html
      <uib-tabset>
        <uib-tab ng-repeat="tab in mainController.tabs" heading="{{tab.title}}" select="mainController.setSelectedTab(tab.title)"></uib-tab>
      </uib-tabset>
```

ここがUI Bootstrapを使ってタブ表示をしている部分です。
[公式サンプル](https://angular-ui.github.io/bootstrap/)を見るに、本来であれば```<uib-tab>～</uib-tab>```の中にgridを差し込むのですが
そうするとフィルタが上手く効かなかった為、今回はそうしませんでした。
タブが変更される度に```MainController```クラスの```change(tab.title)```がコールされます。

```MainController.ts
    change(tab: string): void {
      this.selectedTab = tab;
      if (this.selectedTab === "DATA1") {
        this.gridOptions.data = this.getData(1);
      }
      else {
        this.gridOptions.data = this.getData(2);
      }
      this.update();
    }
```

 ```change(tab: string)```メソッドでは

* this.selectedTabに現在選択されているTab値を格納 (特に必要ないが、とりあえず持っています)
* 現在選択されているタブ値に対応したデータを```gridOptions.data```に格納
* ```update()```をコール(=gridのリフレッシュ)

という処理を行っています。
そして

```index.html
      <div id="grid" ui-grid="mainController.gridOptions" class="grid"></div>
```

この部分がui-gridを使用して、gridを表示している部分です。
 ```MainController```クラスのプロパティである```gridOptions```で
gridの設定をしているのですが```gridOptions.data```に設定されたデータが表示されます。

まとめると

* DataServiceクラスが今回のサンプルデータを管理している
* MainControllerがDataServiceを使い、grid表示をコントロールしている
* gridにはMainControllerクラスのgridOptions.dataに設定されているデータが表示される
* タブを切り替え時に```gridOptions.data```に設定されているデータを変更し、gridをリフレッシュすることでgridの表示データが変わる
* filterフィールド(名前検索部)に文字が入力される度にgridをリフレッシュする。
* gridのリフレッシュ時、```gridOptions.onRegisterApi```に登録されている処理が実行され、
行ごとにフィルタ部に入力された文字列と```name```フィールドをつき合わせ表示する行、しない行を設定後
gridのリフレッシュを行う

簡単ですが、以上です。