Excelにオリジナルの機能を追加したい
早速ですが、今回作成したアドインの機能を紹介します。
最近エクセルを使う機会があり、こんな機能があったら便利だなと思ったものです。
1. 選択中の行をハイライトする
クリック・キー入力によるカーソル移動に追従して行がハイライトされます。
好きな色が選べます。
2.列の幅を保存する
各列の幅の状態を保存して切り替えできます。
Column Width Profile として、各シート単位で独立したストレージに保存されます。
リポジトリ
git clone https://github.com/mimimiku778/Excel-View-Extension.git
cd Excel-View-Extension
npm install
npm run dev-server # required only by mac
npm start # Excel starts with add-in sideloaded
現時点ではストアに公開していないため、デバッグ環境と同様に開発用サーバーを起動してアドインをサイドロードしないと使用できません。
Officeアドインとは
Officeアドインは、クロスプラットフォームのOffice拡張機能です。
従来はVBA(Visual Basic for Applications)が主に使われていましたが、Officeアドインは HTML・CSS・JavaScript(Office JavaScript API) からなるWEBアプリケーションです。
Chrome拡張機能などにも似ています。
OfficeアドインにはWord、Excelなどアプリケーション毎にAPIがありますが、この記事ではExcelに焦点を当てます。
ChatGPTの回答:
VBAのメリットとデメリット:
メリット:
- Excelに組み込まれており、ファイル操作やシステムへのアクセスが自由で、すべてのExcel機能を利用可能。
- オフラインでも動作し、セットアップが簡単。
デメリット:
- 古い技術で、Webアプリやクラウドとの連携が難しい。
- 最近、セキュリティ強化のため、VBAマクロはデフォルトで無効化されることが多くなり、ユーザーにとって不便な場合もある。
Office JavaScript APIのメリットとデメリット:
メリット:
- Web技術に基づいており、全てのデバイス(Windows・Mac・Web版・iPad)で同じコードが動作。
- クラウドや他のアプリケーションとの連携が容易で、最新の技術を活用可能。
デメリット:
- セキュリティや互換性の観点から、VBAに比べて利用できる機能に制限がある。
- 開発がやや複雑で、特に非同期処理やセキュリティ設定に注意が必要
開発環境
公式でOfficeアドイン用のジェネレーターが配布されており、それを基に比較的簡単に開発を始めることができました。
ジェネレーターやOfficeアドイン全体を含め全てがOSSです。
個人的に MUI(UIライブラリ) をよく使っているため、React・TypeScript・ MUI で構築しました。
最初のチュートリアル的なものを終え、エクセルにサンプルのアドインが表示されるまでなら、初めての方でも1〜2時間程度で出来るかと思います。
ここまでは順調に行こなえるのですが、アドイン公開までのハードルの高さや、Office JavaScript APIの難しさに直面することになります。
公開までのハードルが高い
ストアに公開するためには、まず開発者アカウントを作り、審査を通したり色々必要なようです。
審査のレビューは厳しいらしいです。
アカウントの開設は法人である事が前提で、直接問い合わせることで個人事業主(商号登記があればいけるらしい)でも申請できるという情報があったりします。
ビジネスになるレベルではないので、公開などは試さずにフェードアウトするかもしれません。
ストア公開の前段階として、組織アカウントのスコープのみで公開する方法もあるそうです。
正規の方法ではありませんが、Windows・Mac環境においては特定ディレクトリにファイルを配置してサイドロードで読み込ませる方法があるので、そのようなインストーラを作れば個人レベルなら十分かもしれません。(とても簡単)
Web版は正規の方法でファイルからサイドロードできるようです。
いずれの場合もサーバーを用意してファイルを公開する必要があります。
開発環境の場合はNodeサーバーをローカルで起動させて、サイドロードを行います。
情報が少ない
著名なOSSだったり、サンプルコードなどが皆無です。内容の薄い簡単な記事がちらほらある程度です。
Office JavaScript API 以外は普通のWEBアプリと変わらないのですが、Office JavaScript API での実践的なコードやライブラリがほぼなく、公式ドキュメントしか頼りになりません。
ただ、AIを駆使すればそれなりになんとかなるかもしれません。
Office JavaScript API でエクセルを操作
今開いているシートの名前を取得する処理のサンプルです。
今回のアドインで実際に使っているコードです。
async function getActiveSheetName(
setError: (error: string[]) => void,
callback: (activeSheetName: string) => Promise<void> | void
) {
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getActiveWorksheet();
sheet.load("name");
await context.sync();
await callback(sheet.name);
}).catch((error) => {
setError([error.message, error.code, "getActiveSheetName"]);
});
}
エクセルのデータ読み書きは殆どが非同期処理です。
Excel.run() に処理を書いたコールバック関数を渡し、関数内で await context.sync() を呼び出してデータを同期するのが基本形です。
「JavaScriptならVBAよりモダンで簡単になっているんだろうなぁ。行のハイライトぐらい簡単に実装できるやろ」なんて考えていましたが、実際には想像の10倍ぐらい難しかったです。
難しいポイントを考えてみました
- 複雑な処理をする場合、多くの非同期処理が必要(処理の順番が狂うと思った結果にならない)
- 取得できるデータに制限があるため、実現したい機能によってはトリッキーなコードを書く
- 一度の
await context.sync()で取得できるデータの範囲に制限があり、呼び出しには時間的コストがかかるため、ステップごとに取得するデータの範囲をよく考える必要がある - ユーザー操作の状況により避けられないエラーが発生するパターンがあり、細かいエラーハンドリングが必要
- 情報が少ないうえ、公式ドキュメントだけで上記の性質に気づくのは至難の業
他にもありそうですが、主な躓きポイントはこの辺りだと思います。
私は直接画面を見てデバッグする事が多いのですが、ステップ実行や公式で提供しているテスト用モック(充実している)を使えば幾分かデバッグ・開発が楽になると思います。
実装
全体のコードは結構ボリュームがあるので、セルをハイライトする機能に焦点を当てて説明します。
セルをハイライトする機能は下記の流れで処理します。
- イベントリスナーからセルの選択を検知する
- 前回選択されていた行のハイライトを解除する
- 今選択されている行を全てハイライトする (条件付き書式を追加する)
- ハイライトした行の情報をローカルストレージに保存する
行をハイライトするには、直接書式を適用する必要があるため、ある意味シートに対して破壊的変更を加えることになります。
そのため、クラッシュに備えてその履歴をローカルストレージに保存することで、確実に元通りに戻せることを目指しました。
下記は、今選択されている行を全てハイライトする処理のコードです。
private async executeHighlight() {
// Clear the previously highlighted rows
await Cleaner.create(this.setError).clearPrevious();
await Excel.run(async (context) => {
// Get the active worksheet and the selected range
const sheet = context.workbook.worksheets.getActiveWorksheet();
const rangeArea = context.workbook.getSelectedRanges();
rangeArea.load("cellCount");
sheet.load("name");
await context.sync();
// Check if the selected range is too large
if (rangeArea.cellCount > 1638400 || rangeArea.cellCount < 1) {
return;
}
// Get the selected rows
rangeArea.areas.load("items");
await context.sync();
// Highlight the active row
for (const item of rangeArea.areas.items) {
await this.processHighlight(context, item.getEntireRow(), sheet.name);
}
}).catch((error) => {
this.setError([error.message, error.code, "executeHighlight"]);
});
}
private async processHighlight(context: Excel.RequestContext, range: Excel.Range, sheetName: string) {
// Highlight the active row
const conditionalFormat = range.conditionalFormats.add(Excel.ConditionalFormatType.custom);
conditionalFormat.custom.rule.formula = CONDITIONAL_FORMAT_FORMULA;
conditionalFormat.custom.format.fill.color = this.option.xColor;
// Save the highlighted row to the local storage
range.conditionalFormats.load("count");
range.load("address");
const count = range.conditionalFormats.getCount();
try {
await context.sync();
// Save the highlighted row to the local storage
await Storage.setPrevious(sheetName, range.address, count.value - 1);
} catch (error) {
this.setError([error.message, error.code, "processHighlight"]);
}
}
Officeアドインなぞ知らない方にとっては読むのもきついと思いますが、いくつかのポイントを挙げてみます。
- 大量のデータを一気に取得するとクラッシュする場合があるので、まず選択範囲のセルの数だけを取得する
- 現在設定されている条件付き書式の内容を取得することはできない(セキュリティの為か?)
- 条件付き書式を設定した後、その書式のインデックスを取得して保持しないと元に戻せない
- 直接塗りつぶしの色を変えると元に戻せなくなるので、条件付き書式でハイライトする
この辺りは実装する上で重要な要素でした。
ハイライトを解除する処理もなかなかに複雑なのですが、詳しくはリポジトリをご覧ください。
ReactのHookから上記処理のクラスを利用しています
https://github.com/mimimiku778/Excel-View-Extension/blob/master/src/taskpane/utils/highlightRow/useHighlightRow.ts
ハイライトを解除するクラス
https://github.com/mimimiku778/Excel-View-Extension/blob/master/src/taskpane/utils/highlightRow/HighlightCleaner.ts
ちなみにVBAでこの機能を実現する場合は少し考え方が違いますが、幾分簡単になります
まとめ
想像と違い難易度が高かったため途中でやめようかと思いましたが、とりあえず目的の機能は実装できました。
現時点では開発環境を経由して使用することしかできないため、非正規の方法ですがマニフェストファイルをサイドロードできるスクリプトを作成する予定です。
それに加えてサーバーで公開する必要もあります。