「素晴らしいですね。Excel インポート機能を追加していただけますか?」
長年ソフトウェア開発に携わってきた方なら、マネージャーからこの質問を何度も聞いたことがあるでしょう。技術者以外の人にとって、Excel のインポート/エクスポートを依頼するのは大したことではないように思えます。そんなに難しいことではないですよね?
しかし、この質問は開発者の心に恐怖を植え付けることが多すぎます。ほとんどのプラットフォームでは、Excel ファイルの操作には多くの作業が必要です。歴史的に、これは Web ではさらに当てはまります。Web アプリで Excel データを操作すると、この xkcd コミックに少し似ています。「簡単なことと事実上不可能なことの違いを説明するのは難しい場合があります。」Excel のインポートとエクスポートを使用して独自のブラウザー内スプレッドシートを構築することは、解決に 5 年の年月と研究チームを要する問題のように思えます。
状況は変わりつつあります。今では、完全に機能するスプレッドシートを Web アプリに組み込むことができるターンキー ライブラリがあります。SpreadJSもその 1 つです。既存の Vue アプリ (Vuex ストアを使用する実際のアプリ) を SpreadJS を使用して強化する方法を説明します。
この記事の残りの部分では、読者が HTML、CSS、JavaScript をすでに理解していることを前提としています。また、Web UI を作成するためのプログレッシブ JavaScript フレームワークであるVue.jsの実用的な知識があることも前提としています。状態管理にVuexを使用したことがあると役立ちますが、そうでなくても心配はいりません。直感的で理解しやすく、Vue を使用したことがある人ならコードを読むだけで何が起こっているのか理解できるでしょう。
このブログでは、次の手順に従って、Vue アプリに Excel のインポートとエクスポートを追加する方法について説明します。
私たちが取り組む Vue アプリは、いくつかの概要パネルとデータ テーブルを備えたシンプルな販売ダッシュボードです。これは、「悪くない」カテゴリに分類される種類のアプリです。
これは単なるデモですが、企業の Web 開発者が作成しなければならないアプリそのものです。また、Excel 機能の追加を求められることが多いアプリでもあるため、この記事の残りの部分では完璧な例となります。
このアプリケーションのコードはここにあります。
Vue アプリケーションを最初から作成する方法を知りたい場合は、このクイック スタート ガイドをご覧ください。
まず、ダッシュボードは Vue で作成されたシングルページ アプリケーションです。最新かつ最高のVue ベスト プラクティスである、シングル ファイル コンポーネントと Vuex データ ストアを使用しています。また、CSS コンポーネントとグリッド システムにはBootstrapを使用しています。
Bootstrap は以前ほど人気はありませんが、実際には、Bootstrap は今でもいたるところで使用されています。特に、Excel サポートが通常必要なエンタープライズ Web アプリではそうです。2030 年になっても、多くの新しいエンタープライズ Web アプリで Bootstrap が使用されることは間違いないでしょう。
スプレッドシート対応アプリでBulmaまたはTachyons を使用したい場合は、どうぞご自由にお使いください。SpreadJS はどちらでも問題なく動作します。
コードの構造を見てみましょう。Vuex ストアと Vue アプリケーションは両方ともmain.jsで定義されています。単一ファイルの Vue コンポーネントがいくつかあり、すべて components フォルダーに配置されています。
Vuex ストアを見ると、次のことがわかります。
const store = new Vuex.Store({ state: { recentSales } mutations: { UPDATE_RECENT_SALES(state) { state.recentSales.push([]); state.recentSales.pop(); } } });
ストアの初期状態は、インポートしたダミー データのセットである recentSales の値に設定されています。また、最近の売上が変更されたときに更新を処理する関数もあります。
ちょっと待ってください。データセットが 1 つしかないのに、どうやって 3 つのグラフと1 つの表を生成するのでしょうか。何が起こっているのかを確認するには、 Dashboard.vueコンポーネントを開いてください。その中で、Vuex ストアのデータに基づいていくつかの計算プロパティが生成されていることがわかります。
<template> <div style="background-color: #ddd"> <NavBar title="Awesome Dashboard"/> <div class="container"> <div class="row"> <TotalSales :total="totalSales"/> <SalesByCountry :salesData="countrySales"/> <SalesByPerson :salesData="personSales"/> <SalesTable :tableData="salesTableData"/> </div> </div> </div> </template> <script> import NavBar from "./NavBar"; import TotalSales from "./TotalSales"; import SalesByCountry from "./SalesByCountry"; import SalesByPerson from "./SalesByPerson"; import SalesTable from "./SalesTable"; import { groupBySum } from "../util/util"; export default { components: { NavBar, SalesByCountry, SalesByPerson, SalesTable, TotalSales }, computed: { totalSales() { const items = this.$store.state.recentSales; const total = items.reduce((acc, sale) => (acc += sale.value), 0); return parseInt(total); }, chartData() { const items = this.$store.state.recentSales; const groups = groupBySum(items, "country", "value"); return groups; }, personSales() { const items = this.$store.state.recentSales; const groups = groupBySum(items, "soldBy", "value"); return groups; }, salesTableData() { return this.$store.state.recentSales; }, } }; </script>
これで、より理解しやすくなりました。単一のデータ セットには、ダッシュボードのすべての数値とテーブルを生成するために必要なものがすべて含まれています。データはリアクティブ Vuex ストアにあるため、データが更新されると、ダッシュボード パネルがすべて自動的に更新されます。
この反応性は、退屈な古い静的テーブルを編集可能なスプレッドシートに置き換える次のセクションで役立ちます。
ここからが楽しいところです。ダッシュボードはできましたが、使いにくい古い HTML テーブルを削除する必要があります。そのため、少し変更する必要があります。素晴らしい出発点ができましたが、ライセンスなしで SpreadJS を開発モードで使用するには、アプリをローカルで実行する必要があります。
完成したコードをダウンロードして最終結果を確認できます。
まず、SpreadJS のない元のプロジェクトを開きます。ターミナルを開き、リポジトリをクローンしたディレクトリに移動して、「npm install」を実行します。これにより、アプリケーションの実行に必要な依存関係がインストールされます。依存関係のインストールが完了したら、「npm serve」を実行して、更新されたアプリの動作を確認します。使用するライブラリを具体的にインポートする場合は、次のコマンドを使用できます。
npm install @mescius/spread-sheets @mescius/spread-sheets-vue @mescius/spread-excelio file-saver bootstrap
古いアプリを新しい改良版にアップグレードするために必要な変更について見ていきましょう。売上表をスプレッドシートに置き換えるので、シートを既存のSalesTable.vueコンポーネントに配置しますが、まず古い表を削除する必要があります。古い表を削除すると、SalesTable テンプレートは次のようになります。
<template> <TablePanel title="Recent Sales"> </TablePanel> </template>
テーブルを削除したら、テーブル パネルの準備が整い、スプレッドシートを待つ状態になったので、スプレッドシートを追加してみましょう。SpreadJS シートを追加すると、テンプレートは次のようになります。
<template> <TablePanel title="Recent Sales"> <gc-spread-sheets :hostClass='hostClass' @workbookInitialized='workbookInit'> <gc-worksheet :dataSource='tableData' :autoGenerateColumns='autoGenerateColumns'> <gc-column :width='50' :dataField="'id'" :headerText="'ID'" :visible = 'visible' :resizable = 'resizable' > </gc-column> <gc-column :width='300' :dataField="'client'" :headerText="'Client'" :visible = 'visible' :resizable = 'resizable' > </gc-column> <gc-column :width="350" :headerText="'Description'" :dataField="'description'" :visible = 'visible' :resizable = 'resizable' > </gc-column> <gc-column :width="100" :dataField="'value'" :headerText="'Value'" :visible = 'visible' :formatter = 'priceFormatter' :resizable = 'resizable' > </gc-column> <gc-column :width="100" :dataField="'itemCount'" :headerText="'Quantity'" :visible = 'visible' :resizable = 'resizable' > </gc-column> <gc-column :width="100" :dataField="'soldBy'" :headerText="'Sold By'" :visible = 'visible' :resizable = 'resizable' ></gc-column> <gc-column :width="100" :dataField="'country'" :headerText="'Country'" :visible = 'visible' :resizable = 'resizable' ></gc-column> </gc-worksheet> </gc-spread-sheets> </TablePanel> </template>
理解すべきことがたくさんあるので、何が起こっているのかを理解するために、順を追って説明していきましょう。
まず、gc-spread-sheets 要素を使用してスプレッドシートを作成し、それをコンポーネントの 2 つのプロパティ (hostClass と workbookInit) にバインドします。
スプレッドシート内で、gc-worksheet 要素を使用して新しいワークシートを作成し、それをコンポーネントの tableData および autoGenerateColumns プロパティにバインドします。tableData は、プレーン HTML テーブルを生成するために使用した tableData とまったく同じであることに注意してください。変更を加えることなく、データをそのまま SpreadJS に配置できます。
最後に、ワークシート内で、SpreadJS にデータの表示方法を指示する列を定義します。dataField プロパティは、この列に表示する基になるデータセットのプロパティを指示し、headerText は SpreadJS が使用する適切な形式の列名を指定します。各列の残りのバインディングは簡単です。SpreadJS のドキュメントには、gc-column に渡すことができるすべてのものの完全なリストがあります。
では、テンプレートができたら、これをすべて機能させるにはどのくらいのコードが必要でしょうか? 幸いなことに、ほとんど必要ありません! SalesTable.vueコンポーネントの新しいスクリプト コードは次のとおりです。
import "@mescius/spread-sheets/styles/gc.spread.sheets.excel2016colorful.css"; // SpreadJS imports import GC from "@mescius/spread-sheets"; import "@mescius/spread-sheets-vue"; import Excel from "@mescius/spread-excelio"; import TablePanel from "./TablePanel"; export default { components: { TablePanel }, props: ["tableData"], data(){ return { sheetName: 'Sales Data', hostClass:'spreadsheet', autoGenerateColumns:true, width:200, visible:true, resizable:true, priceFormatter:"$ #.00" } }, methods: { workbookInit: function(_spread_) { this._spread = spread; var self = this; spread.bind(GC.Spread.Sheets.Events.ValueChanged, function () { const store = self.$store; var sheet = self._spread.getSheetFromName("Sales Data"); var newSalesData = sheet.getDataSource(); store.commit('UPDATE_RECENT_SALES', newSalesData); }); } } };
Vue はシンプルなので、これを機能させるのに必要なコードは非常に少なくなります。ここでわからないことがあれば、Vue ドキュメントの「コンポーネントの詳細」セクションで Vue コンポーネントについて詳しく説明しています。変更されたのは、いくつかのインポート、いくつかのデータ プロパティ、およびいくつかのメソッドだけです。データ プロパティは見覚えがあるはずです。先ほどテンプレートで確認しました。これらは、SpreadJS スプレッドシートのコンポーネントにバインドする構成オプションです。
workbookInit メソッドは、シートが初期化されるときに SpreadJS が呼び出すコールバックです。このメソッドでは、SheetJS スプレッドシート オブジェクトをコンポーネントのインスタンス変数として保存し、必要に応じて直接操作できるようにします。また、SpreadJS インスタンスで値が変更されたときにデータを自動的に更新するために、ValueChanged イベントのバインディング関数を追加しました。
最後にもう 1 つ変更します。コンポーネントにスコープ スタイルを与えて、スプレッドシートのスタイル自体を支援します。これは、前に hostClass を gc-spread-sheets 要素に渡したときに確認しました。hostClass は 'spreadsheet' に設定されているため、spreadsheet という名前の CSS クラスを作成します。
<style scoped> .spreadsheet { width: 100%; height: 400px; border: 1px solid lightgray; } </style>
この時点で、他に変更を加えずにダッシュボードを読み込むと、次のようになります。
でも待ってください、まだあります!
データ セットに変更を加えずに、テーブル データをスプレッドシートに渡した方法を覚えていますか? データがスプレッドシートにあるので、編集できます。
売上番号 6 の値を 35,000 ドルから 3,500 ドルに変更するとどうなるでしょうか? シートに移動して値を編集すると、次のようなダッシュボードが表示されます。
わあ!何が起こったの?
SpreadJS シートを更新すると、Vuex ストアが自動的に更新されました。
また、アンジェラは、売上が素晴らしい月だったのに、平凡な月になってしまったようです。アンジェラ、ごめんなさい!
これで、マネージャーが満足する強化されたダッシュボードができました。マネージャーはデータを変更し、ダッシュボードの更新を目の前で確認できますが、Excel ファイルのインポートとエクスポートの機能を追加することで、さらに優れたダッシュボードを作成できます。次に、その方法について学習します。
シートに Excel エクスポートを追加するのは簡単です。まず、ダッシュボードにエクスポート ボタンを追加しましょう。SalesTable.vue ファイルのテーブル パネルの下部、gc-spread-sheets 終了タグの直後に配置します。
… </gc-spread-sheets> <div class="dashboardRow"> <button class="btn btn-primary dashboardButton" @click="exportSheet"> Export to Excel </button> </div> </TablePanel> </template>
ご覧のとおり、ボタンには exportSheet という名前のクリック ハンドラーが必要です。これはすぐに追加しますが、まずは、file-saver という名前の NPM パッケージから関数をインポートします。
import { saveAs } from 'file-saver';
次に、コンポーネントのメソッド オブジェクトに exportSheet を追加します。
exportSheet: function() { const spread = this._spread; const fileName = "SalesData.xlsx"; //const sheet = spread.getSheet(0); const excelIO = new IO(); const json = JSON.stringify(spread.toJSON({ includeBindingSource: true, columnHeadersAsFrozenRows: true, })); excelIO.save(json, (blob) => { saveAs(blob, fileName); }, function (e) { console.log(e) }); }
コードが行っていることは次のとおりです。まず、売上データシートへの参照を取得します。これはスプレッドシート内の唯一のシートであるため、インデックス 0 にあり、getSheet を呼び出してアクセスします。シートを直接操作する必要がある場合は、関数内の他の場所でこれを使用できます。
次に、SpreadJS の ExcelIO ライブラリをインスタンス化し、シートを JSON に変換して、SpreadJS に保存するように要求します。 出来上がり! スプレッドシート対応の Vue アプリから Excel ファイルをエクスポートできました。
シートの toJSON 呼び出しに、includeBindingSource と columnHeadersAsFrozenRows という 2 つのシリアル化オプションを渡していることに注意してください。これらのオプションを組み合わせることで、シートにバインドしたデータが正しくエクスポートされ、シートに列ヘッダーが含まれるようになります。そのため、エクスポートされた Excel ファイルを見ると、各列がわかります。
次に、Excel ファイルをインポートする機能を追加します。
エクスポート ボタンのすぐ下に、次のマークアップを追加します。
<div> <b>Import Excel File:</b> <div> <input type="file" class="fileSelect" @change='fileChange($event)' /> </div> </div>
ご覧のとおり、標準の HTML ファイル ピッカーを使用し、ファイルが選択されたときに fileChange というコンポーネント メソッドをトリガーします。
テンプレートを追加したので、コンポーネントのメソッド オブジェクトに変更ハンドラーを追加しましょう。
fileChange: function (_e_) { if (this._spread) { const fileDom = e.target || e.srcElement; const excelIO = new Excel.IO(); const spread = this._spread; const store = this.$store; /*const deserializationOptions = { includeBindingSource: true, frozenRowsAsColumnHeaders: true };*/ excelIO.open(fileDom.files[0], (_data_) => { // Used for simply loading the JSON from a file //spread.fromJSON(data, deserializationOptions); var newSalesData = extractSheetData(data); store.commit('IMPORT_RECENT_SALES', newSalesData) }); } }
Excel ファイルのインポートは、エクスポートとほぼ同じですが、逆になります。ファイルを選択したら、ExcelIO にインポートを依頼します。完了すると、シート情報が JavaScript オブジェクトとしてコールバック関数に渡されます。次に、インポートしたデータをカスタム関数に渡して必要なデータを抽出し、Vuex ストアにコミットします。
通常、ファイルのインポートは、ExcelIO の open メソッドを呼び出すだけですが、ワークブックの「fromJSON」メソッドを使用します。この場合、インポートしたファイルからデータを解析してストアを更新し、SpreadJS を更新します。
src/util.util.js ファイルにある extractSheetData 関数では、ExcelIO によって返された JavaScript オブジェクトからデータを抽出し、Vuex ストア内のデータの形状に合わせて再構築していることがわかります。
インポート機能では、インポートされたシートのデータが元のデータ セットと同じ列を持つことを前提としています。この要件を満たさないスプレッドシートをアップロードした場合、アプリはそれを処理できません。これは、ほとんどの基幹業務アプリで許容される制限です。ダッシュボードは特定のデータ タイプを表示するように設計されているため、アプリが想定する形式でデータを提供するようユーザーに求めるのは妥当です。
データの抽出が完了したら、Vuex ストアでコミットを呼び出し、更新された販売取引データを送信します。その後、SpreadJS シートとダッシュボード パネルが更新され、新しいデータが反映されます。実際には、インポートと値の変更に異なるミューテーション関数を使用して、 main.jsファイルに「IMPORT_RECENT_SALES」として追加することができます。
const store = new Vuex.Store({ state: { recentSales }, mutations: { UPDATE_RECENT_SALES(state) { state.recentSales.push([]); state.recentSales.pop(); }, IMPORT_RECENT_SALES(state, sales) { state.recentSales = sales; } } });
コードを確認したので、Vue アプリで Excel のインポートとエクスポートをテストしてみましょう。
まず、「Excel にエクスポート」ボタンをクリックします。すると、ダッシュボードのスプレッドシートに表示されたすべてのデータを含む Excel スプレッドシートが Web ブラウザにダウンロードされます。
Excel でシートを開き、数行のデータを追加します。新しい国や新しい営業担当者を使用しても問題ありません。すべてのダッシュボード コンポーネントで対応できます。列の順序や名前を変更しないように注意してください。完了したら、[最近の売上] パネルの下部にある [ファイルの選択] ボタンをクリックします。編集した Excel ファイルを選択します。
ファイルを選択すると、更新されたダッシュボード コンポーネントが表示されます。
完了です。通常の Vue ダッシュボード アプリにライブ スプレッドシートを追加しました。これで、シート内のデータを編集し、ダッシュボード全体が更新されるのを確認できます。強化されたダッシュボードでは、Excel ファイルのインポートとエクスポートも可能です。
Vue、Vuex、SpreadJS は互いに補完し合います。Vue の簡単なテンプレートとデータ バインディング、Vuex のリアクティブ データ ストア、SpreadJS のインタラクティブなスプレッドシートを使用すると、複雑なエンタープライズ JavaScript アプリを数時間で作成できます。
これは素晴らしいことのように思えますが、SpreadJS でできることのほんの一部を紹介したにすぎません。SpreadJS で何ができるのかをより深く理解するには、 SpreadJS デモを参照してください。デモには、SpreadJS のさまざまな機能の完全なデモ、説明、およびそれらの機能を紹介するライブ コードが含まれています。独自のアプリで SpreadJS をより深く使用したい場合は、 SpreadJS ドキュメントに必要な情報が記載されています。
この JavaScript スプレッドシート コンポーネントの詳細については、以下をご覧ください。