【Handsontable】導入と設定

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

お仕事でWeb開発をした際にグリッド入力が必要で、以前ネット記事で見たJavascript用グリッドライブラリの「Handsontable」を使用することにしました。
その開発で培ったTipsをAdvent Calendarを利用して書いていきます。

[フリーで商用利用可能なグリッドライブラリ - しぐれがきのブログ](http://shiguregaki.com/?p=1436)
数あるJavascript用のフリーのグリッドライブラリの中でも使い勝手が一番いい。
【2021/08/10追記】
「[jspreadsheet-ce](https://bossanova.uk/jspreadsheet/v4/)」や「[Cheetah Grid](https://future-architect.github.io/articles/20200901/)」をフリーで商用利用可能なグリッドライブラリとして追加しておきます。

【2019/03/28追記】
>Handsontable7.0.0は6.2.2と大きく変わりました。
まずHandsontable ProはHandsontable Community Editionと統合されてHandsontableは1つだけになりました。(中略)
MITライセンスではなくなり、非商用および評価版のみ無償のライセンスになりました。
[Handsontable 7.0.0を使う人へ](https://qiita.com/misohagi/items/9fcf4d3c0329cf086828)

Handsontable6.2.2以前のバージョンならMITライセンスが適用されます。

>最近、Handsontableのライセンスモデルについて長い間熱心に話し合っていました。私たちは、オープンソースのライセンスソフトウェアに基づいて持続可能なビジネスを構築することが非常に困難になっていることに気付きました。(中略) Handsontable Proから得たお金のおかげで走り続けました。残念ながら、コマーシャルとフリーユーザーの比率は1から25です。したがって、フリーユーザーへの投資を継続する唯一の方法は、より多くのフリーユーザーを有料に変換することです。Handsontableを継続的に向上させることが唯一の使命である企業として成長したいです。
[Handsontable drops open source for a non-commercial license](https://github.com/handsontable/handsontable/issues/5831)

# 商用利用について
【2021/08/10追記】
商用利用（非営利目的の利用）とは、特に法律で定義されているものではありません。企業によって商用利用の定義が変わっています。
一般的な商用利用の定義で言えば、「結果として何らかの対価を受け取るに繋がる行為」なら商用利用になります。
社内用向けのプログラムとしてのみで使用して販売するわけではないとしても、このプログラムによってコスト削減が出来たのであれば、対価を受け取る行為となるわけです。赤字だからとか儲かっているからとかも関係なく対価を貰うことに繋がるかどうかの判断です。あと、評価する段階であれば、商用利用としては扱わない。

下記サイトのように商品の種類によって商用利用の定義が変わっていますので、疑問があれば問い合わせましょう。
[VMwarePlayerの「非営利目的の使用（商用利用）」はどこまで？](https://blog.cnu.jp/2012/08/31/vmwareplayer/)

# Handsontableとは
「Handsontable(ハンズオンテーブル)」は、WEBでExcelのようなスプレッドシートライクな入力を可能にしてくれるJavaScriptライブラリです。
[ポーランド](https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%BC%E3%83%A9%E3%83%B3%E3%83%89)に会社がある「Handsontable」が開発しています。
公式サイト：https://handsontable.com/
ライセンスは、MITとなっており、有料版「Handsontable Pro」があります。

[デモ](https://handsontable.com/examples?manual-resize&manual-move&conditional-formatting&context-menu&filters&dropdown-menu&headers)で見るとわかりますが、Excelのようにデータ入力ができるだけでなく、セルの書式の指定やチャートが作れたりと、機能が多いのも魅力です。

# 必要パッケージ導入
下記サイトに導入方法が提示されています。
https://handsontable.com/download?version=ce

handsontableは公式でVue.js向けのラッパーを出しています。

* [Vue.jsでhandsontableを使う](https://qiita.com/hyaroy_pg/items/3d484ee26988299561a8)
* [Handsontable for Vueの紹介](https://qiita.com/yaju/items/6fabe92923261d5719ab)
* [Handsontable for Vueを使ってみる](https://qiita.com/yaju/items/7bf9cb45bf180b6a600c)

今回はVisual Studio 2015による開発だったので NuGetでインストールしました。

# 使い方
* [Handsontable 使い方メモ１（基本）](https://qiita.com/opengl-8080/items/ac19deec388c357cd498)
* [Handsontable 使い方メモ２（グリッドのオプション）](https://qiita.com/opengl-8080/items/9d25e106ff48b66cb908)
* [Handsontable 使い方メモ３（カラム・セルオプション）](https://qiita.com/opengl-8080/items/ccf5cbccd0ceb0a6bb47)
* [Handsontable 使い方メモ４（メソッド）](https://qiita.com/opengl-8080/items/e756af444147771fab18)
* [【Handsontable】データの読み込みと行追加](https://qiita.com/yaju/items/3d7f598867556077dac5)
* [【Handsontable】全選択/全解除チェックと行削除](https://qiita.com/yaju/items/5acbb061baa3a495cf8e)
* [【Handsontable】変更マークの付与と必須チェック](https://qiita.com/yaju/items/900cef462f0daf69c19b)
* [【Handsontable】最大文字数制限(MaxLength)](https://qiita.com/yaju/items/16dc38684314157a1736)
* [【Handsontable】確定ボタンのチェック処理](https://qiita.com/yaju/items/b1b6a5deea8a67f0d0be)
* [【Handsontable】ドロップダウンと変更イベント](https://qiita.com/yaju/items/c0b1a58b4cf5a6885935)
* [【Handsontable】RichTextエディターによる入力(Summernote)](https://qiita.com/yaju/items/f41db7ee1dcdf052df7d)
* [【Handsontable】複数行ヘッダー(NestedHeaders)もどきの実現](https://qiita.com/yaju/items/d42e74fc22126792ff65)
* [【Handsontable】セル内グリッド表示(Nested Grid)の実現](https://qiita.com/yaju/items/6856553510bca3ae913a)
* [【Handsontable】セル内HTML要素の表示(Nested HTML)の実現](https://qiita.com/yaju/items/49b4197ccbe2de345d7b)
* [【Handsontable】セル内にプログレスバー(Progress)の表示](https://qiita.com/yaju/items/771e216d7256fc47ade3)
* [【Handsontable】セル内に時刻入力(ClockPicker)の表示](https://qiita.com/yaju/items/25ea509c153af41e64e5)
* [【Handsontable】セル内に日時入力(DateTimePicker)の表示](https://qiita.com/yaju/items/d037ce7e4ed8ebf6c467)
* [【Handsontable】セル内に複数選択セレクト入力(Multi Select ComboTree)の表示](https://qiita.com/yaju/items/1e74ef159d5a095875e6)
* [【Handsontable】シンプルデザインの実現](https://qiita.com/yaju/items/a8f7186c30655e7c2621)
* [BlazorでExcel ライクなグリッドJSライブラリ「Handsontable」を使ってみる](https://qiita.com/yaju/items/bcca337af393f15a6795)
* [【Handsontable】無償版で数式を実現してみる](https://qiita.com/yaju/items/265f38cd0cfa01b342de)

# サンプル
今回はExcelのような自由な入力形式ではなく、一般的なグリッド入力を想定します。
例として、簡易的な商品マスタを作成および改良していきます。

![商品マスタ.png](https://qiita-image-store.s3.amazonaws.com/0/14053/b1eb98cc-26fd-270d-03c5-81774c26a9f2.png)

## 仕様
|項目|内容|
|:--|:--|
|全選択/全削除|選択欄の全てをチェックオン/オフにします。|
|行追加|最下行に行追加します。|
|確定|商品マスタを保存します。|
|行削除|選択欄のチェックオンを行削除します。|
|編集|新規に編集した行に「*」マークを付けます。編集不可|
|選択|行削除する行を選択します。|
|商品CD|商品コードを入力します。5桁|
|商品名|商品名を入力します。30桁|
|単価|単価を入力します。|
|備考|備考を入力します。100桁|

## 実装
NuGetで下記ライブラリーをインストールしています。

* bootstrap v4.1.3
* FontAwesome v4.7.0
* Handsontable v6.2.0
* jQuery v3.3.1

```html:product-master.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Handsontable Example</title>
    <link rel="stylesheet" href="../Content/bootstrap.min.css">
    <link rel="stylesheet" href="../Content/handsontable/handsontable.full.min.css" />
    <link rel="stylesheet" href="../Content/font-awesome.min.css">
    <link rel="stylesheet" href="../Content/product-master.css">
</head>
<body>
    <div class="card-body bg-custom">
        <div class="container">
            <div class="row">
                <div class="title-custom">商品マスタ</div>
            </div>
            <div class="row">
                <span class="pr-4"></span>
                <label class="btn btn-default btn-custom"><input type="checkbox" id="allCheck" onclick="allCheck(this)"><span class="pr-2"></span>全選択/全削除</label>
                <button type="button" class="btn btn-labeled btn-info btn-custom" onclick="addRow()">
                    <span class="btn-label"><i class="fa fa-edit"></i></span>行追加
                </button>
                <button type="button" class="btn btn-labeled btn-success btn-custom" onclick="register()">
                    <span class="btn-label"><i class="fa fa-check-circle"></i></span>確　定
                </button>
                <button type="button" class="btn btn-labeled btn-danger btn-custom" onclick="delRow()">
                    <span class="btn-label"><i class="fa fa-trash-o"></i></span>行削除
                </button>
            </div>
            <div class="row">
                <div id="grid" class="ml-3"></div>
            </div>
        </div>
    </div>
    <script src="../Scripts/handsontable/handsontable.full.min.js"></script>
    <script src="../Scripts/jquery-3.3.1.min.js"></script>
    <script src="../Scripts/product-master.js"></script>
</body>
</html>
```

```css:product-master.css
.title-custom {
    padding: 0px 0px 10px 0px;
    font-size:20px;
    font-weight: bold;
}

.btn-custom {
    margin-right: 20px;
}

.btn-label {
    position: relative;
    left: -12px;
    display: inline-block;
    padding: 6px 12px;
    background: rgba(0,0,0,0.15);
    border-radius: 3px 0 0 3px;
}

.btn-labeled {
    padding-top: 0;
    padding-bottom: 0;
}

.btn {
    margin-bottom: 10px;
}

.handsontable th,
.handsontable td {
    padding: 2px 10px 2px 10px;
    font-size: 16px;
    text-align: center;
}

.handsontable th:last-child {
    padding-left: 8px;
    text-align: left;
}

.handsontable td:first-child {
    background: #EEE;
}
```

```javascript:product-master.js
var data = [
    ['', false, 'S0001', 'りんご', 100, '青森産'],
    ['', false, 'S0002', 'みかん', 80, '静岡産'],
    ['*', false, 'S0003', 'メロン', 1000, '袋井クラウンメロン']
];

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

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

# 説明
Handsontable 用に幾つか設定したグリッドオプションについて説明していきます。

## enterMoves：Enterキーによるセルの横移動の設定
Enter でセルを抜ける時、デフォルトだと下のセルに移動するようになっているため、横に移動するようにします。ちなみに Tab でセルの横に移動できます。

```javascript
enterMoves: { row: 0, col: 1}
```

  * [enterMoves：Enter でセルを抜けたときの移動先を指定する](https://qiita.com/opengl-8080/items/9d25e106ff48b66cb908#enterMoves%EF%BC%9AEnter%20%E3%81%A7%E3%82%BB%E3%83%AB%E3%82%92%E6%8A%9C%E3%81%91%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AE%E7%A7%BB%E5%8B%95%E5%85%88%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B)
  * [On "Enter" Press move the focus to the Cell in the Right](https://github.com/handsontable/handsontable/issues/68)

## outsideClickDeselects：表外をクリックしたら選択状態を解除させる
```javascript
outsideClickDeselects: true
```
デフォルトは `true` で、表外をクリックすると選択状態が解除されます。
選択状態を残した状態にするなら、 `false` にします。 

## manualColumnResize：列の幅をドラッグで変更できるようにする
```javascript
manualColumnResize: true
```

列ヘッダーを表示させた状態の時に、列の幅をドラッグで変更できるようにしています。
ダブルクリックすれば、セルの内容に合わせて幅を最小サイズになります。
行のサイズ変更は、`manualRowResize` を使えばできます。

## fillHandle：フィルハンドルの無効
```javascript
fillHandle: false
```

フィルハンドルは選択したセルの右下にある小さい四角の形のもので、カーソルをドラッグすることで、セルの内容を上下 or 左右にコピーできる機能です。
今回は不要なので無効にしています。

* [Handsontable 使い方メモ２（グリッドのオプション） fillHandle：フィルハンドル](https://qiita.com/opengl-8080/items/9d25e106ff48b66cb908#fillhandle%E3%83%95%E3%82%A3%E3%83%AB%E3%83%8F%E3%83%B3%E3%83%89%E3%83%AB)

## 文字配置
```css
.handsontable th,
.handsontable td {
    padding: 2px 10px 2px 10px;
    font-size: 16px;
    text-align: center;
}
```

デフォルトはヘッダー部は中央寄せ、データ部は左寄せになっているが、CSSを使用してヘッダー部とデータ部を中央寄せと余白を追加している。
データ部はカラム属性にて、`className: "htLeft htMiddle"` で左寄せの指定ができる。
`className` のアライメントの指定は下記の値を使用する。

* Horizontal: htLeft, htCenter, htRight, htJustify,
* Vertical: htTop, htMiddle, htBottom

ヘッダー部にカラム属性は見当たらないので、末尾の備考欄のみを左寄せにしたい場合は、下記のようにCSSの疑似クラスを使用して左寄せにすることができます。

```css
.handsontable th:last-child {
    padding-left: 8px;
    text-align: left;
}
```

## 読み取り専用セル
編集欄は読み取り専用であるため、カラム属性にて`readOnly: true` を設定しています。
読み取り専用にすると文字色は読み取り専用色に変更になりますが背景色は何も変わらないため、今回はCSSの疑似クラスを使用して先頭列の背景色を変更しています。

```css
.handsontable td:first-child {
    background: #EEE;
}
```
※`Custom renderers` で行う方法はまた別の記事で紹介します。

# 最後に
これから記事に即して、少しずつ商品マスタを完成に近づけていきます。