Handsontableに新しいコンテキストメニューを追加する

Handsontableに新しいコンテキストメニューを入れていく記事です。
目指すのはこんな感じです。
![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230043/8adeccb0-fdab-56e4-1c5b-9c98875a29b6.jpeg)
コンテキストメニューから転置を選ぶと
![2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230043/858ab459-7938-bc1b-1fe8-c35884ffc5a2.jpeg)
行列がひっくり返る。

いろいろ汎用的にしたもの→https://github.com/misogihagi/HandsontableContextmenu
今回使うソースコード

```html:index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>コンテキストメニュー例</title>
    <link rel="stylesheet" type="text/css" href='handsontable.full.min.css'>
    <script src='handsontable.full.min.js'></script>
    <script src='ja-JP.js'></script>
</head>
<body>
    <div id="handsontable" class="handsontable"></div>
    <script>
        var container = document.getElementById('handsontable');
        var hot = new Handsontable(container, {
            data: Handsontable.helper.createSpreadsheetData(20, 20),
            contextMenu: {
                items: {
                    'transpose': {
                        key: 'transpose',
                        name() {
                                    return this.getTranslatedPhrase(Handsontable.languages.dictionaryKeys.CONTEXTMENU_ITEMS_TRANSPOSE);
                                },
                        callback: function(key, selection, clickEvent) {
                           if(cells[1]){
                           alert(this.getTranslatedPhrase(Handsontable.languages.dictionaryKeys.CONTEXTMENU_ITEMS_TRANSPOSE_ALERT))
                           return
                           }
                           var src=hot.getData(cells[0].start.row,cells[0].start.col,cells[0].end.row,cells[0].end.col)
                           var cols=src.length
                           var rows=src[0].length
                           var tra=new Array(rows)
                           for (var row = 0; row < rows; row++)    {
                               tra[row]=new Array(cols)
                               for (var col = 0; col < cols; col++)        {
                                   tra[row][col] = src[col][row];
                               }
                           }
                           hot.populateFromArray(cells[0].start.row,cells[0].start.col,tra)
                        }
                    }
                }
            },
            licenseKey: 'non-commercial-and-evaluation'
        }
    </script>
</body>
</html>
```
ではやっていきましょう。

## 1まずは見た目から

コンテキストメニューには3つの指定方法があります。
* 一つ目はtrueにすること。
* 二つ目は配列にすること。
* 三つ目に全てをオブジェクトで表現することです。
一つ目の場合はデフォルトになり、二つ目の場合はメニューを選べるものの新しいメニューを作る他で作る必要があります。
三つ目の方法はコンテキストメニュー内にごちゃごちゃ書いていくので少ないコードならこの方法かと思います。今回は三つ目です。

コンテキストメニューにnameプロパティを設定するだけで簡単に実装できます。

```javascript:&lt;script&gt;内
var hot = new Handsontable(container, {
    data: Handsontable.helper.createSpreadsheetData(20, 20),
    contextMenu: {
        items: {
            'transpose': {
                name: 'transpose',
            }
        }
    }
}
```
この転置にいろいろいじっていくことになります。

## 2本処理
コンテキストメニューにはコールバック関数が備わっています。コンテキストメニュー以外にもHandsontable自体コールバック関数やイベントフックを備えかなり柔軟な開発ができますがここでは割愛します。
コールバック関数には3つの引数が渡されます。

```javascript
callback: function(key, selection, clickEvent) {}
```
* 一つ目の引数はメニュー自体のキーで、ただの文字列を返します。
* 二つ目の引数はオブジェクトの配列で選択されているセルの範囲の配列で、startプロパティとendプロパティを持ちその中にrowプロパティとcolプロパティがあります。
* 三つ目の引数はクリックイベントになります。

今回は二つ目の引数を使い、このプロパティからgetDataメソッドでデータを取得します。

```javascript:callback内
var src=hot.getData(cells[0].start.row,cells[0].start.col,cells[0].end.row,cells[0].end.col)
```
取得したデータを入れ替えます。多分これが一番速いと思います。

```javascript:callback内
var cols=src.length
var rows=src[0].length
var tra=new Array(rows)
for (var row = 0; row < rows; row++)    {
tra[row]=new Array(cols)
    for (var col = 0; col < cols; col++)        {
    tra[row][col] = src[col][row];
    }
}
```
いよいよ貼り付けです。公式のドキュメントにはセルに値を設定するのにsetDataAtCellかsetDataAtRowPropしかありません。
どうにか値を渡して一気に更新する方法はないかと探していたらありました。

```javascript:callback内
hot.populateFromArray(cells[0].start.row,cells[0].start.col,tra)
```
ググってもないメソッドで説明もあったもんじゃありませんが使い方がシンプルでわかりやすいです。さすがHandsontableですhandsomeですね。

ところで行列がひっくり返っても大丈夫なのは矩形であるからです。複数のセルが飛び飛びだと困るので最初にアラートをだし、処理を終了させます。

```javascript:callback内
if(selection[1]){
alert(this.getTranslatedPhrase(Handsontable.languages.dictionaryKeys.CONTEXTMENU_ITEMS_TRANSPOSE_ALERT))
return
}
```
これでオリジナルのコンテキストメニューができました。

## 3国際化
このままでもいいのですが、この際国際化してしまいましょう。

Handsontableにはキーと辞書が備わっています。Handsontableはキーと辞書を照らし合わせながら国際化をしています。

Handsontable.languages.dictionaryKeysのあとに任意の名前でプロパティを登録すれだけでキーは登録されます。

```javascript:&lt;script&gt;内
var dickey=Handsontable.languages.dictionaryKeys
dickey.CONTEXTMENU_ITEMS_TRANSPOSE= "ContextMenu:items.transpose"
```
一方辞書には登録用のメソッドが備わっています。とはいえこの登録用のメソッドをそのまま使うと辞書を破壊してしまいますので、先に変数に辞書を格納し、その変数に名前を追加し、その変数を登録するのをおすすめします。
またここではアラートの中身も辞書に入れ国際化しています。

```javascript:&lt;script&gt;内
var dickey=Handsontable.languages.dictionaryKeys
dickey.CONTEXTMENU_ITEMS_TRANSPOSE= "ContextMenu:items.transpose"
dickey.CONTEXTMENU_ITEMS_TRANSPOSE_ALERT= "ContextMenu:items.transpose.alert"
var dic=Handsontable.languages.getLanguageDictionary('en-US')
dic['ContextMenu:items.transpose']= "transpose"
dic['ContextMenu:items.transpose.alert']= "'transpose' does not support multiple selection"
Handsontable.languages.registerLanguageDictionary('en-US',dic)
```


英語でも同じような作業をしていきます。あとはあなたの語学力次第です。

```javascript:&lt;script&gt;内
dic=Handsontable.languages.getLanguageDictionary('ja-JP')
dic['ContextMenu:items.transpose']= "転置"
dic['ContextMenu:items.transpose.alert']= "「転置」は複数セルを扱えません"
Handsontable.languages.registerLanguageDictionary('ja-JP',dic)
```
これでコンテキストメニューは国際化され、コントロールキーで複数セルを選択してから転置するとアラートも国際化されているはずです。

冒頭のgithubのページはこれまで説明したものをコンストラクタにしたものです。復習もかねてコンテキストメニューから言語を変えられるようにメニューを調整しました。