Vue.jsで選択した項目によって表示内容が変わるtableを作ってみよう
はじめに
選択した項目によってtableの表示内容を切り替えたい。
さらにtable要素内のthead要素を固定してtbody要素をスクロール可能にしたい。
今回はそんな場合の対応方法をVue.jsで考えてみました。
こんにちは。クラウドソリューショングループのtakinami.sです。
というわけで、上の画像のような表を作っていきます。
動作確認環境
vue: 2.6.12
bootstrap: 4.5.0
今回は.vue拡張子の単一ファイルコンポーネントで書いていきます。
コードを見ていく
<script>の中身
単一ファイルコンポーネントでは<template>,<script>,<style>の順番で書いていかなくてはいけませんが、画面に表示させるデータを先に確認したいのでまずは<script>の中を見ていきます。
export default { data() { return { isActive: 0, foods: [ { category: "野菜", kinds: [ { name: "じゃがいも", place: "北海道" }, { name: "人参", place: "北海道" }, { name: "大根", place: "神奈川県" }, { name: "トマト", place: "熊本県" }, ] }, { category: "肉", kinds: [ { name: "豚肉", place: "鹿児島県" }, { name: "鶏肉", place: "宮崎県" }, { name: "牛肉", place: "北海道" }, { name: "猪肉", place: "兵庫県" }, ] }, { category: "果物", kinds: [ { name: "りんご", place: "青森県" }, { name: "梨", place: "千葉県" }, { name: "みかん", place: "和歌山県" }, { name: "桃", place: "山梨県" }, { name: "りんご", place: "青森県" }, { name: "梨", place: "千葉県" }, { name: "みかん", place: "和歌山県" }, { name: "桃", place: "山梨県" }, { name: "りんご", place: "青森県" }, { name: "梨", place: "千葉県" }, { name: "みかん", place: "和歌山県" }, { name: "桃", place: "山梨県" }, ] }, ] } } }
foodsという配列の中に項目として野菜・肉・果物を取り上げ、それぞれの種類と産地をまとめてみました。
isActiveではボタンでどの項目が選択されているのかを見ています。初期状態では0、つまり一番最初の項目が選択されている状態です。今回の場合は野菜が選択されていることになります。
<template>の中身
次に<template>の中身を見ていきましょう。
項目選択用のボタンはこのようになっています。
<div class="btn-group btn-group-toggle mb-3"> <label class="btn btn-outline-primary" v-for="(btnItem, key) in foods" :key="key" :class="[key == 0 ? {active: isActive == key} : '', {active: isActive == key}]"> <input type="radio" :value="key" v-model="isActive">{{ btnItem.category }} </label> </div>
1つ目のポイントは
v-for="(btnItem, key) in foods" :key="key"
の部分です。
v-forを使ってリストレンダリングをしています。さらに、項目の順番が入るkeyを設定しています。
次のポイントは
:class="[key == 0 ? {active: isActive == key} : '', {active: isActive == key}]">
の部分です。
keyには最初は0を入れ、<script>内で記述したisActiveの値とkeyの値が一致したらactiveというclassを追加するように設定しています。
続いて表の部分のコードも見ていきましょう。
<div class="cover-table"> <table class="table table-border border mb-0 food-table"> <thead> <tr> <th class="food-name border-right">名前</th> <th class="food-place">産地</th> </tr> </thead> </table> <div class="overflow-auto table-body" v-for="(item, key) in foods" :key="key"> <table class="table table-border border food-table" v-show="isActive == key"> <tbody> <tr v-for="(tableItem, key) in item.kinds" :key="key"> <td class="food-name border-right">{{ tableItem.name }}</td> <td class="food-place">{{ tableItem.place }}</td> </tr> </tbody> </table> </div> </div>
Vue,jsを使うところで注目してほしいのは、
<div class="overflow-auto table-body" v-for="(item, key) in foods" :key="key">
<table class="table table-border border food-table" v-show="isActive == key">
<tr v-for="(tableItem, key) in item.kinds" :key="key">
この3点です。
表の部分でもv-forのkeyを設定し、isActiveとkeyの値が一致したときに対応するtableを表示させています。
これでボタンで選択した項目によって、tableの表示内容を切り替えていくことができるようになりました。
さて、table全体を覆う
<div class="cover-table">
があることと、thead要素とtbody要素で別々のtable要素を使っていることに気がついたでしょうか。
これらがtbody要素のみをスクロールさせるときに活きてきます。
tbody要素を含むtableのほうにはbootstrapのoverflow-autoクラスを設定し、スクロールを可能にしています。
<style>の中身
<style scoped> .cover-table { width: 418px; } .table-body { max-height: 200px; } .food-table { width: 400px; } .food-name, .food-place { width: 200px; } </style>
今回はtable全体を覆うcover-tableクラスには418pxの幅を、tbody要素を含むtable要素のfood-tableクラスには400pxの幅を設定しました。
18pxの差がありますが、これはレイアウトが崩れないようスクロールバーの幅を考慮に入れているためです。
tbody要素の最大の高さは200pxに設定、セルの幅も200pxに設定しました。
これでtbody要素が200pxを超えるときにはtbody要素がスクロールできるようになります。
最後に
今回はbootstrapを使いましたが、もちろんスタイルはCSSで自分で設定することもできます。
最後に今回のコードの全体を載せておきます。
<template> <div> <div class="btn-group btn-group-toggle mb-3"> <label class="btn btn-outline-primary" v-for="(btnItem, key) in foods" :key="key" :class="[key == 0 ? {active: isActive == key} : '', {active: isActive == key}]"> <input type="radio" :value="key" v-model="isActive">{{ btnItem.category }} </label> </div> <div class="cover-table"> <table class="table table-border border mb-0 food-table"> <thead> <tr> <th class="food-name border-right">名前</th> <th class="food-place">産地</th> </tr> </thead> </table> <div class="overflow-auto table-body" v-for="(item, key) in foods" :key="key"> <table class="table table-border border food-table" v-show="isActive == key"> <tbody> <tr v-for="(tableItem, key) in item.kinds" :key="key"> <td class="food-name border-right">{{ tableItem.name }}</td> <td class="food-place">{{ tableItem.place }}</td> </tr> </tbody> </table> </div> </div> </div> </template> <script> export default { data() { return { isActive: 0, foods: [ { category: "野菜", kinds: [ { name: "じゃがいも", place: "北海道" }, { name: "人参", place: "北海道" }, { name: "大根", place: "神奈川県" }, { name: "トマト", place: "熊本県" }, ] }, { category: "肉", kinds: [ { name: "豚肉", place: "鹿児島県" }, { name: "鶏肉", place: "宮崎県" }, { name: "牛肉", place: "北海道" }, { name: "猪肉", place: "兵庫県" }, ] }, { category: "果物", kinds: [ { name: "りんご", place: "青森県" }, { name: "梨", place: "千葉県" }, { name: "みかん", place: "和歌山県" }, { name: "桃", place: "山梨県" }, { name: "りんご", place: "青森県" }, { name: "梨", place: "千葉県" }, { name: "みかん", place: "和歌山県" }, { name: "桃", place: "山梨県" }, { name: "りんご", place: "青森県" }, { name: "梨", place: "千葉県" }, { name: "みかん", place: "和歌山県" }, { name: "桃", place: "山梨県" }, ] }, ] } } } </script> <style scoped> .cover-table { width: 418px; } .table-body { max-height: 200px; } .food-table { width: 400px; } .food-name, .food-place { width: 200px; } </style>