关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复162或者20151016可方便获取本文,同时可以在第一时间得到我发布的最新的博文信息,follow me!
注释在CRM中的显示是比较特别,我们可以自定义一个页面来显示注释吗?如果可以,可以做成通用的吗?这篇博文将会带你制作一个通用的注释显示页面,并为每条注释生成了超级链接,点击可以在弹出页面中查看或者编辑注释。
首先要新建一个类型为 HTML Page 类型的Web资源,操作步骤如下。我这里使用了Developer Toolkit进行无缝开发,怎么配置请参考我这篇文章:为Dynamics CRM 2015配置Visual Studio准备无缝开发 。



值得吐槽的是OData的表达式的filter条件不支持跨实体,比如我想用的是注释实体的IsDocument 属性为true,然后还要用来和它关联的客户的Id字段,却告诉我不能这么用。比如我想使用这个URL:http://lyazurevm.cloudapp.net:5555/Demo/xrmservices/2011/OrganizationData.svc/AnnotationSet?$select=DocumentBody,FileName,MimeType&$expand=Account_Annotation&$filter=Account_Annotation/AccountId eq guid'858AB47F-494A-E511-80D2-000D3A802FAC' and IsDocument eq true
却告诉我:filter conditions of different entity types, in the same expression, are not supported ,囧。如果有谁找到OData查询条件能跨实体的方法请联系我。

查询出来两条是正确的:

当然我们实际上要使用的语法是类似这样的:http://lyazurevm.cloudapp.net:5555/Demo/xrmservices/2011/OrganizationData.svc/AnnotationSet?$select=DocumentBody,FileName,MimeType&$expand=Account_Annotation&$filter=Account_Annotation/AccountId eq guid'858AB47F-494A-E511-80D2-000D3A802FAC'
这个Web资源我使用的全部代码如下,各位看官做实验的时候要确保在HTML Web资源中引入的JavaScript文件在CRM中已经存在,请注意引用时候使用的路径要对。当然 ClientGlobalContext.js.aspx 这个文件是CRM自带的,注意引用路径就好了。XrmServiceToolkit 这个文件请自行到 这里 下载。
<!DOCTYPE HTML> <html> <head> <title>微软MVP罗勇测试注释</title> <style type="text/css"> table { border:1px solid #666666; border-collapse:collapse; } table thead th { padding: 8px; border:1px solid #666666; background-color: #dedede; } table tbody td { border: 1px solid #666666; padding: 8px; background-color: #ffffff; } table thead tr th { font-family:Microsoft YaHei,SimSun,Tahoma,Arial; font-size:12px; font-weight:bold; color:#000000; } table tbody tr td { color:#444444; font-family:Microsoft YaHei,SimSun,Tahoma,Arial; font-size:12px; } </style> <script type="text/javascript" src="../../ClientGlobalContext.js.aspx"></script> <script type="text/javascript" src="../common/jquery.min.js"></script> <script type="text/javascript" src="../common/XrmServiceToolkit.min.js"></script> <script type="text/javascript"> Date.prototype.format = function (fmt) { var o = { "M+": this.getMonth() + 1,//月份 "d+": this.getDate(),//日 "h+": this.getHours(),//小时 "m+": this.getMinutes(),//分 "s+": this.getSeconds()//秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); return fmt; } $(function () { var clientUrl = GetGlobalContext().getClientUrl(); //var id = window.parent.Xrm.Page.data.entity.getId(); //这种方法可以获取表单中的很多信息,包括id var match = RegExp('[?&]id=([^&]*)').exec(window.location.search);//这里是外接通过url传递id的值过来 var id = match && decodeURIComponent(match[1].replace(/+/g, ' ')); XrmServiceToolkit.Rest.RetrieveMultiple( "AnnotationSet", "?$select=AnnotationId,Subject,NoteText,MimeType,FileName,FileSize,IsDocument,CreatedOn,CreatedBy,ModifiedOn,ModifiedBy&$filter=Account_Annotation/AccountId eq guid'" + id + "'&$orderby=CreatedOn asc", function (results) { for (var i = 0; i < results.length; i++) { var tr = $("<tr></tr>"); tr.appendTo($("#notestable tbody")); var td = $("<td>" + (i+1) + "</td>"); td.appendTo(tr); td = $("<td>" + (results[i].Subject == null ? "" : results[i].Subject) + "</td>"); td.appendTo(tr); td = $("<td><a href='" + clientUrl + "/main.aspx?etn=annotation&pagetype=entityrecord&id=%7B" + results[i].AnnotationId + "%7D' target='_blank'>" + results[i].NoteText + "</a></td>"); td.appendTo(tr); td = $("<td>" + results[i].CreatedBy.Name + "</td>"); td.appendTo(tr); td = $("<td>" + results[i].CreatedOn.format('yyyy-MM-ddThh:mm:ssZ') + "</td>"); td.appendTo(tr); td = $("<td>" + results[i].ModifiedBy.Name + "</td>"); td.appendTo(tr); td = $("<td>" + results[i].ModifiedOn.format('yyyy-MM-ddThh:mm:ssZ') + "</td>"); td.appendTo(tr); td = $("<td>" + (results[i].IsDocument ? "是" : "否") + "</td>"); td.appendTo(tr); td = $("<td>" + (results[i].FileName == null ? "" : results[i].FileName) + "</td>"); td.appendTo(tr); td = $("<td>" + (results[i].FileSize == null ? "" : Math.round((results[i].FileSize)/1024)) + "</td>"); td.appendTo(tr); } }, function (error) { alert(error.message); }, function () { }, true ); }); </script> </head> <body> <table id="notestable"> <thead> <tr> <th>序号</th> <th>注释标题</th> <th>注释内容</th> <th>创建人</th> <th>创建时间</th> <th>修改人</th> <th>修改时间</th> <th>是否包含附件</th> <th>附件名称</th> <th>附件大小(KB)</th> </tr> </thead> <tbody> </tbody> </table> </body> </html>
在Visual Studio中部署下,也就是右击名称为 CrmPackage 的项目,选择 部署。

我进入解决方案修改客户实体的窗体内容为主要的那个窗体,增加一个一列的的选项卡,并在这个选项卡中插入前面创建的Web资源,主要设置如下,还在 格式化 Tab设置了 不现实边框。

可以看到我勾选了 将记录对象类型代码和唯一标识符作为参数传递,这个打勾以后将会通过URL的查询参数向Web资源传递什么参数?弹出来看看如下,可以知道传递了CRM组织的基础语言参数orglcid,组织的名称orgname,用户使用CRM时候选择的语言userlcid,当前实体的整数标识type (按照SDK的说法,除了标准实体,自定义的实体这个数字在不同的CRM中可能不同),当前实体的逻辑名称typename,当前记录的唯一id这些参数,当然还有可能会有data,如果在前面设置了 自定义参数(数据) 的话。具体请参考SDK的 Webpage (HTML) web resources 章节。

然后发布解决方案,按F5刷新浏览器,如果文件被缓存,可以按 F12,在如下地方清除缓存后试试:

我们去看效果如下,请原谅我是写代码的,显示的内容不一定美观,呵呵:

可以看到显示的数据正常,而且注释内容页面我做了一个超级链接,是可以点击的,点击效果如下:我也顺手测试了一下,是可以编辑保存的,但是点击 保存并新建按钮是会报错的。

上面的代码不够通用,因为写死了是查询哪个实体对应的注释,如果我放入别的实体中,就需要修改,后来我发现了更加通用的方法,如下:
<!DOCTYPE HTML> <html> <head> <title>微软MVP罗勇测试注释</title> <style type="text/css"> table { border:1px solid #666666; border-collapse:collapse; } table thead th { padding: 8px; border:1px solid #666666; background-color: #dedede; } table tbody td { border: 1px solid #666666; padding: 8px; background-color: #ffffff; } table thead tr th { font-family:Microsoft YaHei,SimSun,Tahoma,Arial; font-size:12px; font-weight:bold; color:#000000; } table tbody tr td { color:#444444; font-family:Microsoft YaHei,SimSun,Tahoma,Arial; font-size:12px; } </style> <script type="text/javascript" src="../../ClientGlobalContext.js.aspx"></script> <script type="text/javascript" src="../common/jquery.min.js"></script> <script type="text/javascript" src="../common/XrmServiceToolkit.min.js"></script> <script type="text/javascript"> Date.prototype.format = function (fmt) { var o = { "M+": this.getMonth() + 1,//月份 "d+": this.getDate(),//日 "h+": this.getHours(),//小时 "m+": this.getMinutes(),//分 "s+": this.getSeconds()//秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); return fmt; } $(function () { var clientUrl = GetGlobalContext().getClientUrl(); //var id = window.parent.Xrm.Page.data.entity.getId(); //这种方法可以获取表单中的很多信息,包括id var match = RegExp('[?&]id=([^&]*)').exec(window.location.search);//这里是外接通过url传递id的值过来 var id = match && decodeURIComponent(match[1].replace(/+/g, ' ')); match = RegExp('[?&]typename=([^&]*)').exec(window.location.search); var typename = match && decodeURIComponent(match[1].replace(/+/g, ' ')); XrmServiceToolkit.Rest.RetrieveMultiple( "AnnotationSet", "?$select=AnnotationId,Subject,NoteText,MimeType,FileName,FileSize,IsDocument,CreatedOn,CreatedBy,ModifiedOn,ModifiedBy&$filter=ObjectTypeCode eq '" + typename + "' and ObjectId/Id eq guid'" + id + "'&$orderby=CreatedOn asc", function (results) { for (var i = 0; i < results.length; i++) { var tr = $("<tr></tr>"); tr.appendTo($("#notestable tbody")); var td = $("<td>" + (i+1) + "</td>"); td.appendTo(tr); td = $("<td>" + (results[i].Subject == null ? "" : results[i].Subject) + "</td>"); td.appendTo(tr); td = $("<td><a href='" + clientUrl + "/main.aspx?etn=annotation&pagetype=entityrecord&id=%7B" + results[i].AnnotationId + "%7D' target='_blank'>" + results[i].NoteText + "</a></td>"); td.appendTo(tr); td = $("<td>" + results[i].CreatedBy.Name + "</td>"); td.appendTo(tr); td = $("<td>" + results[i].CreatedOn.format('yyyy-MM-ddThh:mm:ssZ') + "</td>"); td.appendTo(tr); td = $("<td>" + results[i].ModifiedBy.Name + "</td>"); td.appendTo(tr); td = $("<td>" + results[i].ModifiedOn.format('yyyy-MM-ddThh:mm:ssZ') + "</td>"); td.appendTo(tr); td = $("<td>" + (results[i].IsDocument ? "是" : "否") + "</td>"); td.appendTo(tr); td = $("<td>" + (results[i].FileName == null ? "" : results[i].FileName) + "</td>"); td.appendTo(tr); td = $("<td>" + (results[i].FileSize == null ? "" : Math.round((results[i].FileSize)/1024)) + "</td>"); td.appendTo(tr); } }, function (error) { alert(error.message); }, function () { }, true ); }); </script> </head> <body> <table id="notestable"> <thead> <tr> <th>序号</th> <th>注释标题</th> <th>注释内容</th> <th>创建人</th> <th>创建时间</th> <th>修改人</th> <th>修改时间</th> <th>是否包含附件</th> <th>附件名称</th> <th>附件大小(KB)</th> </tr> </thead> <tbody> </tbody> </table> </body> </html>
然后我放入我的测试用的自定义实体,不用修改代码就可以显示,搞定。
