Microsoft Dynamics 365でSpreadJSを使用する

SpreadJS(スプレッドJS)」はExcelライクなスプレッドシートをWeb上で実現するJavaScriptライブラリです。サーバー環境に依存せず、クライアントサイドで動作するので、様々なクラウドサービスとも連携が可能です。

今回の記事ではMicrosoft社が提供する、CRMとERPが統合されたクラウド型のビジネスアプリケーション「Microsoft Dynamics 365」の中から営業支援に特化した「Dynamics 365 Sales」とSpreadJSを連携し、さらに「テーブルシート」の機能を使用してCRUD処理を行う方法をご紹介します。

今回使用する環境と作成するサンプルの概要

今回はDynamics 365 Salesの試用版の環境を使用してカスタマイズを試してみたいと思います。

Dynamics Sales試用版

今回作成するサンプルでは、クライアントサイドWebアプリケーションにSpreadJSを組みこみ、DynamicsのAPIを介してCRUD処理を行います。そして作成したアプリケーションはDynamicsの「Webリソース」としてホストします。また、Dynamicsの既存のグリッドニューのコマンドバーにカスタムのコマンドを追加し、シームレスにアクセスできるようにしてみます。

サンプル概要

クライアントサイドWebアプリケーションの作成

まずはDynamicsのAPIとデータ連携を行うクライアントサイドWebアプリケーションを作成します。今回は「spreadjs.html」という名前で以下のようなHTMLを用意し、Dynamicsのリードのデータを編集するようなアプリを作成します。

<!DOCTYPE html>
<html>

<head>
    <title>SpreadJS デモ</title>
    <meta charset="utf-8" />

    <!-- SpreadJS関連のCSSとライブラリを参照します -->
    <link
        href="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets@16.0.4/styles/gc.spread.sheets.excel2013white.min.css"
        rel="stylesheet" type="text/css" />
    <script src="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets@16.0.4/dist/gc.spread.sheets.all.min.js"
        type="text/javascript"></script>

    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-charts@16.0.4/dist/gc.spread.sheets.charts.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-shapes@16.0.4/dist/gc.spread.sheets.shapes.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-print@16.0.4/dist/gc.spread.sheets.print.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-barcode@16.0.4/dist/gc.spread.sheets.barcode.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-pdf@16.0.4/dist/gc.spread.sheets.pdf.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-excelio@16.0.4/dist/gc.spread.excelio.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-pivot-addon@16.0.4/dist/gc.spread.pivot.pivottables.min.js
        "></script>

    <script
        src="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-tablesheet@16.0.4/dist/gc.spread.sheets.tablesheet.min.js"
        type="text/javascript"></script>

    <link
        href="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-designer@16.0.4/styles/gc.spread.sheets.designer.min.css"
        rel="stylesheet">
    <script
        src="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-designer-resources-ja@16.0.4/dist/gc.spread.sheets.designer.resource.ja.min.js"></script>
    <script
        src="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-designer@16.0.4/dist/gc.spread.sheets.designer.all.min.js"></script>


    <!-- SpreadJSの日本語リソースを参照します -->
    <meta name="spreadjs culture" content="ja-jp" />
    <script
        src="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-resources-ja@16.0.4/dist/gc.spread.sheets.resources.ja.min.js"
        type='text/javascript'></script>

    <style>
        /* ページ背景色を設定します */
        body {
            background-color: #ECE9DD;
            color: #2f2f2f;
        }

        /* SpreadJSリボンコンテナDOMサイズを設定します */
        #ribbonContainerHost {
            width: 1400;
            height: 750px;
            border: 1px solid #ac9a9a;
        }
    </style>
</head>

<body>
    <!-- SpreadJSのリボンコンテナを配置するDOM要素を設定します -->    
    <div id="ribbonContainerHost"></div>
    <!-- パネルを配置するDOM要素を設定します -->
    <div id="panel" class="panelContainer">
        <div>
            <input id="add" type="button" value="追加">
            シートの末尾にデータを追加します
        </div>
        <div>
            <input id="remove" type="button" value="削除">
            選択したデータを削除します
        </div>
        <div>
            <input id="save" type="button" value="保存">
            選択したデータの変更内容を保存します
        </div>
        <div>
            <input id="reset" type="button" value="リセット">
            編集内容をリセットします(保存や削除による変更は戻せません)
        </div>
    </div>
</body>

<script>
    var tableSheet;
    var selections = [{ col: 0, colCount: 1, row: 0, rowCount: 1 }];

    // ライセンスキーの設定
    GC.Spread.Sheets.LicenseKey = "SpreadJS本体の配布ライセンスキーを設定";
    GC.Spread.Sheets.Designer.LicenseKey = "リボンコンテナの配布ライセンスキーを設定"

    // カスタムリクエスト
    function sendRequest(url, options) {
        // HTTPメソッドを設定します。引数内のメソッド内容が空の場合はPOSTを設定します
        options.method = options.method || 'POST';
        // リクエストのコンテンツ種別をJSONに設定します
        options.headers = { 'Content-Type': 'application/json; charset=utf-8' };
        // 変更を要求するデータのIDをURLに付与します
        if (options.method != 'POST') {
            var id = options.body.leadid;
            url = url + '(' + id.toString() + ')';
        }
        // リクエストのBODY部をJSON文字列にして設定します
        if (options.body) {
            options.body = JSON.stringify(options.body);
        } else {
            return options;
        }
        // リクエストを送信します
        return fetch(url, options).then(resp => {
            // リクエスト正常完了時の処理を実施します
            if (resp.ok) {
                return resp;
            }
            // リクエスト失敗時の処理を実施します
            else {
                throw resp.statusText;
            }
        });
    }

    // ワークブックの基本設定
    function initSpread(spread) {

        // スクロールバーの終端がシートの最終行および最終列になるように設定します
        spread.options.scrollbarMaxAlign = true;

        // スクロールバーを非表示に設定します
        spread.options.showVerticalScrollbar = true;
        spread.options.showHorizontalScrollbar = true;

        // 既存のシートを非表示にします
        var workSheet = spread.getSheet(0);
        workSheet.visible(false);
    };

    // ページロード時の処理
    window.onload = function () {

        // リボンコンテナを生成します
        var ribbonContainer = new GC.Spread.Sheets.Designer.Designer('ribbonContainerHost');
        var spread = ribbonContainer.getWorkbook();
        
        // ワークブックの基本設定(テーブルシート関連以外の設定)を行います
        initSpread(spread);

        // ワークブックにテーブルシートを追加します
        tableSheet = spread.addSheetTab(0, "テーブルシート", GC.Spread.Sheets.SheetType.tableSheet);

        // データマネージャーを生成します
        var dataManager = spread.dataManager();

        // データマネージャーにテーブルを追加します
        var apiUrl = 'https://*************/api/data/v9.2/leads';
        var leadTable = dataManager.addTable("リード情報", {
            schema: {
                columns: {
                    statecode: {
                        dataMap: { 0: "オープン", 1: "見込みあり", 2: "見込みなし" },
                        lookup: ["オープン", "見込みあり", "見込みなし"]
                    },
                    statuscode: {
                        dataMap: { 1: "新規", 2: "連絡済み" },
                        lookup: ["新規", "連絡済み"]
                    },
                    leadsourcecode: {
                        dataMap: { 1: "広告", 2: "社員紹介", 3: "外部紹介", 4: "パートナー", 5: "広報活動", 6: "セミナー", 7: "見本市", 8: "Web", 9: "口コミ", 10: "その他" },
                        lookup: ["広告", "社員紹介", "外部紹介", "パートナー", "広報活動", "セミナー", "見本市", "Web", "口コミ", "その他"]
                    },
                    leadqualitycode: {
                        dataMap: { 1: "高", 2: "中", 3: "低" },
                        lookup: ["高", "中", "低"]
                    }
                }
            },
            autoSync: true,
            remote: {
                /* データ登録処理設定します */
                create: function (item) {
                    return sendRequest(apiUrl, { body: item, method: 'POST' });
                },
                /* データを取得するWebAPIのURLを設定します */
                read: {
                    url: apiUrl + '?$select=companyname,jobtitle,lastname,firstname,address1_stateorprovince,address1_city,address1_line1,emailaddress1,telephone1,mobilephone,subject,statecode,statuscode,leadsourcecode,leadqualitycode,createdon,modifiedon',
                    adapter: 'odata',
                },
                /* データ更新処理を設定します */
                update: function (item) {
                    return sendRequest(apiUrl, { body: item, method: 'PATCH' });
                },
                /* データ削除処理を設定します */
                delete: function (item) {
                    return sendRequest(apiUrl, { body: item[0], method: 'DELETE' });
                }
            }
        });

        //------------------------------------------
        // テーブルシートに表示するビューを設定します
        //------------------------------------------
        // ビューの列情報を定義します
        var columnInfos = [
            { value: "companyname", caption: "会社", width: 400 },
            { value: "jobtitle", caption: "役職", width: 180 },
            { value: "lastname", caption: "名前(姓)", width: 130 },
            { value: "firstname", caption: "名前(名)", width: 130 },
            { value: "address1_stateorprovince", caption: "都道府県", width: 130 },
            { value: "address1_city", caption: "市区町村", width: 150 },
            { value: "address1_line1", caption: "町大字通称名", width: 200 },
            { value: "emailaddress1", caption: "電子メール", width: 200 },
            { value: "telephone1", caption: "電話番号", width: 150 },
            { value: "mobilephone", caption: "携帯電話", width: 150 },
            { value: "subject", caption: "トピック", width: 500 },
            { value: "statecode", caption: "状態", width: 120 },
            { value: "statuscode", caption: "ステータス", width: 120 },
            { value: "leadsourcecode", caption: "リードソース", width: 120 },
            { value: "leadqualitycode", caption: "評価", width: 80 },
            { value: "createdon", caption: "作成日", width: 150 },
            { value: "modifiedon", caption: "更新日", width: 150 }
        ];

        //------------------------------------------
        // データの取得と表示を行います
        //------------------------------------------
        // テーブルに設定するデータの取得し、成功時にコールバック関数を実行します
        leadTable.fetch().then(function () {
            // テーブルシートに表示するビューを設定します
            var view = leadTable.addView("リード情報", columnInfos, true);
            // テーブルシートにビューを設定して表示します
            tableSheet.setDataView(view);
        });

        //------------------------------------------
        // 選択状態変更時の処理を行います
        //------------------------------------------
        spread.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, args) {
            selections = args.newSelections;
        });

        //------------------------------------------
        // ボタン押下時の処理
        //------------------------------------------
        var addButton = document.getElementById('add');
        var removeButton = document.getElementById('remove');
        var saveButton = document.getElementById('save');
        var resetButton = document.getElementById('reset');

        addButton.addEventListener('click', onAddButton);
        removeButton.addEventListener('click', onRemoveButton);
        saveButton.addEventListener('click', onSaveButton);
        resetButton.addEventListener('click', onResetButton);
    };

    // 引数に設定された処理をテーブルシート上の選択範囲分実行します
    function traverseSelectionsRowsWithOperation(operation) {
        if (selections) {
            selections.sort(function(a, b){
                return b.row - a.row;
            });
            for (var i = 0; i < selections.length; i++) {
                var selection = selections[i];
                var row = selection.row;
                var rowCount = selection.rowCount;
                for (var r = row + rowCount - 1; r >= row; r--) {
                    operation(r);
                    console.log(r);
                }
                alert(rowCount + "件のデータを更新しました。")
            }

        }
    }
    // 「追加」ボタン押下時の処理を実行します
    function onAddButton() {
        traverseSelectionsRowsWithOperation(function (row) {
            tableSheet.addRow({});
        });
    }
    // 「削除」ボタン押下時の処理を実行します
    function onRemoveButton() {
        traverseSelectionsRowsWithOperation(function (row) {
            tableSheet.removeRow(row);
        });
    }
    // 「保存」ボタン押下時の処理を実行します
    function onSaveButton() {
        traverseSelectionsRowsWithOperation(function (row) {
            tableSheet.saveRow(row);
        });
    }
    // 「リセット」ボタン押下時の処理を実行します
    function onResetButton() {
        traverseSelectionsRowsWithOperation(function (row) {
            tableSheet.resetRow(row);
        });
    }
</script>

</html>

以下でコードの詳細を解説していきます。

SpreadJSの参照設定

まずは使用するSpreadJSのライブラリやスタイルシートの参照設定を行います。SpreadJSのライブラリは弊社のCDNでも公開していますが、一時的なテスト等の用途以外での使用は想定していません。ご利用の際はお客様自身で適切に配置してご利用ください。
※ ライブラリをDynamicsのWebリソースに登録して参照することも可能ですが、登録できるファイルサイズには制限があるので注意してください。

今回はnpmで公開されているパッケージをCDNとして配信している「jsDelivr」を使用して各ライブラリを参照します。

また、今回はスプレッドシートだけでなく、Excelライクなリボンメニューや数式バー、コンテキストメニューやステータスバーといったUIを一挙に備える「リボンコンテナ」を使用するため、それに対応したライブラリの参照も追加しています。

<!DOCTYPE html>
<html>

<head>
    <title>SpreadJS デモ</title>
    <meta charset="utf-8" />

    <!-- SpreadJS関連のCSSとライブラリを参照します -->
    <link
        href="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets@16.0.4/styles/gc.spread.sheets.excel2013white.min.css"
        rel="stylesheet" type="text/css" />
    <script src="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets@16.0.4/dist/gc.spread.sheets.all.min.js"
        type="text/javascript"></script>

    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-charts@16.0.4/dist/gc.spread.sheets.charts.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-shapes@16.0.4/dist/gc.spread.sheets.shapes.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-print@16.0.4/dist/gc.spread.sheets.print.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-barcode@16.0.4/dist/gc.spread.sheets.barcode.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-pdf@16.0.4/dist/gc.spread.sheets.pdf.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-excelio@16.0.4/dist/gc.spread.excelio.min.js
        "></script>
    <script src="
        https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-pivot-addon@16.0.4/dist/gc.spread.pivot.pivottables.min.js
        "></script>

    <script
        src="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-tablesheet@16.0.4/dist/gc.spread.sheets.tablesheet.min.js"
        type="text/javascript"></script>

    <link
        href="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-designer@16.0.4/styles/gc.spread.sheets.designer.min.css"
        rel="stylesheet">
    <script
        src="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-designer-resources-ja@16.0.4/dist/gc.spread.sheets.designer.resource.ja.min.js"></script>
    <script
        src="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-designer@16.0.4/dist/gc.spread.sheets.designer.all.min.js"></script>

    <!-- SpreadJSの日本語リソースを参照します -->
    <meta name="spreadjs culture" content="ja-jp" />
    <script
        src="https://cdn.jsdelivr.net/npm/@grapecity/spread-sheets-resources-ja@16.0.4/dist/gc.spread.sheets.resources.ja.min.js"
        type='text/javascript'></script>
・・・(中略)・・・

style要素とbody要素の定義

style要素でWebページ全体の背景色とSpreadJSをホストするコンテナ部分のスタイルを設定します。

またbody要素ではSpreadJS本体と登録、更新、削除などの各処理を行うボタンをホストするコンテナをそれぞれ定義します。

・・・(中略)・・・
    <style>
        /* ページ背景色を設定します */
        body {
            background-color: #ECE9DD;
            color: #2f2f2f;
        }

        /* SpreadJSリボンコンテナDOMサイズを設定します */
        #ribbonContainerHost {
            width: 1400;
            height: 750px;
            border: 1px solid #ac9a9a;
        }
    </style>
・・・(中略)・・・
<body>
    <!-- SpreadJSのリボンコンテナを配置するDOM要素を設定します -->    
    <div id="ribbonContainerHost"></div>
    <!-- パネルを配置するDOM要素を設定します -->
    <div id="panel" class="panelContainer">
        <div>
            <input id="add" type="button" value="追加">
            シートの末尾にデータを追加します
        </div>
        <div>
            <input id="remove" type="button" value="削除">
            選択したデータを削除します
        </div>
        <div>
            <input id="save" type="button" value="保存">
            選択したデータの変更内容を保存します
        </div>
        <div>
            <input id="reset" type="button" value="リセット">
            編集内容をリセットします(保存や削除による変更は戻せません)
        </div>
    </div>
</body>

・・・(中略)・・・

ライセンスキーの設定

今回はSpreadJSを組み込んだアプリをDynamicsの環境に配布しますので、配布ライセンスキーを設定します。
※ 設定しない場合はSpreadJSを実行できません。

・・・(中略)・・・
    // ライセンスキーの設定
    GC.Spread.Sheets.LicenseKey = "SpreadJS本体の配布ライセンスキーを設定";
    GC.Spread.Sheets.Designer.LicenseKey = "リボンコンテナの配布ライセンスキーを設定"
・・・(中略)・・・

カスタムリクエストの定義

今回はテーブルシートの機能を使ってCRUD処理を行います。テーブルシートでAPIをコールする場合、以下のようなリクエスト送信用のメソッドを定義し、後述するテーブルシートの設定時に使用します。

・・・(中略)・・・
// カスタムリクエスト
    function sendRequest(url, options) {
        // HTTPメソッドを設定します。引数内のメソッド内容が空の場合はPOSTを設定します
        options.method = options.method || 'POST';
        // リクエストのコンテンツ種別をJSONに設定します
        options.headers = { 'Content-Type': 'application/json; charset=utf-8' };
        // 変更を要求するデータのIDをURLに付与します
        if (options.method != 'POST') {
            var id = options.body.leadid;
            url = url + '(' + id.toString() + ')';
        }
        // リクエストのBODY部をJSON文字列にして設定します
        if (options.body) {
            options.body = JSON.stringify(options.body);
        } else {
            return options;
        }
        // リクエストを送信します
        return fetch(url, options).then(resp => {
            // リクエスト正常完了時の処理を実施します
            if (resp.ok) {
                return resp;
            }
            // リクエスト失敗時の処理を実施します
            else {
                throw resp.statusText;
            }
        });
    }
・・・(中略)・・・

SpreadJSの初期化処理

次にSpreadJSの初期化処理を行います。「initSpread」関数でスプレッドシートの各種初期設定を定義し、ページロード時にそれらを実行します。

・・・(中略)・・・
    // ワークブックの基本設定
    function initSpread(spread) {

        // スクロールバーの終端がシートの最終行および最終列になるように設定します
        spread.options.scrollbarMaxAlign = true;

        // スクロールバーを表示します
        spread.options.showVerticalScrollbar = true;
        spread.options.showHorizontalScrollbar = true;

        // 既存のシートを非表示にします
        var workSheet = spread.getSheet(0);
        workSheet.visible(false);
    };

    // ページロード時の処理
    window.onload = function () {

        // リボンコンテナを生成します
        var ribbonContainer = new GC.Spread.Sheets.Designer.Designer('ribbonContainerHost');
        var spread = ribbonContainer.getWorkbook();
        
        // ワークブックの基本設定(テーブルシート関連以外の設定)を行います
        initSpread(spread);
・・・(中略)・・・

テーブルシートとデータマネージャーの設定

次にテーブルシートの設定を行います。テーブルシートでは「データマネージャー」というデータ管理コンポーネントを使用することで、データの表示だけでなく登録や更新、削除といった処理を実現できます。

以下のようにテーブルシートを追加、及びデータマネージャーの生成を行います。

Dynamicsではステータスのようなデータを、「0、1、2」のようなコード値で管理していますが、実際に画面上に表示するときは「オープン、見込みあり、見込みなし」のような文字列を表示します。「columns」オプションで定義したデータマップの機能を使用することで、このような要件にも対応可能です。

「remote」オプションではCRUD処理を行う場合に実行する処理を定義します。今回は参照以外は先ほど定義した「sendRequest」メソッドを呼び出してAPIを実行します。

・・・(中略)・・・
        // ワークブックにテーブルシートを追加します
        tableSheet = spread.addSheetTab(0, "テーブルシート", GC.Spread.Sheets.SheetType.tableSheet);

        // データマネージャーを生成します
        var dataManager = spread.dataManager();

        // データマネージャーにテーブルを追加します
        var apiUrl = 'https://*************/api/data/v9.2/leads';
        var leadTable = dataManager.addTable("リード情報", {
            schema: {
                columns: {
                    statecode: {
                        dataMap: { 0: "オープン", 1: "見込みあり", 2: "見込みなし" },
                        lookup: ["オープン", "見込みあり", "見込みなし"]
                    },
                    statuscode: {
                        dataMap: { 1: "新規", 2: "連絡済み" },
                        lookup: ["新規", "連絡済み"]
                    },
                    leadsourcecode: {
                        dataMap: { 1: "広告", 2: "社員紹介", 3: "外部紹介", 4: "パートナー", 5: "広報活動", 6: "セミナー", 7: "見本市", 8: "Web", 9: "口コミ", 10: "その他" },
                        lookup: ["広告", "社員紹介", "外部紹介", "パートナー", "広報活動", "セミナー", "見本市", "Web", "口コミ", "その他"]
                    },
                    leadqualitycode: {
                        dataMap: { 1: "高", 2: "中", 3: "低" },
                        lookup: ["高", "中", "低"]
                    }
                }
            },
            autoSync: true,
            remote: {
                /* データ登録処理設定します */
                create: function (item) {
                    return sendRequest(apiUrl, { body: item, method: 'POST' });
                },
                /* データを取得するWebAPIのURLを設定します */
                read: {
                    url: apiUrl + '?$select=companyname,jobtitle,lastname,firstname,address1_stateorprovince,address1_city,address1_line1,emailaddress1,telephone1,mobilephone,subject,statecode,statuscode,leadsourcecode,leadqualitycode,createdon,modifiedon',
                    adapter: 'odata',
                },
                /* データ更新処理を設定します */
                update: function (item) {
                    return sendRequest(apiUrl, { body: item, method: 'PATCH' });
                },
                /* データ削除処理を設定します */
                delete: function (item) {
                    return sendRequest(apiUrl, { body: item[0], method: 'DELETE' });
                }
            }
        });

        //------------------------------------------
        // テーブルシートに表示するビューを設定します
        //------------------------------------------
        // ビューの列情報を定義します
        var columnInfos = [
            { value: "companyname", caption: "会社", width: 400 },
            { value: "jobtitle", caption: "役職", width: 180 },
            { value: "lastname", caption: "名前(姓)", width: 130 },
            { value: "firstname", caption: "名前(名)", width: 130 },
            { value: "address1_stateorprovince", caption: "都道府県", width: 130 },
            { value: "address1_city", caption: "市区町村", width: 150 },
            { value: "address1_line1", caption: "町大字通称名", width: 200 },
            { value: "emailaddress1", caption: "電子メール", width: 200 },
            { value: "telephone1", caption: "電話番号", width: 150 },
            { value: "mobilephone", caption: "携帯電話", width: 150 },
            { value: "subject", caption: "トピック", width: 500 },
            { value: "statecode", caption: "状態", width: 120 },
            { value: "statuscode", caption: "ステータス", width: 120 },
            { value: "leadsourcecode", caption: "リードソース", width: 120 },
            { value: "leadqualitycode", caption: "評価", width: 80 },
            { value: "createdon", caption: "作成日", width: 150 },
            { value: "modifiedon", caption: "更新日", width: 150 }
        ];

        //------------------------------------------
        // データの取得と表示を行います
        //------------------------------------------
        // テーブルに設定するデータの取得し、成功時にコールバック関数を実行します
        leadTable.fetch().then(function () {
            // テーブルシートに表示するビューを設定します
            var view = leadTable.addView("リード情報", columnInfos, true);
            // テーブルシートにビューを設定して表示します
            tableSheet.setDataView(view);
        });
・・・(中略)・・・

登録、更新、削除処理の設定

最後に画面上から[追加]、[保存]、[削除]、[リセット]のボタンをクリックしたときの処理を定義します。今回はシート上で選択した行のデータに対して更新処理を行うように定義しています。

・・・(中略)・・・
        //------------------------------------------
        // 選択状態変更時の処理を行います
        //------------------------------------------
        spread.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, args) {
            selections = args.newSelections;
        });

        //------------------------------------------
        // ボタン押下時の処理
        //------------------------------------------
        var addButton = document.getElementById('add');
        var removeButton = document.getElementById('remove');
        var saveButton = document.getElementById('save');
        var resetButton = document.getElementById('reset');

        addButton.addEventListener('click', onAddButton);
        removeButton.addEventListener('click', onRemoveButton);
        saveButton.addEventListener('click', onSaveButton);
        resetButton.addEventListener('click', onResetButton);
    };

    // 引数に設定された処理をテーブルシート上の選択範囲分実行します
    function traverseSelectionsRowsWithOperation(operation) {
        if (selections) {
            selections.sort(function(a, b){
                return b.row - a.row;
            });
            for (var i = 0; i < selections.length; i++) {
                var selection = selections[i];
                var row = selection.row;
                var rowCount = selection.rowCount;
                for (var r = row + rowCount - 1; r >= row; r--) {
                    operation(r);
                    console.log(r);
                }
                alert(rowCount + "件のデータを更新しました。")
            }

        }
    }
    // 「追加」ボタン押下時の処理を実行します
    function onAddButton() {
        traverseSelectionsRowsWithOperation(function (row) {
            tableSheet.addRow({});
        });
    }
    // 「削除」ボタン押下時の処理を実行します
    function onRemoveButton() {
        traverseSelectionsRowsWithOperation(function (row) {
            tableSheet.removeRow(row);
        });
    }
    // 「保存」ボタン押下時の処理を実行します
    function onSaveButton() {
        traverseSelectionsRowsWithOperation(function (row) {
            tableSheet.saveRow(row);
        });
    }
    // 「リセット」ボタン押下時の処理を実行します
    function onResetButton() {
        traverseSelectionsRowsWithOperation(function (row) {
            tableSheet.resetRow(row);
        });
    }
</script>

Webリソースの登録

作成したWebアプリケーションをDynamicsのWebリソースとして登録します。

Dynamics Salesの画面右上のメニューから「詳細設定」をクリックします。

詳細設定

「カスタマイズ」のメニューをクリックします。

カスタマイズ

カスタマイズメニューの一覧から「システムのカスタマイズ」をクリックします。

システムのカスタマイズ

Dynamicsの既定のソリューションのオブジェクトの一覧が表示されます。この画面からも登録は可能ですが、新しいデザイナが使えるので、画面上部の[新しいエクスペリエンスを試す]のボタンをクリックします。

新しいエクスペリエンス

Power Appsのアプリデザイナーでオブジェクトの一覧が表示されます。

アプリデザイナー

「新規」⇒「その他」⇒「Webリソース」を選択します。

Webリソースの追加

ダイアログから[ファイルを選択]ボタンをクリックし、先ほど作成した「spreadjs.html」をアップロードします。

ファイルを選択

「表示名」、「名前」にそれぞれ適当な名前を付けます。今回はそれぞれ「spreadjs」と設定します。設定後[保存]ボタンをクリックし、Webリソースの登録を完了します。

表示名と名前の設定

登録したWebリソースが一覧に表示されるので、表示名の部分をクリックします。
※ 以下はフィルタを使用して表示しています。

登録したWebリソース

編集のダイアログが表示されるので、「高度なオプション」を開くと「URL」の項目で登録したWebリソースのURLを取得できます。

URLの確認

取得したURLにブラウザからアクセスすると、以下のようにDynamicsのリードのデータをSpreadJS上に表示できました。

DynamicsのデータをSpreadJSに表示

動作確認

以上でWebアプリケーションをDynamicsのWebリソースにホストして実行することができましたので、SpreadJSの機能をそれぞれ確認してみます。

テーマの変更

今回使用しているテーブルシートでは84と非常に豊富な数のテーマが使用可能です。テーマはコードから設定できるほか、リボンコンテナを使えばユーザーがリボンメニューから任意のテーマに変更できます。

印刷とエクスポート

さらにリボンの「ファイル」メニューからは、シートをそのままExcelやCSV、PDFといった各種ファイル形式へエクスポートしたり、印刷を実行したりといったことも可能です。

フィルタ

列ヘッダ右側のアイコンをクリックすると、Excelライクなフィルタが使用可能です。

グループ化

コードからあらかじめ項目を指定してグループ化することもできますが、以下のようにグループパネルを使ってユーザーがインタラクティブにグループ化を行うこともできます。

データマップ

Dynamicsから取得した各種ステータスの値などは「0、1、2」のようなコード値ですが、データマップの機能を使うことで画面に表示するときは対応した文字列を表示できます。

データマップ

登録、更新、削除

シートの下に配置した[追加]、[削除]、[保存]ボタンをクリックすることで変更をDynamicsのデータに反映することが出来ます。以下はデータの更新する場合の例です。

Dynamicsのリードの一覧画面を開いてみると、きちんと更新が反映されていることがわかります。

リードのメイングリッドで更新を確認

コマンドバーにリンクの追加

最後に仕上げとしてリードのグリッドビューにあるコマンドバー(リボン)をカスタマイズして、先ほど登録したWebリソースの画面にリンクするカスタムのコマンドを追加してみたいと思います。

コマンドバーの編集方法は以下の記事をご参考ください。

上記の記事の手順に従いリードのメイングリッドのコマンドバーのカスタマイズ画面を開き、左上のメニューから新規コマンドを追加します。

コマンドバーのカスタマイズ

新しいコマンドが追加されました。

コマンドバーのカスタマイズ2

右側のメニューからコマンドの設定を行います。「ラベル」の項目からコマンドの表示ラベルを「Edit with SpreadJS」に変更します。

また、「アクション」の項目にLaunch関数を使った計算式を設定し、クリックされたら先ほどのWebリソースのページが開くようにリンクを設定します。

コマンドバーのカスタマイズ3

設定完了後、「保存して公開する」をクリックし、カスタマイズした内容を公開します。

コマンドバーのカスタマイズ4

公開完了後、ブラウザを再読み込みするとリードのビューのコマンドバーにカスタムのコマンドが表示されます。

コマンドバーのカスタマイズ5

カスタムのコマンドをクリックすると、リンクに設定したWebリソースのページが開き、SpreadJSでリードのデータの表示と編集ができます。

さいごに

今回の記事では、「Microsoft Dynamics 365 Sales」とJavaScriptスプレッドシートライブラリ「SpreadJS」を連携し、「テーブルシート」の機能を使用してCRUD処理を行う方法をご紹介しました。

製品サイトでは、今回ご紹介したSpreadJSの機能を手軽に体験できるデモアプリケーションやトライアル版も公開しておりますので、こちらもご確認ください。

また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /