Vue.jsで簡単な一覧画面を作ってみた

# 概要
サーバ側しかほぼ実装しない自分が、仕事上Vue.jsを触る必要が出てきたので簡単な一覧画面を作ってみる。
あくまでチュートリアルレベルなのであしからず。。。
出来上がったもの：　[jsFiddle](https://jsfiddle.net/HagaSpa/Lyubmr2m/)  [github](https://github.com/HagaSpa/simple_vue_list) 
参考サイト様は[こちら](https://kuroeveryday.blogspot.jp/2018/01/sortable-table-with-vuejs.html)

## データを表示させてみる
まずはHTMLに値を表示させてみます。
vueインスタンスを生成し、表示させたいデータをdataに格納し、インスタンスを適用させたい要素をelに指定します。
HTML側ではそのプロパティから値をレンダリングする仕組みになっています。

```index.js
const items = [
　{'Id':'1', 'Name':'Tanaka', 'Age':'20'},
  {'Id':'2', 'Name':'Suzuki', 'Age':'30'},
  {'Id':'3', 'Name':'Takahasi', 'Age':'40'}
];

const ids = [
　'1', '2', '3'
];

const ages = [
　'20', '30', '40'
];

new Vue({
  el: '#app',
  data: {
    items: items
});
```

```app.html
<!DOCTYPE html>
<meta charset="UTF-8">
<html lang="ja">
    <div id="app">
        <div>
            <table>
                <thead>
                   <tr>
                      <th>Id</th>
                      <th>Name</th>
                      <th>Age</th>
                   </tr>
                </thead>
                
                <tbody>
                    <tr v-for="row in items">
                       <td>{{row.Id}}</td>
                       <td>{{row.Name}}</td>
                       <td>{{row.Age}}</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</html>
```
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/128338/21d89520-ca3d-0cb3-bab8-2df6a3499b19.png)

ここでは`<div id="app">`に対して、Vueインスタンスが割り当てられています。
そのため`v-for="row in items"`のitemsは`data.items`を指しており、それを回して値を表示させています。
これらのデータを元にして他機能を実装していきます。

## ソートを実装してみる
表示させたデータに対してソートする機能を実装します。
ヘッダ部を押下して、押下されたカラムに対してソートさせるようにします。


```index.js
new Vue({
  el: '#app',
  data: {
    items: items,
    sort: {
      key: '', // ソートキー
      isAsc: false // 昇順ならtrue,降順ならfalse
    }
  },
  computed: {
    eventedAction: function() {
      let list = this.items.slice();
      
      // ソート実施
      if(this.sort.key) {
        list.sort((a, b) => {
          a = a[this.sort.key];
          b = b[this.sort.key];
          return (a === b ? 0 : a > b ? 1 : -1) * (this.sort.isAsc ? 1 : -1);
        });
      }
      
      return list;
    }	
  },
  methods: {
    // sort用キーをセットし、昇順・降順を入れ替える
    sortBy: function(key) {
      this.sort.isAsc = this.sort.key === key ? !this.sort.isAsc : false;
      this.sort.key = key;
    },
    sortedClass: function(key) {
      return this.sort.key === key ? `sorted ${this.sort.isAsc ? 'asc' : 'desc' }` : '';
    }
  }
});
```

```app.html
<!DOCTYPE html>
<meta charset="UTF-8">
<html lang="ja">
    <div id="app">
        <div>
            <table>
                <thead>
                   <tr>
                      <th @click="sortBy('Id')" :class="sortedClass('Id')">Id</th>
                      <th @click="sortBy('Name')" :class="sortedClass('Name')">Name</th>
                      <th @click="sortBy('Age')" :class="sortedClass('Age')">Age</th>
                   </tr>
                </thead>
                
                <tbody>
                    <tr v-for="row in eventedAction">
                       <td>{{row.Id}}</td>
                       <td>{{row.Name}}</td>
                       <td>{{row.Age}}</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</html>
```

```app.css
th.sorted.desc::after{
    display: inline-block;
    content: '▼';
}
th.sorted.asc::after{
    display: inline-block;
    content: '▲';
}
```

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/128338/21d89520-ca3d-0cb3-bab8-2df6a3499b19.png)![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/128338/33f7fa53-f281-28cf-34bc-6ed25bc17b51.png)

※ここの実装は[こちら](https://kuroeveryday.blogspot.jp/2018/01/sortable-table-with-vuejs.html)を参考にしています。

まずはVueインスタンスに新たなプロパティとして、```sort{key: '', isAsc: false}```を定義します。
このsortの状態が変更されることにより、```computed:{}```が実行されます。
今後この```computed:{}```の中に、インスタンスの状態が変更されるたびに行いたい処理を書いていく形になります。
つまりこの```computed:{}```がインスタンスを常に監視し、然るべきタイミングで実行してくれるらしいです・・・

次にsortの値を変更するメソッドを定義します。
```computed:{}```と同じようにインスタンスの別プロパティ、```methods:{}```に定義しその発火タイミングと場所をHTML側で指定します。
今回はヘッダ部をクリックしたらソートして欲しいため
sortの値を変更（```methods:{}```）→　変更を感知してソート処理を実行（```computed:{}```）
という流れになります。
~~methodsに処理内容を記載するのはNGのようです~~

`@click="sortBy('Id')"`によりthを押下した時、sortの値を変更するメソッドを呼び出しています。
ここでsortの値が変わることにより、```eventedAction: function()```が呼び出されます。
※ソート処理などの説明は、[こちら](https://kuroeveryday.blogspot.jp/2018/01/sortable-table-with-vuejs.html)に記載されているので割愛します。



## セレクトボックスによるフィルタリング実装してみる
次によくありがちなセレクトボックスによるフィルタリングを実装してみます。
ここではIdに対して、選択された値のみ表示するようにします。

```index.js
const ids = [
	'1', '2', '3'
];

new Vue({
  el: '#app',
  data: {
    items: items,
    sort: {
      key: '',
      isAsc: false
    },
    ids: ids,      // セレクトボックスの値
    selectId: ''   // 選択されたセレクトボックスの値
  },
  computed: {
    eventedAction: function() {
      let list = this.items.slice();
      
      // Idでフィルタリング実施
      if(this.selectId) {
      	list = list.filter(element => {
        	return element.Id === this.selectId;
        });
      }
      
      return list;
    }	
  },
　　　　// ～省略
```
```app.html
<!DOCTYPE html>
<meta charset="UTF-8">
<html lang="ja">
    <div id="app">
        <div>
            <span>Id：</span>
            <select v-model="selectId">
                <option v-for="id in ids" v-bind:value="id">{{ id }}</option>
            </select>
        </div>
　　　　// ～省略
```
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/128338/fcd4b77b-bfc5-edef-590c-952b08ff097b.png)![キャプチャ2.PNG](https://qiita-image-store.s3.amazonaws.com/0/128338/e191e32d-cdf2-686c-fbd2-cbfdce620679.png)

選択されたセレクトボックスの値を保持するプロパティ、`selectId`を定義します。
その後セレクトボックスと対応させるためhtml側で、`v-model="selectId"`の記載をselectタグに定義します。
実際のフィルタリング処理は、選択された`Id`と同じ`Id`を持つデータを`filter()`を使用して配列化してます。



## チェックボックスによるフィルタリング実装してみる
次はチェックボックスによるフィルタリングを実装してみます。
内容としてはチェックボックスが`true`になっている`Age`を持つ、データのみを表示させます。
初期表示は全てのチェックボックスを`true`にして、全件表示させます。

```index.js
const ages = [
	'20', '30', '40'
];

new Vue({
  el: '#app',
  data: {
    items: items,
    sort: {
      key: '',
      isAsc: false
    },
    ids: ids,
    selectId: '',
    ages: ages,      // チェックボックスの値
    selectAges: ages // 選択されたチェックボックスの値
  },
  computed: {
    eventedAction: function() {
      let list = this.items.slice();
      
      // Ageでフィルタリング実施
      if(this.selectAges) {
      	list = list.filter(element => {
          for(const age of this.selectAges) {
            if(element.Age === age) {
              return true;
            }
          }
        });
      }
      
      return list;
    }	
  },
　　　　// ～省略
```
```app.html
<!DOCTYPE html>
<meta charset="UTF-8">
<html lang="ja">
    <div id="app">
        <div>
            <span>Id：</span>
            <select v-model="selectId">
                <option v-for="id in ids" v-bind:value="id">{{ id }}</option>
            </select>
        </div>
        <div>
            <span>Age：</span>
            <span v-for="age in ages">
                <input type="checkbox" v-bind:value="age" v-model="selectAges">{{ age }}
            </span>
        </div>
　　　　// ～省略
```

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/128338/3de40061-14f0-2d10-9d88-1eaf91fdcd2d.png)![キャプチャ2.PNG](https://qiita-image-store.s3.amazonaws.com/0/128338/58ebfb31-2759-221b-a762-345b874e4b2c.png)

ここでは`ages`の件数分、チェックボックスを表示させたいため外側の`span`タグ内で`ages`を回して実装しました。
また初期表示時は全てのチェックボックスが`true`で表示させたいため、`selectAges = ages;`で全件を選択した状態にしてます。
今回はチェックボックスのため、フィルタリング処理は`this.selectAges`が複数の値が存在することを考慮しての実装になります。


## 入力された文字列によるフィルタリング実装してみる
次にユーザにより入力された文字列で`Name`を検索する機能を実装します。

```index.js
const ages = [
	'20', '30', '40'
];

new Vue({
  el: '#app',
  data: {
    items: items,
    sort: {
      key: '',
      isAsc: false
    },
    ids: ids,
    selectId: '',
    ages: ages,      
    selectAges: ages,
    searchName: '' // 入力された文字列を格納
  },
  computed: {
    eventedAction: function() {
      let list = this.items.slice();
      
      // Nameで検索実施
      if (this.searchName) {
        list = list.filter(element => {
          return Object.keys(element).some(key => {
            if(key === 'Name') {
              return element[key].indexOf(this.searchName) > -1;
            }
          });
        });
      }
      
      return list;
    }	
  },
　　　　// ～省略
```
```app.html
<!DOCTYPE html>
<meta charset="UTF-8">
<html lang="ja">
    <div id="app">
        <div>
            <span>Id：</span>
            <select v-model="selectId">
                <option v-for="id in ids" v-bind:value="id">{{ id }}</option>
            </select>
        </div>
        <div>
            <span>Age：</span>
            <span v-for="age in ages">
                <input type="checkbox" v-bind:value="age" v-model="selectAges">{{ age }}
            </span>
        </div>
        <div>
            <span>Name：</span>
            <input type="text" v-model="searchName" placeholder="Search">
        </div>
　　　　// ～省略
```

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/128338/1d99b68c-53cf-a5ff-286f-4b747c55183e.png)![キャプチャ2.PNG](https://qiita-image-store.s3.amazonaws.com/0/128338/b4eed0ae-8269-84e8-d0d6-9c647338f834.png)

入力欄に入力された文字を含んでるデータを表示します。
フィルタリングのロジックとしては、`items`(`list`)を全て回し、`Name`の値に入力された文字列(`this.searchName`)を含む
データのみを抽出します。
Vueが状態の変更を常に監視してくれているので、入力される文字列が変化すると毎にフィルタリングも実施されます。


## クリアボタンを実装してみる
最後にここまで実装してきたソート・フィルタリング機能は、全て重複して実施することが可能です。
そのため全ての処理をクリアできるように、クリアボタンを実装してみます。

```index.js
  methods: {
    sortBy: function(key) {
      this.sort.isAsc = this.sort.key === key ? !this.sort.isAsc : false;
      this.sort.key = key;
    },
    sortedClass: function(key) {
      return this.sort.key === key ? `sorted ${this.sort.isAsc ? 'asc' : 'desc' }` : '';
    },
    // 全ての処理をクリアする
    resetting: function() {
      this.sort.key = '';
      this.sort.isAsc = false;
      this.selectId = '';
      this.selectAges = ages;
      this.searchName = '';
      this.items = items;
    }
  },
　　　　// ～省略
```
```app.html
<!DOCTYPE html>
<meta charset="UTF-8">
<html lang="ja">
    <div id="app">
        <div>
            <span><button @click="resetting()">元に戻す</button></span>
        </div>
　　　　// ～省略
```
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/128338/f8cfbbfa-3b63-e143-2bba-e02ca118db7a.png)![キャプチャ2.PNG](https://qiita-image-store.s3.amazonaws.com/0/128338/82502c7c-266e-0525-bbc7-d684a9487885.png)

これで実装する機能は全てとなります。

## 感想
基本的にサーバ側しか実装したことがなく、クライアント側もjQueryしか触ったことない身としてはとても新鮮でした。
特にVueが値の変更を監視してくれているので、そこに意識をほぼ割くことなく実装できたのは面白かったです。
これを機に逃げずにクライアント側も少しは追ってみようかと思います・・・