smemo

技術メモです。

【Dynamics】オートコンプリートの実装

フィールドに値を入力する際のオートコンプリート(補完機能)の実装方法のメモ

以下MSDNを参考にしました。
https://msdn.microsoft.com/ja-jp/library/gg334266.aspx#BKMK_AutoCompletion


オートコンプリートのイメージ
業種コードに入力をすると候補を表示

f:id:smemo:20170102203809p:plain

全角入力時は入力後、スペースなどを入力すると候補が表示。。。(いまいちですね。。)
f:id:smemo:20170102204233p:plain


サンプルコードは以下

var OnKeyPressSample = (function () {

    return {
        Onload: function () {
            // List of sample account names to suggest
            sicCodes = [
              { name: 'G01', code: 'A01' },
              { name: 'G02', code: 'A02' },
              { name: 'G03', code: 'A03' },
              { name: 'G10', code: 'A04' },
              { name: 'G11', code: 'A05' },
              { name: 'G12', code: 'A06' },
              { name: '業種コード1', code: 'A07' },
            ];

            // キープレス時動作
            var keyPressFcn = function (ext) {
                try {
                    var userInput = Xrm.Page.getControl("sic").getValue();
                    resultSet = {
                        results: new Array(),
                        commands: {
                            id: "sp_commands",
                            label: "Learn More",
                            action: function () {
                                // "Learn More" link をクリックしたときのイベント
                                window.open("http://www.microsoft.com/en-us/dynamics/crm-customer-center/create-or-edit-an-account.aspx");
                            }
                        }
                    };

                    var userInputLowerCase = userInput.toLowerCase();

                    for (i = 0; i < sicCodes.length; i++) {
                        if (userInputLowerCase === sicCodes[i].name.substring(0, userInputLowerCase.length).toLowerCase()) {
                            resultSet.results.push({
                                id: i,
                                fields: [sicCodes[i].name]
                            });
                        }
                        if (resultSet.results.length >= 10) break;
                    }

                    if (resultSet.results.length > 0) {
                        ext.getEventSource().showAutoComplete(resultSet);
                    } else {
                        ext.getEventSource().hideAutoComplete();
                    }
                } catch (e) {
                    console.log(e);
                }
            };

            // 業種コードキープレスイベントに動作割り当て
            Xrm.Page.getControl("sic").addOnKeyPress(keyPressFcn);
        }
    };
})();

この機能を使って、ユーザーの入力を補助することが可能になりそうです。
(定型文入力+フリー入力などで使えそうでしょうか?)

【Dynamics】javascriptで業務プロセスを進める

javascriptで業務プロセスを進めたり、戻したりするメソッドがあったので使い方のメモ

var ProcessScriptSample = (function () {
    return {
        // 進める
        Next: function () {
            parent.Xrm.Page.data.process.moveNext(function (result) {
                Xrm.Utility.alertDialog(result);
            });
        },
        // 戻す
        Prev: function () {
            parent.Xrm.Page.data.process.movePrevious(function (result) {
                Xrm.Utility.alertDialog(result);
            });
        }
    };
})();

イメージは以下。
プロセス移動前
f:id:smemo:20161228233430p:plain

Nextボタン押下でプロセス移動後
f:id:smemo:20161228233436p:plain

今回はボタン押下をトリガーにプロセスのステージを移動させましたが、
フォームの保存時に特定の項目に値が入力させたらプロセスを自動的に次のステージに移動することなどが実装可能です。

【C#】xmlを読み込んでオブジェクトへ変換する -デシリアライズ-

Dynamics365のビュー定義が格納されているSavedQueryエンティティ。
このエンティティからビューの定義をExcelに出力したいと思い、その中でビューの定義情報を格納するfetchxmlとlayoutxmlをオブジェクト化する方法を調べたのでメモ。

今回はXmlSerializerクラスのDeserializeメソッドを使用しました。
https://msdn.microsoft.com/ja-jp/library/tz8csy73%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396

読み込んだXMLを格納するオブジェクト

    [System.Xml.Serialization.XmlRoot("grid")]
    public class ViewLayoutXmlInfo
    {
        [System.Xml.Serialization.XmlArray("row")]
        [System.Xml.Serialization.XmlArrayItem("cell")]
        public List<ViewLayoutXmlCell> Cells { get; set; }

        public class ViewLayoutXmlCell
        {
            [System.Xml.Serialization.XmlAttribute("name")]
            public string Name { get; set; }

            [System.Xml.Serialization.XmlAttribute("width")]
            public string Width { get; set; }

            [System.Xml.Serialization.XmlAttribute("imageproviderwebresource")]
            public string ImageProviderWebresource { get; set; }

            [System.Xml.Serialization.XmlAttribute("imageproviderfunctionname")]
            public string ImageProviderFunctionname { get; set; }
        }
    }

読み込むXMLのサンプル(実際はSavedQueryエンティティを検索して取得します)

<grid name="resultset" object="1" jump="name" select="1" icon="1" preview="1">
    <row name="result" id="accountid">
        <cell name="name" width="300" />
        <cell name="primarycontactid" width="150" />
        <cell name="telephone1" width="100" />
    </row>
</grid>
 // LayoutXmlの読み込み
var xmlReader = XmlUtil.CreateXmlReader(layoutXml);

XmlSerializer serializer = new XmlSerializer(typeof(ViewLayoutXmlInfo));

ViewLayoutXmlInfo layoutXmlRowInfo = (ViewLayoutXmlInfo)serializer.Deserialize(xmlReader);

【C#】C#プログラムでExcelを出力する

C#プログラムからExcelを出力する方法について少し調べたのでメモ

Microsoft.Office.Interop.Excelライブラリを使用する方法
Excelをインストールしていないと使用できないのとオブジェクトの解放を意識する必要がある。

ClosdeXmlを使用する方法
Microsoft Open XMLを使用したオープンソースExcelのインストールは不要


今回はClosedXmlを使用してDynamicsのエンティティのリストをExcelを出力してみました。

public void ExportEntityList()
{
    // エンティティメタデータ取得
    var entityMetadatas = _crmsrv.GetAllEntityMetadata();

    XLWorkbook workbook = new XLWorkbook();

    IXLWorksheet ws = workbook.AddWorksheet("sample");

    // エンティティの表示名と論理名をオブジェクトとして設定
    ws.Cell(1, 1).Value = entityMetadatas
        .OrderBy(entity => entity.LogicalName)
        .Select(entityMetadata => new
        {
            DisplayName = (entityMetadata.DisplayName != null && entityMetadata.DisplayName.UserLocalizedLabel != null)
                            ? entityMetadata.DisplayName.UserLocalizedLabel.Label + "(" + entityMetadata.LogicalName + ")" : entityMetadata.LogicalName,
            LogicalName =  entityMetadata.LogicalName
        }).ToList();

    workbook.SaveAs("sample.xlsx");
}


実行結果イメージは以下。オブジェクトのリストを渡すだけで簡単に一覧の出力が可能でした。
f:id:smemo:20161221001836p:plain

以上です。

【Dynamics】【XrmTooling】XrmToolingを使用してEntityMetadataをすべて取得

XrmToolingを使用してEntityMetadataを取得する方法のメモ。


エンティティのメタデータを取得するには以下1行で取得可能(全エンティティ分取得)

// _crmsrvはCrmServiceClientオブジェクト
_crmsrv.GetAllEntityMetadata();

その他のメソッドについてはMSDNを参考に
CrmServiceClient のメソッド (Microsoft.Xrm.Tooling.Connector)

【Dynamics】フォームでのグラフ表示

Dynamicsではダッシュボードやビューでグラフを表示することができますが、フォームでもグラフを表示することができます。

以下、取引先企業毎の営業案件についての情報をフォームダッシュボードとして表示した際のイメージになります。
f:id:smemo:20161214221857p:plain

設定方法

フォームのサブグリッドを設定する際にグラフを表示するチェックボックスがあるのでそれにチェックをつけるとグラフを表示することができます。
f:id:smemo:20161214221942p:plain

レコード毎の情報を視覚化したい場合に使える機能だと思います。

【Dynamics】WebAPIの機能拡張 作成または更新プログラム上のリターン エンティティ データ

WebAPIでエンティティレコードを作成、更新した際に以前までは作成したレコードのGUIDのみがリターンされていました。今回のアップデートでエンティティのフィールドをリターンすることができるようになりました。


以下、MSDNのサンプルの抜粋です。
Web API を使用してエンティティを作成する



リクエストのサンプル

POST [Organization URI]/api/data/v8.2/accounts?$select=name,creditonhold,address1_latitude,description,revenue,accountcategorycode,createdon HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8
Prefer: return=representation

{
    "name": "Sample Account",
    "creditonhold": false,
    "address1_latitude": 47.639583,
    "description": "This is the description of the sample account",
    "revenue": 5000000,
    "accountcategorycode": 1
}

レスポンスのサンプル

{
  "@odata.context":"https://orgname.api.crm7.dynamics.com/api/data/v8.2/$metadata#accounts(name,creditonhold,address1_latitude,description,revenue,accountcategorycode,createdon)","value":[
    {
      "@odata.etag":"W/\"635745\"","name":"001","creditonhold":false,"address1_latitude":null,"description":null,"revenue":null,"accountcategorycode":null,"createdon":"2017-01-07T12:07:42Z","_transactioncurrencyid_value":"eee7fb82-6fc8-e611-80fc-c4346bc52044","accountid":"7ccb8ce3-d1d4-e611-80fd-c4346bad16b0"
    }
}

この機能を使うことでCREATEやUPDATE後に何か処理を行う場合に、取得処理を省くことができるようになりました。

追記:リターンエンティティデータを試した際のサンプルコード(備忘)

function CreateRecord(entitySetName, data) {
    return new Promise(function (resolve, reject) {
        var req = new XMLHttpRequest();
        req.open("POST","api/data/v8.2/accounts?$select=name,creditonhold,address1_latitude,description,revenue,accountcategorycode,createdon", true);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.setRequestHeader("Prefer", "return=representation");
        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 201) {
                    console.log("JSON.parse:", JSON.parse(this.responseText));
                    resolve(JSON.parse(this.responseText));
                }
                else {
                    reject(Common.WebAPI.ErrorHandler(req.response));
                }
            }
        };
        req.send(JSON.stringify(data));
});