【Vue】ドラッグで選択できるようにする方法。（結合ありのテーブルセルをドラッグで選択する）

結合セルがあるテーブルの各セルをドラッグで選択できるようにする。

スプシと同じように、選択範囲の中に結合したセルがあれば、そのサイズに合わせて選択範囲を拡張し、常に範囲が四角になるようにする。

![mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/06aecf2c-5ce8-6fdd-edc6-820d15429222.gif)


##目次

1. [考え方](#考え方)
    2. [mouse系イベントの活用](#1-mouse系イベントの活用)
    3. [セルの列方向の位置を算出](#2-セルの列方向の位置を算出)
    4. [watchプロパティの活用](#3-watchプロパティの活用)
5. [実際のコード](#実際のコード)
    2. [mouse系イベントの活用](#mouse系イベントの活用)
    3. [セルの列方向の位置を算出](#セルの列方向の位置を算出)
    4. [watchプロパティの活用](#watchプロパティの活用)
5. [（参考）ドラッグ選択の処理を外部ファイルに移動](#参考ドラッグ選択の処理を外部ファイルに移動)


<br>
##考え方

###1. mouse系イベントの活用
ドラッグ選択を実現するためには、**mousedown, mouseup, mousemove**イベントを使用する。

| イベント | 内容 |　ここでの用途 |
|:-:|:--|:--|
| mousedown  | ポインティングデバイスのボタンが要素上で押されたときに発生  | 選択が開始したセルの情報を取得。開始フラグを立てる。  |
| mousemove  | 要素の上を移動したときに発生  | 現在選択している要素の情報を取得  |
| mouseup  | ポインティングデバイスのボタンが要素の上で離されたときに発生  | 終了フラグ  |

イベントは各セルに仕込むのではなく、tableタグに仕込む。（セルが大量の場合に余計なメモリ消費を防ぐため）


>ちなみに、よく目にするclickイベントはmousedownとmouseupを組み合わせて作られている。このため、mousedownなどのイベントを使えなくするとclickイベントも使えなくなる。


<br>
###2. セルの列方向の位置を算出
結合したセルを扱うため、セルの列番号と実際のセルの位置ズレが発生する。

このため、テーブルの状態をスキャンして、各セルの列方向の位置を求める。


<br>
###3. watchプロパティの活用
ドラッグに合わせて選択範囲を自動算出するため、Vue.jsのwatchプロパティを使う。

mousemoveイベントで選択中のセルが変化した場合に、watchで検知して、選択範囲を算出する。


<br>
**▼位置ズレの例**
例えば、(0, 0)位置のセルのcolspanが3の場合、(0, 1)セルの実際の位置は列番号3の位置となる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/97088841-eaee-18e9-4e71-2709fb8d0dff.png)



<br>
##実際のコード
##mouse系イベントの活用
vue.jsでイベントを仕込むのは超簡単。タグの中に以下を記述。

`@イベント名 = "メソッド名"`

```vue:vue.jsのtemplateタグ内
<table
      @mousedown="mouseDown"
      @mouseup="mouseUp"
      @mousemove="mouseMove"
      @click="clickCell"
>
```

個別に複数選択できるようにもしておくため`@click="clickCell"`も設置しておく。


<br>
各イベントの処理は以下。methodの中に記述

```js:
mouseDown(e){
      this.isDrag = true
      this.startCell = {
        rowIndex: e.target.parentNode.rowIndex,
        cellIndex: e.target.cellIndex,
        rowspan: e.target.rowSpan,
        colspan: e.target.colSpan
      }
    },
    mouseUp(e){
      this.isDrag = false
    },
    mouseMove(e){
      if(this.isDrag == true && e.target.tagName != "TABLE"){
        this.endCell = {
          rowIndex: e.target.parentNode.rowIndex,
          cellIndex: e.target.cellIndex,
          rowspan: e.target.rowSpan,
          colspan: e.target.colSpan
        } 
      }
    }
```

引数のeにはイベントが発生した情報が入っている。
`e.target.cellIndex`のようにして、欲しい情報を抜き出す。


`isDrag`はドラッグ中を示すフラグ。このフラグがtrueの間のみ`mousemove`でデータを取得し続ける。

mousemoveの条件式の`e.target.tagName != "TABLE"`は、ポインターの場所によって、targetがtdタグではなくtableタグを指してしまうことがあり、その場合を除外するため。



<br>
イベント内で使う全体共通の変数を3つ追加したため、これをdataプロパティに追加する。

```js:dataプロパティ
data(){
    return{
      略
      // Drag用
      startCell:[],
      endCell:[],
      isDrag: false,
}
```

以上でマウスのイベントを検知して必要なデータを取得する処理が完了。


<br>
##セルの列方向の位置を算出
watchプロパティの中で使用するテーブルのセルの実際の列方向位置を算出するプログラムを作成する。

テーブルのデータを渡すと、各セルがどこの列位置になっているかを返す。

外部にjsファイルを作成し、後からvueにimportする。


```js:scantable.js
const scanTable = (rows) => 
  {
      const arr = [];
      const colIndices = []
      for(var y=0; y < rows.length; y++)
      {
          var row = rows[y]
      
          for(var x=0;x<row.table_cells.length;x++)
          {
              var cell = row.table_cells[x], xx = x, tx, ty;
              
              for(;arr[y] && arr[y][xx]; ++xx);

              for(tx = xx; tx < xx + (cell.colspan || 1); ++tx) {
                  for(ty = y; ty < y + (cell.rowspan || 1); ++ty)
                  {
                      if( !arr[ty] ) arr[ty] = []          
                      arr[ty][tx] = 1
                      
                      if ( !colIndices[y] ) colIndices[y] = []
                      colIndices[y][x] = xx
                  }
              }

          }
      }
      return colIndices
  }

export { scanTable }
```

引数で配列情報を渡す（変数rowsに格納）と、処理結果を`colIndices`という変数で返す。

<br>
**▼実際の処理の例**

```js:引数で渡すテーブル
      rows= [
        {
          "table_cells": [
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 2,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 1,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 3,
            }
          ]
        },
        {
          "table_cells": [
            {
              "cell_type": "TD",
              "rowspan": 2,
              "colspan": 1,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 1,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 2,
            }
          ]
        },
                {
          "table_cells": [
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 1,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 1,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 1,
            }
          ]
        },
      ]
```

上記は以下図の元データ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/68e40eba-b51e-fc13-b610-a144519ea700.png)

これに作成したscanTableを実行すると、以下となる。

```js:処理結果
scanTable(rows)

//出力結果
[0, 2, 3]
[0, 1, 2]
[1, 2, 3]
```

<br>
##watchプロパティの活用
watchプロパティを使って、マウスのドラッグがあった場合に自動で選択範囲を算出する処理を作る。

まずは基点となるセル（rowIndexとcellIndex）を求める。
ドラッグは左上から右下のみでなく、右下から左上にいくパターンも考えられる。

```js:watchプロパティ
     // 基点となるrowIndexとcolIndex
      let startRowIndex = this.startCell.rowIndex
      if ( startRowIndex > this.endCell.rowIndex ){
        startRowIndex = this.endCell.rowIndex
      }

      let startColIndex = this.colIndicies[this.startCell.rowIndex][this.startCell.cellIndex]
      if ( startColIndex > this.colIndicies[this.endCell.rowIndex][this.endCell.cellIndex] ){
        startColIndex = this.colIndicies[this.endCell.rowIndex][this.endCell.cellIndex]
      }

      let endRowIndex = this.startCell.rowIndex + this.startCell.rowspan - 1 
      if ( endRowIndex < this.endCell.rowIndex + this.endCell.rowspan - 1  ){
        endRowIndex = this.endCell.rowIndex + this.endCell.rowspan - 1
      }

      let endColIndex = this.colIndicies[this.startCell.rowIndex][this.startCell.cellIndex] + this.startCell.colspan - 1 
      if ( endColIndex < this.colIndicies[this.endCell.rowIndex][this.endCell.cellIndex] + this.endCell.colspan - 1 ){
        endColIndex = this.colIndicies[this.endCell.rowIndex][this.endCell.cellIndex] + this.endCell.colspan - 1
      }
```

上記処理で、`startRowIndex`、`endRowIndex`、`startColIndex`、`endColIndex`の４つのデータを作成。


####セルを選択する
対象となるセルを算出し、選択中の配列（currentCells）に格納する処理。

```js:
      //対象セルの抽出
      const dragSelectedCells = () => {
        for( let i = 0; i <= endRowIndex; i++ ){
          for( let j = 0; j < this.rows[i].table_cells.length; j++ ){            
            //範囲拡張チェック
            //colIndexの延長処理
            if( i <= startRowIndex && startRowIndex <= i + this.rows[i].table_cells[j].rowspan - 1
                || i <= startRowIndex && startRowIndex <= i + this.rows[i].table_cells[j].rowspan - 1 
                || startRowIndex <= i && i + this.rows[i].table_cells[j].rowspan - 1 <= endRowIndex){
                  if( this.colIndices[i][j] < startColIndex &&  startColIndex <= this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 ){
                    startColIndex = this.colIndices[i][j]
                    dragSelectedCells()
                  }

                  if( this.colIndices[i][j] <= endColIndex &&  endColIndex < this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 ){
                    endColIndex = this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1
                    dragSelectedCells()
                  }
            }

            //rowIndexの延長処理
            if( this.colIndices[i][j] <= startColIndex && startColIndex <= this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1
                || this.colIndices[i][j] <= endColIndex && endColIndex <= this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 
                || startColIndex <= this.colIndices[i][j] && this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 <= endColIndex ){
                  if( i < startRowIndex && startRowIndex <= (i + this.rows[i].table_cells[j].rowspan - 1) ){
                    startRowIndex = i
                    dragSelectedCells()
                  }

                  if( i <= endRowIndex && endRowIndex < (i + this.rows[i].table_cells[j].rowspan - 1) ){
                    endRowIndex = i + this.rows[i].table_cells[j].rowspan - 1
                    dragSelectedCells()
                  }
            }
            
            
            if( startRowIndex <= i && i <= endRowIndex
                && startColIndex <= this.colIndices[i][j] && this.colIndices[i][j] <= endColIndex ){
                  this.currentCells.push({
                    rowIndex: i,
                    cellIndex: j,
                    colIndex: this.colIndices[i][j],
                    rowspan: this.rows[i].table_cells[j].rowspan,
                    colspan: this.rows[i].table_cells[j].colspan,
                  })
            }
          }
        }
      }
      dragSelectedCells()
```

テーブルのセルが選択範囲に該当するかを一つづつチェックしていく。

colspanやrowspanが１以上（結合してあるセル）を含む場合は、再起的に`dragSelectedCells()`を実行することで、選択範囲を再計算する。

以上でドラッグ選択の記述が完了。


<br>
##フルコード

```vue:vue.js
<template>
  <div>
    <p>〜TmpAddRow.vue〜</p>
    <button @click="clear">選択解除</button>
    <br>

    <table
      @mousedown="mouseDown"
      @mouseup="mouseUp"
      @mousemove="mouseMove"
      @click="clickCell"
      >
      <template v-for="(tr, rowIndex) in rows">
        <tr :key="rowIndex">
          <template v-for="(cell, cellIndex) in tr.table_cells">
            <td :key="cellIndex" 
                :class="{'is-active': isActive(rowIndex, cellIndex)}"
                :rowspan="cell.rowspan || 1"
                :colspan="cell.colspan || 1"
                >
              ( {{rowIndex}} , {{cellIndex}} )
            </td>
          </template>
        </tr>
      </template>
    </table>

    <br>
    <p>currentCells : {{currentCells}}</p>

  </div>
</template>

<script>
import { scanTable } from "./scantable"

export default {
  data(){
    return{
      currentCells:[],
      // Drag
      startCell:[],
      endCell:[],
      isDrag: false,
      rows: [
        {
          "table_cells": [
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 2,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 1,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 3,
            }
          ]
        },
        {
          "table_cells": [
            {
              "cell_type": "TD",
              "rowspan": 2,
              "colspan": 1,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 1,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 2,
            }
          ]
        },
                {
          "table_cells": [
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 1,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 1,
            },
            {
              "cell_type": "TD",
              "rowspan": 1,
              "colspan": 1,
            }
          ]
        },
      ]
    }
  },
  methods:{
    //セルに選択状態付与
    isActive(rowIndex, cellIndex){
      return this.currentCells.findIndex((elem) =>
        elem.rowIndex == rowIndex && elem.cellIndex == 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.rowIndex == tr.rowIndex && elem.cellIndex == cell.cellIndex 
        )

        this.currentCells = [
          ...this.currentCells.slice(0, rmIndex),
          ...this.currentCells.slice(rmIndex + 1)
        ]

      } else{
        this.currentCells = [
          ...this.currentCells,
          {
            rowIndex: tr.rowIndex,
            cellIndex: cell.cellIndex
          }
        ]
      }
    },
    //行内の要素（セル数）の最大値を取得する（行作成用）
    getMaxCellNum(){
      return this.rows.reduce((acc, tr) => {
        if (acc < tr.table_cells.length){
          return tr.table_cells.length
        }else{
          return acc
        }
      }, 0)
    },
    clear(){
      this.currentCells = []
    },
    //ドラッグ選択
    mouseDown(e){
      console.log("mouseDown:",e)
      this.isDrag = true
      this.startCell = {
        rowIndex: e.target.parentNode.rowIndex,
        cellIndex: e.target.cellIndex,
        rowspan: e.target.rowSpan,
        colspan: e.target.colSpan
      }
      console.log("startcell:",this.startCell)
    },
    mouseUp(e){
      console.log("mouseUp:",e)
      this.isDrag = false
    },
    mouseMove(e){
      if(this.isDrag == true && e.target.tagName != "TABLE"){
        console.log("mouseMove:",e)
        this.endCell = {
          rowIndex: e.target.parentNode.rowIndex,
          cellIndex: e.target.cellIndex,
          rowspan: e.target.rowSpan,
          colspan: e.target.colSpan
        } 
        console.log("endCell:",this.endCell)
      }
    }
  },
  //dragによる選択範囲の算出
  computed:{
    colIndices(){
      return scanTable( this.rows )
    }
  },
  watch:{
    endCell(){
      this.currentCells = [this.startCell]

      // 基点となるrowとcol
      let startRowIndex = this.startCell.rowIndex
      if ( startRowIndex > this.endCell.rowIndex ){
        startRowIndex = this.endCell.rowIndex
      }

      let startColIndex = this.colIndices[this.startCell.rowIndex][this.startCell.cellIndex]
      if ( startColIndex > this.colIndices[this.endCell.rowIndex][this.endCell.cellIndex] ){
        startColIndex = this.colIndices[this.endCell.rowIndex][this.endCell.cellIndex]
      }

      let endRowIndex = this.startCell.rowIndex + this.startCell.rowspan - 1 
      if ( endRowIndex < this.endCell.rowIndex + this.endCell.rowspan - 1  ){
        endRowIndex = this.endCell.rowIndex + this.endCell.rowspan - 1
      }

      let endColIndex = this.colIndices[this.startCell.rowIndex][this.startCell.cellIndex] + this.startCell.colspan - 1 
      if ( endColIndex < this.colIndices[this.endCell.rowIndex][this.endCell.cellIndex] + this.endCell.colspan - 1 ){
        endColIndex = this.colIndices[this.endCell.rowIndex][this.endCell.cellIndex] + this.endCell.colspan - 1
      }
      
      //対象セルの抽出
      const dragSelectedCells = () => {
        for( let i = 0; i <= endRowIndex; i++ ){
          for( let j = 0; j < this.rows[i].table_cells.length; j++ ){            
            //範囲拡張チェック
            //colIndexの延長処理
            if( i <= startRowIndex && startRowIndex <= i + this.rows[i].table_cells[j].rowspan - 1
                || i <= startRowIndex && startRowIndex <= i + this.rows[i].table_cells[j].rowspan - 1 
                || startRowIndex <= i && i + this.rows[i].table_cells[j].rowspan - 1 <= endRowIndex){
                  if( this.colIndices[i][j] < startColIndex &&  startColIndex <= this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 ){
                    startColIndex = this.colIndices[i][j]
                    dragSelectedCells()
                  }

                  if( this.colIndices[i][j] <= endColIndex &&  endColIndex < this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 ){
                    endColIndex = this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1
                    dragSelectedCells()
                  }
            }

            //rowIndexの延長処理
            if( this.colIndices[i][j] <= startColIndex && startColIndex <= this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1
                || this.colIndices[i][j] <= endColIndex && endColIndex <= this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 
                || startColIndex <= this.colIndices[i][j] && this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 <= endColIndex ){
                  if( i < startRowIndex && startRowIndex <= (i + this.rows[i].table_cells[j].rowspan - 1) ){
                    startRowIndex = i
                    dragSelectedCells()
                  }

                  if( i <= endRowIndex && endRowIndex < (i + this.rows[i].table_cells[j].rowspan - 1) ){
                    endRowIndex = i + this.rows[i].table_cells[j].rowspan - 1
                    dragSelectedCells()
                  }
            }
            
            
            if( startRowIndex <= i && i <= endRowIndex
                && startColIndex <= this.colIndices[i][j] && this.colIndices[i][j] <= endColIndex ){
                  this.currentCells.push({
                    rowIndex: i,
                    cellIndex: j,
                    colIndex: this.colIndices[i][j],
                    rowspan: this.rows[i].table_cells[j].rowspan,
                    colspan: this.rows[i].table_cells[j].colspan,
                  })
            }
          }
        }
      }
      dragSelectedCells()
    }
  }
}
</script>

<style lang="scss" scoped>
table{
  width: 80%;
  user-select: none;
  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: lightcoral;
   padding: 5px 20px;
   color: white;
   border-radius: 50px;
}
</style>
```


<br>
**▼外部のjsファイル**

```js:scantable.js
const scanTable = (rows) => 
{
  const arr = [];
  const colIndices = []
  for(var y=0; y < rows.length; y++)
  {
      var row = rows[y]
  
      for(var x=0;x<row.table_cells.length;x++)
      {
          var cell = row.table_cells[x], xx = x, tx, ty;
          
          for(;arr[y] && arr[y][xx]; ++xx);

          for(tx = xx; tx < xx + (cell.colspan || 1); ++tx) {
              for(ty = y; ty < y + (cell.rowspan || 1); ++ty)
              {
                  if( !arr[ty] ) arr[ty] = []          
                  arr[ty][tx] = 1
                  
                  if ( !colIndices[y] ) colIndices[y] = []
                  colIndices[y][x] = xx
              }
          }

      }
  }
  return colIndices
}

export { scanTable }
```


<br>
<br>
##（参考）ドラッグ選択の処理を外部ファイルに移動
Vueファイルのwatchプロパティに記述している下記処理が長いので、こちらも外部ファイルに移動する。

ポイントは変数を**プロパティ名を指定して渡す**こと。

```js:.vue
      //対象セルの抽出
      const dragSelectedCells = () => {
        for( let i = 0; i <= endRowIndex; i++ ){
          for( let j = 0; j < this.rows[i].table_cells.length; j++ ){            
            //範囲拡張チェック
            //colIndexの延長処理
            if( i <= startRowIndex && startRowIndex <= i + this.rows[i].table_cells[j].rowspan - 1
                || i <= startRowIndex && startRowIndex <= i + this.rows[i].table_cells[j].rowspan - 1 
                || startRowIndex <= i && i + this.rows[i].table_cells[j].rowspan - 1 <= endRowIndex){
                  if( this.colIndices[i][j] < startColIndex &&  startColIndex <= this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 ){
                    startColIndex = this.colIndices[i][j]
                    dragSelectedCells()
                  }

                  if( this.colIndices[i][j] <= endColIndex &&  endColIndex < this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 ){
                    endColIndex = this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1
                    dragSelectedCells()
                  }
            }

            //rowIndexの延長処理
            if( this.colIndices[i][j] <= startColIndex && startColIndex <= this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1
                || this.colIndices[i][j] <= endColIndex && endColIndex <= this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 
                || startColIndex <= this.colIndices[i][j] && this.colIndices[i][j] + this.rows[i].table_cells[j].colspan - 1 <= endColIndex ){
                  if( i < startRowIndex && startRowIndex <= (i + this.rows[i].table_cells[j].rowspan - 1) ){
                    startRowIndex = i
                    dragSelectedCells()
                  }

                  if( i <= endRowIndex && endRowIndex < (i + this.rows[i].table_cells[j].rowspan - 1) ){
                    endRowIndex = i + this.rows[i].table_cells[j].rowspan - 1
                    dragSelectedCells()
                  }
            }
            
            
            if( startRowIndex <= i && i <= endRowIndex
                && startColIndex <= this.colIndices[i][j] && this.colIndices[i][j] <= endColIndex ){
                  this.currentCells.push({
                    rowIndex: i,
                    cellIndex: j,
                    colIndex: this.colIndices[i][j],
                    rowspan: this.rows[i].table_cells[j].rowspan,
                    colspan: this.rows[i].table_cells[j].colspan,
                  })
            }
          }
        }
      }
      dragSelectedCells()
```


###Vueファイル内の記述変更
上記記述を以下のように変更する。

```js:.vue
    this.currentCells = dragSelectedCells({
        startRowIndex: startRowIndex,
        endRowIndex: endRowIndex,
        startColIndex: startColIndex,
        endColIndex: endColIndex,
        rows: this.rows,
        colIndices: this.colIndices,
        currentCells: this.currentCells,
    })
```

渡す変数が多いのでごちゃごちゃしているが、記述量はシンプルになった。

また、外部から関数を読み込むのでimportに追記。

```js:vue
import { scanTable, dragSelectedCells } from "./scantable"
```


<br>
###jsファイルへの追記
関数を外部のjsファイルに移動する。


```js:.js
const dragSelectedCells = ({
  startRowIndex,
  endRowIndex,
  startColIndex,
  endColIndex,
  rows,
  colIndices,
  currentCells,
}) => {
  for( let i = 0; i <= endRowIndex; i++ ){
    for( let j = 0; j < rows[i].table_cells.length; j++ ){            
      //範囲拡張チェック
      //colIndexの延長処理
      if( i <= startRowIndex && startRowIndex <= i + rows[i].table_cells[j].rowspan - 1
          || i <= startRowIndex && startRowIndex <= i + rows[i].table_cells[j].rowspan - 1 
          || startRowIndex <= i && i + rows[i].table_cells[j].rowspan - 1 <= endRowIndex){
            if( colIndices[i][j] < startColIndex &&  startColIndex <= colIndices[i][j] + rows[i].table_cells[j].colspan - 1 ){
              startColIndex = colIndices[i][j]
              dragSelectedCells({
                startRowIndex,
                endRowIndex,
                startColIndex,
                endColIndex,
                rows,
                colIndices,
                currentCells,
              })
            }

            if( colIndices[i][j] <= endColIndex &&  endColIndex < colIndices[i][j] + rows[i].table_cells[j].colspan - 1 ){
              endColIndex = colIndices[i][j] + rows[i].table_cells[j].colspan - 1
              dragSelectedCells({
                startRowIndex,
                endRowIndex,
                startColIndex,
                endColIndex,
                rows,
                colIndices,
                currentCells,
              })
            }
      }

      //rowIndexの延長処理
      if( colIndices[i][j] <= startColIndex && startColIndex <= colIndices[i][j] + rows[i].table_cells[j].colspan - 1
          || colIndices[i][j] <= endColIndex && endColIndex <= colIndices[i][j] + rows[i].table_cells[j].colspan - 1 
          || startColIndex <= colIndices[i][j] && colIndices[i][j] + rows[i].table_cells[j].colspan - 1 <= endColIndex ){
            if( i < startRowIndex && startRowIndex <= (i + rows[i].table_cells[j].rowspan - 1) ){
              startRowIndex = i
              dragSelectedCells({
                startRowIndex,
                endRowIndex,
                startColIndex,
                endColIndex,
                rows,
                colIndices,
                currentCells,
              })
            }

            if( i <= endRowIndex && endRowIndex < (i + rows[i].table_cells[j].rowspan - 1) ){
              endRowIndex = i + rows[i].table_cells[j].rowspan - 1
              dragSelectedCells({
                startRowIndex,
                endRowIndex,
                startColIndex,
                endColIndex,
                rows,
                colIndices,
                currentCells,
              })
            }
      }
      
      
      if( startRowIndex <= i && i <= endRowIndex
          && startColIndex <= colIndices[i][j] && colIndices[i][j] <= endColIndex ){
            currentCells.push({
              rowIndex: i,
              cellIndex: j,
              colIndex: colIndices[i][j],
              rowspan: rows[i].table_cells[j].rowspan,
              colspan: rows[i].table_cells[j].colspan,
            })
      }
    }
  }
  return currentCells
}

export { scanTable, dragSelectedCells }
```

再起処理の度に変数を渡す必要がある。

最後に外部ファイルに渡す関数を追加。
`export { scanTable, dragSelectedCells }`

以上。