【Handsontable】データの読み込みと行追加

# はじめに
これは、[Handsontable Advent Calendar 2018](https://qiita.com/advent-calendar/2018/handsontable)の2日目の記事となります。

前回「[【Handsontable】導入と設定](https://qiita.com/yaju/items/c0307fa9ea69ad02c189)」では商品マスタの画面設定をしました。今回から少しずつ実装を組んでいきます。
先ずはデータの読み込みと行追加を実装していきます。

# データの読み込み
前回はダミーデータをソースコード上に書きましたが、今回はJSONデータを読み込むようにします。

2件のデータを登録します。

```json:product-master.json
{
  "ProductMaster":[
    {
      "Edit":"",
      "Select":false,
      "ProductCode":"S0001",
      "ProductName":"りんご",
      "UnitPrice":100,
      "Comment":"青森産"
    },
    {
      "Edit":"",
      "Select":false,
      "ProductCode":"S0002",
      "ProductName":"みかん",
      "UnitPrice":80,
      "Comment":"静岡産"
    }
  ]
}
```
ajax にてJSONデータを読み込み、loadDataメソッドを使用してHandsontableにデータをロードさせます。
columns属性の`data` にJSONデータに合わせたキー名を指定しておきます。

```javascript:product-master.js
const COL_EDIT = 'Edit';
const COL_SELECT = 'Select';
const COL_PRODUCTCODE = 'ProductCode';
const COL_PRODUCTNAME = 'ProductName';
const COL_UNITPRICE = 'UnitPrice';
const COL_COMMENT = 'Comment';

var grid = document.getElementById('grid');

var hot = new Handsontable(grid, {
    data: [],
    colHeaders: ['編集', '選択', '商品CD', '商品名', '単価', '備考'],
    columns: [
        { data: COL_EDIT, readOnly: true, type: 'text' },
        { data: COL_SELECT, type: 'checkbox' },
        { data: COL_PRODUCTCODE, type: 'text' , width: 80 },
        { data: COL_PRODUCTNAME, type: 'text' , width: 200, className: "htLeft htMiddle" },
        { data: COL_UNITPRICE, type: 'numeric', numericFormat: { pattern: '0,00', culture: 'ja-JP' }},
        { data: COL_COMMENT, type: 'text' , width: 300, className: "htLeft htMiddle" }
    ],
    enterMoves: { row: 0, col: 1 },
    outsideClickDeselects: true,
    manualColumnResize: true,
    fillHandle: false
});

// 読み込み時
$(function () {
    loadData()
})

// GET用非同期処理
function getAjax(method, data, callback) {
    $.ajax({
        url: method,
        type: 'GET',
        dataType: 'json',
        data: data,
        contentType: 'application/json; charset=utf-8',
        async: true,
        processData: true,
        cache: false
    }).fail(function (xhr, status, error) {
        alert(error);
    }).done(function (data) {
        callback(data);
    });
}

// 商品マスタデータ取得
function loadData() {
    var process = function (data) {
        hot.loadData(data.ProductMaster);
    }
    getAjax('../data/product-master.json', null, process);
}
```

# 行追加
## 最下行に追加
最下行は、現在の行数(countRows)を指定します。

```javascript
hot.alter('insert_row', hot.countRows());
```

## 指定セルのフォーカスをセット
行追加後に新規行にフォーカスを移動させたい。
指定は行、列の順番となります。

```javascript
hot.selectCell(row, col);
```
## 行追加ボタンの実装
行追加ボタンをクリックしたら最下行に新規行を追加します。
そして、セルを新規行の商品コードにセットします。

![商品マスタ2.png](https://qiita-image-store.s3.amazonaws.com/0/14053/5472ef06-ac3a-75ca-7578-58f1fa8edac5.png)

```javascript:ProductMaster.js
// 行追加ボタン処理
function addRow() {
    hot.alter('insert_row', hot.countRows());
    var col = hot.propToCol(COL_PRODUCTCODE);
    hot.selectCell(hot.countRows() - 1, col);
}
``` 

## 選択位置の上下に行を挿入
コンテキストメニューには「上に行を挿入:Insert Row Above」と「下に行を挿入:Insert Row Below」と2種類がデフォルトで用意されている。

![行追加.png](https://qiita-image-store.s3.amazonaws.com/0/14053/219ceb1d-62e1-b20b-2b22-f60884365102.png)

Handsontable のグリッドオプションに追加すれば使える。
[Handsontable 使い方メモ２（グリッドのオプション）- デフォルトで使用できるメニューのラベルを変更する](https://qiita.com/opengl-8080/items/9d25e106ff48b66cb908#%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%81%A7%E4%BD%BF%E7%94%A8%E3%81%A7%E3%81%8D%E3%82%8B%E3%83%A1%E3%83%8B%E3%83%A5%E3%83%BC%E3%81%AE%E3%83%A9%E3%83%99%E3%83%AB%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B)

```javascript
contextMenu: {
    items: {
        row_above: { name: '上に行を挿入' },
        row_below: { name: '下に行を挿入' }
    }
}
```

上記だとフォーカスを新規行にセットされないため、メニューが選択されたときのコールバック関数を指定して実現させる。

```javascript
contextMenu: {
    items: {
        row_above: {
            name: '上に行を挿入',
            callback: function (key, normalizedSelection) {
                var latestSelection = normalizedSelection[Math.max(normalizedSelection.length - 1, 0)];
                this.alter('insert_row', latestSelection.start.row);
                var col = hot.propToCol(COL_PRODUCTCODE);
                hot.selectCell(latestSelection.start.row, col);
            }
        },
        row_below: {
            name: '下に行を挿入',
            callback: function (key, normalizedSelection) {
                var latestSelection = normalizedSelection[Math.max(normalizedSelection.length - 1, 0)];
                this.alter('insert_row', latestSelection.end.row + 1);
                var col = hot.propToCol(COL_PRODUCTCODE);
                hot.selectCell(latestSelection.end.row + 1, col);
            }
        }
    }
}
```