ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

C#だけでJavaScriptを書く! Blazor.DynamicJSを作りました ②Handsontable

2023/03/03 にある meetup app osaka@7 で話すやつです。
meetupapp.connpass.com

年末のBlazorのアドベントカレンダーでHandsontableをBlazorで使う話が書かれてたのでやってみました。若干非効率ながら全部C#で書けました!コードはそれを参考にさせてもらいました。(てかそのJSをC#に書きなおさせてもらいました)
qiita.com

@page "/"
@using Blazor.DynamicJS
@inject IJSRuntime jsRuntime
<div @ref="_grid"></div>

@code {
    private ElementReference _grid;
    class Col
    {
        public object? data { get; set; }
        public object? readOnly { get; set; }
        public object? width { get; set; }
        public object? className { get; set; }
        public object? type { get; set; }
        public object? numericFormat { get; set; }
    }

    public class ProductMaster
    {
        public string? Edit { get; set; }
        public bool Select { get; set; }
        public string? ProductCode { get; set; }
        public string? ProductName { get; set; }
        public int UnitPrice { get; set; }
        public string? Comment { get; set; }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender) return;
        var js = await jsRuntime.CreateDymaicRuntimeAsync();

        const string COL_EDIT = "edit";
        const string COL_SELECT = "select";
        const string COL_PRODUCTCODE = "productCode";
        const string COL_PRODUCTNAME = "productName";
        const string COL_UNITPRICE = "unitPrice";
        const string COL_COMMENT = "comment";
        const string EDIT_MARK = "*";

        //コンストラクタ引数をJavaScriptの世界につくる
        var arg = js.ToJS(new
        {
            data = new object[0],
            colHeaders = new[] { "編集", "選択", "商品CD", "商品名", "単価", "備考" },
            columns = new Col[]
            {
                new Col{ data = COL_EDIT, readOnly = true, type = "text" },
                new Col{ data = COL_SELECT, type = "checkbox" },
                new Col{ data = COL_PRODUCTCODE, type = "text", width = 80 },
                new Col{ data = COL_PRODUCTNAME, type = "text", width = 200, className = "htLeft htMiddle" },
                new Col{ data = COL_UNITPRICE, type = "numeric", numericFormat = new { pattern = "0,00", culture = "ja-JP" } },
                new Col{ data = COL_COMMENT, type = "text", width = 300, className = "htLeft htMiddle" }
            },
            enterMoves = new { row = 0, col = 1 },
            outsideClickDeselects = true,
            manualColumnResize = true,
            fillHandle = false,
        });

        //コールバックで使うので先に宣言
        dynamic? hot = null;

        //オブジェクトの中にシリアライズできないメンバーは入れれないので別途ここで入れる
        arg.afterChange = (Action<dynamic, dynamic>)((changes, source) =>
        {
            if (source == "loadData") return;

            //これほんとは重いからやらない方がいい
            //changesの型を調べてシリアライズして持ってきた方がいい
            for (var i = 0; i < (int)changes.length; i++)
            {
                var change = changes[i];
                // 編集と選択は対象外
                if (change[1] == COL_EDIT || change[1] == COL_SELECT) continue;
                // 変更前と変更後が同じは対象外
                if (change[2] == change[3]) continue;
                // 編集に"*"を付ける
                hot?.setDataAtCell(changes[0][0], 0, EDIT_MARK);
            }
        });

        hot = js.New("Handsontable", _grid, arg);

        var products = new List<ProductMaster>()
        {
            { new ProductMaster() { Edit = "", Select = false, ProductCode = "S0001", ProductName = "りんご", UnitPrice = 100, Comment = "青森産" } },
            { new ProductMaster() { Edit = "", Select = false, ProductCode = "S0002", ProductName = "みかん", UnitPrice = 80, Comment = "静岡産" } },
            { new ProductMaster() { Edit = "", Select = true,  ProductCode = "S0003", ProductName = "メロン", UnitPrice = 1000, Comment = "袋井クラウンメロン" } }
        };

        hot.loadData(products);
    }
}

コールバックの中の処理がちょっと非効率。ループ回るたびにJSの世界とやり取りしてるんですよね・・・。これはchangesがシリアライズできたらいんですけどね、これ複数の型が入ってる配列だから今うまくシリアライズできないんですよねー。なんかいい方法考えよう。