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>









