JavaScript files become large, very quickly, when it comes to customising Dynamics 365 forms. Code reusability tends to be low across forms, and external libraries often have vast amounts of redundant code.

The module pattern in JavaScript is similar to classes (if you're familiar with C#, Java, Python etc.). Each module is self-contained with distinct functionality, and can be decoupled from other chunks of code with minimal fuss. In Dynamics 365, a module might represent:

  • logic to support toggling sections on a form
  • parts of a framework to support Web API calls
  • reusable security role querying and validation helpers
  • common numeric functions such as VAT calculation

The benefits should be becoming apparent at this point. We've touched on maintainability and reusability, but there's also testability. Each module can have its own individual unit tests, and each form (i.e. contact, account) can be tested end to end. See my GitHub page for an example.

How do I write modular JavaScript for Dynamics?

In this example, I'll demonstrate a requirement that locks every field on the contact form when it is loaded, if the user does not have a specific security role.

We need three modules: contact, security and fields. Security needs to get the users roles and determine if a given security role exists within the users roles:

(function () {
    "use strict";

    var Security = function () { };

    var userRoles;

    Security.prototype.getUserRoles = function () {
        return userRoles || Xrm.Page.context.getUserRoles();
    };

    Security.prototype.userHasRole = function (roleId) {
        var userHasRole = false;
        var roles = this.getUserRoles();

        if (roleId && roles && roles.length) {
            if (roles.indexOf(roleId) > -1) {
                userHasRole = true;
            }
        }

        return userHasRole;
    };
    
    module.exports = new Security();
}());

Note: certain syntax will be unfamiliar here, if you're new to modules. The contents of the functions should feel familiar though.

Fields needs to be able to lock all fields on a form:

(function () {
    "use strict";

    var Fields = function () { };

    Fields.prototype.toggleAllReadOnly = function (disabled) {
        var controls = Xrm.Page.ui.controls.get();

        for (var i in controls) {
            var control = controls[i];
            if (control.getDisabled && !control.getDisabled()) {
                control.setDisabled(disabled);
            }
        }
    };

    module.exports = new Fields();
}());

Notice the line module.exports = new(); This exposes the module to other modules, which can make use of it by using the require syntax, as shown in the final form contact:

(function () {
    "use strict";

    var Contact = function () { };

    var Security = require("../security.js");
    var Fields = require("../fields.js");

    var adminId = "{3F8C61D9-4986-E711-8100-70106FAAE7D2}"

    Contact.prototype.onLoad = function () {
        this.lockFieldsIfNotAdmin();
    };

    Contact.prototype.lockFieldsIfNotAdmin = function () {
        var userHasRole = Security.userHasRole(adminId);

        if (!userHasRole) {
            Fields.toggleAllReadOnly(true);
        }
    };

    module.exports = new Contact();
}());

And you're done! The contact form pulls in functions from other reusable files and makes use of them to complete some business logic end-to-end inside of its onLoadfunction.

But wait... the file path "../security.js" might mean something to your code editor, but it means nothing inside a browser. Chrome or IE therefore won't be able to load the files required by the contact form.

You're going to need a bundler

Bundlers recursively check your application's dependencies and package the modules needed into one (or more) browser-safe bundle.

In this example, I use my preferred bundler, Webpack.

Start by ensuring you have Node.jsinstalled. Then, open a command window in your script's project directory e.g. C:\source\repos\crm-project\web-resources.

To install the latest release of Webpack locally, run: npm install --save-dev webpack

You're now going to need a script to build your contact module. Open your package.jsonfile (which should exist having installed Webpack). Add the following script:

"scripts": {    
    "build-contact": "webpack ./src/contact/contact.js ./dist/contact.bundle.min.js --optimize-minimize"
}

Replace ./src/contact/.. with the relative path of your contact.jsfile.

Running this script packages your contact script and all of its dependencies and outputs them to the file ./dist/contact.bundle.min.js. The additional tag of --optimize-minimize minimises your script, so that it's smaller in size and production-ready. The script can be run from your command line using npm run build-contact

Now you're (really) done!


Some notes and caveats:

  • Using JavaScript to lock fields often isn't the best approach because it's client-side and can therefore be overriden.
  • Minimised scripts aren't readable or easily debugged. You may wish to exclude --optimize-minimize if you're deploying to a development or sandbox environment.
  • I've chosen one of several ways to implement JavaScript modules. There are pros and cons to each. For detail see here.