0. todo
- デプロイの方法...はJSONPではなくJSONとして実装した場合の方が面倒になるので、そちらの記事で書くことにします。ということで、この記事は完了とします。
1. 準備、環境
1.1 Visual Studio
筆者はCommunity Editionを使用。他のバージョンについては未検証
1.2 Excel
筆者は Office365 ProPlusを使用。Excel Web Add-inはExcelのバージョンによりAPIレベルが設定されており、低いとほとんど何もできないです。365だと何も考えずにやれますが、最低でも2016、推奨2019。
1.3 Webサーバ
筆者はnode.jsを使用。とりあえずはスタティックなデータを送るデモなのでなんでも良いです。実用システムならJavaScript(JSONではない)を動的に生成することになります。
2. Quick Start
2.1 Visual Studio のWizardでプロジェクトを作成
詳細は割愛。ほとんど何も考えずにはいはいでExcelWebAddin1というプロジェクトができあがります(スクショでは2になっていますが)。とりあえず、そのまま動かしてみてください。
2.2 WebサーバにJavaScriptを置く
JSONPでは、サーバが供給するJavaScriptのコードをクライアントのコンテキストで実行することになります。今回は、2x2の乱数のマトリックスを作って関数を呼ぶようにしました。
test.js
(function() {
var values = [
[Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000)],
[Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000)]
];
jsonp_func(values);
})();
2.3 JSONPを組込む
【強調表示!】ボタンを押すと、test.jsが生成した2x2の乱数マトリックスをA1:B2にフィルするようにしてみました。
Wizardが吐いたHome.jsにJSONPを追加してください。
Home.js
var jsonp_func; // 追加 #1
(function () {
// 略
Office.initialize = function (reason) {
// 略
// 強調表示ボタンのクリック イベント ハンドラーを追加します。
$('#highlight-button').click(hightlightHighestValue);
jsonp_func = get_data; // 追加 #2
});
};
function call_jsonp() { // 追加 #3
const id = "get-data-script";
var s = document.getElementById(id);
if (s) {
s.parentElement.removeChild(s);
}
s = document.createElement("script");
s.src = encodeURI('<<サーバからtest.jsをダウンロードできるURL>>?ts=' + Date.now());
s.id = id;
document.getElementsByTagName('head')[0].appendChild(s);
}
function get_data(values) { // 追加 #4
Excel.run(function (ctx) {
var sheet = ctx.workbook.worksheets.getActiveWorksheet();
sheet.getRange("A1:B2").values = values;
return ctx.sync();
})
.catch(errorHandler);
}
function hightlightHighestValue() {
call_jsonp(); // 追加 #5
// 略
}
// 略
})();
3. 解説
3.1 JSONPとしての呼び出し
Home.js: call_jsonp(); // 追加 #5
- JSONPを呼びだすところ。
Home.js: function call_jsonp() { // 追加 #3
- JSONPの実体
- JSONPは、DOMにおいて、scriptエレメントが生成され、src属性が設定されると、JavaScriptのコードをどこかから(どこでもよい)をダウンロードし、(おおむね)即座に実行するという仕組みのようです。
- Visual Studioのデバッガ上でステップ実行すると、src属性がセットされたタイミングでサーバからGETしています。ただし、それはデバッガでステップ実行しているからだと思いますので、実際のタイミングにあまり仮定を置くのは危険です。
- ちなみに、scriptエレメント再利用してsrc属性の再設定だけで呼び出しが発生するかを試しましたが、呼び出されませんでした。
- src属性に(とりあえず)タイムスタンプのQUERY_STRINGを付け加えています。これは、src属性のURIが呼び出しごとに全く同じだと、新たに生成したscriptエレメントに設定しても、ダウンロードが発生しないためのworkaroundです。URIが呼出毎に変化すればタイムスタンプでもカウンターでも乱数でも何でも良いはずです。
- 生成したscriptエレメントは、次回の呼び出しの際に外して、新たに別のscriptエレメントを生成するようにしています。これが必要かどうかは不明です。JavaScriptでは参照が無くなるとオブジェクトは消えますが、scriptエレメントはDOMオブジェクトですので、念のため増殖しないようにしています。
3.2 JSONPで得られたデータを取り込むための仕組み
Home.js: var jsonp_func; // 追加 #1
- 生成されたscriptエレメントの中から実行される関数はページとしてのグローバルスコープに無ければなりませんので、変数宣言だけしておきます。
Home.js: jsonp_func = get_data; // 追加 #2
- 関数の実体を設定します。
Home.js: function get_data(values) { // 追加 #4
- 生成されたscriptエレメントの中から呼び出され、サーバからのデータを受けとる関数の実体
- データは引数として渡されます。