Using RequireJS in CRM2013

Javascript development often takes the backseat, compared to C#, while extending Dynamics CRM. There are valid reasons for implementing a business logic as a plugin or a custom workflow step. But, in this post I will talk only about how to effectively manage and write Javascript code for Dynamics CRM. These are my primary objectives:1.) Manage dependencies
2.) Control load sequence based on dependencies
3.) Promote code reuse
4.) Organise code effectively into manageable chunks

Dynamics CRM, doesn’t offer anything out-of-the box for accomplishing any of the above objectives. Ecmascript 6 will offer the much needed module feature1, but until all major browsers support Ecmascript 6, we can use any of the open-source script loaders. My framework of choice is require.js.

Current Process

The current process for Dynamics CRM script development:
1.) Create JS web resources
2.) Add web resources to the form
3.) Add event handlers for form load, form save and field change

Javascript webresources may not load in the order specified in the form2. This could cause script errors if you are expecting the scripts to load in a certain order. It is also cumbersome to add all the required scripts and setting up the event handlers.

Using require.js with Dynamics CRM 2013

RequireJS3 automatically loads dependent scripts/modules, when they are needed. Dynamics CRM, caching strategy4 means that we also need to know the magic string in the web resource url, so that we can use it in require.js. There are two ways to get this magic string:
1.) Parse it in the url of the initial loading script
2.) Use the undocumented WEB_RESOURCE_ORG_VERSION_NUMBER4 global

Setup

I have the following scripts
1.) Require.js – Require JS library
2.) Main.js – Initial loading script
3.) Form_script.js – Form script, contains event handlers for form load, form save and field change
4.) Common_script.js – Script library to be reused throughout the CRM organisation

Here is how the solution webresources are organised.

/// <reference path="require.js" />
(function() {
    var webResourceVersionNumber = '';
    //get the url for the script, so that we can extract the guid to prefix
    [].forEach.call(document.querySelectorAll('script'), function(element) {
        if (element.src.indexOf('main.js') > -1) {
            webResourceVersionNumber = element.src;
        }
    });
    webResourceVersionNumber = webResourceVersionNumber.replace(Xrm.Page.context.getClientUrl(), '').substr(1, 24);
    var defaultConfig = {
        //could also use undocumented WEB_RESOURCE_ORG_VERSION_NUMBER
        baseUrl: '/' + webResourceVersionNumber + '/WebResources/scripts_/form',
        shim: {
            'lodash': {
                exports: '_'
            }
        },
        deps: ['lodash', 'common_script', 'form_script'],
        callback: function() {
            console.log('callback before requirejs has been loaded');
        },
        paths: {
            lodash: '../library/lodash'
        },
        onError: function(err) {
            console.log(err.requireType);
            if (err.requireType === 'timeout') {
                console.log('modules: ' + err.requireModules);
            }
            throw err;
        }
    };
    if (!window['require']) {
        window['require'] = defaultConfig;
    } else {
        defaultConfig.callback = function() {
            console.log('callback after requirejs has been loaded');
        };
        require.config(defaultConfig);
    }
})();

 

/// <reference path="XrmPage-vsdoc.js" />
/// <reference path="require.js" />
define(['common_script', 'lodash'], function (common,_) {
    console.log('loading form_script.js');
    //can use lodash, as it is specified as a dependency and should have been loaded
    console.log(_.VERSION);

    var form = {
        onSave: function(context) {
            common.log('onSave');
            common.log(context);
        },
        onLoad: function() {
            common.log('onLoad');
            Xrm.Page.data.entity.addOnSave(this.onSave);
            Xrm.Page.getAttribute('emailaddress1').addOnChange(function(context) {
                Xrm.Page.ui.setFormNotification('Email Address has changed', 'INFO', '1');
            });
        }
    };
    //call onload and start onload processing
    form.onLoad();
    return form;
});

 

/// <reference path="require.js" />
define(['lodash'], function (_) {
    console.log('loading common_script.js');
    //can use lodash, as it is specified as a dependency and should have been loaded
    console.log(_.VERSION);
    return {
        log: function(message) {
            if (console && console.log) {
                console.log(message);
            } else {
                alert(message);
            }
        }
    };
});

We first have to add the first two scripts, inside Dynamics CRM.

Here is the console output during script load.

Now we will verify whether the field change event handler fires on change of email field.

Now we will verify if the form save event is firing.

We have now successfully used requirejs to load scripts in Dynamics CRM.

EDIT (23/01/15): Have a look at https://dreamingincrm.com/2015/01/14/an-alternative-approach-to-loading-form-scripts-in-dynamics-crm/ for supported option using IFrame.

References
1. http://www.2ality.com/2013/07/es6-modules.html
2. http://wiki.ecmascript.org/doku.php?id=harmony:modules
3. http://www.develop1.net/public/post/Asynchronous-loading-of-JavaScript-in-CRM-2013.aspx
4. http://www.develop1.net/public/post/CRM-Developer-e28098Must-Knowe28099-2-Web-Resource-Caching.aspx

3 comments

Leave a comment