Excel Web Add-inからJSONPをする


# 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になっていますが）。とりあえず、そのまま動かしてみてください。

![JDAM-2019-10-23-16-37-35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252926/beea56bf-f435-85c5-2d34-5e991cb688d2.png)

![JDAM-2019-10-23-16-37-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252926/ea8187a3-0dd9-5358-810c-a03ae37d82a4.png)

![JDAM-2019-10-23-16-38-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252926/ec9a9bdf-b111-3de4-7879-2d52d483359d.png)

![JDAM-2019-10-23-16-38-58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252926/77e720c3-662e-3351-1dfa-6657cd9a1e04.png)

![JDAM-2019-10-23-16-39-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252926/4bb040fc-9c75-f7ae-db39-3491570587f3.png)

![JDAM-2019-10-23-16-39-50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252926/2d68fab2-0679-5871-9770-ebb9c5b9463e.png)

## 2.2 WebサーバにJavaScriptを置く

JSONPでは、サーバが供給するJavaScriptのコードをクライアントのコンテキストで実行することになります。今回は、2x2の乱数のマトリックスを作って関数を呼ぶようにしました。

```JavaScript: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を追加してください。

```JavaScript: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エレメントの中から呼び出され、サーバからのデータを受けとる関数の実体
 - データは引数として渡されます。