页面上有一个Lookup field,当选择一个记录时,同时要把这个记录的其他值也带到本页面来,其中有好多是Lookup来的。
开始我用LinkEntity来取得它们的名字值,但是后来发现LinkEntity有数量限制,每条查询语句只能有10个LinkEntity,超过就报错,Advanced Find里也是一样的。
所以,用LinkEntity并不是一个好办法,而且我只是要取得一个名字的值而已,直觉告诉我不用那么麻烦的。
研究了一下,其实Fetchxml取的Lookup的值,不仅有GUID,名字的值也取到了,只不过名字值要经过一定的步骤转化才能拿到。
下面是具体方法:
首先,我们从返回的xml对象中取出我们的要的元素:
var county = oXmlDoc.getElementsByTagName("my_county")[0];
调用一个方法来取得名字值:
var countyName= GetAttributeName(county);
GetAttributeName方法,其实就是把字符串转化为Xml Node,再对里面的属性进行分析,得到我们要的那个属性的值:
function GetAttributeName(xmlNode) { var xmlStr = xmlNode.xml; var doc; if (window.ActiveXObject) { doc = new ActiveXObject('Microsoft.XMLDOM'); doc.async = 'false'; doc.loadXML(xmlStr); } else { var parser = new DOMParser(); doc = parser.parseFromString(xmlStr, 'text/xml'); } var name = doc.documentElement.attributes.getNamedItem("name").text; return name; }
好的,这样Id和Name都有了,就可以给目标设值了。
这是一个非常普通的需求。
页面上有一个按钮,点击之后提交表单,如果什么都不管的话,用户可以在服务器响应完成之前再次点击,这样就出现了二次提交,后果可大可小。
那么我们应该防止二次点击,就要在用户点第一次之后马上Disable这个按钮。
具体:
按钮代码:
<asp:Button ID="Button1" runat="server" UseSubmitBehavior="false"
OnClick="Button1_Click" Text="Button" OnClientClick="DisableButton(this)" />
Javascript:
<script> function DisableButton(b) { b.disabled = true; b.value = 'Submitting'; } </script>
然后在后台代码里的Button1_Click事件里再做你想做的事情。
我们可以注意到这里是用OnClientClick的属性来调用Javascript从而实现Disable按钮的。开始的时候,我没有加入UseSubmitBehavior属性,就变成了点击按钮只执行客户端的代码,服务器端代码就不执行了。为了解决这个问题,加入了UseSubmitBehavior属性,并且要把他的值设为false。
为什么要设为false呢?简单来说,false表示不用客户端方法提交,则从服务器端提交,也就是执行OnClick的事件。反之,如果是true,则从客户端提交,忽略服务器端的事件。具体请看MSDN:http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.button.usesubmitbehavior.aspx
需求:页面上有一个字段是Account类型的Lookup,另一个字段是Contact类型的Lookup。要求打开Contact lookup时只显示Account的Primary Contact的过滤视图。
分析:要做到这个功能,首先要重写(增加)Contact lookup的“显示查找视图”的事件;然后,在页面加载的时候就要把这个事件添加到Contact lookup上,在Account的值发生变化时也要做相应的改变,比如Account的值被清空的情况下,我们的自定义过滤视图就应该显示全部的Contact,所以这时要再次改写“显示查找视图”的事件。
步骤:
(1)
创建一个Javascript文件,上传到Webresource,并添加到Form Libraries。
代码:
oArg = { items: null, customViews: null, setCustomView: function (viewId, objectType, viewDisplayName, fetchXml, layoutXml) { customView = { recordType: objectType, id: viewId, name: viewDisplayName, fetchXml: fetchXml, layoutXml: layoutXml }; this.customViews = new Array(); this.customViews[0] = customView; }, setLookupItems: function (ids, names, type) { var idArr = ids.split(';'); var nameArr = names.split(';'); this.items = new Array(); for (var i = 0; i < idArr.length; i++) { if (idArr[i].length >= 32) { this.items[i] = { oid: idArr[i], outerText: nameArr[i], oType: type, getAttribute: function (value) { switch (value) { case 'oid': { return this.oid; } case 'otype': { return this.oType; } default: { return null; } } } }; } } } };
(2)
创建打开过滤视图的Javascript,添加到Form Libraries:
代码:
//Open filterd lookup view of contact base on Account function OpenLookUpView() { var element = document.getElementById("contactid");//The contact lookup field id on the form var serverUrl = Xrm.Page.context.getServerUrl(); var client = Xrm.Page.getAttribute("customerid").getValue(); var viewId = "{00000000-0000-0000-0000-000000000001}"; // a dummy view ID var objectType = 2; //contact var viewDisplayName = 'Client contact'; if (client != null) { client = client[0].id; var fetchXml = "<fetch version='1.0' " + "output-format='xml-platform' " + "mapping='logical'>" + "<entity name='contact'>" + "<attribute name='fullname' />" + "<attribute name='telephone1' />" + "<attribute name='contactid' />" + "<attribute name='emailaddress1' />" + "<attribute name='address1_stateorprovince' />" + "<attribute name='address1_county' />" + "<attribute name='address1_city' />" + "<order attribute='fullname' " + "descending='false' />" + "<link-entity name='account' from='primarycontactid' to='contactid' alias='ab'>" + "<filter type='and'>" + "<condition attribute='accountid' " + "operator='eq' " + "value='" + client + "' />" + "</filter>" + "</link-entity>" + "</entity>" + "</fetch>"; var layoutXml = "<grid name='resultset' " + "object='1' " + "jump='name' " + "select='1' " + "icon='1' " + "preview='1'>" + "<row name='result' " + "id='contactid'>" + "<cell name='fullname' " + "width='100' />" + "<cell name='address1_city' " + "width='80' />" + "<cell name='address1_stateorprovince' " + "width='80' />" + "<cell name='address1_county' " + "width='80' />" + "<cell name='telephone1' " + "width='80' />" + "<cell name='emailaddress1' " + "width='100' />" + "</row>" + "</grid>"; oArg.setCustomView(viewId, objectType, viewDisplayName, fetchXml, layoutXml); //Overwrite the lookup event to show custom filtered view element.onshowdialog = function (event) { var result = window.showModalDialog(serverUrl + '/_controls/lookup/lookupinfo.aspx?AllowFilterOff=1&DefaultType=' + objectType + '&DisableQuickFind=0&DisableViewPicker=0&LookupStyle=single&ShowNewButton=1&objecttypes=' + objectType + '&ShowPropButton=1&browse=0&DefaultViewId=' + viewId, oArg, 'dialogHeight:630px;dialogWidth:600px;resizable:yes;'); event.oLookupItems = result; } } else { viewDisplayName = 'All contacts'; var fetchXml = "<fetch version='1.0' " + "output-format='xml-platform' " + "mapping='logical'>" + "<entity name='contact'>" + "<attribute name='fullname' />" + "<attribute name='telephone1' />" + "<attribute name='contactid' />" + "<attribute name='emailaddress1' />" + "<attribute name='address1_stateorprovince' />" + "<attribute name='address1_county' />" + "<attribute name='address1_city' />" + "<order attribute='fullname' " + "descending='false' />" + "</entity>" + "</fetch>"; var layoutXml = "<grid name='resultset' " + "object='1' " + "jump='name' " + "select='1' " + "icon='1' " + "preview='1'>" + "<row name='result' " + "id='contactid'>" + "<cell name='fullname' " + "width='100' />" + "<cell name='address1_city' " + "width='80' />" + "<cell name='address1_stateorprovince' " + "width='80' />" + "<cell name='address1_county' " + "width='80' />" + "<cell name='telephone1' " + "width='80' />" + "<cell name='emailaddress1' " + "width='100' />" + "</row>" + "</grid>"; oArg.setCustomView(viewId, objectType, viewDisplayName, fetchXml, layoutXml); //Overwrite the lookup event to show custom filtered view element.onshowdialog = function (event) { var result = window.showModalDialog(serverUrl + '/_controls/lookup/lookupinfo.aspx?AllowFilterOff=1&DefaultType=' + objectType + '&DisableQuickFind=0&DisableViewPicker=0&LookupStyle=single&ShowNewButton=1&objecttypes=' + objectType + '&ShowPropButton=1&browse=0&DefaultViewId=' + viewId, oArg, 'dialogHeight:630px;dialogWidth:600px;resizable:yes;'); event.oLookupItems = result; } } }
(3)
在Form onload和Account onchange的event添加刚才我们加进来的library,Function填“OpenLookUpView”
好的。就是这样。
补充:
如果遇到这个错误:You do not have sufficient privileges to open this Lookup dialog box. 请检查一下objecttypecode对了没有。
如果自定义视图没有生效,请检查第(1)步的Javascript有没有添加到页面库中。
之前接手这个项目的时候,看到的设计是这样的:
实体A的页面上有一个Lookup,连到实体B;
实体B的所有Fields都要展示到实体A;
实体A不能改动这些Fields;
实体B改动了这些Fields,也要体现到实体A;
在实体A重复创建和实体B一模一样的Fields……
这样的设计非常无聊,既然实体A要展示那些Fields,又不能改,其实只要直接拿实体B的信息即可,不需要重复创建Fields。
但是他们这样做是有原因的,因为CRM的sub grid展示形式很单一,无法达到用户的需要。
于是,现在,我改成了用Htm Web Resource来实现。
如图:

上图中的红框的信息全是来自“Property”这个Lookup的实体。
实现方法如下:
1. 创建一个Css,用来控制样式保持和CRM一致。我这里的名字是master.css,上传到CRM,(new_/Styles/master.css)
body { font-family:Tahoma; font-size:11px; color:393839; margin:0px; background-color:F6F8FA; border:0px; }
table {font-size:11px;}
td{padding: 5px 10px 3px 0px;}
h3{font-size:12px;}
2. 上传sdk里的jquery到CRM(new_/Scripts/jquery1.4.1.min.js), jquery库可以在sdk找到:sdk\samplecode\js\restendpoint\jqueryrestdataoperations\jqueryrestdataoperations\scripts\jquery1.4.1.min.js
3. 创建Htm页面,上传到CRM(new_/PropertyInfo.htm),这里我省略部分方法只是重复的代码,大家要根据需要修改:
<HTML><HEAD><TITLE></TITLE>
<SCRIPT src="../ClientGlobalContext.js.aspx"></SCRIPT>
<SCRIPT type=text/javascript src="Scripts/jquery1.4.1.min.js"></SCRIPT>
<SCRIPT type=text/javascript src="Scripts/sdk.metadata.js"></SCRIPT>
<LINK rel=stylesheet type=text/css href="Styles/master.css"></LINK>
<SCRIPT language=javascript>
var _serverUrl;
function retrievePropertyInfo() {
var customerAttribute = parent.Xrm.Page.data.entity.attributes.get("new_property");
if (customerAttribute != null) {
customerValue = customerAttribute.getValue();
if (customerValue != null && customerValue[0] != null) {
var customerType = customerValue[0].typename;
var customerId = customerValue[0].id;
return retrieveRecord(customerId, "new_propertySet", retrieveCompleted, null);
}
}
}
function retrieveRecord(id, odataSetName, successCallback, errorCallback) {
var context = GetGlobalContext();
var serverUrl = context.getServerUrl();
_serverUrl = serverUrl;
var odataEndPoint = "/XRMServices/2011/OrganizationData.svc";
if (!id) {
alert("record id is required.");
return;
}
if (!odataSetName) {
alert("odataSetName is required.");
return;
}
$.ajax({
type: "GET",
contentType: "application/json; charset=utf-8",
datatype: "json",
url: serverUrl + odataEndPoint + "/" + odataSetName + "(guid'" + id + "')",
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("Accept", "application/json");
},
success: function (data, textStatus, XmlHttpRequest) {
if (successCallback) {
successCallback(data.d, textStatus, XmlHttpRequest);
}
},
error: function (XmlHttpRequest, textStatus, errorThrown) {
if (errorCallback)
errorCallback(XmlHttpRequest, textStatus, errorThrown);
else
errorHandler(XmlHttpRequest, textStatus, errorThrown);
}
});
}
function retrieveCompleted(data, textStatus, XmlHttpRequest) {
/*Display the required fields and hide if the fields are null */
var entity = data;
//Picklist
var Type = (entity.new_Type.Value == null) ? "" : RetrieveOptionsetLabel("new_property", "new_type", entity.new_Type.Value, "type");
//Text
var AddressLine1 = (entity.new_AddressLine1 == null) ? "" : entity.new_AddressLine1;
if (addressline1 != "") {
document.getElementById("addressline1").innerHTML = AddressLine1;
}
//Hyperlink
var PortfolioName = (entity.new_Portfolio.Name == null) ? "" : entity.new_Portfolio.Name;
var PortfolioId = (entity.new_Portfolio.Id == null) ? "" : entity.new_Portfolio.Id;
if (portfolio != "") {
document.getElementById("portfolio").innerHTML =
"<a href='" + _serverUrl + "/main.aspx?etc=10014&id=%7b" + PortfolioId + "%7d&pagetype=entityrecord' target=_blank>" + PortfolioName + "</a>";
}
}
function RetrieveOptionsetLabel(entityName, optionSetName, optionSetValue, attributeId) {
// Entity schema name
var entityLogicalName = entityName;
// option set schema name
var RetrieveAttributeName = optionSetName;
// Target Field schema name to which optionset text needs to be assigned
var AssignAttributeName = attributeId;
// Option set value for which label needs to be retrieved
var stateValue = optionSetValue;
// Calling Metadata service to get Optionset Label
SDK.MetaData.RetrieveEntityAsync(SDK.MetaData.EntityFilters.Attributes,
entityLogicalName,
null,
false,
function (entityMetadata) {
successRetrieveEntity(entityLogicalName, entityMetadata, RetrieveAttributeName, stateValue, AssignAttributeName);
},
errorDisplay);
}
// Called upon successful metadata retrieval of the entity
function successRetrieveEntity(logicalName, entityMetadata, RetrieveAttributeName, OptionValue, AssignAttributeName) {
///<summary>
/// Retrieves attributes for the entity
///</summary>
var success = false;
for (var i = 0; i < entityMetadata.Attributes.length; i++) {
var AttributeMetadata = entityMetadata.Attributes[i];
if (success) break;
if (AttributeMetadata.SchemaName.toLowerCase() == RetrieveAttributeName.toLowerCase()) {
for (var o = 0; o < AttributeMetadata.OptionSet.Options.length; o++) {
var option = AttributeMetadata.OptionSet.Options[o];
if (option.OptionMetadata.Value == OptionValue) {
document.getElementById(AssignAttributeName).innerHTML = option.OptionMetadata.Label.UserLocalizedLabel.Label;
success = true;
break;
}
}
}
}
}
function errorDisplay(XmlHttpRequest, textStatus, errorThrown) {
alert(errorThrown);
}
</SCRIPT>
<META charset=utf-8></HEAD>
<BODY onload=retrievePropertyInfo() contentEditable=false>
<div id=propertyinfo>
<table style="width:100%">
<tbody>
<tr>
<td style="width:8%">Address Line 1</td>
<td id="addressline1" style="width:18%"></td>
<td style="width:8%">Type</td>
<td id="type" style="width:18%"></td>
<td style="width:8%">Asset ID</td>
<td id="assetid" style="width:18%"></td>
<td style="width:8%">Country</td>
<td id="country" style="width:auto"></td>
</tr>
<tr>
<td>Address Line 2</td>
<td id="addressline2"></td>
<td>Property Type</td>
<td id="propertytype"></td>
<td>Portfolio</td>
<td id="portfolio"></td>
<td>State/Province</td>
<td id="state"></td>
</tr>
<tr>
<td>Address Line 3</td>
<td id="addressline3"></td>
<td>Property SubType</td>
<td id="propertysubtype"></td>
<td>County</td>
<td id="county"></td>
<td>City</td>
<td id="city"></td>
</tr>
</tbody>
</table>
</div>
</BODY></HTML>
4. 将这个Htm Webresource插入我们需要它的地方,这样就可以了。
注意,我们的property lookup必须要在页面上才可以,设为不可见也行,因为如果它不在页面上,我们就拿不到它的id了,当然,通过别的方法拿到也是可以,但是就增加了不必要的麻烦。
补充,我们这里是用odata来取得数据的,写odata query的时候要注意。我是觉得很讨厌写这个query,所以我是用这个工具的:CRM 2011 OData Query Designer
如果我们是取一个实体的数据(
http://XXXX:8080/OOOOO/xrmservices/2011/OrganizationData.svc/odataSetName (guid'3653C6D5-2077-E111-920A-000C29139AA1')),那么返回的肯定是一个实体,则我们分析数据的时候只要用类似entity.fieldname 就可以拿到相应的值。
如果我们取得是多个实体的数据(http://XXXX:8080/OOOOO/xrmservices/2011/OrganizationData.svc/odataSetName?$select=new_Field1Name,new_new_entityB_new_entityA_entityBName/new_Field2Name$expand=new_new_entityB_new_entityA_entityBName&$filter=new_entityAId eq guid'3653C6D5-2077-E111-920A-000C29139AA1'),那么返回的是一个数组,我们分析数据的时候,对于a的数据要这样entity.results[0].field1name;对于b的数据则是:entity.results[0].new_new_entityB_new_entityA_entityBName.field2name
再补充:
如果取得是数据是Date,需要进行一下转换,odata返回的Date是这样的Date(1235764800000)
这样转换:
function ConvertTime(date) {
//This function use for convert the Date value get from odata, convert to javascript readable value
if (date == "") {
return "";
}
else {
var utcTime = parseInt(date.substr(6)); //date is like Date(1235764800000)
var result = new Date(utcTime);
result.setHours(result.getHours() + 8); //Database time is 8 hours late before local time
return result.format("M/dd/yyyy");
}
}
好的,基本上就是这样。
function UserHasRole(roleName)
{
var serverUrl = Xrm.Page.context.getServerUrl();
var oDataEndpointUrl = serverUrl + "/XRMServices/2011/OrganizationData.svc/";
oDataEndpointUrl += "RoleSet?$top=1&$filter=Name eq '" + roleName + "'";
var service = GetRequestObject();
if (service != null)
{
service.open("GET", oDataEndpointUrl, false);
service.setRequestHeader("X-Requested-Width", "XMLHttpRequest");
service.setRequestHeader("Accept", "application/json, text/javascript, */*");
service.send(null);
var requestResults = eval('(' + service.responseText + ')').d;
if (requestResults != null && requestResults.results.length == 1)
{
var role = requestResults.results[0];
var id = role.RoleId;
var currentUserRoles = Xrm.Page.context.getUserRoles();
for (var i = 0; i < currentUserRoles.length; i++)
{
var userRole = currentUserRoles[i];
if (GuidsAreEqual(userRole, id))
{
return true;
}
}
}
}
return false;
}
function GetRequestObject()
{
if (window.XMLHttpRequest)
{
return new window.XMLHttpRequest;
}
else
{
try
{
return new ActiveXObject("MSXML2.XMLHTTP.3.0");
}
catch (ex)
{
return null;
}
}
}
function GuidsAreEqual(guid1, guid2)
{
var isEqual = false;
if (guid1 == null || guid2 == null)
{
isEqual = false;
}
else
{
isEqual = guid1.replace(/[{}]/g, "").toLowerCase() == guid2.replace(/[{}]/g, "").toLowerCase();
}
return isEqual;
}
应用:
var isManager=UserHasRole("Manager");
if(isManager)
{
//Do something
}
else
{
//Do something
}


