Pages

Wednesday, January 16, 2013

Cannot sign in to CRM Online: LiveDevice.xml

Ok. I have stumbled on this too many times. Each time I think I'll remember for the next time and then, lo and behold, I spend time scratching my head trying to figure out what the issue is. So it's time I wrote it down - if nothing else for my own sake as a reference post (hopefully I'll remember that I wrote up this post!).

What am I talking about?

Trying to connect to CRM Online. It happens on occasion that I am denied access to CRM Online when trying to connect from a 3rd party application when I'm certain that my credentials are correct. For example, below is what happens when trying to connect using the Visual Ribbon Editor tool.



Usually I spend time thinking that maybe my URL is incorrect. Maybe I should have left off the http/https. Maybe I should use the Organization Unique Name (appearing under Developer Resources in CRM) instead of the "friendly name" in front of the crm.dynamics.com etc. etc.

The solution of course is simply to delete the LiveDevice.xml file from under the Root\Users\<user>\LiveDeviceID folder and then to try and sign in again... Voila!

Perhaps now I'll remember for next time? Let's hope so...


Tuesday, January 15, 2013

Duplicate Field Mapping/Defaulting


I encountered a scenario where whenever a child entity is created from within the context of a parent entity, the mapping automatically creates two references from the parent entity on the child entity.


The reason for this is due to the fact that the parent entity is related in a 1:N relationship to the child entity more than once:


And each of these relationships automatically creates a mapping that resembles the one below. That is both mapping contain a link from the parent entity to each of the foreign keys from the child entity and you are unable to delete these as they are required by the CRM platform.


Moreover any mapping added to one of the relationships is automatically added to the other relationship (and vice versa). I can't figure out if this is absolutely necessary from a technical design perspective or if this is just a design flaw. I suspect the latter because I can't come up with a good reason for this design logic. Or it could just be something specific that managed to sneak into the environment I'm working in.

The major issue is that this results in a confusing design because both fields get defaulted when only one should depending on the navigational context i.e.:
  • When navigating from "Entities Owned" the "Entity" field should be defaulted from the parent entity whereas the "Is Invested In" entity should remain null
  • Conversely, when navigating from "Investors in this Entity" the "Is Invested In" field should be defaulted from the parent entity whereas the "Entity" should remain null



As standard field mapping seems to rule out the ability to differentiate between these two different contexts, the only way to do so would seem to be by passing in custom parameters which is an example of when this technique is absolutely required.

To do so, the form was modified to remove the default "Add New" buttons and replace with custom Add buttons (which as an aside also provides the ability to give much clearer names to the add operation). The Ribbon Workbench Tool ably supports this kind of configuration. Each of these buttons simply calls the Xrm.Utility.openEntityForm to open the same child form button, but passes in parameters to set the lookup fields as required in each case.



Access Main form fields from Nav Area via Jscript

Let's say you need to access form fields via jscript when you have navigated to one of the navigational links on the entity. For example, when the account form is open but you are currently on the "More Addresses" navigation.


A typical scenario for such a requirement is if you have configured a custom button on the sub-grid ribbon that links to a jscript function where you need to pass through some parameters that are set from fields on the main form.

If you try and access the fields using the standard Xrm.Page approach while in the above navigational scenario, your jscript will error out. Like so:

function CustomAction() {

 var parameters = {};
 parameters["snt_customid"] = Xrm.Page.data.entity.getId();
    parameters["snt_customidname"] = Xrm.Page.data.entity.attributes.get("snt_name").getValue();
 
 Xrm.Utility.openEntityForm("snt_custom", null, parameters);

}

The solution is pretty much the same as how page elements are referenced from html web resources linked to a form i.e. using the prefix of "window.parent". Therefore updating the function as follows should do the trick:

function CustomAction() {

 var parameters = {};
 parameters["snt_customid"] = window.parent.Xrm.Page.data.entity.getId();
    parameters["snt_customidname"] = window.parent.Xrm.Page.data.entity.attributes.get("snt_name").getValue();
 
 Xrm.Utility.openEntityForm("snt_custom", null, parameters);

}

Update:

The above design has one flaw and that is if you decide to use a sub-grid to bring in the navigation link into the main body of the form. When you do so, the same sub-grid menu shows up when you click on the sub-grid but there is one essential difference...



In the case of the former, the main form is no longer in view and therefore you cannot access the form elements (Xrm.Page.data) without using the "window.parent" prefix. I you don't use this prefix you will receive a jscript errror.

In the case of the latter the main form is still in view and therefore Xrm.Page.data is accessible. And therefore if you use the "window.parent" prefix you will receive a jscript error.

In order to cater for both scenarios the solution is quite simple - simply use the try/catch exception handling feature. This way if an exception occurs it will go to the alternate access method which will then succeed.

function CustomAction() {

 var parameters = {};
 var id;
 var name;
 try {
  // if called from Nav Link navigation
  id = window.parent.Xrm.Page.data.entity.getId();
  name = window.parent.Xrm.Page.data.entity.attributes.get("snt_name").getValue();
 } catch (e) {
  // if called from Embedded Grid navigation
  id = Xrm.Page.data.entity.getId();
  name = Xrm.Page.data.entity.attributes.get("snt_name").getValue();
 }
 parameters["snt_customid"] = id;
 parameters["snt_customidname"] = name;

 Xrm.Utility.openEntityForm("snt_custom", null, parameters);
}