【Vue】table(表)を結合する方法。rowspanとcolspanをv-bindで付与（基本編）

v-forで作成している表のセルを結合する方法。
本格的な表操作ではなく、初歩的なrowspanとcolspanの動作を確認。


#やりたいこと
colspan, rowspan用に割り振られた数値をtd, thタグの属性として読み取りセルを結合する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/381ba8c3-7c2c-51f6-99e4-1d3db9666c82.png)

↓　結合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/49200d23-ada7-d070-3dea-d2682185b604.png)




##rowspanとcolspanについて（注意点）
rowspanとcolspanはth、tdタグの属性。数値を指定することで指定した分だけセルを伸ばす。

excelやスプシを使っていると２つのセルを結合しているイメージが強いが、実際の操作は**セルを縦方向／横方向に伸ばしている**。

・`<td rowspan="整数"></td>`
・`<td colspan="整数"></td>`


<br>
**▼colspanの場合**
例えば、以下の表の左上セル(0,0)に`colspan="2"`をつけると列が伸びて、後ろがはみ出す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/381ba8c3-7c2c-51f6-99e4-1d3db9666c82.png)

↓　colspan="2"

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/f40903dc-31ea-e0ec-c636-d10198bd751b.png)


<br>
**▼rowspanの場合**
rowspanも同じで、結合した分した分下段の行が右にズレる。セル(0,0) rowspan="3"
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/0096f2cc-fad6-a79f-4f0a-f22f8b07dad3.png)


このため、セルを伸ばした後（結合）後も表を正方形に保ちたい場合は、**不要なセルを削除していく必要がある**。

上記の例だと、(1,4)と(2,4)が不要。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/1bd22661-043e-c7bc-4e96-8039fc3a8483.png)

1行目のみセル数が５つ、2行目と3行目のセル数は４つとなる。





##v-forで結合した表を作る方法

1. 一行の構成
2. v-forによるtd, thタグ分岐
3. rowspan, colspanプロパティに合わせて属性を付与
4. セルにrowspan, colspanプロパティ付与
5. 余計なセルを削除

まず、表のベースとなる配列は以下となる。
一つの

###1. 一行の構成

```js:1行分の要素の例
      rows: [
        {
          "table_cells": [
            {"cell_type": "TH", "rowspan":3},
            {"cell_type": "TD", "colspan":2},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        }
      ]
```

一つの行要素の中に、celltypeを用意。これがTHの時はthタグに、TDのときはtdタグを作成する。

次に、rowspanやcolspan属性となる、rowspan, colspanプロパティを必要に応じて設置。

このプロパティ名と値を読み取り、タグ属性として付与する。

この行要素を重ねた分だけ、表に行が追加される。



<br>
###2. v-forによるtd, thタグ分岐

```vue:
    <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'"
                >
              ( {{rowIndex}} , {{cellIndex}} )
            </th>
          
            <td :key="cellIndex" 
                v-else-if="cell.cell_type == 'TD'"
                >
              ( {{rowIndex}} , {{cellIndex}} )
            </td>
          </template>
        </tr>
      </template>
    </table>
```

(1)行要素の入った配列rowsをv-forで回す。
templateタグ内でv-forを使った場合は、配下の実際のタグとなる要素に:keyを設定する必要がある。

(2)各行要素のセルをv-forで取り出す。
2回目のv-for。ここで各セル要素を変数cellとして取り出し、cell_typeプロパティの値に合わせてthタグか、tdタグかをフリわける。

使うのは`v-if`と`v-else-if`



<br>
###3. rowspan, colspanプロパティに合わせて属性を付与
rowspanやcolspanプロパティをタグの属性に紐づける。classに対してv-bindを使う。

・`:class名="値"`

class名は設置したいものをつける。（※ここがデータバインディングするわけではない）

データバインディングするのは値。値にvueの書き方でプロパティの中のデータを呼び出す。

・`オブジェクト名.プロパティ名`


```vue:実例
<td :key="cellIndex" 
     v-else-if="cell.cell_type == 'TD'"         
     :colspan="cell.colspan || 1"
     :rowspan="cell.rowspan || 1">
```

パイプ２本は、cell.colspanの値がない場合に代わりに表示する値。


<br>
###4. セルにrowspan, colspanプロパティ付与
プロパティに合わせて、出し分ける用意ができたので、セル要素に対してcolspan, rowspanを付与する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/81c41829-e54f-1487-336d-122557cf006c.png)

上図になるよう各セルにプロパティを付与。

```js:rowspan,colspanプロパティを付与
      rows: [
        {
          "table_cells": [
            {"cell_type": "TD", "rowspan":2},
            {"cell_type": "TD", "colspan":2},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD", "rowspan":2, "colspan":2},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
      ]
```


<br>
##5. 余計なセルを削除
最後に余計なセルを削除する。

```js:セルの削除
      rows: [
        {
          "table_cells": [
            {"cell_type": "TD", "rowspan":2},
            {"cell_type": "TD", "colspan":2},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            // {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD", "rowspan":2, "colspan":2},
            // {"cell_type": "TD"},
            // {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            // {"cell_type": "TD"},
            // {"cell_type": "TD"},
          ]
        },
      ]
```

以上で目的とした表が完成する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/3abee947-4009-59da-81b5-5482a14985e0.png)



<br>
rowspan, colspanの操作はわかりやすいが、結合・解除に合わせてセルを増やしたり減らしたりするのはややこしそう。


<br>
##フルコード

```vue:
<template>
    <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)}"
                :colspan="cell.colspan || 1"
                :rowspan="cell.rowspan || 1"
                @click="clickCell($event)">
              ( {{rowIndex}} , {{cellIndex}} )
            </th>
          
            <td :key="cellIndex" 
                v-else-if="cell.cell_type == 'TD'"
                :class="{'is-active': isActive(rowIndex, cellIndex)}"
                :colspan="cell.colspan || 1"
                :rowspan="cell.rowspan || 1"
                @click="clickCell($event)">
              ( {{rowIndex}} , {{cellIndex}} )
            </td>
          </template>
        </tr>
      </template>
    </table>
</template>

<script>
export default {
  data(){
    return{
      currentCells:[],
      rows: [
        {
          "table_cells": [
            {"cell_type": "TD", "rowspan":2},
            {"cell_type": "TD", "colspan":2},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            // {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD", "rowspan":2, "colspan":2},
            // {"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
          }
        ]
      }
    },
  }
}
</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>
```