【Handsontable】ドロップダウンと変更イベント

# はじめに
これは、[Handsontable Advent Calendar 2018](https://qiita.com/advent-calendar/2018/handsontable)の22日目の記事となります。
前回「【Handsontable】確定ボタンのチェック処理」では確定ボタンのチェック処理を行いました、今回はドロップダウンと変更イベント処理を実装していきます。

これまで商品マスタ画面でしたが、今回から受注入力画面にしていきます。

![orderInput.png](https://qiita-image-store.s3.amazonaws.com/0/14053/a6582e74-1624-19a2-baf1-387255ba11ff.png)

# ドロップダウン
明細入力の商品列にて、商品マスタで入力した商品コードと商品名を表示します。
![orderDropdown.png](https://qiita-image-store.s3.amazonaws.com/0/14053/857ee760-a0a2-eadc-d682-bce23121c90f.png)

予定では、明細入力で商品CDと商品名で列を分けるつもりだったのですが、それはまた次の機会とします。
一応方法としては、typeオプションを 'handsontable' にすればいいです。
https://handsontable.com/docs/5.0.2/demo-handsontable.html
![MultiColumnDropDown.png](https://qiita-image-store.s3.amazonaws.com/0/14053/d30e9b52-d388-9d9b-7bfe-19dace8c7c9c.png)

今回は商品コードと商品名をコロン区切りで連結した形式とします。
typeオプションを 'dropdown' にして、sourceオプションに productcode_list() を指定します。

```javascript
var hot = new Handsontable(grid, {
    data: [],
    colHeaders: ['編集', '選択', '商品', '数量', '単価', '金額', '出荷予定日', '備考'],
    columns: [
        { data: COL_EDIT, readOnly: true, type: 'text' },
        { data: COL_SELECT, type: 'checkbox' },
        { data: COL_PRODUCTCODE, type: 'dropdown', width: 200, className: "htLeft htMiddle", source: productcode_list() },
```

商品コードと商品名をコロン区切りで文字列連結してリストに格納します。

```javascript
function productcode_list() {
    var list = [];

    var process = function (data) {
        for (i in data.ProductMaster) {
            list.push(data.ProductMaster[i].ProductCode + ":" + data.ProductMaster[i].ProductName);
        }
    }
    getAjax('../data/product-master.json', null, process);

    return list;
}
```
## リストの高さ
リストが少ないとスクロールバーが付いてしまう場合の対応
[Dropdown/autocomplete menu hidden with preventOverflow](https://github.com/handsontable/handsontable/issues/5032)

```css
.handsontableEditor.autocompleteEditor,
.handsontableEditor.autocompleteEditor .ht_master .wtHolder {
  min-height: 138px;
}
```

リストが多い場合の高さ制御
[Handsontable in Handsontable Dropdown Height Adjustment](https://stackoverflow.com/questions/51317426/handsontable-in-handsontable-dropdown-height-adjustment)

## リストの背景色と選択色
ドロップダウンのリスト表示は、リスト数が少ないと表と背景色が同色で見分け難い状態となってしまいます。そこで背景色を変更して見やすくしています。

```css
.handsontable.listbox tr td.current, .handsontable.listbox tr:hover td {
    background: #d1ecf1;
}

.handsontable.listbox tr td {
    background: #fafaca;
}
```

## 修正して欲しいところ
Handsontable の高さを固定にして縦スクロールバーが出るようにした際に、ドロップダウンのリストがスクロールバーの内側に表示されてしまいます。選択するには縦スクロールバーを動かしてリストを選択するようになってしまうのです。
カレンダーはスクロールバーの外側に表示できるんですけどね。

このため、高さを固定はやめて Handsontable の下には何も表示しないようにしました。 ボタンなどは Handsontable の上に配置するように直しました。

# 変更イベント
beforeChange オプションに変更イベント処理を記述します。

商品を入力したら、商品マスタの該当する単価を取得してセットします。
また、数量を入力したら 数量 x 単価 で金額をセットします。 

金額を訂正可能として入力項目としている時の自動計算はどうするのか？
[【設計時の見落とし】関連項目値の変更について](https://qiita.com/yaju/items/90704ccc1922a02a3215)

```javascript
var hot = new Handsontable(grid, {

    beforeChange: function (changes, source) {
        var row = changes[0][0];
        var prop = changes[0][1];
        var value = changes[0][3];

        switch (prop) {
            case COL_PRODUCTCODE:
                var data = getProductMasterData(value);
                
                this.setDataAtRowProp(row, COL_UNITPRICE, data.UnitPrice);
                break;
            case COL_QUANTITY:
                var unit = this.getDataAtCell(row, COL_UNITPRICE);
                this.setDataAtRowProp(row, COL_PRICE, value * unit);
                break;
            default:
                break;
        }
    }
```

商品コードの商品マスタデータの取得には同期処理で行っています。

```javascript
// GET用非同期/同期処理
function getAjax(method, data, callback, async) {
    if (async == null) {
        async = true;
    }

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

// 商品コードの商品マスタデータの取得
function getProductMasterData(value) {

    var code = value.split(":")[0];
    var result = null;

    var process = function (data) {
        for (i in data.ProductMaster) {
            if (code == data.ProductMaster[i].ProductCode) {
                result = data.ProductMaster[i];
                break;
            }
        }
    }
    getAjax('../data/product-master.json', null, process, false);

    return result;
}
```