Web上でExcel風のテーブルを実装するHandsontableを使う-3
【所要時間】
約2時間(2019年5月3日)
【概要】
- 完成ページ
- gitHubレポジトリ
もっともハマった箇所の紹介。
- ドロップダウンの選択肢にapiから取得した配列を入れる。
- 初期データの一部のみを編集不可にする。
【要約・学んだこと】
ドロップダウンの選択肢にapiから取得した配列を入れる。
columnsで下記のようにdropdownの選択肢を設定している。
// Handsontable.vuedata: function() {
return {
test: "testttt",
hotSettings: { ... 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" }
],
....
}
};
},
しかし、sourceから他のデータは参照できない。columnsを関数で記述する方法でも参照できないので、updateSettingsを利用する。難しい部分であるが大丈夫。
// Handsontable.vuedata: function() {
return {
hotSettings: { ... columns: [ ... {
data: "department",
type: "dropdown",
placeholder: "department"
// source: ["test", "test2", "test3"] ←これを削除
},
{ data: "position", type: "text", placeholder: "position" }
], ... }
};
},
created: async function() {
await this.$emit("getTableContents");
await this.setCellMeta();
},
methods: { ... setCellMeta: function() {
const departmentData = this.department; this.$refs.hotTable.hotInstance.updateSettings({
cells: function(row, col, prop) {
const cellProperties = {};
// ドロップダウンメニュー項目の設定
if (prop === "department") {
cellProperties.source = departmentData;
}
return cellProperties;
}
});
},
まずはhotSettingsのcolumnsで設定したsourceを削除。
次にsetCellMeta関数を定義。非常に重要なテクニックとして、setCellMetaの最初に宣言した
const departmentData = this.department;
がある。
$refs内部ではhotInstance以外のthisが参照できないため、this.departmentを一度ここで別の変数に割り当ててあげる必要がある。(重要テク)
また、ここではprop === departmentと一致するならば、という条件にしているが、
cells: function(row, col, prop) {
const cellProperties = {};
// ドロップダウンメニュー項目の設定
if (prop === "department") {
cellProperties.source = departmentData;
}
return cellProperties;
}
col === 4としても同じ結果となる。(propの方がどこに代入しているかコードを見てわかりやすい、順番を変えても変更が不要なためpropを利用)
これで選択肢にapiから取得したdepartmentデータを代入できた。
初期データの一部のみを編集不可にする。
このような設定がしたくなる場合も多々あるが、ここも難しい。先ほどのupdateSettingsに設定を追加する。IEだとうまくいかないパターンなどあるので、かなり繊細な部分となる。
今回は初期データのidとnameのみ編集不可能にする。
初期データが持っているデータがあるかないかで判別するため、今回はinitialというデータを与える。apiから取得しているが、テーブルで表示、しないデータがあればこれは不要。
getTableContents: async function() {
const getData = await api.all([this.getMembers(), this.getDepartment()]);
const membersData = getData[0].data;
const departmentData = getData[1].data;membersData.map(key => (key.initial = true));this.members = membersData;
this.department = departmentData;
},
続いて本題の”初期データのみ編集不可にする”だが、下記のようにする。
// Handsontable.vue
data: function() {
return {
test: "testttt",
hotSettings: { ... columns: [
{ data: "checkbox", type: "checkbox" },
{ data: "id", type: "text", placeholder: "ID" }, ←ここのreadOnlyを削除
{ data: "name", type: "text", placeholder: "name" },
{ data: "mail", type: "text", placeholder: "E-mail" },
{
data: "department",
type: "dropdown",
placeholder: "department"
},
...
}
};
},
watch: {
department: function() {
this.setCellMeta();
}
},
created: async function() {
await this.$emit("getTableContents");
},
methods: { ... setCellMeta: function() {
const departmentData = this.department;this.$refs.hotTable.hotInstance.updateSettings({
cells: function(row, col, prop) {
const cellProperties = {};
// ドロップダウンメニュー項目の設定
if (prop === "department") {
cellProperties.source = departmentData;
}
const table = this.instance.getSourceData();// readOnlyにするセルの設定。
if (table[row]) {
if (prop === "id" && table[row].initial) {
cellProperties.readOnly = true;
}
if (prop === "name" && table[row].initial) {
cellProperties.readOnly = true;
}
}
return cellProperties;
}
});
}, ... }
};
解説すると、
- if(table[row])の意味
const table = this.instance.getSourceData();if (table[row]) {
if (prop === "id" && table[row].initial) {
cellProperties.readOnly = true;
}
if (prop === "name" && table[row].initial) {
cellProperties.readOnly = true;
}
}
if (table[row])とすることで、複数行をペーストした際に発生するundefinedエラーを回避する。実際は初回読み込み時以降、設定を更新したいわけではないが、updateSettingsは何か動作をするたびに実行されてしまう。
一度実行されたら再実行しないようにする関数を書いたが、これがIEだと正常に動作しない。そのためこのような形が万能である。
このif文がないと複数行をペーストした際、下記のようにエラーが発生する。
- watchについて
created: async function() {
await this.$emit("getTableContents");
await this.setCellMeta();
},
としても、getTableContentsはresolveを返さないので非同期処理が行われないので、departmentカラムの選択肢が空になってしまう。
resolveを返すように書き換えるのも大変なので、departmentが更新されたタイミングで実行されるように、createdではなくwatachで実行するとうまくいく。
このようなよくわからない難所を潜り抜けるのがhandsontableの醍醐味。
これで理想通り行の挿入、ペーストなどを行っても問題なく初期データのみが編集不可となった。
【わからなかったこと】
$emitで呼ぶと返り値も変わるのか。
【感想】
またしても業務時とは違う部分でつまずいて困惑。本当に難しいライブラリだけど、今までの経験ですんなり乗り切れたのはハンズオンテーブラーとしては嬉しい限り。
業務では今回紹介した部分でめちゃくちゃハマったので、これを参考にさっと切り抜けてください。