【Handsontable】確定ボタンのチェック処理

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

前回「[【Handsontable】最大文字数制限(MaxLength)](https://qiita.com/yaju/items/16dc38684314157a1736)」では最大文字数制限を行いました、今回は確定ボタンのチェック処理を実装していきます。

# 確定ボタンのチェック処理
確定ボタンのチェック処理では、複数列セルのバリデーションを順次実行するようにします。そのため非同期処理を操作する `Promise` を利用しています。エラーは `catch` で一括チェックするようにしました。
※IE11対応をするため、`await` は使用しませんでした。

必須チェックをした際、必須項目が入力されていないところは赤くなりました。
`validateCells` メソッドでは全てのセルのバリデージョンを明示的に実行します。
全てのセルを対象とするため、エラー箇所は全て赤くなります。
![全てエラー.png](https://qiita-image-store.s3.amazonaws.com/0/14053/f8c110cd-ec1d-7b71-ae45-d6f6ce5e4d69.png)

1箇所でもバリデージョンエラーがあった場合、バリデージョンエラーの位置を `getCellMeta(i, j).valid` を使用して特定し必須項目または値の不正エラーメッセージを表示します。
![必須項目エラー.png](https://qiita-image-store.s3.amazonaws.com/0/14053/1a766846-fcb5-596c-c873-0b7c8ede4506.png)

次は主キーである商品コードが重複していないかをチェックします。
![重複エラー.png](https://qiita-image-store.s3.amazonaws.com/0/14053/bc5eb61a-8477-ebbd-6659-e5f85ac12ce5.png)

全てのエラーがなくなった場合、更新の確認メッセージを表示します。
![商品マスタ保存.png](https://qiita-image-store.s3.amazonaws.com/0/14053/1fadc8a4-5e23-8836-cd60-586ae48fcb71.png)


```javascript
// チェック処理
function check() {
    // 全入力項目チェック
    var checkAllItem = function () {
        return new Promise(function (resolve, reject) {
            hot.validateCells(function (valid) {
                if (valid) {
                    resolve();
                } else {
                    result = { msg: 'All', row: 0, col: 0 };
                    reject(result);
                }
            });
        });
    }

    // 重複チェック
    var checkDuplicate = function () {
        const colPrimaryKey = 2;

        return new Promise(function (resolve, reject) {
            var isDuplicate = false;
            result = { msg: 'Duplicate', row: 0, col: colPrimaryKey };

            for (var i = 0; i < hot.countRows(); i++) {
                var key = hot.getDataAtCell(i, colPrimaryKey);
                if (key == 0) continue;
                for (var j = 0; j < hot.countRows(); j++) {
                    if (i != j) {
                        if (key === hot.getDataAtCell(j, colPrimaryKey)) {
                            result.row = j;
                            isDuplicate = true;
                            break;
                        }
                    }
                }
                if (isDuplicate) break;
            }

            if (!isDuplicate) {
                resolve();
            } else {
                reject(result);
            }
        });
    }

    // 非同期チェック処理
    var promise = Promise.resolve();

    promise
        .then(checkAllItem)
        .then(checkDuplicate)
        .then(function () {
            var result = window.confirm('商品マスタを更新します。よろしいですか？');

            if (result) {
                console.log('保存処理');
            }
            else {
                console.log('キャンセル');
            }
            return

        }).catch(function (result) {
            var isValid = true;
            var errorMsg = '';

            switch (result.msg) {
                case 'All':
                    setError(result);
                    errorMsg = result.msg;
                    break;
                case 'Duplicate':
                    errorMsg = "重複しています。";
                    break;
            }

            // エラーセルのフォーカスをセット
            hot.selectCell(result.row, result.col);
            // エラー表示
            alert(errorMsg);
        });
}

// エラーセット
function setError(obj) {
    var headers = hot.getColHeader();

    for (var i = 0; i < hot.countRows(); i++) {
        for (var j = 2; j < hot.countCols(); j++) {
            var isValid = hot.getCellMeta(i, j).valid;
            if (isValid === false) {
                obj.row = i;
                obj.col = j;

                var value = hot.getDataAtCell(i, j);
                obj.msg = headers[j];
                if (value === null || value.trim() === '') {
                    obj.msg += "は、必須項目です。";;
                }
                else {
                    obj.msg += "は、不正な値です。";
                }
                return;
            }
        }
    }
}
```

# データ保存処理
AjaxのPostを使用してサーバー側処理(ASP.NET)にJSON形式のデータを送信します。
今回はサーバー側処理の実装は省略します。
本来はデータ読込処理もサーバー側処理(ASP.NET)にする必要があります。

```javascript
// POST用非同期処理
function postAjax(method, data, callback) {
    $.ajax({
        url: method,
        type: 'POST',
        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 saveData() {
    var process = function (data) {
        if (data.success)
            alert("保存しました。");
    }
    postAjax('SetProductMasterData', JSON.stringify(hot.getSourceData()), process);
}
```