Handsontable 使い方メモ１（基本）

[グリッドのオプション](http://qiita.com/opengl-8080/items/9d25e106ff48b66cb908)
[セル・カラムのオプション](http://qiita.com/opengl-8080/items/ccf5cbccd0ceb0a6bb47)
[メソッド](http://qiita.com/opengl-8080/items/e756af444147771fab18)

# Handsontable とは
JavaScript のライブラリ。
ブラウザ上で Excel のようなグリッドを実現できる。

jQuery などの他のライブラリには依存していない。

# Hello World
## インストール
[Releases · handsontable/handsontable](https://github.com/handsontable/handsontable/releases)

Github から zip を落とすなりする（Bower とかで落とせるっぽい）。

解凍したら、 `dist` フォルダの下に minify 化されたソースとかが入っている。

## 実装
```text:フォルダ構成
|-index.html
|-js/
|  |-handsontable.full.min.js
|  `-script.js
`-css/
   `-handsontable.full.min.css
```

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Handsontable</title>
    <link rel="stylesheet" href="css/handsontable.full.min.css" />
  </head>
  <body>
    <div id="grid">
    </div>

    <script src="js/handsontable.full.min.js"></script>
    <script src="js/script.js"></script>
  </body>
</html>
```

```js:script.js
var data = [
  ['佐藤', 28],
  ['鈴木', 19],
  ['田中', 25]
];

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

new Handsontable(grid, {data: data});
```

## 実行結果
![handsontable.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/47883b0c-3c52-8e4e-a94e-7371b5b0683b.jpeg)

## 説明
- 以下の２つのファイルを読み込む。
    - `handsontable.full.min.js`
    - `handsontable.full.min.css`
- グリッドを表示する場所として `<div>` タグを用意して、その Element オブジェクトを取得する。
- `Handsontable` を `new` してテーブルを作成する。
    - コンストラクタ引数の第一引数に先ほど取得した Element オブジェクトを渡す。
    - 第二引数にオプションを渡す。
        - `data` オプションに、グリッドに表示するデータを渡す。

# データソース
Handsontable では、配列やオブジェクトなど幾つかのデータを取り込むことができる。

## 配列
```js
var data = [
  ['佐藤', 28],
  ['鈴木', 19],
  ['田中', 25]
];

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

new Handsontable(grid, {data: data});
```

![handsontable.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/198d1003-a810-49bc-7498-656919f173e1.jpeg)

- 二次元配列を使用した最も基本的な方法。

## オブジェクト
```js
var data = [
  {name: '佐藤', age: 25, a: 'A'},
  {name: '鈴木', age: 11, b: 'B'},
  {name: '田中', age: 21}
];

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

new Handsontable(grid, {data: data});
```

![handsontable.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/d008eb96-cd16-7b49-0074-d4561b5a045e.jpeg)

- オブジェクトの配列も渡すことができる。
- 各プロパティから値が取得され、グリッドに表示される。
- どのプロパティが使用されるかは、先頭のオブジェクトで決まる模様。

## ネストされたオブジェクト
```js
var data = [
  {name: {first: '佐藤', last: '一郎'}, age: 25},
  {name: {first: '鈴木', last: '二郎'}, age: 11},
  {name: {first: '田中', last: '三郎'}, age: 21}
];

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

new Handsontable(grid, {
    data: data,
    columns: [ // ★列のマッピングを定義できる
        {data: 'age'},
        {data: 'name.last'}, // ★ネストされたオブジェクトのどのプロパティを表示するかを指定できる
        {data: 'name.first'}
    ]
});
```

![handsontable.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/7d9b6e69-a55a-43bc-ae64-4e46fe76c40a.jpeg)

- `column` オプションで、列ごとの細かい定義を宣言できる。
- `column` の `data` オプションで、その列に表示する値をオブジェクトのどのプロパティから取得するかを指定できる。
- ネストされたオブジェクトの場合は `name.first` のようにドット区切りでプロパティを指定できる。

## 任意のオブジェクトを使う
例えば Backbone.js のモデルなんかは、単純に `オブジェクト.プロパティ名` ではプロパティの値を参照することができない。

そんなオブジェクトでも、データソースとして使用することができるようになっている。

```html:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Handsontable</title>
    <link rel="stylesheet" href="css/handsontable.full.min.css" />
  </head>
  <body>
    <div id="grid">
    </div>

    <button id="button">click</button> <!-- ★ボタンを追加 -->

    <script src="js/handsontable.full.min.js"></script>
    <script src="js/script.js"></script>
  </body>
</html>
```

```js
var data = [
    new MyModel('佐藤', 22),
    new MyModel('鈴木', 31),
    new MyModel('田中', 19)
];

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

new Handsontable(grid, {
    data: data,
    dataSchema: new MyModel(), // ★空のインスタンスを渡す
    columns: [
        {
            // ★セルの値が参照・変更されたときにコールバックされるので、
            //   第二引数に値が渡されたかどうかでモデルから値を取得するか、
            //   値を設定するかを決める。
            data: function(myModel, name) {
                if (name) {
                    myModel.setName(name);
                } else {
                    return myModel.getName();
                }
            }
        },
        {
            data: function(myModel, age) {
                if (age) {
                    myModel.setAge(age);
                } else {
                    return myModel.getAge();
                }
            }
        }
    ]
});

// ボタンをクリックしたら、現在の data の内容を表示する
document.getElementById('button').addEventListener('click', function() {
    data.forEach(function(myModel) {
        console.log(myModel.toString());
    });
});

// 独自クラス
function MyModel(name, age) {
    var _name = name,
        _age = age;

    this.getName = function() {
        return _name;
    };

    this.setName = function(name) {
        _name = name;
    };

    this.getAge = function() {
        return _age;
    };

    this.setAge = function(age) {
        _age = age;
    };

    this.toString = function() {
        return 'MyModel{name=' + _name + ', age=' + _age + '}';
    };
}
```

![handsontable.gif](https://qiita-image-store.s3.amazonaws.com/0/28302/d1da1ee4-f834-d498-38ff-9c2694cbd58d.gif)

- `data` の各行には独自クラス（`MyModel`）のインスタンスを設定している。
    - このクラスは、 getter, setter でしかプロパティにアクセスできないようにしている。
- `dataSchema` には、独自クラスのインスタンスを渡しておく。
- 重要なのは `columns` の `data` オプションで、ここに関数を渡す。
    - この関数は、セルに値が表示されるときや、セルの値が書き換わったときにコールバックされる。
    - その際、第一引数にはその行に対応するオブジェクトが、
    - 第二引数には書き換わった後の値が渡される（値が参照されたときは何も渡されない）。
    - `columns.data` オプションに設定した関数は、値が参照されたか変更されたかを判断し、第一引数のオブジェクトから値を取得するか設定するかハンドリングする。


# 空のグリッドを作る
```js
var grid = document.getElementById('grid');

new Handsontable(grid, {
    data: [],
    dataSchema: {
        name: null,
        age: null
    },
    columns: [
        {data: 'name'},
        {data: 'age'}
    ],
    minSpareRows: 1
});
```

![handsontable.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/ebc8bb2c-e58f-e799-aef4-6167d7f03176.jpeg)

- `dataSchema` に、カラム定義となるオブジェクトを渡す。
- `columns` で、列の順序を指定する。
- `data` に空の配列を渡す。
- `minSpareRows` に最低限表示する行数を設定する。

# データとグリッドを切り離す
グリッドを再描画してもデータの変更が反映されないようにしたい場合は、コンストラクタ引数で渡す `data` をディープコピーにする方法がある。

ディープコピーを作る手段の１つとして、 JSON に一旦エンコードして、 JavaScript オブジェクトにデコードし直す方法がある。

```js
var data = [
  ['佐藤', 28],
  ['鈴木', 19],
  ['田中', 25]
];

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

var table = new Handsontable(grid, {
    // JSON 文字列にしてから JavaScript オブジェクトに戻す
    data: JSON.parse(JSON.stringify(data))
});

data.push(['山田', 22]);

table.render();
```

![handsontable.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/30170bf8-fb9c-69c8-6bf6-a5564437e2dc.jpeg)


# プラグイン
Handsontable の一部の機能（セルコメント・コンテキストメニュー・リドゥ/アンドゥ etc...）は、プラグインという形で実現されている。

プラグインは、 `Handsontable` を `new` するときにオプションを指定することで有効にできる。

有効にしたプラグインは、 `Handsontable` インスタンスから `getPlugin()` メソッドでそのインスタンスを取得できる。

```js:プラグインのインスタンスを取得する
var grid = document.getElementById('grid');

var table = new Handsontable(grid, {
    comments: true
});

var comments = table.getPlugin('comments');

comments.setRange({
    from: {row: 1, col: 1}
});
comments.editor.setValue('Commnets Plugin');
comments.saveComment();
```

![handsontable.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/153e4ad8-23c3-e89e-6f76-c8f77fd3a865.jpeg)

`getPlugin()` には、取得したいプラグインを指定するための文字列を渡す。
この値は、プラグイン名の先頭を小文字にしたものになるっぽい。
プラグインの名前は、 [公式のマニュアル](http://docs.handsontable.com/0.24.1/tutorial-introduction.html)から確認できる。

取得したプラグインインスタンスを使えば、オプションではできなかった複雑な制御も可能になる。

# Hook（イベントハンドリング）
## `add`：イベントハンドラを追加する
```js
var grid = document.getElementById('grid');

new Handsontable(grid);

Handsontable.hooks.add('afterSelection', function() {
    console.log(arguments);
});
```

![handsontable.gif](https://qiita-image-store.s3.amazonaws.com/0/28302/4a3ffb2f-7d0c-27e0-d53f-b9b4e3dc377d.gif)

- `Handsontable.hooks` の `add()` メソッドを使うことで、イベントハンドラを登録できる。
- 複数の `Handsontable` インスタンスを生成している場合は、全てのグリッドのイベントを監視する。

### 特定のグリッドだけを監視対象にする
```js
var grid1 = document.getElementById('grid1');
var grid2 = document.getElementById('grid2');

var table1 = new Handsontable(grid1);
var table2 = new Handsontable(grid2);

Handsontable.hooks.add('afterSelection', function() {
    console.log(arguments);
}, table1);
```

![handsontable.gif](https://qiita-image-store.s3.amazonaws.com/0/28302/7a6a1b5b-4597-5a27-5a08-5c428cc5b56e.gif)

- `add()` メソッドの第三引数に `Handsontable` インスタンスを渡すと、そのグリッドだけがイベントの監視対象になる。

### イベントの種類
[公式ドキュメント](http://docs.handsontable.com/0.24.1/Hooks.html#event:afterAutofillApplyValues) に数えきれないほど載っている。

## `remove`：イベントハンドラを削除する
```js
var grid = document.getElementById('grid');

new Handsontable(grid);

function afterSelection(row, col) {
    console.log('select(' + row + ', ' + col + ')');
}

Handsontable.hooks.add('afterSelection', afterSelection);

document.getElementById('remove').addEventListener('click', function () {
    Handsontable.hooks.remove('afterSelection', afterSelection);
});
```

![handsontable.gif](https://qiita-image-store.s3.amazonaws.com/0/28302/bbc1000c-865e-4b31-05e1-92be0ea746f5.gif)

- `remove` でイベントハンドラを削除できる。
- 第三引数には、 `add()` の時と同じように `Handsontable` インスタンスを渡せる。

## `getBucket`：登録されているイベントハンドラを取得する
```js
var grid = document.getElementById('grid');

var table = new Handsontable(grid);

var bucket = Handsontable.hooks.getBucket(table);

console.log(bucket);
```

![handsontable.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/34b6169c-2895-3c6a-c5b2-aa0c3652d067.jpeg)

- `Handsontable.hooks.getBucket()` で、全てのイベントハンドラを取得できる。
- キーはイベント名で、値にイベントハンドラの関数オブジェクトが配列で設定されている。
- `getBucket()` の引数に `Handsontable` インスタンスを渡せば、そのテーブルに登録されているイベントハンドラだけが取得できる。
- 引数を省略した場合は、全イベントハンドラが取得できる。

## `once`：一度だけ実行されるイベントハンドラを登録する
```js
var grid = document.getElementById('grid');

var table = new Handsontable(grid);

Handsontable.hooks.once('afterSelection', function() {
    console.log('selection!');
});
```

![handsontable.gif](https://qiita-image-store.s3.amazonaws.com/0/28302/a9715e9f-f815-d761-1de4-4238fde005de.gif)

## `run`：任意のイベントを発火させる
```js
var grid = document.getElementById('grid');

var table = new Handsontable(grid);

// 独自のイベントハンドラを登録
Handsontable.hooks.add('onButtonClick', function () {
    console.log('click!!');
});

document.getElementById('button').addEventListener('click', function() {
    // イベントを発火
    Handsontable.hooks.run(table, 'onButtonClick');
});
```

![handsontable.gif](https://qiita-image-store.s3.amazonaws.com/0/28302/62bafabc-815f-7490-adc3-8517853283fa.gif)

- `run()` メソッドで、任意のイベントを発火させられる。
    - 第一引数には、イベントを発火する対象となる `Handsontable` インスタンスを渡す。
    - 第二引数に発火するイベント名を渡す。

### イベントハンドラに値を渡す
```js
var grid = document.getElementById('grid');

var table = new Handsontable(grid);

// 独自のイベントハンドラを登録
Handsontable.hooks.add('onButtonClick', function () {
    console.log('click!!');
});

document.getElementById('button').addEventListener('click', function() {
    // イベントを発火
    Handsontable.hooks.run(table, 'onButtonClick', 'foo', 'bar');
});
```

```text:実行結果
["foo", "bar", undefined, undefined, undefined, undefined]
```

- `run()` メソッドのイベント名の後ろに引数を続けると、イベントハンドラにその値が連携される。
- 最大６つまで連携できる（７つ目以降は切り捨てられる）。
    - どうしても７つ以上渡したいなら、配列に詰めて渡せばいいと思う。

### キーコード
```js
var grid = document.getElementById('grid');

var table = new Handsontable(grid);

Handsontable.hooks.add('beforeKeyDown', function (e) {
    if (e.keyCode === Handsontable.helper.KEY_CODES.SPACE) {
        console.log('space!');
    } else if (Handsontable.helper.isKey(e.keyCode, 'ARROW_LEFT|ARROW_RIGHT')) {
        console.log('left or right!!');
    }
});
```

![handsontable.gif](https://qiita-image-store.s3.amazonaws.com/0/28302/d9a1e753-df73-69bd-4e99-452a94e16105.gif)

- `Handsontable.helper.KEY_CODES` に、よく使いそうなキーコードが定義されている。
- `Handsontable.helper.isKey()` メソッドを使えば、 `|` 区切りで複数のキーのいずれかが入力されたことをチェックできる。
- 詳しくは [実装](https://github.com/handsontable/handsontable/blob/master/src/helpers/unicode.js) を参照。


# Editor
セルを編集しているときに表示されるテキストエリアを制御するために、 Editor というオブジェクトが用意されている。

Editor を自作すれば、セルを編集するときの UI を自由に変更できるが、ここでは割愛。詳しくは [公式ドキュメント](http://docs.handsontable.com/0.24.0/tutorial-cell-editor.html) を参照。

ここでは、標準で用意されている Editor の取得方法と、その簡単な使い方だけ。

Editor が提供しているメソッドについては、 [こちら](http://docs.handsontable.com/0.24.0/tutorial-cell-editor.html#common-methods) に記載されている。

## `beginEditing`：セルの編集を開始する
```js
var grid = document.getElementById('grid');

var table = new Handsontable(grid);

document.getElementById('button').addEventListener('click', function () {
    Handsontable.hooks.run(table, 'editCell');
});

Handsontable.hooks.add('editCell', function () {
    // セルを選択して、
    this.selectCell(0, 0);
    // Editor を取得し、
    var editor = this.getActiveEditor();
    // 編集を開始
    editor.beginEditing();
});
```

![handsontable.gif](https://qiita-image-store.s3.amazonaws.com/0/28302/4021887a-2ff7-9d2e-a01d-28877d4d62db.gif)

- `getActiveEditor()` で、現在選択されているセルの Editor オブジェクトを取得できる。
    - イベントハンドラ内では、 `this` は `Handsontable` インスタンスを指す。
    - 先に `selectCell()` でセルを選択しておかないと、 `getActiveEditor()` は `undefined` を返すので注意。
- `beginEditing()` には引数で編集時の初期値を渡すことができる。
    - 初期値を渡さなかった場合は、現在のセルの値で編集が開始される。

## `isOpened`：Editor が表示されているか確認する
```js
var grid = document.getElementById('grid');

var table = new Handsontable(grid);

Handsontable.hooks.add('beforeKeyDown', function (e) {
    var editor = this.getActiveEditor();
    
    if (editor.isOpened() && isArrowKey(e.keyCode)) {
        e.stopImmediatePropagation();
    }
});

function isArrowKey(keyCode) {
    return Handsontable.helper.isKey(keyCode, 'ARROW_UP|ARROW_DOWN|ARROW_LEFT|ARROW_RIGHT');
}
```

![handsontable.gif](https://qiita-image-store.s3.amazonaws.com/0/28302/87a64c15-12e7-ba76-623f-4532a966ebbf.gif)

- `isOpend()` で、 Editor が表示されているかどうかを確認できる。
- 上記例では、 Editor が表示されているときだけ、矢印キーの入力によるセル移動を無効にしている。
    - デフォルトでは、上下の矢印を入力すると、上下のセルに移動する。
    - `stopImmediatePropagation()` は、 Editor によるイベント制御を無効化させるメソッド。


# 参考
- [Tutorial: Introduction | Handsontable - open source spreadsheet solution. Excel-like grid editing with HTML & JavaScript](http://docs.handsontable.com/0.24.0/tutorial-introduction.html)