スクラッチから始める、Nuxt.jsでBootstrap4 (続・テーブル表示編)

開発Divのeciです。

社内にも徐々にVueを始めるメンバーが増えてきました。
布教活動は順調です。

私より詳しくなるメンバーが出たら色々教えてもらおうと密かに狙っています。

さて、本題ですが、前回からの続きからとなります。

今回のやること

  1. Pagenationを入れてみる
  2. ダイアログを表示してみる
  3. メッセージを表示してみる

下準備

まずはデータを少し増やしておきましょう。
index.vueのitemsを↓のように書き換えます。

<script>
  export default {
    data: () => {
      return {
        items: [
          {id:1, name: 'みかん', price: 1000, stock: true},
          {id:2, name: 'りんご', price: 2000, stock: true},
          {id:3, name: 'いちご', price: 800, stock: false},
          {id:4, name: 'メロン', price: 5000, stock: true},
          {id:5, name: 'すいか', price: 3000, stock: true},
          {id:6, name: 'キウイ', price: 680, stock: true},
          {id:7, name: 'パイナップル', price: 1300, stock: true},
          {id:8, name: '桃', price: 2000, stock: true},
          {id:9, name: 'ぶどう', price: 750, stock: true},
          {id:10, name: 'グレープフルーツ', price: 900, stock: true},
          {id:11, name: '柿', price: 800, stock: true},
          {id:12, name: '梨', price: 630, stock: true},
        ],
      }
    }
  }
</script>

Pagenation編

ここからが本番です。
データが増えて、そろそろPagenationが欲しくなってきましたので、入れましょう。

変数を定義

currentPage: 1,
perPage: 5,      

methodsにデータ数を返すメソッドを追加

methods: {
  getRowCount (items) {
    return items.length
  }
},    

b-tableタグに属性を追加

:current-page="currentPage" 
:per-page="perPage"

navタグを追加

<nav>
  <b-pagination :total-rows="getRowCount(items)" :per-page="perPage" v-model="currentPage" prev-text="前へ" next-text="次へ" />
</nav>

いかがでしょうか?
これでPagenationが表示されたと思います。
ここまでのソースはこんな感じになっています。

pages/index.vue

<template>
  <div class="container">
    <h1>
      hello-bv&nbsp;<b-badge>New!</b-badge>
    </h1>
    <b-table :items="items" :fields="fields" :current-page="currentPage" :per-page="perPage">
      <template slot="HEAD_name" slot-scope="data">
        名前
      </template>
      <template slot="HEAD_price" slot-scope="data">
        価格 
      </template>
      <template slot="HEAD_stock" slot-scope="data">
        在庫 
      </template>
    </b-table>
      <nav>
        <b-pagination :total-rows="getRowCount(items)" :per-page="perPage" v-model="currentPage" prev-text="前へ" next-text="次へ" />
      </nav>

  </div>
</template>
<script>
  export default {
    data: () => {
      return {
        fields:[
          {key: "id"},
          {key: "name"},
          {key: "price",
            formatter:(value)=> {
              return value.toLocaleString()
            },
            tdClass: "text-sm-right"          
          },
          {key: "stock",
            formatter:(value)=> {
              return value ? "あり" : "なし"
            },          
          },
        ],
        items: [
          {id:1, name: 'みかん', price: 1000, stock: true},
          {id:2, name: 'りんご', price: 2000, stock: true},
          {id:3, name: 'いちご', price: 800, stock: false},
          {id:4, name: 'メロン', price: 5000, stock: true},
          {id:5, name: 'すいか', price: 3000, stock: true},
          {id:6, name: 'キウイ', price: 680, stock: true},
          {id:7, name: 'パイナップル', price: 1300, stock: true},
          {id:8, name: '桃', price: 2000, stock: true},
          {id:9, name: 'ぶどう', price: 750, stock: true},
          {id:10, name: 'グレープフルーツ', price: 900, stock: true},
          {id:11, name: '柿', price: 800, stock: true},
          {id:12, name: '梨', price: 630, stock: true},
        ],
        currentPage: 1,
        perPage: 5,        
      }
    },
    methods: {
      getRowCount (items) {
        return items.length
      }
    },    
  }
</script>

削除用リンクと確認ダイアログ編

データを一覧からサクサク削除したい時もあると思います。
削除リンクをクリックしたらサクッと削除できるようにしてみましょう。

削除リンク用のfieldを追加

fields:[
  {key: "id"},
  {key: "name"},
  {key: "price",
    formatter:(value)=> {
      return value.toLocaleString()
    },
    tdClass: "text-sm-right"          
  },
  {key: "stock",
    formatter:(value)=> {
      return value ? "あり" : "なし"
    },          
  },
  'delete',   //←ここ       
],

削除ダイアログ用の変数を追加

currentPage: 1,
perPage: 5,
delItem: "", //←ここ

削除用リンクの追加

削除リンク用のカラム定義をb-tableの一番後ろに追加します。

<template slot="delete" slot-scope="data">
  <a href="#"  v-on:click="showDelModal(data.item)">
      削除
  </a>          
</template>

ダイアログの定義を追加

templateの下の方にこっそり追加します。

<div>
  <!-- Modal Component -->
  <b-modal id="modalDelete" ref="delModal"
    @ok="del" 
    centered 
    title="削除確認">
    <p class="my-4">削除しますか?</p>
  </b-modal>
</div>     

ダイアログ表示メソッドの追加

ダイアログ表示用メソッドとOKボタンを押された時に呼び出されるメソッドも追加します。

showDelModal (value) {
  this.delItem = value
  this.$refs.delModal.show()
},
del (value) {
  const n = this.items.filter(value => value.id != this.delItem.id)
  this.items.splice(0)
  this.items = n
},            

いかがでしょうか? ここまでで、削除リンクをクリックして、ダイアログで「OK」を押すと、該当の行が削除されると思います。 f:id:eci16:20180614203316p:plain

ここまでのソースはこんな感じになっています。

pages/index.vue

<template>
  <div class="container">
    <h1>
      hello-bv&nbsp;<b-badge>New!</b-badge>
    </h1>
    <b-table :items="items" :fields="fields" :current-page="currentPage" :per-page="perPage">
      <template slot="HEAD_name" slot-scope="data">
        名前
      </template>
      <template slot="HEAD_price" slot-scope="data">
        価格 
      </template>
      <template slot="HEAD_stock" slot-scope="data">
        在庫 
      </template>
      <template slot="delete" slot-scope="data">
        <a href="#"  v-on:click="showDelModal(data.item)">
            削除
        </a>          
      </template>
    </b-table>
    <nav>
        <b-pagination :total-rows="getRowCount(items)" :per-page="perPage" v-model="currentPage" prev-text="前へ" next-text="次へ" />
    </nav>
    <div>
      <!-- Modal Component -->
      <b-modal id="modalDelete" ref="delModal"
        @ok="del" 
        centered 
        title="削除確認">
        <p class="my-4">削除しますか?</p>
      </b-modal>
    </div>     
  </div>
</template>
<script>
  export default {
    data: () => {
      return {
        fields:[
          {key: "id"},
          {key: "name"},
          {key: "price",
            formatter:(value)=> {
              return value.toLocaleString()
            },
            tdClass: "text-sm-right"          
          },
          {key: "stock",
            formatter:(value)=> {
              return value ? "あり" : "なし"
            },          
          },
          'delete',          
        ],
        items: [
          {id:1, name: 'みかん', price: 1000, stock: true},
          {id:2, name: 'りんご', price: 2000, stock: true},
          {id:3, name: 'いちご', price: 800, stock: false},
          {id:4, name: 'メロン', price: 5000, stock: true},
          {id:5, name: 'すいか', price: 3000, stock: true},
          {id:6, name: 'キウイ', price: 680, stock: true},
          {id:7, name: 'パイナップル', price: 1300, stock: true},
          {id:8, name: '桃', price: 2000, stock: true},
          {id:9, name: 'ぶどう', price: 750, stock: true},
          {id:10, name: 'グレープフルーツ', price: 900, stock: true},
          {id:11, name: '柿', price: 800, stock: true},
          {id:12, name: '梨', price: 630, stock: true},
        ],
        currentPage: 1,
        perPage: 5,
        delItem: "",        
      }
    },
    methods: {
      getRowCount (items) {
        return items.length
      },
      showDelModal (value) {
        this.delItem = value
        this.$refs.delModal.show()
      },
      del (value) {
        const n = this.items.filter(value => value.id != this.delItem.id)
        this.items.splice(0)
        this.items = n
      },            
    },    
  }
</script>

処理完了メッセージ編

削除が正常に行われたのに、何のメッセージがないのは不親切です。
正常終了時は何も返さないUNIX文化は素敵ですが、Web画面でそれを主張したら「こいつ実装が面倒くさいだけじゃねーか?」と疑われますので、ここは素直にメッセージ表示しましょう。

メッセージ表示エリアの定義を追加

<b-alert :show="dismissCountDown"
     dismissible
     variant="info"
     @dismissed="dismissCountDown=0"
     @dismiss-count-down="countDownChanged">{{infoMessage}}
</b-alert>      

メッセージ表示用変数の追加

currentPage: 1,
perPage: 5,
delItem: "",
dismissCountDown: 0,  // ←ここ
dismissSecs: 3,      // ←ここ メッセージを表示しておく秒数
infoMessage: "",     // ←ここ   

メッセージ表示メソッドと表示カウントダウンメソッドの追加

countDownChanged(dismissCountDown)  {
  this.dismissCountDown = dismissCountDown
},
showInfo(message) {
  this.infoMessage = message
  this.dismissCountDown = this.dismissSecs
},            

メッセージ表示メソッドの呼び出しを追加

delメソッドに以下を追加します。

this.showInfo("削除できました。")

いかがでしょうか?
削除されると同時に、メッセージが表示されるようになったと思います。

最終的なソースはこんな感じになっています。

pages/index.vue

<template>
  <div class="container">
    <h1>
      hello-bv&nbsp;<b-badge>New!</b-badge>
    </h1>
    <b-alert :show="dismissCountDown"
             dismissible
             variant="info"
             @dismissed="dismissCountDown=0"
             @dismiss-count-down="countDownChanged">{{infoMessage}}
    </b-alert>          
    <b-table :items="items" :fields="fields" :current-page="currentPage" :per-page="perPage">
      <template slot="HEAD_name" slot-scope="data">
        名前
      </template>
      <template slot="HEAD_price" slot-scope="data">
        価格 
      </template>
      <template slot="HEAD_stock" slot-scope="data">
        在庫 
      </template>
      <template slot="delete" slot-scope="data">
        <a href="#"  v-on:click="showDelModal(data.item)">
            削除
        </a>          
      </template>
    </b-table>
    <nav>
        <b-pagination :total-rows="getRowCount(items)" :per-page="perPage" v-model="currentPage" prev-text="前へ" next-text="次へ" />
    </nav>
    <div>
      <!-- Modal Component -->
      <b-modal id="modalDelete" ref="delModal"
        @ok="del" 
        centered 
        title="削除確認">
        <p class="my-4">削除しますか?</p>
      </b-modal>
    </div>     
  </div>
</template>
<script>
  export default {
    data: () => {
      return {
        fields:[
          {key: "id"},
          {key: "name"},
          {key: "price",
            formatter:(value)=> {
              return value.toLocaleString()
            },
            tdClass: "text-sm-right"          
          },
          {key: "stock",
            formatter:(value)=> {
              return value ? "あり" : "なし"
            },          
          },
          'delete',          
        ],
        items: [
          {id:1, name: 'みかん', price: 1000, stock: true},
          {id:2, name: 'りんご', price: 2000, stock: true},
          {id:3, name: 'いちご', price: 800, stock: false},
          {id:4, name: 'メロン', price: 5000, stock: true},
          {id:5, name: 'すいか', price: 3000, stock: true},
          {id:6, name: 'キウイ', price: 680, stock: true},
          {id:7, name: 'パイナップル', price: 1300, stock: true},
          {id:8, name: '桃', price: 2000, stock: true},
          {id:9, name: 'ぶどう', price: 750, stock: true},
          {id:10, name: 'グレープフルーツ', price: 900, stock: true},
          {id:11, name: '柿', price: 800, stock: true},
          {id:12, name: '梨', price: 630, stock: true},
        ],
        currentPage: 1,
        perPage: 5,
        delItem: "",
        dismissCountDown: 0,
        dismissSecs: 3,
        infoMessage: "",                 
      }
    },
    methods: {
      getRowCount (items) {
        return items.length
      },
      showDelModal (value) {
        this.delItem = value
        this.$refs.delModal.show()
      },
      del (value) {
        const n = this.items.filter(value => value.id != this.delItem.id)
        this.items.splice(0)
        this.items = n
        this.showInfo("削除できました。")
      },
      countDownChanged(dismissCountDown)  {
        this.dismissCountDown = dismissCountDown
      },
      showInfo(message) {
        this.infoMessage = message
        this.dismissCountDown = this.dismissSecs
      },            
    },    
  }
</script>

まとめ

ダイアログのアニメーションとかいいですね!
メッセージ表示周りはちょっと面倒なので、もっといい方法があったら試してみたいと思います。