Web上でExcel風のテーブルを実装するHandsontableを使う-4

Tatsuya Asami
19 min readMay 4, 2019

--

【所要時間】

約2時間(2019年5月4日)

【概要】

  • 完成ページ
  • gitHubレポジトリ

【要約・学んだこと】

カスタムHooksを追加する

カスタムHooksも追加できる。最大文字数制限をカスタム。

参考サイト

公式サイト

// Handsontable.vu<script>
import { HotTable } from "@handsontable/vue";
import Handsontable from "handsontable";
const MaxLengthEditor = Handsontable.editors.TextEditor.prototype.extend();MaxLengthEditor.prototype.prepare = function() {
Handsontable.editors.TextEditor.prototype.prepare.apply(this, arguments);
this.TEXTAREA.maxLength = this.cellProperties.maxLength;
};
Handsontable.editors.MaxLengthEditor = MaxLengthEditor;
Handsontable.editors.registerEditor("maxlength", MaxLengthEditor);
export default {
name: "Handsontable",
components: {
HotTable
},
...data: function() {
return {
test: "testttt",
hotSettings: {
...
columns: [
{ data: "checkbox", type: "checkbox" },
{ data: "id", type: "text", placeholder: "ID" },
{
data: "name",
type: "text",
placeholder: "name",
maxLength: 15,
editor: "maxlength"
},
... ],
}
};
},

Handsontableの本体をインポート

import Handsontable from "handsontable";

カスタムの内容を定義

const MaxLengthEditor = Handsontable.editors.TextEditor.prototype.extend();MaxLengthEditor.prototype.prepare = function() {
Handsontable.editors.TextEditor.prototype.prepare.apply(this, arguments);
this.TEXTAREA.maxLength = this.cellProperties.maxLength;
};
Handsontable.editors.MaxLengthEditor = MaxLengthEditor;
Handsontable.editors.registerEditor("maxlength", MaxLengthEditor);

簡単に説明すると”maxLength”を定義している。

定義内容がよくわからないというのはJavaScriptの知識の問題だと思うので頑張っていただきたい。私はよくわかっていない。

定義した関数を呼び出し。

{
data: "name",
type: "text",
placeholder: "name",
maxLength: 15,
editor: "maxlength"
},
わかりにくいが16文字目以降が入力できない

カスタム用のファイルを作成して、そこで定義するとナイスでしょう。

// CustomHooks.jsimport Handsontable from "handsontable";export const MaxLengthEditor = Handsontable.editors.TextEditor.prototype.extend();
MaxLengthEditor.prototype.prepare = function() {
Handsontable.editors.TextEditor.prototype.prepare.apply(this, arguments);
this.TEXTAREA.maxLength = this.cellProperties.maxLength;
};
Handsontable.editors.MaxLengthEditor = MaxLengthEditor;
Handsontable.editors.registerEditor("maxlength", MaxLengthEditor);

Handsontable.vueではimportしてあげればいい。

// Handsontable.vueimport { maxLength } from "./CustomHooks.js";...

バリデーションチェックを追加

  • セルの編集後にチェック

用意されているオプション、正規表現、関数で定義できる。また、カスタムで追加することも可能のようだ。

メールアドレスの正規表現を入れてみる。

// Handsontable.vuedata: function() {
return {
test: "testttt",
hotSettings: {

...
columns: [ ...
{
data: "mail",
type: "text",
placeholder: "E-mail",
validator: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
},

動作している。

  • 空セルのチェック

allowEmptyをfalseにすると空セルのチェックも行うことができる。デフォルトでfalseになっているが、typeによっては動作しない。
その場合はvalidatorに何らかの設定をして確認する。(textの場合はvalidatorが必要。validatorを設定することで allowEmptyが作動するというよりは、validatorが走ることで空欄がエラーになっているといえる。)

空セルを許可したいはtrueを指定する。

// Handsontable.vue
data: function() {
return {
test: "testttt",
hotSettings: {
... columns: [
{ data: "checkbox", type: "checkbox" },
{
data: "id",
type: "text",
placeholder: "ID",
allowEmpty: false,
validator: "numeric"
},
...

allowEmpty: trueにすると

空欄にバリデーションチェックが走らない。チェックが入るタイミングはセルを編集状態にして解除したタイミング。つまり何も編集していないタイミングではそのまま、一度編集したセルでチェックが走る。

allowEmptyをtrueにしても、正規表現でvalidatorを指定している場合はチェックが入ってしまう。

{
data: "mail",
type: "text",
placeholder: "E-mail",
allowEmpty: true,
validator: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
},
  • まとめてチェック

その都度バリデーションチェックが走るのが嫌な場合は、validateCellsを使えばテーブルの一括チェックができる。

例えば初期データにバリデーション違反しているデータを入れてみると

警告はでない。

validateCellsを実行すると

バリデーションチェックが走る。

エラーがあればfalse,なければ trueが返ってくるので、別の関数を合わせて実行するなどができる。

// Handsontable.vue...methods: {
getTableData: function() {
console.log(this.$refs.hotTable.hotInstance.getSourceData());
this.$refs.hotTable.hotInstance.validateCells(valid => {
if (valid) {
console.log(valid);
} else {
console.log(valid);
}
});
},

エラーがなければtrue

エラーがあるとfalseを返す。

しかし、minSpareRowsを設定していると、自動で追加された列にもvalidationが走ってしまい、必ずfalseになってしまう。使いどころが難しい・・・

似たようなHooksにvalidateColumns, validateRowsがある。邪道な気がするが、未編集の空行を無視したければ、validateRowsを使えばうまくできる。

getTableData: function() {
const tableData = this.$refs.hotTable.hotInstance.getSourceData();
console.log(tableData);
// numOfRowsが現在のテーブルの行-1までの配列になるようにする。
const numOfRows = [];
for (let i = 0; i < tableData.length - 1; i++) {
numOfRows.push(i);
}
console.log(numOfRows);
const numberOfTableRows = this.$refs.hotTable.hotInstance.validateRows(
numOfRows,
valid => {
if (valid) {
console.log(valid);
} else {
console.log(valid);
}
}
);
},

[0,1,2,3,4,5]まであるテーブルの[0,1,2,3,4]までしかチェックしないので、最後に必ず存在する空行以外をチェックし、最後の行を無視する。

これを使えば、post送信時などにfalseがある場合はエラーメッセージを表示、といったことができる。

// Handsontable.vue...getTableData: function() {
const tableData = this.$refs.hotTable.hotInstance.getSourceData();
console.log(tableData);
// numOfRowsが現在のテーブルの行-1までの配列になるようにする。
const numOfRows = [];
for (let i = 0; i < tableData.length - 1; i++) {
numOfRows.push(i);
}
console.log(numOfRows);
const numberOfTableRows = this.$refs.hotTable.hotInstance.validateRows(
numOfRows,
valid => {
if (!valid) {
console.log(valid);
alert("空欄または不正なデータが入力されています。");
return;
}
// ここに次の処理を書く。
alert("送信完了しました!");
}

);
},

Gifで動作を見てみよう。

この状態だとボタンが紛らわしいのでPOSTボタンと関数を追加。

さらに、何も編集していなければpostしないようにするため、また編集している行を取得するためafterBeginEditingを使う。

最終行の入力を開始しているが、セルの編集を解除する前にPOSTを押すとバリデーションチェックが行われずに送信されてしまうので、その対応をする。

// Handsontable.vue<template>
<div>
<button @click="getTableData">tableData</button>
<button @click="getMembers">members</button>
<button @click="postSimulate">POST!</button>
<HotTable ref="hotTable" :data="members" :settings="hotSettings"/>
</div>
</template>
... data: function() {
return {
test: "testttt",
editedRow: null, ←編集中のセルの値が代入される。
hotSettings: {

...
minSpareRows: 1,
afterBeginEditing: this.afterBeginEditingVue, ←セルの編集をすると発動
afterChange: this.afterChangeVue
}
};
},
methods: {
getTableData: function() {
const tableData = this.$refs.hotTable.hotInstance.getSourceData();
console.log(tableData);
},

getMembers: function() {
console.log(this.members);
},
postSimulate: function() {
const tableData = this.$refs.hotTable.hotInstance.getSourceData();
// 何も編集していなければここで終了
if (!this.editedRow) {
return;
}
// numOfRowsが現在のテーブルの行-1までの配列になるようにする。
const numOfRows = [];
for (let i = 0; i < tableData.length - 1; i++) {
numOfRows.push(i);
}
// 最終行を編集し始めていたらバリデーションチェックする。
if (this.editedRow === tableData.length - 1) {
numOfRows.push(this.editedRow);
}
this.$refs.hotTable.hotInstance.validateRows(numOfRows, valid => {
if (!valid) {
console.log(valid);
alert("空欄または不正なデータが入力されています。");
return;
}
// ここに次の処理を書く。
alert("送信完了しました!");
});
},

afterBeginEditingVue: function(row) {
this.editedRow = row; ←編集中の行を取得
}

}
};
</script>
<style src="../../node_modules/handsontable/dist/handsontable.full.css"></style>

json-serverはローカルサーバーを立てなければいけないので、デプロイしにくい。今回はデモのため、api取得をやめて、生の配列をhandsontableに入れるように修正した。

// TableIndex.vuegetTableContents: function() {
const membersData = [
{
id: 1,
name: "Paul",
mail: "paul@mail.com",
department: "Marketing",
position: "Manager"
},
{
id: 2,
name: "Tom",
mail: "tom@mail.com",
department: "Engineering",
position: "Manager"
},
{
id: 3,
name: "Ethan",
mail: "tom@mail.com",
department: "Accounting",
position: "Manager"
},
{
id: 4,
name: "Michael",
mail: "tom@mail.com",
department: "Engineering",
position: "Manager"
},
{
id: 5,
name: "Ann",
mail: "tom@mail.com",
department: "Marketing",
position: "Manager"
}
];
membersData.map(key => (key.initial = true));const departmentData = ["Marketing", "Engineering", "Accounting"];this.members = membersData;
this.department = departmentData;
},

最後に

抜け道を探しながら実装する必要があるhandsontableだが、多機能で、なおかつexcel風。実装する前に要件の確認を行い、うまく行きそうなら使ってみるのもありだと思われる。何が行われているのかよくわからないがとにかくすごい。

updateSettingsが何回実行されているのか計測してしてみたのでご覧あれ。(testが何回実行されるか)

初回の読み込みで180回実行され、セルに何かを入力するたびに実行が繰り返されていく。ちなみになぜかIEだとこの実行回数がさらに増える。

そういう実装になっております。

以上。

【わからなかったこと】

なぜ休日になると普段思い浮かばないコードが思い浮かぶのか。

Added non-passive event listener to a scroll-blocking ‘wheel’ event. Consider marking event handler as ‘passive’ to make the page more responsive.

これ無視してるけどどうしたらいいんだろう。

【感想】

1ヶ月くらい戯れたおかげでだいぶ詳しくなった気がするが、vue/cliの動作をもっと知っていないと大怪我すると思うので、そっち方面をもっとやらないといけないと思った。

--

--

Tatsuya Asami
Tatsuya Asami

Written by Tatsuya Asami

Front end engineer. React, TypeScript, Three.js

No responses yet