#【Vue】テーブル操作エキスパートへの道。ボタンクリックで行を追加する方法
作成した表をユーザー操作できるようにする。まずは手始めに「下に行を追加」ボタンをクリックすると行が追加されるようにする。
▼完成形のイメージ
①要素が選択されていない場合は下に追加
↓ 1行追加
②要素が選択されている場合はその行の下に追加 ※複数選択の場合は最初に選択した要素を対象とする。
↓ 一番上の要素を軸に下に2行追加。
選択中のセルがシフトするようにする。
##考え方
- クリックイベントを検知
- 選択中の要素がある場合はその行番号を取得(ない場合は一番下に追加)
- 選択した行とそれ以降で分割し、間に新しい行を追加してマージ
- 選択中の要素の行番号を必要に応じて一つ下にずらす。
##作成方法
テーブルの作成方法についてはこちら
以下では追加処理についてのみ記述。
##ボタンの作成
以下のようなボタンを作成。クリックイベントを設定する。
<button
@click="addRowAfter"
>下に行を追加
</button>
@click="addRowAfter"
ボタンをクリックすると、addRowAfterメソッドが発火する。
<style lang="scss" scoped>
table{
width: 80%;
th,td{
border: thin solid rgba(0, 0, 0, 0.12);
text-align: center;
color: gray;
}
th{
background: #ccc;
}
th, td{
//選択状態
&.is-active{
border: 1px double #0098f7;
}
}
}
button{
background: lightblue;
padding: 5px 20px;
color: white;
border-radius: 50px;
}
</style>
##行追加メソッド
addRowAfterメソッド
の中身を作成する。
addRowAfter(){
//現在選択中のセルの行番号を取得(複数選択の場合は最初の行)
let addRowIndex = this.rows.length
if(this.currentCells[0] != null){
addRowIndex = this.currentCells[0].xxxrowIndex
}
this.rows = [
...this.rows.slice(0, addRowIndex+1), //自分も含めるため+1
...[this.createTr()],
...this.rows.slice(addRowIndex +1)
]
//選択中の要素をずらす
for (let currentCell of this.currentCells){
if (currentCell.xxxrowIndex > addRowIndex){
currentCell.xxxrowIndex += 1
}
}
},
###行の判定 まずは、どこに行を追加するかを判定するため、addRowIndexという変数を設ける。
let addRowIndex = this.rows.length
if(this.currentCells[0] != null){
addRowIndex = this.currentCells[0].xxxrowIndex
}
・let addRowIndex = this.rows.length
rowsには表の情報が入っている、中の要素は行ごとに入っているため、lengthで行の数を取得。
何も選択されていない時は、この数値を基準にすることで、表の一番下に行を追加していく。
rows: [
{
"table_cells": [
{"cell_type": "TD"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
{
"table_cells": [
{"cell_type": "TD"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
]
**▼選択中の要素がある場合** 選択した要素の0番目の行番号を取得する。
if(this.currentCells[0] != null){
addRowIndex = this.currentCells[0].xxxrowIndex
}
・this.currentCells[0] != null
currentCellsは現在選択中のセルの行番号とセル番号が入っている。
値がある(空でない)なら変数addRowIndexに行番号を代入する。
currentCells : [
{ "xxxrowIndex": 1, "xxxcellIndex": 1 },
{ "xxxrowIndex": 0, "xxxcellIndex": 1 }
]
###行の追加 sliceメソッドとスプレッド構文を利用して、選択した行の下に新たな行を追加する。
this.rows = [
...this.rows.slice(0, addRowIndex+1), //自分も含めるため+1
...[this.createTr()],
...this.rows.slice(addRowIndex +1)
]
createTr()
は、現在の表の状態に合わせて作成した行要素。このメソッドは別で定義している(後述)。
###選択中の要素をずらす 指定した行より下の行が選択中の場合、選択中のセルを下にズラす必要がある。
for (let currentCell of this.currentCells){
if (currentCell.xxxrowIndex > addRowIndex){
currentCell.xxxrowIndex += 1
}
}
もし、追加した行より下なら、各要素の行番号に1を足していく。
##追加するための行の作成 間に追加する行は現在の表セル数に合わせる必要がある。
クリックイベントが発生した時点で、表の状態から追加用の行要素を作成する。
createTr(){
const maxCellNum = this.getMaxCellNum()
let tds = [] //作成した行を格納する
for(var i = 0; i < maxCellNum; i++){
tds = [...tds, {cell_type:'TD'}]
}
return {
table_cells: tds
}
},
・const maxCellNum = this.getMaxCellNum()
現在のセルの最大数をmaxCellNumに代入する。
最大のセル数を取得するgetMaxCellNumについては後述する。
追加用の行として変数tdsを用意し、最大のセル数分tdタグになる要素{cell_type:'TD'}
を格納する。
{
"table_cells": [
{"cell_type": "TD"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
}
##行内の要素(セル数)の最大値を取得 現在の表の最大のセル数を取得するメソッドを作成する。
指定した配列の要素を一つづつ取得し、最終的に一つの処理結果を返すreduce
メソッドを使う。
・arr.reduce({(accumulater, currentValue) => 処理}, 初期値)
getMaxCellNum(){
return this.rows.reduce((acc, tr) => {
if (acc < tr.table_cells.length){
return tr.table_cells.length
}else{
return acc
}
}, 0)
}
reduceメソッドの詳細についてはこちら
以上の処理で狙った通りに行が追加できる。
##フルコード
<template>
<div>
<button
@click="addRowAfter"
>下に行を追加
</button>
</p>
<table>
<template v-for="(tr, rowIndex) in rows">
<tr :key="rowIndex">
<td :key="cellIndex"
:class="{'is-active': isActive(rowIndex, cellIndex)}"
@click="clickCell($event)">
( {{rowIndex}} , {{cellIndex}} )
</td>
</template>
</tr>
</template>
</table>
</div>
</template>
<script>
export default {
data(){
return{
currentCells:[],
rows: [
{
"table_cells": [
{"cell_type": "TD"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
{
"table_cells": [
{"cell_type": "TD"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
]
}
},
methods:{
//isActiveの判定
//currentCellsの中にあればtrueにする
//指定した行列番号の要素がある=数値が-1以外ならtrueにする。
isActive(rowIndex, cellIndex){
return this.currentCells.findIndex((elem) =>
elem.xxxrowIndex == rowIndex && elem.xxxcellIndex == cellIndex
) > -1
},
clickCell(event){
//クリックされたセルの情報
const cell = event.target
const tr = event.target.parentNode
//クリックされたセルが既に選択されている場合は、配列から削除する
if(this.isActive(tr.rowIndex, cell.cellIndex)){
//選択中の配列の何番目の要素かを求める
const rmIndex = this.currentCells.findIndex((elem)=>
elem.xxxrowIndex == tr.rowIndex && elem.xxxcellIndex == cell.cellIndex
)
//選択した要素を選択中の配列から削除する
this.currentCells = [
...this.currentCells.slice(0, rmIndex),
...this.currentCells.slice(rmIndex + 1)
]
} else{
this.currentCells = [
...this.currentCells,
{
xxxrowIndex: tr.rowIndex,
xxxcellIndex: cell.cellIndex
}
]
}
},
addRowAfter(){
//現在選択中のセルの行番号を取得(複数選択の場合は最初の行)
let addRowIndex = this.rows.length
if(this.currentCells[0] != null){
addRowIndex = this.currentCells[0].xxxrowIndex
}
this.rows = [
...this.rows.slice(0, addRowIndex+1), //自分も含めるため+1
...[this.createTr()],
...this.rows.slice(addRowIndex +1)
]
//選択中の要素をずらす
for (let currentCell of this.currentCells){
if (currentCell.xxxrowIndex > addRowIndex){
currentCell.xxxrowIndex += 1
}
}
},
//追加するための行を作成する
createTr(){
const maxCellNum = this.getMaxCellNum()
let tds = [] //作成した行を格納する
for(var i = 0; i < maxCellNum; i++){
tds = [...tds, {cell_type:'TD'}]
}
return {
table_cells: tds
}
},
//行内の要素(セル数)の最大値を取得する(行作成用)
getMaxCellNum(){
return this.rows.reduce((acc, tr) => {
if (acc < tr.table_cells.length){
return tr.table_cells.length
}else{
return acc
}
}, 0)
}
}
}
</script>
<style lang="scss" scoped>
table{
width: 80%;
th,td{
border: thin solid rgba(0, 0, 0, 0.12);
text-align: center;
color: gray;
}
th{
background: #ccc;
}
th, td{
//選択状態
&.is-active{
border: 1px double #0098f7;
}
}
}
button{
background: lightblue;
padding: 5px 20px;
color: white;
border-radius: 50px;
}
</style>
作成するセルの値に( {{rowIndex}} , {{cellIndex}} )
を設定することで、セルの行列番号を表示することができる。
##上に行を追加する。 下に行を追加したのと同じ流れで、上に行を追加する処理を追加する。
下に行を追加する処理と異なるのは3点。
- 現在選択中のセルがない場合は、一番上に行を追加する。(下の場合は一番下に追加した)
- 既存の行を分割する際に、基準となる行のより前と、基準となる行を含んだ後ろ部分で分断する
- 選択中のセルをズラす際に、基準となる行が変わる
###1. 現在選択中のセルがない場合は、一番上に行を追加する。(下
let addRowIndex = 0
if(this.currentCells.length != 0){
addRowIndex = this.currentCells[0].xxxrowIndex
}
let addRowIndex = 0
基準とする行番号をデフォルトで0にする。
###2. 既存の行を分割する際に、基準となる行のより前と、基準となる行を含んだ後ろ部分で分断する
this.rows = [
...this.rows.slice(0, addRowIndex),
...[this.createTr()],
...this.rows.slice(addRowIndex)
]
###3. 選択中のセルをズラす際に、基準となる行が変わる
//選択中の要素をずらす
for (let currentCell of this.currentCells){
if (currentCell.xxxrowIndex >= addRowIndex){
currentCell.xxxrowIndex += 1
}
}
},
if (currentCell.xxxrowIndex >= addRowIndex){}
符号が、「>」から「>=」に変更となる。
###実装結果
↓ 上に2行を追加する
###フルコード
<template>
<div>
<p>〜TmpAddRow.vue〜</p>
<p>currentCells : {{currentCells}}</p>
<p>
<button
@click="addRowAfter"
>下に行を追加
</button>
<button
@click="addRowBefore"
>上に行を追加
</button>
</p>
<table>
<template v-for="(tr, rowIndex) in rows">
<tr :key="rowIndex">
<template v-for="(cell, cellIndex) in tr.table_cells">
<th :key="cellIndex"
v-if="cell.cell_type == 'TH'"
:class="{'is-active': isActive(rowIndex, cellIndex)}"
@click="clickCell($event)">
( {{rowIndex}} , {{cellIndex}} )
</th>
<td :key="cellIndex"
v-else-if="cell.cell_type == 'TD'"
:class="{'is-active': isActive(rowIndex, cellIndex)}"
@click="clickCell($event)">
( {{rowIndex}} , {{cellIndex}} )
</td>
</template>
</tr>
</template>
</table>
</div>
</template>
<script>
export default {
data(){
return{
currentCells:[],
rows: [
{
"table_cells": [
{"cell_type": "TD"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
{
"table_cells": [
{"cell_type": "TD"},
{"cell_type": "TD"},
{"cell_type": "TD"},
]
},
]
}
},
methods:{
//isActiveの判定
//currentCellsの中にあればtrueにする
//指定した行列番号の要素がある=数値が-1以外ならtrueにする。
isActive(rowIndex, cellIndex){
return this.currentCells.findIndex((elem) =>
elem.xxxrowIndex == rowIndex && elem.xxxcellIndex == cellIndex
) > -1
},
clickCell(event){
//クリックされたセルの情報
const cell = event.target
const tr = event.target.parentNode
//クリックされたセルが既に選択されている場合は、配列から削除する
if(this.isActive(tr.rowIndex, cell.cellIndex)){
//選択中の配列の何番目の要素かを求める
const rmIndex = this.currentCells.findIndex((elem)=>
elem.xxxrowIndex == tr.rowIndex && elem.xxxcellIndex == cell.cellIndex
)
//選択した要素を選択中の配列から削除する
this.currentCells = [
...this.currentCells.slice(0, rmIndex),
...this.currentCells.slice(rmIndex + 1)
]
} else{
this.currentCells = [
...this.currentCells,
{
xxxrowIndex: tr.rowIndex,
xxxcellIndex: cell.cellIndex
}
]
}
},
addRowAfter(){
//現在選択中のセルの行番号を取得(複数選択の場合は最初の行)
let addRowIndex = this.rows.length
if(this.currentCells.length != 0){
addRowIndex = this.currentCells[0].xxxrowIndex
}
this.rows = [
...this.rows.slice(0, addRowIndex+1), //自分も含めるため+1
...[this.createTr()],
...this.rows.slice(addRowIndex +1)
]
//選択中の要素をずらす
for (let currentCell of this.currentCells){
if (currentCell.xxxrowIndex > addRowIndex){
currentCell.xxxrowIndex += 1
}
}
},
addRowBefore(){
//現在選択中のセルの行番号を取得(複数選択の場合は最初の行)
//選択なしの場合は一番上を基準とする
let addRowIndex = 0
if(this.currentCells.length != 0){
addRowIndex = this.currentCells[0].xxxrowIndex
}
this.rows = [
...this.rows.slice(0, addRowIndex),
...[this.createTr()],
...this.rows.slice(addRowIndex)
]
//選択中の要素をずらす
for (let currentCell of this.currentCells){
if (currentCell.xxxrowIndex >= addRowIndex){
currentCell.xxxrowIndex += 1
}
}
},
//追加するための行を作成する
createTr(){
const maxCellNum = this.getMaxCellNum()
let tds = [] //作成した行を格納する
for(var i = 0; i < maxCellNum; i++){
tds = [...tds, {cell_type:'TD'}]
}
return {
table_cells: tds
}
},
//行内の要素(セル数)の最大値を取得する(行作成用)
getMaxCellNum(){
return this.rows.reduce((acc, tr) => {
if (acc < tr.table_cells.length){
return tr.table_cells.length
}else{
return acc
}
}, 0)
}
}
}
</script>
<style lang="scss" scoped>
table{
width: 80%;
th,td{
border: thin solid rgba(0, 0, 0, 0.12);
text-align: center;
color: gray;
}
th{
background: #ccc;
}
th, td{
//選択状態
&.is-active{
border: 1px double #0098f7;
}
}
}
button{
background: lightblue;
padding: 5px 20px;
color: white;
border-radius: 50px;
}
</style>
コードを眺めると複雑そうで引くけど、一つづつのパーツはシンプル。シンプルな物を重ねていくと思っていた以上に簡単にできる。
##(追記)行の追加処理をもっと簡単に書く方法
sliceメソッドとスプレッド構文で要素を追加する処理は、spliceメソッドを使うことでかなり簡単に書ける。
###spliceとは?
spliceとは配列への要素追加で何かと便利なメソッド。
指定した位置に追加するだけでなく、その追加位置以降の要素を削除することもできる。
・arr.splice(挿入番号, 削除する要素数, 挿入する要素)
**▼spliceでできること**
-
指定した要素を追加する。
┗arr.splice(挿入番号, 0, 挿入する要素)
┗ 削除する要素を0とすることで、要素追加のみに止める。 -
指定した要素で置き換える。
┗arr.splice(挿入番号, 1, 挿入する要素)
┗ 元々挿入した場所にあったセルを削除する
削除する要素数を増やせば、複数個の要素と置き換えることができる。
###1. 指定した要素を追加する
a = [1,2,3,4]
a.splice(2,0,"aaa")
console.log(a)
//出力
[1, 2, "aaa", 3, 4]
a.splice(2,0,"aaa")
配列aの配列番号2に、aaaという要素を追加。
###2. 指定した要素で置き換える
a = [1,2,3,4]
a.splice(2,1,"aaa")
console.log(a)
//出力
[1, 2, "aaa", 4]
a.splice(2,1,"aaa")
配列番号2にaaaを挿入し、後ろの要素を一つ削除。
###3. 指定した要素と複数の要素を置き換える
a = [1,2,3,4,5,6]
a.splice(1,4,"aaa")
console.log(a)
//出力
[1, "aaa", 6]
a.splice(1,4,"aaa")
配列番号1にaaaを挿入して、後ろの要素を4つ削除する。
##spliceで行追加処理をかく
以上のspliceを応用すると、行追加が簡単になる。
this.rows.splice(addRowIndex, 0, this.createTr())
かなりシンプルな記述になった。
**▼(参考)spliceを使わない場合(sliceとスプレッド構文**
this.rows = [
...this.rows.slice(0, addRowIndex),
...[this.createTr()],
...this.rows.slice(addRowIndex)
]