はじめに
第1、2回では、「SpreadJS」の基本的な使い方や、サーバー側のWebフレームワークとしてASP.NET MVCの現状を確認しました。
本稿では、SpreadJSとサーバー側を連携し、実際にとあるシナリオを想定して帳票Webアプリを作成します。サンプルの一部を抜粋しながら、以下の機能がどのように実装できるか、紹介します。
想定シナリオ
- "請求書"をブラウザ上で表示・編集し、データをサーバー側に保存する。
- 作成した"請求書"を再度読みこむ。
- 作成した"請求書"を、印刷・Excelファイルとして出力する。
構成
- サーバー側 Web フレームワーク:ASP.NET MVC、ASP.NET Web API
- クライアント側:TypeScript、SpreadJSライブラリ
- IDE:Visual Studio 2015
作成する帳票Webアプリの図
対象読者
- ASP.NET、JavaScriptを利用する開発者
- スプレッドシートの機能に興味がある方
- Excelのような帳票Webアプリを作成したい方
必要な環境
本稿では、次の環境で開発・動作確認を行っています。
- Chrome
- Visual Studio 2015
- Windows 10
サンプルについて
サンプルにはSpreadJSライブラリは含まれておりません。ご利用いただく場合には、別途SpreadJSのトライアル版(または製品版)をダウンロードする必要があります。ダウンロードした後、サンプル内のREADME.txtに従ってファイルを配置してください。
既存のExcelファイルをSpreadJSに適用する
はじめに、帳票のデザインをSpreadJSで表示するにはどのようにしたらよいでしょうか。例えば、サンプルの請求書のデザインをSpreadJSに設定したいとします。
SpreadJSのデザインはJavaScriptの実装より行うことができますが、コード量が多くなり複雑になる可能性があります。そこで、すでにデザインされたExcelファイルを利用し、SpreadJSをあらかじめシリアル化しておくことで、アプリケーションの主となるロジックと区別することができます。
本稿のサンプルでは、製品ヘルプの「Excelのインポート機能」と「SpreadJSのシリアル化・逆シリアル化」を利用し、次の方法を選択しました。
- デザインされたExcelファイルをSpreadJSに読み込む。
- SpreadJSをシリアル化したJSONデータを「*.json」ファイルとして出力する。
- 作成した「*.json」ファイルをサーバー側に配置する。
- ブラウザ側で、初期表示に「*.json」ファイルの内容をSpreadJSに読み込む。
まず、(1)のExcelファイルをSpreadJSに読み込むための、クライアント側のTypeScriptの記述です。
// Excelファイルをインポートします export function ImportExcelFile() { let input: any = document.getElementById("fileDemo"); let excelFile = input.files[0]; excelIO.open(excelFile, function (json) { var workbookObj = JSON.parse(json); workbook.fromJSON(workbookObj); }, e => { console.log(e); }); }
このクライアント側のExcelインポート機能については、連載第2回の「クライアントサイドのExcelインポート・エクスポート」でも紹介しておりますのでご参照ください。
次に、(2)のSpreadJS全体の設定をJSONデータとしてシリアル化するTypeScriptの記述です。
//SpreadJSの初期化 var workbook = new GC.Spread.Sheets.Workbook(document.getElementById("spreadjs-container"), { sheetCount: 1 }); //JSONデータとして出力する let json = JSON.stringify(workbook.toJSON({ includeBindingSource: false }));
詳細は、チュートリアルデモの「JSONによる初期化と保存」をご覧ください。
SpreadJSのシリアル化されたJSONデータは次のようになっています(一部のみ記載しています)。
{ "version": "9.20161.0", "sheets": { "Sheet1": { "name": "Sheet1", "frozenRowCount": 10, "frozenTrailingRowCount": 12, "rowCount": 33, "columnCount": 4, "activeRow": 4, "activeCol": 2, "spans": [ { "row": 32, "rowCount": 1, "col": 0, "colCount": 4 }, //..続く
以上の手順で取得したJSONデータを「*.json」ファイルとして出力し、サーバー側のASP.NET MVCプロジェクトの「App_Data/spread」フォルダ配下に配置します。
この「*.json」ファイルは、初期表示の際にサーバー側のAPIを通してクライアント側で取得し、SpreadJSに読み込みます。
「*.json」ファイルの中身を返すASP.NET Web APIの実装は次のようになります。
/// はじめに表示するSpreadJSのコンテンツを取得します [HttpGet] public HttpResponseMessage ReadSpread() { string fileName = "initialSpread.json"; string filePath = Path.Combine(spreadFolder, fileName); var response = new HttpResponseMessage(HttpStatusCode.OK); var fileStream = new FileStream(filePath, FileMode.Open); response.Content = new StreamContent(fileStream); response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); return response; }
注
ASP.NET Web APIで提供するAPIではなく、直接Webサーバー側のファイルを取得する方法でも構いません。ただし、*.jsonファイルが外部に公開されることに注意してください。またIISでは、規定では「*.json」ファイルの送信は許可されていませんので、MIMEマッピングを追加する必要があります。
クライアント側でAPIリクエストを送信し、「*.json」ファイルの中身を読み込み、SpreadJSに適用します。
function LoadWorkbook() { //初期表示のSpreadJSを読み込みます $.ajax({ url: "/api/spread/", datatype: "json", cache: false, success: (data) => { workbook.suspendPaint(); workbook.fromJSON(data); let sheet = workbook.getSheet(0); //シートに対してセットアップを行います。 SetupSheet(sheet); workbook.resumePaint(); }, error: (ex) => { console.log(ex); } } as JQueryAjaxSettings); }
以上で、デザインされたExcelファイルをSpreadJSに適用できます。作成したExcelファイルはサンプル内にありますのでご参照ください。
データを保存する(1)
データの保存といえども、"とある値のみ保存する"のか、"帳票全体を保存"するのか等、決定しなければいけません。サンプルでは両方を実装していますが、まず、前者の"とある値のみ保存する"方法について紹介します。
管理するデータをJSONデータとして定義する
サーバー側で管理したいデータを帳票から切り出し、JSON形式のモデルとして定義します。例えば"請求書"では、次の図のように切り分けました。
赤枠がJSON形式のモデルとして定義する場所です。青枠は、Excelの数式のように、表示するときに計算される場所です。この青枠は、JSONモデルとして定義はしません(ただし、要件によっては、計算結果もサーバー側で管理することも考えられます)。
切り出したJSONモデルは次のようになります。
{ "publishDate": "2016-12-01", "paymentDate": "2016-12-20", "invoiceNumber": 10003, "discount": 500, "destination": { "name": "杉本 昭 様", "companyName": "杉本 昭 事務所", "address1": "東京都", "address2": "港区" }, "publisher": { "name": "山田 太郎", "companyName": "山田 太郎 事務所", "address1": "東京都", "address2": "中央区" }, "paymentInfo": { "name": "山田 太郎", "nameKana": "ヤマダタロウ", "bankName": "銀行名", "bankAccount": "1234567" }, "items": [ { "id": 1, "amount": 11, "name": "項目名1", "unitPrice": 1500, "sumPrice": 0 }, { "id": 2, "amount": 13, "name": "項目名2", "unitPrice": 2500, "sumPrice": 0 }, //…続く ] }
このようなJSONデータをサーバー側とAPIを通してやり取りすることにより、データの管理を行います。
SpreadJSにJSONデータを表示するには、「セルレベルバインディング」と「テーブルバインディング」の機能で実装します。バインディングにより、SpreadJSで編集した値は、クライアント側のObjectにも反映されます。詳細は、製品ヘルプの「データの連結」または連載第2回の「基本的な使い方(2)」をご参照ください。
次のコードは、バインディングの設定を行っているクライアント側のTypeScriptのコードです。JSONデータとSpreadJSの表示場所の関連付けを行い、テーブルを追加しています。テーブルはExcelファイルにて設定していても、Excelのインポート時に破棄されてしまうため、TypeScriptにて設定しています。
function BindSheet(sheet: GC.Spread.Sheets.Worksheet, data: any) { let tableDataRowCount = data.items.length; //請求書番号 sheet.setBindingPath(0, 1, "invoiceNumber"); //発行日 sheet.setBindingPath(1, 1, "publishDate"); //支払期日 sheet.setBindingPath(2, 1, "paymentDate"); //宛先 sheet.setBindingPath(4, 0, "destination.name"); sheet.setBindingPath(5, 0, "destination.companyName"); sheet.setBindingPath(6, 0, "destination.address1"); sheet.setBindingPath(7, 0, "destination.address2"); //発行者 sheet.setBindingPath(4, 2, "publisher.name"); sheet.setBindingPath(5, 2, "publisher.companyName"); sheet.setBindingPath(6, 2, "publisher.address1"); sheet.setBindingPath(7, 2, "publisher.address2"); //割引 sheet.setBindingPath(10 + tableDataRowCount, 3, "discount"); sheet.getCell(1, 1) //支払情報 sheet.setBindingPath(16 + tableDataRowCount, 1, "paymentInfo.name"); sheet.setBindingPath(17 + tableDataRowCount, 1, "paymentInfo.nameKana"); sheet.setBindingPath(18 + tableDataRowCount, 1, "paymentInfo.bankName"); sheet.setBindingPath(19 + tableDataRowCount, 1, "paymentInfo.bankAccount"); //シートにテーブルをバインドします let amountInfo = new GC.Spread.Sheets.Tables.TableColumn("数量"); amountInfo.name("数量"); amountInfo.dataField("amount"); let nameInfo = new GC.Spread.Sheets.Tables.TableColumn("項目"); nameInfo.name("項目"); nameInfo.dataField("name"); let unitPriceInfo = new GC.Spread.Sheets.Tables.TableColumn("単価"); unitPriceInfo.name("単価"); unitPriceInfo.dataField("unitPrice"); let sumPriceInfo = new GC.Spread.Sheets.Tables.TableColumn("金額"); sumPriceInfo.name("金額"); sumPriceInfo.dataField("sumPrice"); let table = sheet.tables.addFromDataSource("TableDetails", 9, 0, data.items, GC.Spread.Sheets.Tables.TableThemes.medium23); table.bindColumns([amountInfo, nameInfo, unitPriceInfo, sumPriceInfo]); //金額の列の計算式を設定します table.setColumnDataFormula(3, "=[@単価]*[@数量]"); //小計の計算式を設定します sheet.getCell(11 + tableDataRowCount, 3).formula("=SUM(TableDetails[金額])-Discount"); //シートにデータをバインディングします sheet.setDataSource(new GC.Spread.Sheets.Bindings.CellBindingSource(itemData)); }
「数式」や「名前の定義」は、Excelインポートを行っても保持されるので、あらかじめExcelにて設定しておくとよいでしょう。サンプルでは、「割引」や「小計」「税」などに名前を設定しています。
このようなバインディング機能により、JSONデータとSpreadJSの表示を分離して開発を行うことができます。
データを保存する(2)
JSONデータの保存と読み込み
前節のように定義したJSONデータは、サーバー側から取得し、SpreadJSに表示して編集した後、サーバーに送信することで管理します。サンプルでは、JSONデータの保存方法として「サーバー側のローカルにてファイルを保存する」方法を選択しました。この他にも、サーバー側にてJSONデータを逆シリアル化し、Microsoft SQL ServerやRedisなど、さまざまな永続化の方法を選択できます。技術資料の「ASP.NET MVCでの使い方」では、ADO.NET Entity Data Modelでの実装があります。
次のコードは、サーバー側のASP.NET Web APIにて、初期データを提供する実装です。
/// <summary> /// 初期表示に表示するデータ initial.json を取得します /// </summary> /// <returns></returns> [HttpGet, Route("initial")] public HttpResponseMessage GetInitialItem() { string fileName = "initialItem.json"; var filePath = Path.Combine(dataFolder, fileName); var response = new HttpResponseMessage(HttpStatusCode.OK); var fileStream = new FileStream(filePath, FileMode.Open); response.Content = new StreamContent(fileStream); response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json); return response; }
JSONデータをクライアント側で読み込み、SpreadJSにバインディングします。
//シートのセットアップを行います function SetupSheet(sheet: GC.Spread.Sheets.Worksheet) { $.ajax({ url: "/api/item/initial", datatype: "json", cache: false, success: data => { //シートにデータをバインドします BindSheet(sheet, itemData); //列の自動生成をOFFにします sheet.autoGenerateColumns = false; //上下の行を固定します。 sheet.frozenRowCount(9); sheet.frozenTrailingRowCount(12); }, error: (XMLHttpRequest, textStatus, errorThrown) => { console.log(errorThrown) } } as JQueryAjaxSettings); }
例として、次のようにテーブルの行を編集します。
このとき、JavaScriptのオブジェクトも変更されている状態になります。
さらに続けて、テーブルに行を追加してみましょう。行を追加するコードは以下のようになります。この場合もJavaScriptのオブジェクトに1行追加された状態になります。
//SpreadJSの行を追加します export function AddItem() { var sheet = workbook.getActiveSheet(); //現在選択している行を取得します var activeRowIndex = sheet.getActiveRowIndex(); //選択行の下に1行追加します。 sheet.addRows(activeRowIndex + 1, 1); }
保存する際は、JSONデータをAPIリクエストとしてサーバー側へ送信します。
//データをuser1として保存します export function SaveData() { $.ajax({ url: "api/item/user1", type: "POST", data: JSON.stringify(itemData), dataType: "json", contentType: "application/json,charset=UTF-8" }); }
サーバー側のASP.NET Web APIでは、送信されてきたJSONデータをファイルに保存しています。
/// データをJSONファイルとして、user1.json の名前で保存します [HttpPost, Route("user1")] public async Task Save() { string fileName = "user1.json"; var filePath = Path.Combine(dataFolder, fileName); //リクエストのBodyからJSONデータを読み込みます var stream = await Request.Content.ReadAsStreamAsync(); //省略
これまで説明してきたように、管理したいデータをJSONデータとして定義し、SpreadJSにはバインディングで表示を制御し、サーバー側とはAPIでやり取りすることによりデータを管理できます。また、同じような方法で、サーバー側にSpreadJSのシリアル化データを保存することで、帳票全体の管理が可能です。
サンプルでは実装していませんが、例えば、宛先や銀行情報はマスタデータから読み込むといったことも、JSONデータを操作するだけで実現できます。SpreadJSがサーバーと分離されたクライアントライブラリだからこそ、柔軟に扱うことができます。
印刷
SpreadJSでは印刷をサポートしています。詳細は製品ヘルプの「印刷の概要」をご参照ください。サンプルでは、HTMLのボタンを用意し、印刷機能を実装しました。
クライアント側の印刷の実装は次のようになります。
//印刷します export function Print() { let printSheetIndex = 0; var info = workbook.getSheet(printSheetIndex).printInfo() as GC.Spread.Sheets.Print.PrintInfo; info.showColumnHeader(GC.Spread.Sheets.Print.PrintVisibilityType.hide); info.showRowHeader(GC.Spread.Sheets.Print.PrintVisibilityType.hide); info.showGridLine(false); info.showBorder(false); workbook.print(printSheetIndex); }
下の図は、Chromeブラウザで印刷を実行したときの図です。
保存状態に関わらず、そのままのSpreadJSの状態で印刷を行うことができます。
最後に
連載の最終回である本稿では、実践的な内容として、"請求書"を例にした帳票Webアプリの作成をご紹介しました。
今回のポイントとなる機能は以下の通りです。
- クライアント側Excelインポート・エクスポート
- JSONデータとSpreadJSとのバインディング
- SpreadJSのシリアル化・逆シリアル化
- 印刷機能
- TypeScriptによる記述
これらのポイントと、サーバーとクライアントの分離を意識した設計をおさえると、柔軟性が高い、表機能のWebアプリに一歩近づきます。連載でご紹介した機能以外にも、さまざまな機能がありますので、ぜひSpreadJSサイトをご覧ください。