JavaScript
vue.js

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

概要

サーバ側しかほぼ実装しない自分が、仕事上Vue.jsを触る必要が出てきたので簡単な一覧画面を作ってみる。
あくまでチュートリアルレベルなのであしからず。。。
出来上がったもの: jsFiddle github
参考サイト様はこちら

データを表示させてみる

まずは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

ここでは<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キャプチャ.PNG

※ここの実装はこちらを参考にしています。

まずは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()が呼び出されます。
※ソート処理などの説明は、こちらに記載されているので割愛します。

セレクトボックスによるフィルタリング実装してみる

次によくありがちなセレクトボックスによるフィルタリングを実装してみます。
ここでは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キャプチャ2.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キャプチャ2.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キャプチャ2.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キャプチャ2.PNG

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

感想

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