Pages

Wednesday, June 20, 2012

Read Optimized Forms

I have been wanting to check this new feature out for a while that was introduced along with update rollup 7. There is a pretty decent write up of this new feature and how it can be enabled in the MSDN blogs.

While this feature is interesting and can further optimize the time it takes to open a form there are some important limitations. That is if you scroll down to the bottom of the article it contains a table showing when the "read optimized form" is loaded. And you will quickly realize that if a form has client side scripts it cannot take advantage of this feature.

I tested this out. I took a completely vanilla invoice form and - as advertised - when I opened the record it opened in the nice and clean Read Optimized format.



I then went ahead and added a jscript web resource to the form. To be clear I just added the web resource that are "available to the form", I did not actually wire up any OnLoad, OnSave, or OnChange functions. That is, this web resource was effectively doing nothing.




After I published this, I onced again opened up the invoice form and the Read Optimized setting was no longer respected i.e. it opened in full edit mode. In short, the mere presence of a jscript resource will make the form non-Read Optimized capable.

I am sure there are reasons why it was designed this way. There is certainly some jscript logic that can be impeded by the Read Optimization. Although I guess I'm wondering why Microsoft couldn't have introduced a 7th form type (in addition to create, update, read, disabled, quick, and bulk) in order to target this form option rather than taking the "all or nothing" approach. Perhaps that will be accomodated in a future enhancement or perhaps I haven't properly considered the technical issues that would accompany such a feature...

Anyway, in terms of the practical usage of this feature in it's current incarnation - I have to say I'm a little skeptical.

First of all, it has to be said - what percentage of forms have no jscript acting on them? Jscript really brings tremendous flexibility to form rendering etc. to the extent that not many forms (at least the ones that I touch) remain jscript-less. And even for those that do not have any jscript acting on them - it is of course quite likely over the course of time that you'll want to add some scripting to the form to accomodate some customer requirement. And at this juncture - assuming the client has been happily using Read Optimization for this form - you'll have to explain to the client that this feature that they've now come to know and love will no longer be available.

So when all is said and done, I think I'll personally be employing this feature quite sparingly.

Tuesday, June 19, 2012

Advanced SSRS Reporting Part 3

Continuing with the advanced reporting series let's take a closer look at context sensitivity. In the previous example the report was made context sensitive to just a single CRM entity, namely the marketing list. What happens if you we wanted to make this context sensitive to multiple different entities? For example, let's say we have the following 2 parameters defined:




If the main query can be constructed to join against the various tables such that the context sensitivity will restrict the query sufficiently no matter which entity the report is launched from then that should work. However that is probably not going to be the case in most cases. And if you don't take the both parameters into account (and what happens when one is not passed), then you'll likely find that the query will run very inefficiently when run from the entity which was not taken into proper consideration. That's the best case. The worst and more likely case is either that the report will time out or it will return incorrect data due to not restricting the query sufficiently.

This is because, when a report is not run from within the context sensitive form or grid, the "default filter" is used. So for example, if the default filter is set as follows:



And the report is run from the "contact" context then the variables will be populated as follows:

CRM_FilteredList:

select  [list0].* from FilteredList as "list0" 
where ( list0.modifiedonutc >= '20120520 04:00:00' and list0.modifiedonutc <= '20120619 15:39:16' )

CRM_FilteredContact:

select  [list0].*  from  FilteredContact as "list0" 
where  ("list0".contactid = N'{92F129FB-4099-E111-9EFF-006073502237}')

That is, the marketing list query will be quite unrestricted (there would be no restrictions at all were there no default filters acting on it at all) whereas the contact query is restricted to the contact that is currently being viewed. This is of course expected behaviour (and the situation would be reversed were the report being run from the marketing list context).

So other than creating 2 separate reports for each of the queries - how can the procedure accomodate context sensitivity from both contexts?

The answer lies in interrogating the above variables from within the procedure. As can be seen, when the report is being run from the contact context the CRM_FilteredContact variable will contain "contactid" as part of the string. Similarly when running from the marketing list context the CRM_FilteredList variable will contain "listid" as part of the string. When not in context, the "id" field will not be part of the string.

Armed with this knowledge we can make a simple modification to the report processing logic as follows:

if (@CRM_FilteredList like '%listid%')
 set @SQL = 'insert into #report(ord, contactid, fullname, processed) ' +
    'select (ROW_NUMBER() OVER(ORDER BY c.Fullname))*10, c.contactid, c.fullname, 0 ' +
    'from  (' + @CRM_FilteredList + ') AS list ' + 
    'inner join FilteredListMember m on m.listid = list.listid and EntityType = 2 ' +
    'inner join FilteredContact c on c.contactid = m.EntityId '
else 
 set @SQL = 'insert into #report(ord, contactid, fullname, processed) ' +
    'select 0, c.contactid, c.fullname, 0 ' +
    'from  (' + @CRM_FilteredContact + ') AS c ' 


And the report will execute different queries depending from which context sensitivity it is run.

And once again the sky is the limit.

Monday, June 18, 2012

Synchronous Retrieve Multiple Query

We have covered how to go about constructing retrieve queries for the following cases:

So we need to close out this topic by providing how to go about constructing a synchronous retrieve multiple query. Once again, synchronous queries are necesssary where there is jscript dependency logic.

So let's get right to it.

First of all place the following function into one of your form jscript resources:

function retrieveMultipleSync(entity,filter,fields) {

    try {
  var context = Xrm.Page.context;
  var serverUrl = context.getServerUrl();
  var query = new XMLHttpRequest();
  var oDataSelect = serverUrl + "/XRMServices/2011/OrganizationData.svc/" + entity + "Set"+fields+"&$filter="+filter;
  query.open('GET', oDataSelect, false);
  query.setRequestHeader("Accept", "application/json");
  query.setRequestHeader("Content-Type", "application/json; charset=utf-8");
  query.send(null);
  return JSON.parse(query.responseText).d.results;
 } catch (e) {
  alert("Retrieve multiple failed to return results");
 }

}


The next step is to call the above function to retrieve the values you are looking for by passing the following parameters:
  • entity - the entity logical name e.g. Product, Contact, Account
  • filter - a valid filter clause which will retrieve 1 or more records from the entity (see example)
  • fields - you wish returned from the retrieve query. NB: These are case sensitive so be sure to ensure to check how they are defined on the entity. You also can use the OData Query tool to get the syntax. Errors encountered with configuring this are most likely to be in this area.  

The following is a sample call to the retrieveMultipleSync function:

 var entity = "new_contact_systemuser";
 var entityid = Xrm.Page.data.entity.getId();
 var filter = "contactid eq guid'" + entityid + "'" + " and "+  "systemuserid eq guid'" + Xrm.Page.context.getUserId() + "'";
 var fields = "?$select=systemuserid";
  
 var entityData = retrieveMultipleSync(entity,filter,fields);
 if (entityData.length > 0)
  return false;
 else 
  return true;

And that's a wrap!

Friday, June 15, 2012

Synchronous Retrieve Query

Previously I provided an approach for constructing retrieve queries in CRM 2011. That approach used an asyncronous technique for running the query. That is, the form jscript does not wait for the results of the query to be obtained but instead continues to process with the query being executed in a separate thread. The benefit to this kind of approach is improved performance. For example, if a form field needs to be set then in most cases using an aysnchronous query should work (as long as that query doesn't take too long to execute).

However in many cases it is also necessary to run such queries syncronously in order to accomodate dependency logic in the jscript. The following provides a technique for constructing the synchronous version of this query (please refer to prerequisites mentioned in my previous post).

First of all place the following function into one of your form jscript resources:

function retrieveEntityByIdSync(entity,entityid,fields) {

    try {
		var context = Xrm.Page.context;
		var serverUrl = context.getServerUrl();
		var query = new XMLHttpRequest();
		var oDataSelect = serverUrl + "/XRMServices/2011/OrganizationData.svc/" + entity + "Set(guid'" + entityid + "')" + fields + "";
		query.open('GET', oDataSelect, false);
		query.setRequestHeader("Accept", "application/json");
		query.setRequestHeader("Content-Type", "application/json; charset=utf-8");
		query.send(null);
		return JSON.parse(query.responseText).d;
	} catch (e) {
		alert("Retrieve multiple failed to return results");
	}

}

The next step is to call the above function to retrieve the values you are looking for by passing the following parameters:
  • entity - the entity logical name e.g. Product, Contact, Account
  • entityid - the id of the passed in entity
  • fields - you wish returned from the retrieve query. NB: These are case sensitive so be sure to ensure to check how they are defined on the entity. You also can use the OData Query tool to get the syntax. Errors encountered with configuring this are most likely to be in this area.

For example, the following will retrieve the first and last name from the system user entity using a synchronous query:

	var e1 = retrieveEntityByIdSync("SystemUser",Xrm.Page.context.getUserId(),"?$select=FirstName,LastName");
	alert(e1.FirstName);
	alert(e1.LastName);


Thursday, June 14, 2012

Contact: Details Updated Change Log

This particular issue has been troubling me for quite some time. Every now and then, in a particular client installation, a bunch of contact records would get updated and the update would be logged in the Notes section showing the previous and current values.


Not only is this issue a little bit annoying, but when you combine this with the fact that this contact will then get updated in CRM and subequently propagated to other users' contacts folder - well then you have data pollution. First of all, the change log is unnecessary and it doesn't take long before people start blaming "CRM" or the "Outlook Client" for making these unsolicited changes. Second of all, it can in fact make changes that you don't want. Like in the screenshot above where "USA" is being replaced by "United States" where you might have certain standards and/or functionality based on the specific values that appear in a particular field. Third of all, it generates unnecessary updates that need to be uploaded and then downloaded to all other clients ("what? I didn't make changes to those contacts..."). And let's not even mention the cryptic audit comment that is written to the notes that now will appear on all contact records that get updated and if the values get reverted back to what they were previously (i.e. the correct values) this audit will appear again and again...

This particular update seemed to occur only for internal company contact records and not for external contact records. And through various tricks of the trade I was able to verify 100% that this update was not coming from CRM - rather CRM was just the conduit for propagating it as described above. But the problem was that I could not attribute this to other 3rd party software... so it was a little hard convincing the client without another prime suspect.

I finally came across the following forum posting which seemed to describe the issue I was facing to a tee: http://social.technet.microsoft.com/Forums/en/outlook/thread/8fa240c6-fb3b-48fc-b7d4-8a82919e3912

In short, from this posting 2 prime suspects emerged. The most likely one being the "Microsoft Exchange global address list (GAL) synchronization with Microsoft Outlook 2010" feature and this feature can be disabled using Group Policy as described in this article: http://technet.microsoft.com/en-us/library/ff871432.aspx. This presumably resolves the problem globally as it uses Group Policy.

The second suspect is the "Social Connector" feature that is built into Outlook 2010 and which apparently also has this change logging feature. This can be disabled on a client by client basis by navigating as shown in the screenshot below.


As can be seen, the default is "update without prompting" - a little hard to understand why it would have been designed this way. For me personally, and it seems for many others, it could have saved a few gray hairs if the design of this feature gave a little bit more consideration to the user experience.






Monday, June 4, 2012

Embedding Reports in a Navigation Link

Now that we've explored how context sensivity can be added into a form Navigation Link, we can devise yet another approach for embedding reports into a CRM form and this time - you guessed it - via the form Navigation Links.


The first thing you'll need to do is of course create your report. As we're going to integrate this with the CRM form, you may want to have the report have a similar look and feel to CRM's native look and feel. Once you're happy with your report obtain the report GUID.



The next thing you'll need to is create an html web resource to contain the report.
 










Unlike the web resource that is used for embedding reports within an I-Frame, we need to update this report and supply the GUID for the report that you wish to integrate (copied from above) i.e. you'll need to modify the following:
var reportId = "ReportGuid";
Subsequently, once you have created the web resource, add it to the form and publish:
The result should be something that looks like the following:

Friday, June 1, 2012

Add Context Sensitivity to Navigation Links

Just as you can embed a web resource into a form you can similarly embed a web resource into a form navigation link. However there is an important difference. In the former case you can pass parameters which can be interrogated by the web resource to add context sensitive logic. Whereas when you define a Navigation Link you are presented with no such option (not quite sure why):

Note the absence of "Custom Parameters"

Being able to add context sensitivity to the Navigation Link is crucial. It's actually a little hard to think of a practical (realistic/useful) scenario where you'd add a Navigation Link without some kind of tie in to the record being viewed - or maybe that's just me...

A while back we came up with one solution for this that allowed us to integrate bing maps that employed the combination of web resources and client side jscript.

A better solution though is to have all the logic contained with the html web resource. So how can context sensivity be added? The code snippet below illustrates this.









 
 
  • ClientGlobalContext.js.aspx - You can use this reference to retrieve the CRM context (e.g. URL, user, roles etc.)
  • Use the "window.parent" prefix to reference form elements. Shown in the example are how to go about retrieving the record ID and the record type. You could similarly access other form elements by following this format.