Jspreadsheet のメモ

概要

前置き

自作のWebアプリで使っている The javascript spreadsheet について、備忘録としt

どんなライブラリなのか?

Excel のようなスプレッドシートを Web 上に作成できる Javascript ライブラリです。Excel から直接値を貼り付けることもできますし、JSON からテーブルを作成することも可能です。また、入力値を制限することや、貼り付け前に貼り付けデータをチェックして不適切な値を除外することも可能な多機能なライブラリです。

導入方法

NPM

以下のコマンドでインストールできます。

1npm install jspreadsheet-ce

インストールしたら、.js ファイルの先頭に以下のコードを追加してモジュールを呼び出します。

1import jspreadsheet from 'jspreadsheet-ce';

CDN

HTML ファイルの <head> タグに以下のコードを追加します。

1<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jspreadsheet-ce/dist/jspreadsheet.min.css" type="text/css" />
2<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jspreadsheet-ce/dist/index.min.js"></script>

使い方

HTML ファイルにスプレッドシートを表示する <div> タグを追加します。

1# index.html
2<div id="sourceDataTable"></div>

それから .js ファイルで上記のタグの DOM 要素を使って表を初期化します。このとき、スプレッドシートに表示するデータを指定し、各種設定を行います。

 1// 設定例
 2jspreadsheet(document.getElementById('convertedDataTable'), {
 3  data: initTableData,
 4  columns: columnsConfig,
 5  onbeforechange: beforechangeSourceTableTest,
 6  onbeforepaste: beforePasteConvertedTable,
 7  contextMenu: convertedTableContextMenuItems,
 8  onbeforedeletecolumn: beforeDeleteColumn,
 9  onbeforeinsertcolumn: beforeInsertColumn,
10  text: text,
11  freezeColumns: 2,
12});

基本的な使い方は公式サイトのGetting started with Jspreadsheet CEに掲載されていますので、本記事では、実際に使うにあたって色々調べた結果を備忘録としてまとめます。

コンテキストメニューの設定

デフォルトの状態でコピー・貼り付けなどのメニューが用意されていますが、カスタマイズする場合、スプレッドシートのオプション項目の contextMenu で設定します。

例えば、コンテキストメニューで表示される機能を「行削除・コピー・貼り付け」の3つに限定する場合、次のコードとなります。なお、インラインでも書けると思いますが、さすがにインラインで書くには長いコードなので、定数を使っています。

 1const sourceTableContextMenuItems = (obj, x, y, e) => {
 2  let items = [];
 3  if (obj.options.allowDeleteRow == true) {
 4    items.push({
 5      title: obj.options.text.deleteSelectedRows,
 6      onclick: () => {
 7        obj.deleteRow(obj.getSelectedRows().length ? undefined : parseInt(y));
 8      },
 9    });
10    items.push({
11      title: obj.options.text.paste,
12      shortcut: 'Ctrl + V',
13      onclick: () => {
14        if (obj.selectedCell) {
15          navigator.clipboard.readText().then((text) => {
16            if (text) {
17              jspreadsheet.current.paste(obj.selectedCell[0], obj.selectedCell[1], text);
18            }
19          });
20        }
21      },
22    });
23    items.push({
24      title: obj.options.text.copy,
25      shortcut: 'Ctrl + C',
26      onclick: () => {
27        obj.copy();
28      },
29    });
30  }
31  return items;
32};
33
34// コンテキストメニューの設定部分のみ表示しています(以下同様) 
35jspreadsheet(document.getElementById('sourceDataTable'), {
36  contextMenu: sourceTableContextMenuItems,
37});

上のコードを解説しますと、コンテキストメニューに表示するメニューを格納する items 配列を用意し、そこにメニューを1つずつ格納していきます。各メニューをどうやって設定するかが問題になりますが、いくつかのメニューについては、公式サイトに設定が掲載されています。

しかし、公式サイトに載っていないメニューを設定しようとすると途端に難しくなります。ネットで検索して事例が見つかれば良いのですが、自分の検索方法が悪いのか事例が見つけられなかったので、かなり困りました。

ではどうやって解決したかと言いますと、Github の公式リポジトリの ソースコードの中にあるコンテキストメニューのコードを探し出し、そこから該当するメニューのコードをコピペして対処しました。例えば、貼り付けのコードは以下のリンク先にあり、コードは次のとおりです。

https://github.com/jspreadsheet/ce/blob/8e62e6016d364344aa5a735b918f155ad2afd10b/src/index.js#L7188-L7203

 1// Paste
 2if (navigator && navigator.clipboard) {
 3  items.push({
 4    title: obj.options.text.paste,
 5    shortcut: 'Ctrl + V',
 6    onclick: function () {
 7      if (obj.selectedCell) {
 8        navigator.clipboard.readText().then(function (text) {
 9          if (text) {
10            jexcel.current.paste(
11              obj.selectedCell[0],
12              obj.selectedCell[1],
13              text,
14            );
15          }
16        });
17      }
18    },
19  });
20}

このコードを元にして、以下のとおりコンテキストメニュー用のコードを書きました。上のライブラリのコードとほぼ同じコードで、function をアロー関数に置き換えています。

 1items.push({
 2  title: obj.options.text.paste,
 3  shortcut: 'Ctrl + V',
 4  onclick: () => {
 5    if (obj.selectedCell) {
 6      navigator.clipboard.readText().then((text) => {
 7        if (text) {
 8          jspreadsheet.current.paste(
 9            obj.selectedCell[0],
10            obj.selectedCell[1],
11            text,
12          );
13        }
14      });
15    }
16  },
17});

コピーも貼り付けと同様に公式サイトに事例が掲載されていないため、貼り付けと同様に Github のコードを元に実装しました。

1// Copy
2items.push({
3  title: obj.options.text.copy,
4  shortcut: 'Ctrl + C',
5  onclick: function () {
6    obj.copy(true);
7  },
8});

メニューの日本語化

以下のコードのようにオブジェクト形式で「英語→日本語」の対応関係を定義した定数を用意して、その定数を表の初期化の際に text キーの値に指定すればOKです。

 1const text = {
 2  deleteSelectedRows: '選択した行を削除',
 3  copy: '表の値をコピー',
 4  paste: '表に値を貼り付け',
 5};
 6
 7jspreadsheet(document.getElementById('sourceDataTable'), {
 8  ~~~
 9  text: text,
10  ~~~,
11});

テーブルのデータ削除

テーブルのデータを削除するには、テーブルのインスタンスから setData() メソッドを呼び出して空の配列を引数として渡します。

 1/* テーブルの構造
 2| X     | Y    |
 3|-------|------|
 4| 110.1 | 51.1 |
 5| 120.2 | 52.2 |
 6| 130.3 | 53.3 |
 7*/
 8const sourceTable = jspreadsheet(document.getElementById('sourceDataTable'), {
 9  ~~~
10});
11const tableData = [[,]];
12sourceTable.setData(tableData);

入力規則の設定

Excel の入力規則と同様にセルに入力できる値を制限することができます。この実装方法は2つあります。

columns オプションを利用する

1つ目の実装方法は、columns オプションの type キーに値の型を設定するという方法です。例えば、2つの列をいずれも数値のみ入力できる列とする場合、type'numeric' を設定します。

1const columnsConfig = [
2  { type: 'numeric', title: 'X', name: 'x' },
3  { type: 'numeric', title: 'Y', name: 'y' },
4];
5
6jspreadsheet(document.getElementById('sourceDataTable'), {
7  columns: columnsConfig,
8});

同様に、任意の列をテキストのみ受け付ける列にしたり、日付のみ受け付ける列にしたりできます。列に設定できる入力値の型は、公式サイトの Column types に掲載されています。

onbeforechange イベントを利用する

2つ目の実装方法は、onbeforechange イベントのコールバック関数を使って入力しようとした値を入力前の段階でチェックして、入力して欲しくない値を入力させないという方法です。例えば、数値のみ入力させたいが、全角数値は半角数値に変換して受け入れるという条件にしたい場合、以下のように onbeforechange イベントのコールバック関数で入力値を処理することで、そうした複雑な条件の入力規則を設定できます。

入力値は value という変数で取得できますので、この変数をチェックして、全角・半角数値が入力されたなら半角数値に変換して受け入れて、全角・半角数値以外が入力されたなら false を返すことで、全角・半角数値のみ受け入れるという入力規則を実現できます。

なお、コールバック関数に渡す引数については、value しか利用しない場合でも instancecellxy といった引数を渡す必要があるようです(渡さないとエラーになりました)。

 1const beforechangeSourceTable = (instance, cell, x, y, value) => {
 2  if (isValidNumber(zen2han(value))) {
 3    return zen2han(value);
 4  } else {
 5    return '';
 6  }
 7};
 8const sourceTable = jspreadsheet(document.getElementById('sourceDataTable'), {
 9  data: initTableData,
10  columns: columnsConfig,
11  onbeforechange: beforechangeSourceTable,
12  contextMenu: sourceTableContextMenuItems,
13  onbeforedeletecolumn: beforeDeleteColumn,
14  onbeforeinsertcolumn: beforeInsertColumn,
15  onpaste: afterPaste,
16  text: text,
17  freezeColumns: 2,
18});

info: なお、ここで登場する isValidNumber()zen2han() 関数は自作関数で、前者は数値のように見える値は数値に変換して返すという関数で、後者は全角数値を半角数値に変換して返すという関数です。

表の編集禁止

結果を表示するだけで編集を予定していない表を作成したい場合、表を読み取り専用にすることができます。

方法は、表の初期化の際に設定する columns オプションに readOnly キーを追加して値を true にするというものです(公式サイトの実装例に掲載されています)。columns オプションで設定することから、読み取り専用にするか否か列単位で決めていきます。そのため、表全体を読み取り専用にする場合、全ての列を読み取り専用にする必要があります。

1const columnsConfig = [
2  { type: 'numeric', title: 'X', width: 180, name: 'x', readOnly: true },
3  { type: 'numeric', title: 'Y', width: 180, name: 'y', readOnly: true },
4];
5jspreadsheet(document.getElementById('sourceDataTable'), {
6  columns: columnsConfig,
7});

これで表が読み取り専用になるとともに、セルの値がグレーアウトして読み取り専用であることを示すようになります。

また、表の値を変更しても元に戻ってしまう状態にすることで、データの読み取りにしか使えない表を作ることもできます。

方法は、表の初期化の際に設定する onbeforechange イベントにコールバック関数を設定し、変更前のセルの値をその関数の戻り値にするというものです。ここで使用している getValueFromCoords() メソッドは、XYで指定されたセルの値を返すメソッドです。

1jspreadsheet(document.getElementById('sourceDataTable'), {
2  onbeforechange: (instance, cell, x, y, value) => {
3    return instance.jspreadsheet.getValueFromCoords(x, y);
4  },
5});

貼り付け無効化

表に値を貼り付けできないようにすることもできます。

方法は、表の初期化の際に設定する onbeforepaste イベントにコールバック関数を設定して、そのコールバック関数の戻り値を false にするというものです。公式サイトのFAQに掲載されています。

1jspreadsheet(document.getElementById('spreadsheet'), {
2  onbeforepaste: (instance, data, x, y) => {
3    return false;
4  }
5});