LoginSignup
0
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-10-29

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

作成した表をユーザー操作できるようにする。まずは手始めに「下に行を追加」ボタンをクリックすると行が追加されるようにする。

▼完成形のイメージ

①要素が選択されていない場合は下に追加

image.png

↓ 1行追加

image.png



②要素が選択されている場合はその行の下に追加
※複数選択の場合は最初に選択した要素を対象とする。

image.png

↓ 一番上の要素を軸に下に2行追加。

image.png

選択中のセルがシフトするようにする。


考え方

  1. クリックイベントを検知
  2. 選択中の要素がある場合はその行番号を取得(ない場合は一番下に追加)
  3. 選択した行とそれ以降で分割し、間に新しい行を追加してマージ
  4. 選択中の要素の行番号を必要に応じて一つ下にずらす。

作成方法

テーブルの作成方法についてはこちら

以下では追加処理についてのみ記述。

ボタンの作成

以下のようなボタンを作成。クリックイベントを設定する。

image.png

templateタグ内
      <button
        @click="addRowAfter"
      >下に行を追加
      </button>

@click="addRowAfter"
ボタンをクリックすると、addRowAfterメソッドが発火する。

styleの設定
<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
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の内容例
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の内容例
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'}を格納する。

tdsの例(セル3つの場合)
        {
          "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}} )を設定することで、セルの行列番号を表示することができる。

image.png


上に行を追加する。

下に行を追加したのと同じ流れで、上に行を追加する処理を追加する。

下に行を追加する処理と異なるのは3点。

  1. 現在選択中のセルがない場合は、一番上に行を追加する。(下の場合は一番下に追加した)
  2. 既存の行を分割する際に、基準となる行のより前と、基準となる行を含んだ後ろ部分で分断する
  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){}
符号が、「>」から「>=」に変更となる。


実装結果

image.png

↓ 上に2行を追加する

image.png

フルコード

<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でできること

  1. 指定した要素を追加する。
     ┗ arr.splice(挿入番号, 0, 挿入する要素)
     ┗ 削除する要素を0とすることで、要素追加のみに止める。

  2. 指定した要素で置き換える。
     ┗ 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を応用すると、行追加が簡単になる。

spliceを使用
this.rows.splice(addRowIndex, 0, this.createTr())

かなりシンプルな記述になった。



▼(参考)spliceを使わない場合(sliceとスプレッド構文

spliceを使わない場合(sliceとスプレッド構文)
      this.rows = [
        ...this.rows.slice(0, addRowIndex), 
        ...[this.createTr()],
        ...this.rows.slice(addRowIndex)
      ]
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0