【Vue】テーブル操作エキスパートへの道。ボタンクリックで行を削除する方法

ボタンクリックで表から行を削除する方法について。


これまで、

・[v-forを使ったテーブルの作成](https://qiita.com/yuta-38/items/88ae6036e95f70d7d708)
・[表で複数選択を可能にする方法](https://qiita.com/yuta-38/items/93807929f83991045ee5)
・[ボタンクリックで行を追加する方法](https://qiita.com/yuta-38/items/d0f646c876b0cdabe0e7)
・[ボタンクリックで列を追加する方法](https://qiita.com/yuta-38/items/fa74e603927115302e1b)

について確認したので、次のステップとして行を削除する方法を確認する。


##アウトプットイメージ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/a3fff73c-87cc-52c6-d56b-cf06de101051.png)

↓ 選択している行のみを削除

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/90cb15ea-c642-bad2-483c-afbfff4db521.png)


##行削除の考え方
複数選択時は、選択した行をすべて削除する。
考え方は比較的シンプル

- 選択中の行番号を抽出する（重複削除）
    - Set, mapメソッド
- 抽出した行番号以外の表を作る
    - filter, hasメソッド


##行削除実例

```js:行削除のメソッド
removeRow(){
      //選択中のセルがある場合のみ処理を実行
      if(this.currentCells.length != 0){

        //削除したいセル番号を抽出(重複削除)
        const rowIndicesToRemove = new Set(
          this.currentCells.map(cell => cell.xxxrowIndex )
        )

        //削除したい列を除外（該当した行番号はスルー）
        this.rows = this.rows.filter((row, rowIndex)=>
          !rowIndicesToRemove.has(rowIndex)
        )

        //選択中のセルを解除
        this.currentCells = []
      }
    }
``` 

<br>
####・選択中のセルがある場合のみ処理を実行
`if(this.currentCells.length != 0)`
現在選択中のセルがない場合は何もしない。


####・選択中のセルの行番号を抜き出す
Setオブジェクトを使う。

Setオブジェクトとは、重複がない特別なオブジェクト。配列のように並び順もない。袋の中に個々の値が入っているイメージ。

```js:
const rowIndicesToRemove = new Set(
          this.currentCells.map(cell => cell.xxxrowIndex )
        )
```

`new Set()`でSetオブジェクトを生み出す。

`this.currentCells.map(cell => cell.xxxrowIndex)`
選択中のセルの行番号を一つづつ取得し配列にする。

この処理を実行することで下記のようなイメージになる。


Setオブジェクトの詳しい使い方は[こちら](https://qiita.com/yuta-38/items/b737e486c688c4850846)


####・指定した要素以外を抜き出す

```js:
        this.rows = this.rows.filter((row, rowIndex)=>
          !rowIndicesToRemove.has(rowIndex)
        )
```

filterメソッドはtrueになった場合のみ要素を残す。第二引数はインデックス番号になる。

`rowIndicesToRemove.has(rowIndex)`
現在選択中の行番号が入ったSetオブジェクトrowIndeicesToRemoveに対し、hasメソッドで一致する値があった場合にtrueを返す。

`has`メソッドはSet専用のメソッド。指定した値があるとtrue,該当しない場合はfalseを返す。

Arrayオブジェクトの`indexOf`や`include`メソッドと似た役割。


`!`をつけることで、一致しない場合のみtrueとすることで、残したい列のみを残す。


<br>
##フルコード

```vue:
<template>
  <div>
    <p>〜TmpRemoveRow.vue〜</p>
    <p>currentCells : {{currentCells}}</p>
    

    <p>
      <button
        @click="removeRow"
      >行を削除
      </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"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
                {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"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
          }
        ]
      }
    },
    removeRow(){
      //選択中のセルがある場合のみ処理を実行
      if(this.currentCells.length != 0){

        //削除したいセル番号を抽出(重複削除)
        const rowIndicesToRemove = new Set(
          this.currentCells.map(cell => cell.xxxrowIndex )
        )

        //削除したい列を除外（該当した行番号はスルー）
        this.rows = this.rows.filter((row, rowIndex)=>
          !rowIndicesToRemove.has(rowIndex)
        )

        //選択中のセルを解除
        this.currentCells = []
      }
    }
  }
}
</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: gray;
   padding: 5px 20px;
   color: white;
   border-radius: 50px;
}
input{
  margin: 7px;
  box-sizing: border-box;
}
</style>
```


コード自体はすごく簡単。Set, has, map, filterを使えるかが鍵となる。