Sunday, March 01, 2015

HowTo: HTML/JS WebResources

We (developers) have got great feature with CRM 2011 release. If you want to give your CRM application new client side features or controls that are not available OOB on one hand and you want your solution remain supported you have to use HTML/JS webresources. This article will describe approaches, tricks and code snippets I’ve fount or developed during my usage of HTML/JS webresources.

Embedding and passing parameters
To pass context of execution (entity and identifier or a record) or some custom parameters you have to define it in webresource configuration:


To parse and use parameters in your webresource you can use following code:
var parameters = GetGlobalContext().getQueryStringParameters();

Important Note: to make GetGlobalContext method available you have to reference ClientGlobalContext.js.aspx in your code:


<script type="text/javascript" src="../ClientGlobalContext.js.aspx"></script>


And here is how passed parameters could be used:



So to get identifier or type of a record you can use following code:


var recordid = parameters.id;
var entityname = parameters.typename;


Obviously in case parameters.id equals to null that means that user creates a record or you haven’t checked ‘Pass record object-type code and unique identifier as parameters.’ checkbox. So if you are sure that you have checked the checkbox and id is null that means that webresource is executed in a context of create form.

In case you have added custom parameter or parameters you can access to it using following code:


var customparams = parameters.data;


In case you use multiple parameters and you use format “Parameter1=Value1&Parameter2=Value2” following code could help you in parsing of parameters passed:


function ParseData(query) {
    var result = {};

    if (typeof query == "undefined" || query == null) {
        return result;
    }

    var queryparts = query.split("&");
    for (var i = 0; i < queryparts.length; i++) {
        var params = queryparts[i].split("=");
        result[params[0]] = params.length > 1 ? params[1] : null;
    }
    return result;
}


and here is usage of mentioned method:


var customparams = ParseData(parameters.data);
var customparam1 = customparams.CustomParam1;
var customparam2 = customparams.CustomParam2;



UPD: Today I have got brilliant suggestion from my friend and MVP Artem Grunin – try to use JSON format when you pass custom parameters. I tried and here are results I’ve got:

 Registration of custom parameters:

And usage in webresource code:



As you can see – that work perfectly!

Communication with caller window

In case you need to communicate with caller window you can use following code to get “Xrm” object:

var Xrm = window.parent.Xrm;


and after access to everything that is available for usual JavaScript code for CRM form. For example getting of field value:


var windownamefieldvalue = Xrm.Page.getAttribute("new_name").getValue();






Subscription to fields and form events

Sometimes you need to react on some events that is raised in CRM form like OnChange of field or OnSave of form. You can do it using following code for OnChange:


var Xrm = window.parent.Xrm;
Xrm.Page.getAttribute("new_name").addOnChange(function (context) {
    alert("Field Was Changed");
});



“context” parameter of a method could be used to get information about call context. Here is short code that demonstrates how it could be used:


var Xrm = window.parent.Xrm;
Xrm.Page.getAttribute("new_name").addOnChange(function (context) {
    var originAttribute = context.getEventSource();//Attribute that was changed on a form.
    var callDepth = context.getDepth();//get depth of a call.

    alert("Attribute " + originAttribute.getName() + " was changed and depth of a call is " + callDepth);
});



In case you need to handle OnSave event you can use following code:


var Xrm = window.parent.Xrm;
Xrm.Page.data.entity.addOnSave(function (context) {
    alert("Form was saved");
});


”context” parameter is a standard execution context for Save event we use in standard CRM forms. You can use it to analyze Save mode or cancel Save:


var Xrm = window.parent.Xrm;
Xrm.Page.data.entity.addOnSave(function (context) {
    alert("Form was saved and Save mode is - " + context.getEventArgs().getSaveMode());

    context.getEventArgs().preventDefault();
    alert("Save was cancelled");
});


Tricks

Fixing behavior of webresources for which execution logic for Create and Update forms differs (special thanks to Inogic for the idea, origin - http://inogic.com/blog/2014/08/update-html-on-form-save-button/)

Let’s assume that you have developed webresource for which execution logic is different for Create and Update form. You have opened new form and in this case webresource worked properly (followed ‘Create Form’ logic). You clicked Save and expected that webresource would be reloader and ‘Update Form’ logic would work. Unfortunately not. CRM doesn’t reload webresources in such scenarios. Add following code to CRM Form Onload handler to help CRM reload webresource:


var wrid = "Put your webresource control here";

var currenturl = Xrm.Page.getControl(wrid).getSrc();

if (currenturl.toLowerCase() == "/_static/blank.htm") {
    currenturl = Xrm.Page.getControl(wrid).getInitialUrl();
}

if (currenturl.toLowerCase().indexOf("id=", currenturl.length() - 3) != -1) {
    currenturl += Xrm.Page.data.entity.getId();
    Xrm.Page.getControl(wrid).setSrc(currenturl);
}


Restore of standard events (special thanks to Spectr, origin - http://axforum.info/forums/showthread.php?p=301306#post301306)

If you have referenced ClientGlobalContext.js.aspx you should aware that this would cancel the onselectstart, contextmenu, and ondragstart events. In case you want to restore those handlers you can use following code during onload of webresource:


if (document &&
    document._events &&
    document._events.unload &&
    document._events.unload[0] &&
    document._events.unload[0].handler) {
    document._events.unload[0].handler();
}



General Conclusions

1. Try to use supported approaches otherwise your code could stop working after installation of Updates

2. In case you want to build complex and beautiful extensions start learning (if you haven’t yet) JavaScript Frameworks. Here is what I use at the moment: