はじめに
SpreadJSは、ExcelライクなスプレッドシートをWebページに表示できる、メシウスのJavaScriptライブラリです。SpreadJSは表計算コンポーネントとしてのコンセプトから、バックエンドのデータを表示・更新するデータグリッドとしては使いづらいところがありました。そこでSpreadJSのバージョン15.1で、データグリッドとしての利用を想定した「テーブルシート」の機能が追加されました。本記事ではSpreadJSのテーブルシートについて、利用法を説明していきます。
対象読者
- バックエンドのデータをWebページに表示するデータグリッド部品を探している方
- これまでSpreadJSをデータグリッドとして利用していた方
- バックエンドとWebページとのデータ同期を手間をかけずに実現したい方
必要な環境
本記事のサンプルコードは、以下の環境で動作を確認しています。
- Windows 10 64bit版
- SpreadJS 17.0.4
- Node.js 20.11.1 64bit版
- Microsoft Edge 122.0.2365.92
サンプルコードを実行するには、まずSpreadJSトライアル版ZIPファイルの「SpreadJS_Release/SpreadJS-Libs/SpreadJS」フォルダー内のcss、scriptsフォルダーを、サンプルコードのspreadjsフォルダーにコピーしてください。トライアル版は公式ページからダウンロードできます。コピー後、「npm install」コマンドでWebページ表示用の簡易Webサーバー(lite-server)をインストールして、「npm run start」コマンドを実行すると、Webブラウザーで「http://localhost:3000」としてWebページが表示されます。
テーブルシートの基本的な実装方法
ここでは基本的なデータ表示方法の説明のため、サンプルではデータに静的な配列データを使用しています。 実際のアプリケーションで、データソースと連結してCRUD処理を実装する方法については、後述の「バックエンドとのデータ同期機能」をご覧ください。
図1のサンプルで、テーブルシートの基本的な実装方法を説明します。データ追加用の行やカレンダーによる日付入力といった、データグリッドとしての利用に便利な機能が、基本的な実装だけで利用できるようになります。
index.htmlの実装はリスト1の通りです。(1)はSpreadJSを日本語で表示する指定、(2)はSpreadJSのCSSとJavaScriptを参照する記述です。テーブルシートのJavaScriptはSpreadJS本体とは別に(2a)で参照します。(3)はテーブルシートを表示する要素です。
<head> <!-- SpreadJSのカルチャ指定 ...(1)--> <meta name="spreadjs culture" content="ja-jp" /> <!-- SpreadJSを参照 ...(2)--> <link rel="stylesheet" href="spreadjs/css/gc.spread.sheets.excel2016colorful.17.0.4.css"> <script src="spreadjs/scripts/gc.spread.sheets.all.17.0.4.min.js"></script> <script src="spreadjs/scripts/plugins/gc.spread.sheets.tablesheet.17.0.4.min.js"></script> <!--(2a)--> <script src="spreadjs/scripts/resources/ja/gc.spread.sheets.resources.ja.17.0.4.min.js"></script> </head> <body> <div id="table-sheet"></div> <!-- テーブルシートを表示する要素 ...(3)--> </body>
JavaScript(index.js)の実装内容を説明します。リスト2はSpreadJSの初期化と、データマネージャーを利用してテーブルを定義するまでの実装です。
// SpreadJSライセンスキーの設定 ...(1) GC.Spread.Sheets.LicenseKey = '<ライセンスキー>'; // SpreadJSの初期化 ...(2) const spread = new GC.Spread.Sheets.Workbook('table-sheet', { sheetCount: 0 }); // データマネージャーを取得 ...(3) const dataManager = spread.dataManager(); // phonesTableテーブルを定義 ...(4) const phonesTable = dataManager.addTable('phonesTable', { data: [ // ...(4a) { vendorId: 1, name: 'iPhone 15', price: '124800', releaseDate: '2023-09-22' }, (略) ], schema: { // ...(4b) columns: { name: { dataType: 'string' }, price: { dataType: 'number' }, vendorName: { dataName: 'vendorId', dataMap: { 1: 'Apple', 2: 'Samsung', 3: 'Xiaomi' } }, releaseDate: { dataType: 'date', dataPattern: 'yyyy-MM-dd' } } } });
(1)はSpreadJSのライセンスキー設定です。ライセンスについての詳細は公式ページを参照してください。(2)でSpreadJSを初期化します。GC.Spread.Sheets.Workbookコンストラクターの第1引数にスプレッドシートを表示するHTML要素名を指定します。第2引数の「sheetCount: 0」は、初期表示のシート数を0にする指定です(後でテーブルシートを追加するためです)。
(3)でデータマネージャーを取得し、(4)のdataManager.addTableメソッドでテーブル(phonesTable)を定義します。第1引数はテーブル名、第2引数はテーブル定義です。テーブル定義は、データ内容を表すdata(4a)と、データ構造を表すscheme(4b)に分かれます。dataではスマートフォンの情報として、vendorId(会社ID)name(製品名)price(価格)releaseDate(発売日)を定義します。schemeでは、dataで定義したデータに対して、dataTypeでデータ型、dataNameで元のデータ名、dataMapでデータ値に対応するマップを、それぞれ指定できます。ここで指定されている内容を表1に示します。
No. | データ名 | 指定内容 |
1 | name | データ型:string |
2 | price | データ型:number |
3 | vendorName | vendorIdが1なら「Apple」/2なら「Samsung」/3なら「Xiaomi」 |
4 | releaseDate | データ型:date、パターン:yyyy-MM-dd |
テーブルシートを表示する実装はリスト3の通りです。
// テーブルシートを追加 ...(1) const sheet = spread.addSheetTab( 0, 'PhoneTableSheet', GC.Spread.Sheets.SheetType.tableSheet); // データを取得 ...(2) phonesTable.fetch().then(() => { // ビューを定義 ...(3) const view = phonesTable.addView('myView', [ { value: 'name', width: 110, caption: '機種名' }, { value: 'price', width: 80, caption: '価格', style: { formatter: '#,0' } }, { value: 'vendorName', width: 80, caption: '会社' }, { value: 'releaseDate', width: 140, caption: '発売日', style: { formatter: 'yyyy年MM月dd日' } } ]); // ビューを表示 ...(4) sheet.setDataView(view); });
(1)でスプレッドシートにテーブルシートを追加します。spread.addSheetTabメソッドの第1引数にシートのインデックス(0は先頭を表す)と第2引数にシート名、第3引数にシートの種類(ここではテーブルシート)を指定します。(2)でテーブル(phonesTable)のfetchメソッドでデータを取得した後、データを表示するためのビューを(3)で定義します。phonesTable.addViewメソッドの第1引数にビュー名、第2引数にデータ項目の配列を指定します。配列各要素には表2の属性を指定しています。
No. | 属性名 | 定義内容 |
1 | value | 表示するデータ名 |
2 | width | 列の幅 |
3 | caption | 表のヘッダーに表示する文言 |
4 | style | 表示スタイル(ここではformatter属性でフォーマットを指定) |
最後に(4)で、sheet.setDataViewメソッドにビューを渡して実行し、ビューをテーブルシートに表示します。以上が、テーブルシートを利用する基本的な流れとなります。
データグリッドを想定したテーブルシートの機能
データグリッドとしての利用を想定したテーブルシートの機能を、以下でいくつか紹介します。
複数テーブルのリレーションシップ
テーブルシートでは、2つのテーブル間にリレーションシップを定義できます。図2のサンプル(p002-relationship)では、リレーションシップが定義された2つのテーブルを参照してデータを表示します。
このサンプルでは、スマートフォンの情報を表すphonesTableとは別に、会社の情報を表すvendorsTableが定義されています(リスト4)。
const vendorsTable = dataManager.addTable('vendorsTable', { data: [ { id: 1, name: 'Apple', region: 'アメリカ' }, (略) ], (略) });
この2つのテーブルに、リスト5の記述でリレーションシップを設定します。
dataManager.addRelationship( phonesTable, 'vendorId', 'vendor', vendorsTable, 'id', 'phones');
addRelationshipメソッドの引数は表3の通りです。
No. | 意味 |
1 | テーブル1の変数 |
2 | リレーションシップを設定するテーブル1の列名 |
3 | テーブル2のビューから参照するときの、テーブル1の名前 |
4 | テーブル2の変数 |
5 | リレーションシップを設定するテーブル2の列名 |
6 | テーブル1のビューから参照するときの、テーブル2の名前 |
つまり、リスト5のaddRelationshopでは、以下のリレーションシップが定義されます。
- phonesTableのvendorIdと、vendorsTableのidを関連付ける
- phonesTableのビューから、vendorsTableは「vendor」として参照できる
- vendorsTableのビューからは、phonesTableは「phones」として参照できる(ただし、このサンプルでは未使用)
この定義を使って、リスト6の通りデータを取得・表示できます。phonesTableのビューからはvendorsTableを「vendor」として参照できるため、(1)でvendor.name、(2)でvendor.regionとして、vendorsTableの列を参照できます。定義したビューを利用して(3)でデータを取得してテーブルシートに表示します。リスト3(2)ではテーブルに対してfetchを実行していましたが、リレーションシップを利用する場合は先にビューを定義して、そのfetchを実行する点に注意してください。
const view = phonesTable.addView('myView', [ (略) { value: 'vendor.name', width: 80, caption: '会社' }, // ...(1) { value: 'vendor.region', width: 80, caption: '地域' } // ...(2) ]); view.fetch().then(() => { // ...(3) sheet.setDataView(view); });
データのグループ表示
取得したデータを、テーブルシートでグループに分類して表示する方法を、図3のサンプルで説明します。カテゴリーごとにグループ化された合計金額と、店舗によるスライス(店舗単位の金額)が表示されます。
グループ表示するテーブル(accyTable)はリスト7の通りです。category(カテゴリー)name(製品名)price(価格)count(個数)shop(店舗)を含みます。
const accyTable = dataManager.addTable('accyTable', { data: [ { category: 'ケース', name: 'iPhone 15 ケース', price: 1000, count: 50, shop: '本店' }, (略) ] });
「グループ化」ボタンクリック時に実行されるグループ表示の処理はリスト8の通りです。sheet.groupByメソッドにグループ表示の内容を指定して実行します。なお「グループ解除」ボタンクリックで「sheet.removeGroupBy();」によりグループ解除されます。
sheet.groupBy([ { caption: 'カテゴリー', field: 'category', // ...(1) summaryFields: [ // ...(2) { caption: '合計額', formula: '=SUM([price] * [count])', // ...(2a) width: 80, style: { formatter: '#,0' }, slice: { // ...(2b) field: 'shop', width: 80, style: { formatter: '#,0' } } } ] } ]);
(1)に、グループ表示とする対象のデータ列を指定します。(2)のsummaryFieldsは、グループごとに集計するデータ列で、ここでは合計額の列を1つ指定しています。計算式は(2a)で、price列×count列の合計を指定します。(2b)はスライス(グループごとの集計をさらに分割する条件)で、ここではshop(店舗)での分割を指定します。
同様の指定を画面で対話的に行えるパネル(テーブルシートパネル)を表示させることもできます(図4)。「フィールド」を「グループ」にドラッグアンドドロップして、集計方法(数式、キャプション、スライス)を設定できます。
テーブルシートパネルを表示する実装はリスト9の通りです。GC.Spread.Sheets.TableSheet.TableSheetPanelコンストラクターの第1引数に名前、第2引数にスプレッドシートの変数、第3引数にテーブルシートパネルを表示するHTML要素を指定して実行します。
const panel = new GC.Spread.Sheets.TableSheet.TableSheetPanel( 'myPanel', sheet, document.getElementById('table-sheet-panel'));
データの階層表示
データの階層構造をデータグリッドに表示する機能を、図5のサンプルで説明します。表示した階層データはアイコンをクリックすることで折りたたみや展開も可能です。
テーブルシートでは、階層データの定義方法が異なる、表4の階層タイプが利用できます。詳細は公式ドキュメントを参照してください。
No. | 階層タイプ | 階層データの定義方法 |
1 | Parent | 各データが親データのIDを保持 |
2 | ChildrenPath | 各データが子データの配列を直接保持 |
3 | Level | 各データが階層レベルの数字を保持 |
4 | Custom | 階層を判定する処理を自分で実装 |
図5のサンプルはParentタイプで実装されています(リスト10)。(1)のdataでは、各要素のparentIdに親データのIDを保持します。階層タイプは(2)で指定しており、typeに「Parent」を、columnには親データのIDに対応する属性名(ここではparentId)を指定します。階層を定義するためのデータID(id)は主キーである必要があるため、(3)でisPrimaryKeyをtrueに設定しています。
const gadgetsTable = dataManager.addTable('gadgetsTable', { data: [ // ...(1) { id: 1, name: 'ガジェット', parentId: -1 }, { id: 2, name: 'スマホ', parentId: 1 }, { id: 3, name: 'タブレット', parentId: 1 }, { id: 4, name: 'iPhone', parentId: 2 }, { id: 5, name: 'Androidスマホ', parentId: 2 }, { id: 6, name: 'iPad', parentId: 3 }, { id: 7, name: 'Androidタブレット', parentId: 3 } ], schema: { hierarchy: { type: 'Parent', column: 'parentId' }, // ...(2) columns: { id: { dataName: 'id', isPrimaryKey: true } // ...(3) } } });
データの階層構造を表示するビューの定義はリスト11です。nameに対してoutlineColumn属性にtrueを設定して、階層表示されるようにします。
const view = gadgetsTable.addView('myView', [ { value: 'id', width: 60, caption: 'ID' }, { value: 'name', width: 200, caption: '名前', outlineColumn: true } ]);
バックエンドとのデータ同期機能
テーブルシートは、バックエンドと通信してデータを同期する機能をサポートします。図6のサンプルでは、データ追加用の行および、テーブル左のボタン(行アクション)を利用して、データの追加、更新、削除ができます。バックエンドのWebサーバーは、Node.jsのWebサーバーアプリExpressを利用して、p006-serverサンプルに実装しています(実装内容はREADME.mdを参照)。サンプル実行時は、サーバー(p006-server)とWebページ(p006-client-autosync)の順で「npm run start」コマンドを実行します。
バックエンドと通信させるにはリスト12の通り実装します。(1)のremoteで、データを読込(read)更新(update)追加(create)削除(delete)するときにHTTP通信するURLを指定します。HTTPメソッドは読込がGET、削除がDELETE、それ以外はPOSTがデフォルトですが、ここでは更新時の通信にPUTを指定しています。(2)のautoSyncは、データの変更をすぐにサーバーに同期する指定です。なお、HTTP通信における要求と応答の仕様は、公式デモページの「要求と応答」を参照してください。
const phonesTable = dataManager.addTable('phonesTable', { remote: { // ...(1) read: { url: apiUrl + 'phones' }, update: { url: apiUrl + 'phones', method: 'PUT' }, create: { url: apiUrl + 'phones' }, delete: { url: apiUrl + 'phones' } }, autoSync: true, // データの変更をすぐにサーバーに同期 ...(2) (略) });
テーブル左側のボタン(行アクション)は、リスト13の通り定義します。(1)でoptionsを取得して(2)でアクションを追加後、(3)で再設定しています。
const rowActions = GC.Spread.Sheets.TableSheet.BuiltInRowActions; const options = sheet.rowActionOptions(); // ...(1) options.push( // ...(2) rowActions.removeRow, // 行を削除 rowActions.saveRow, // 行の更新を保存 rowActions.resetRow // 行の更新を戻す ); sheet.rowActionOptions(options); // ...(3)
このサンプルではリスト12(2)のautoSyncにより、行単位でデータを反映させますが、autoSyncの代わりに「batch: true」と設定することで、複数行をまとめて反映させるようにもできます。詳細はp006-client-batchサンプルのREADME.mdを参照してください。
まとめ
本記事では、メシウスのJavaScriptスプレッドシート部品SpreadJSをデータグリッドとして利用できるテーブルシートについて説明しました。カテゴリーや階層表示、バックエンドとの通信といった、データグリッドに最適化された機能が提供されています。