Web上でExcel風のテーブルを実装するHandsontableを使う-2
【所要時間】
約2時間半(2019年5月2日)
【概要】
- 完成ページ
- gitHubレポジトリ
- Web上でExcel風のテーブルを実装するHandsontableを使う-1
- Web上でExcel風のテーブルを実装するHandsontableを使う-2(このページ)
- Web上でExcel風のテーブルを実装するHandsontableを使う-3
- Web上でExcel風のテーブルを実装するHandsontableを使う-4
【要約・学んだこと】
セルのタイプを変更する
テキストだけでなくチェックボックスやドロップダウンセルが使える。また、placeholderやreadOnlyも設定できる。全て公式ドキュメントに書いてあるが、ドキュメントの量が膨大で探しきれなかったり、解説が少ない箇所があるので頑張る必要がある。
// Handsontable.vuehotSettings: {
colHeaders: ["", "ID", "Name", "E-mail", "Department", "Position"],
rowHeaders: true,
columns: [
{ data: "checkbox", type: "checkbox" },
{ data: "id", type: "text", placeholder: "ID", readOnly: true },
{ data: "name", type: "text", placeholder: "name" },
{ data: "mail", type: "text", placeholder: "E-mail" },
{
data: "department",
type: "dropdown",
placeholder: "department",
source: ["test", "test2", "test3"]
},
{ data: "position", type: "text", placeholder: "position" }
],
contextMenu: {
items: {
row_above: true,
row_below: false,
remove_row: {
name: "行を削除"
}
}
},
minSpareRows: 1
}
colHeaders、rowHeaders: falseにするとヘッダーはなくなる。(デフォルトはfalseなので、何も記述しなければなくなる。)今回はカラムのヘッダーにタイトルを追加。タイトルは配列で書けば良い。空白にしたい箇所は空白にすればOK.
columns:
- チェックボックスを追加
- IDを書き換え不可
- 各セルにplaceholderを与える
- Departmentセルをドロップダウン形式に変更。選択肢はsourceに配列で記述できる。ポジションもドロップダウンにしたいところだが、面倒なのでそのまま。
minSpareRows: ここで設定した数だけ空白のセルが自動で追加される。今回はapiから初期データを取得するが、新規ユーザーの追加も行いたいので1行空白のセルを作成。placeholderの中身が表示されている。
ご覧の通り自動で行が追加されていく。複数行をまとめてペーストしてもその分自動で列が追加される。すごい。
初期データをからの状態でplaceholderや配列のキーを与えたい場合にはdataSchemaを設定する必要がある。また、そうでない場合も行を追加した際の配列の初期データになるので、dataSchemaは設定した方がいい。
https://handsontable.com/docs/6.2.2/Options.html#dataSchema
dataSchema: {id: null, name: null, mail: null, department: null, position: null},
本題と関係ないが、jsonデータが10件あると見にくいので5件に減らす。db.jsonのid006~id010を削除。こういう時直接jsonファイルをいじれるjson-serverは楽。
contextMenu: 右クリックメニューが追加できる。itemsに上の行に挿入、下の行に挿入、行の削除を設定。nameオプションで右クリックメニューに表示される内容を変更できる。
HandsontableのHooksを利用する
handsontableにはHooksという関数的なものが用意されている。例えば行を削除する前に何かを実行したい場合、beforeRemoveRowを使う。
//Handsontable.vuehotSettings: {...beforeRemoveRow: function(index, amount, physicalRows, source) {
console.log(index);
console.log(amount);
console.log(physicalRows);
console.log(source);
}}
ドキュメントにあるように、beforeRemoveRowは4つ引数をもっている。
1行目(index:0)を削除すると
上記のデータを取得することが可能。
2,3行目をまとめて削除すると
このようになる。
また、非常に難しい部分として、hotSettings内でthisを参照するとhandsontableが持っているHooksを参照してしまう。
回避策としては、vueのmethodsの関数を参照させる方法がある。超重要テクニックである。
//Handsontable.vuedata: function(){
return {
test: "testttt"
}
},hotSettings: {
...beforeRemoveRow: this.beforeRemoveRowVue},...
methods: {
beforeRemoveRowVue: function(index, amount, physicalRows, source) {
console.log(this.test);
console.log(index);
console.log(amount);
console.log(physicalRows);
console.log(source);
}
}
コードとしてはわかりにくくなるかもしれないが、拡張性を考えると、hotSettingsでHooksを使う場合は、思考停止してmethodsの関数を参照するようにした方がいいと思う。
HooksをhotSettings以外から利用する。
今度は逆にHooksをHandsontable以外から利用する方法。これにはrefを使う。今回はテスト用に現在のテーブルデータを取得するボタンを設置する。
// Handsontable.vue<template>
<div>
<button @click="getTableData">tableData</button>
<HotTable ref="hotTable" :data="members" :settings="hotSettings"/>
</div>
</template>...methods: {
getTableData: function() {
console.log(this.$refs.hotTable.hotInstance.getData());
},
}
ご覧の通りtableDataというボタンをクリックすると、テーブルのデータが取得できる。ややこしい点は、
this.$refs.hotTable.hotInstance.getData()
hotInstanceの中に関数が入っているという点。($refsはhandsontableではなくvueの機能。)
getTableData: function() {
console.log(this.$refs.hotTable);
console.log(this.$refs.hotTable.hotInstance);
console.log(this.$refs.hotTable.hotInstance.getData());
},
わからなくなった際は上記のように何を参照しているか愚直に見ていくと解決策が見つかることが多い。($refsの内部ではhotSettingsと同様、thisが参照するのはhandsontableのHooksになる。)
テスト用のボタンを追加
- 現在のテーブルデータを取得するボタン
- apiから取得した配列を持つmembersデータを取得するボタン
が欲しい。
現在のテーブルデータを取得するボタンは今作ったが、配列のkeyが取得できない。そこでgetSourceDataというHooksを使う。
getTableData: function() {
console.log(this.$refs.hotTable.hotInstance.getSourceData());
},
表示されているデータだけが必要ならgetDataでよいが、getSourceDataの方がmembersと同じコレクション形式でわかりやすい。
membersを取得するボタンは特筆する点はない。
// Handsontable.vue<template>
<div>
<button @click="getTableData">tableData</button>
<button @click="getMembers">members</button>
<HotTable ref="hotTable" :data="members" :settings="hotSettings"/>
</div>
</template>
...
methods: {
getMembers: function() {
console.log(this.members);
},
}
これで画面に表示されているテーブルデータと、TableIndex.vueのdata、membersが取得しやすい。
テーブル上のデータとmembersのデータを一致させる。
テーブル上に見えるデータとmembersのデータを一致させたい場合注意が必要。テーブルに初期表示されているデータを編集すると、membersのデータも修正される。
しかし、行の削除や、新たに登録したデータは反映されない。
tableDataはしっかりと反映されている。
- テーブルの内容をmembersに即時反映させる。
現在のテーブルのデータが取得できるので、シンプルにそのデータをmembersに突っ込む。
まずはTableIndexのデータを変更する関数を仕込む
// TableIndex.vue<template>
<Handsontable
:members="members"
:department="department"
@getTableContents="getTableContents"
@updateMembers="updateMembers"
/>
</template><script>... data: function() {
return {
members: [],
department: []
};
},
methods: {
... updateMembers: function(payload) {
this.members = payload;
}
}
};
</script><style></style>
afterChangeを使う。結構難しい。
// Handsontable.vuedata: function() {
return {
test: "testttt",
hotSettings: {...
beforeRemoveRow: this.beforeRemoveRowVue,
afterChange: this.afterChangeVue
}
};
},methods: {
...
afterChangeVue: function(changes, source) {
if (source === "loadData") {
return;
}
const tableData = this.$refs.hotTable.hotInstance.getSourceData();
this.$emit("updateMembers", tableData);
}
}
sourceがloadDataの時(初期読み込み)の際はreturnするようにしないとバグる。
pushやconcatを使ってmembersを更新→renderを使ってテーブルを更新。というやり方も試したが、パフォーマンスが低下する上にバグる場合があり、何かと難しいので、そのまま現在のtableのdataを代入してあげるのがいいと思われる。
また、afterChangeのsourceを見るとわかるが、文字入力とペーストは違うソースになる。
他のHooksを利用する際に、これを知っていないとペーストがうまく機能しなくなるので覚えておこう。(e.g. セルの内容を編集したら何かを発火。という場合、ペーストした場合はフォーカスしたセル以外は何も起きていない。同じ内容でも文字入力で打ち込んだ場合と、ペーストでまとめて入力した場合異なる動作をしてしまう。)
Web上でExcel風のテーブルを実装するHandsontableを使う-3 へ続く。
【わからなかったこと】
なし。
【感想】
業務で実装した内容のまとめを書いていたつもりだったが、実装した時に気がつかなかったdataSchemaの重要性に気がついた。
テーブルのデータをmembersに入れる方法は気が付いていたが、試していなかったので、問題なくできてよかった。
しかしどこでうまくいかなくなるかわからないのがhandsontableなので、引き続き慎重に使おう。