Bing マップと Microsoft Dynamics CRM オンライン

Microsoft Dynamics CRM 2011 Beta 版では、ダッシュボード機能のように、データを様々な視点で可視化することにより生産性の向上がはかられています。
関心が寄せられてる可視化として、 Bing マップ の統合があります。
今日は、Bing マップと統合についてご紹介します。

たとえば、次のように自分の取引先担当者の所在地をマップ上で表現したい場合を考えます:

このソリューションは HTML Web リソースを使用しており、取引先担当者情報を CRM で検索後、所在地と合わせて Bing マップにレンダリングし、ダッシュボードに表示します。

はじめに

1. Web リソースとダッシュボードを実装するために新しいソリューションを作成します(または既存のものを開きます)。
2. 新しい *.html ファイルを作成し、IDE で (ここでは Visual Studio を使用します) 開きます。
まず、以下のテンプレートで進めます。ここには、Virtual Earth (Bing マップ)、jQuery、および Global Client context など必要なライブラリへの参照が含まれています。
Bing マップは、div 要素 id="map" でレンダリングされ、ページ全体に表示されます(余白が調整され、要素に対する高さ/幅に 100% を指定)。また、URL パラメーターを簡単に解析できる一般的な関数も含めています。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>Bing Map</title>
<script type="text/javascript" src="https://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2"></script>
<script type="text/javascript" src="https://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="ClientGlobalContext.js.aspx"></script>
<script type="text/javascript">
//Code will go here
</script>
</head>
<body style="margin: 0 0 0 0">
<div id="map" style="width: 100%; height: 100%;" />
</body>
</html>
<script type="text/javascript">
var map = new VEMap("map");
map.LoadMap();

//Region: Generic Parsing Functions for URL Parameters
function getParameters(values, unescapeValues) {

//Taken from samples in the CRM SDK
var parameters = new Array();
var vals = ('?' == values.charAt(0) ? values.substr(1) : values).split("&");
for (var i in vals) {
vals[i] = vals[i].replace(/\+/g, " ").split("=");
if (unescapeValues) {
parameters[unescape(vals[i][0])] = unescape(vals[i][1]);
}
else {
parameters[vals[i][0]] = vals[i][1];
}
}

return parameters;
}
//EndRegion

//Code will go here
</script>

サーバーを検索します。

loadAddresses 関数の定義

サーバーからデータを取得するために、この例では、OData (CRM 2011 Beta 版に導入された RESTful エンドポイント) を使用し、JSON 形式のデータを取得します。

function loadAddresses(entityName, idAttribute, nameAttributes, addressAttributes, descriptionAttributes) {

この関数が呼び出され、ピンが埋め込まれたマップが生成されます。 idAttribute は文字列のフィールドで、ID を定義するエンティティ上にあるフィールドを示しています。nameAttributes、addressAttributes、および descriptionAttributes パラメーターは配列で、マップ上に配置が必要な場所とピンのアイコンに表示すべき名前と説明を定義するエンティティの一覧を示しています。

クエリ URL の定義

OData エンドポイントは RESTful で、URL パラメーターを使ってクエリされます。 クエリは全属性を select し、ピンをレンダリングするために必要な属性をすべて取得します。

var serverUrl = GetGlobalContext().getServerUrl();

var requestUrl = serverUrl + "/XRMServices/2011/OrganizationData.svc/" + entityName + "Set?$select=" +

nameAttributes.join(",") + "," + addressAttributes.join(",");

if ("string" == typeof(idAttribute) && 0 != idAttribute.length) {

requestUrl += "," + idAttribute;

}

else {

idAttribute = "";

}

if (null != descriptionAttributes && typeof(descriptionAttributes) == "object" && typeof(descriptionAttributes.length) == "number") {

requestUrl += "," + descriptionAttributes.join(",");

}

サーバーをクエリ

次に、リクエストをサーバーに送信し、マップに追加します:

$.ajax(

{

type: "GET",

url: requestUrl,

contentType: "application/json; charset=utf-8",

dataType: "json",

error: function (request, textStatus, errorThrown) {

   alert("Error occurred: " + request.responseXML);

   return;

},

success: function (data) {

   var results = data.d["results"];

   var addressList = new Array();

   for (resultKey in results) {

       addressList.push(results[resultKey]);

   }

addPushpins(new Array(), addressList);

}

});

このリクエストは非同期になりますので、エラー発生時、成功時に jQuery が呼び出すコールバックを準備する必要があります。
シンプルにするため、エラー時のコールバックでは、警告を表示しています。成功時のコールバックでは、戻されたエンティティをループし、それらを配列に追加します。
配列は、これから定義する addPushpins メソッドに渡されます。

クエリ結果の処理

addPushpins メソッドは、次のような実行フローです:

1.アドレス リストから最初のアイテムを削除します。
2.非同期 で Bing マップを呼び出します。
3.ピンのオブジェクトを作成し、それをピン リストに追加します。
4.次のアドレスに対して addPushpins を呼び出します。

全ピンが生成されると、ピンが Bing マップに追加されます。

 function addPushpins(pushpins, addresses) {
 
    function convertToString(entity, attributes) {
        var attributeValues = Array();
        for (var i = 0; i < attributes.length; i++) {
            var value = entity[attributes[i]];
            if ("undefined" != typeof (value)) {
                if ("string" != typeof (value) || 0 != value.length) {
                    attributeValues.push(value);
                }
            }
        }
 
        return attributeValues.join(" ").trim();
    }
 
    if (addresses.length > 0) {
        var item = addresses.pop();
 
        var title = convertToString(item, nameAttributes);
        var address = convertToString(item, addressAttributes);
        var description = convertToString(item, descriptionAttributes);
 
        var moreInfoUrl = null;
        if (0 != idAttribute.length) {
 
            var id = item[idAttribute];
            moreInfoUrl = serverUrl + "/main.aspx?etn=" + entityName.toLowerCase() + "&id=%7b" + id + "%7d&pagetype=entityrecord";
        }
        
//This can also be done using a $filter parameter against the OData endpoint
        if (0 == address.length) {
 
            //If the address is blank, skip this one. Do this asynchronously so that there
            //is not the risk of a stack overflow.
            setTimeout(function() { addPushpins(pushpins, addresses) }, 0);
            return;
        }
 
        map.Find(null, address, null, null, 0, 1, false, false, false, false,
        function (shapeLayer, results, places, moreResults, error) {
            var place = places[0];
            var newShape = new VEShape(VEShapeType.Pushpin, place.LatLong);
            newShape.SetTitle(title);
            newShape.SetDescription(description);
            if (null != moreInfoUrl) {
                newShape.SetMoreInfoURL(moreInfoUrl);
            }
 
            pushpins.push(newShape);
 
            addPushpins(pushpins, addresses);
        });
    }
    else {
        var shapeLayer = new VEShapeLayer();
        map.AddShapeLayer(shapeLayer);
        shapeLayer.AddShape(pushpins);
    }

タイトル、アドレス、および説明のレンダリングに加え、特定のエンティティ ページにリンクするような More Info URL も生成されます。 すべてのピンが生成されると、Bing マップに図形として追加されます。

Web リソースの仕上げ

ここで、loadAddresses 関数に呼出し部分を追加する必要があります。その結果、マップがレンダリングされます。

 var parameters = getParameters(location.search, true);
if ("undefined" != typeof (parameters["data"])) {
    parameters = getParameters(parameters["data"], false);
}
var entityName = parameters["entity"];
var idAttribute = parameters["id"];
var nameAttributes = ("undefined" == typeof (parameters["name"]) ? Array() : parameters["name"].split(","));
var addressAttributes = ("undefined" == typeof (parameters["address"]) ? Array() : parameters["address"].split(","));
var descriptionAttributes = ("undefined" == typeof (parameters["description"]) ? Array() : parameters["description"].split(","));
if (0 == descriptionAttributes.length) {
    descriptionAttributes = addressAttributes;
}

これは、URL のすべてのパラメーターを取得、解析し、 loadAddresses 関数を呼び出します。

ダッシュボードの作成

HTML が定義され、新しいソリューション用のソリューション エクスプローラーを開き、Web ページ (HTML)型の新しい Web リソースを作成します。
HTML を変更してしまわないように、テキスト エディターではなく、”参照” ボタンを使用してください。 Web リソースを公開することもお忘れなく。
Web リソースについて念頭に置いておくべきことが一つあります。Web リソース名に ”/” を使用する場合、たとえば ”scripts/new_MyWebResource” のようなフォルダー構成と仮定すると、ClientGlobalContext.js.aspx への参照パスを ”../ClientGlobalContext.js.aspx” へ変更する必要があります。

ソリューション エクスプローラーで、新しいダッシュボードを作成し、Web リソースを挿入します。
スクリプトが使用すべきエンティティと属性を把握するために、Web リソースにはカスタム パラメーターを含める必要があります。
次の例では、Contact エンティティと address 属性を使用します:

entity=Contact&id=ContactId&name=FullName&address=Address1_Line1,Address1

_Line2,Address1_Line3,Address1_City,Address1_StateOrProvince

,Address1_PostalCode,Address1_Country&description=Description

さあ、Web クライアントのメイン ページを更新し、ダッシュボードに移動してみましょう。

最後に

ここで使用した Web リソースは、まだ改善中ですが (より豊富なエラー チェック、サーバーからのデータ取得の削減など)、サンプル コードは、Bing マップを統合するために Java Script  がどのようにして使われるかの参考になると思います。
英語版には、ソース コードのダウンロードリンクが案内されています。興味のある方は、英語版もみてください!

情報元 : Bing Maps and Microsoft Dynamics CRM Online
https://blogs.msdn.com/b/crm/archive/2010/10/25/bing-maps-and-microsoft-dynamics-crm-online.aspx

- Dynamics CRM サポート 斎藤 さち江