Excel風一覧(handsontable)からのSubmit

WEBでExcel風の一覧を再現するHandsontableを使って、クライアント側で更新、登録、削除機能を実装しました。今回は編集データをSubmitする仕組みを整備します。

振り返り(クライアント側の編集)

変更の種類には追加(Ins)と変更(Upd)と削除(Del)がありますが、クライアント側では下記のようになっています。これらの変更は、クライアント側での編集状態でしかありません。「反映」ボタンを押下することにより、サーバサイドにSubmitできる仕組みを作ります。

クライアント側の実装は以前記載しました。

https://itdkt.info/archives/177

https://itdkt.info/archives/196

Ins行には”テスト”と入力して 反映ボタン押下後は下記のようになります。

Submitの仕組み

前提として、Ruby On Railsのフォームの仕組みは使えません。form_forの仕組みは最終的にformタグとそれに囲まれたinputタグ(など)に展開されるので、ブラウザの機能で指定パスにPOST、ないしGETリクエストが送信されます。対してhandsontableは所詮はdivタグであり、そのままではSubmitされません。

【参考】handsontableの表示箇所のHTMLソース(DOM)

そこで、Submitボタン(=上記画像では”反映ボタン”)にするためのmyライブラリを作成しました。

ライブラリ実装

(function($) {
    'user strict';
//(中略)
    var postAjax = function(action, method, data, callBack) {
        $.ajax({
            url: action,
            type: method,
            data: data,
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            async: true,
            processData: true,
            cache: false
        }).fail(function(xhr, status, error) {
            var dialog = $('<div></div>').text(xhr.responseJSON.message);
            dialog.dialog({
                modal: true,
                title: "エラーがあります.",
                buttons: [{
                    text: 'OK',
                    click: function() { $(this).dialog('close'); }
                }]
            });
        }).done(function(d) {
            if (callBack === undefined) {} else {
                callBack(d);
            }
        });
    }
//(中略)
    /* 
     * hot: handsontableのオブジェクト
     * beforeFunc: POST前に実行するファンクション.文字列を返すとエラーメッセージとして表示し、POSTを中断する。
     * successFunc: POST後に実行される.-> jqueryのajaxのdone(function (data))
     */
    $.fn.commitHandson = function(hot, beforeFunc = undefined, successFunc = undefined) {
        this.bind('click', function(e) {
            e.preventDefault();
            e.stopPropagation();
            let action = $(this).attr('href');
            let method = $(this).attr('data-method');

            if (beforeFunc === undefined) {} else {
                //エラーメッセージがnull以外ならばpostせずに終わる.
                var errmsg = beforeFunc()
                if (errmsg != null) {
                    var dialog = $('<div></div>').text(errmsg);
                    dialog.dialog({
                        modal: true,
                        title: "エラーがあります.",
                        buttons: [{
                            text: 'OK',
                            click: function() { $(this).dialog('close'); }
                        }]
                    });
                    return;
                }
            }
            postAjax(action, method, JSON.stringify(hot.getSourceData()), successFunc);
        });
    }
})(jQuery);
//処理成功後にダイアログを出しながらファンクション実行する
function concreateSuccess(handleFunc = undefined) {
    var success = function(data) {
        var dialog = $('<div></div>').text("反映しました.");
        dialog.dialog({
            modal: true,
            title: "結果確認",
            buttons: [{
                text: 'OK',
                click: function() { $(this).dialog('close'); }
            }]
        });
        if (handleFunc == undefined) {} else {
            handleFunc(data);
        }
    }
    return success;
}

画面側の実装

反映ボタンのソースと、反映ボタンへのバインドを示します。見た目はBootstrapのクラスが反映されていますが、お好みで指定してください。POST先は、railsのパスファンクションで作ります。methodは必須ですが、routes.rbと合致するように指定します。

<%= link_to '反映', setting_path("d"), method: "PATCH",
 class: "btn btn-labeled btn-success btn-custom", id: "commitBtn" %>

なお、サーバサイドからのレスポンスを更新後の一覧データを受け取って再描画するようにしています。

var reload = function(resJson){
    settings = hot.getSettings();
    settings.data = eval(resJson.data);
    hot.updateSettings(settings);
}
$('#commitBtn').commitHandson(hot, undefined, concreateSuccess(reload));

サーバが受け取るリクエスト

確かなところではサーバサイドでputs paramsしていただきたいのですが、イメージとしては下記のようにリクエストが渡ってきます。

[{
  "id" => 11,
  "man_hour_name" => "Ruby On Rails",
  "results" => 0,
  "valid_from" => "2019/12/31",
  "valid_to" => "2025/12/31",
  "status" => "Upd"
},{
  "id" => 12,
  "man_hour_name" => "ゲーム",
  "results" => 0,
  "valid_from" => "2019/12/31",
  "valid_to" => "2025/12/31",
  "select" => true,
  "status" => "Del"
},{
  "id" => nil,
  "man_hour_name" => "テスト",
  "results" => nil,
  "valid_from" => "2019/12/31",
  "valid_to" => "2025/12/31",
  "status" => "Ins"
}]