CRM JS webresource best practice for ribbon Commands / Forms

Introduction

As a CRM developer, working with script web resources in forms and ribbons, I have learned a few lessons over the years, and I have seen a wide spread from best practice implementation to the complete unstructured mess.

What I often see when taking over an existing implementation, is the lack of namespacing in the JavaScript webresources. A flat function based code structure, where a myriad of functions are clashed directly into the DOM, is not only hard to read, but increases the chance of overriding existing functionality. For example, some commonly seen function names are often “onLoad”, “onSave”, “validate” or “calculate”. Imagine it you are using two different web resources with conflicting definitions of “onLoad” on the same form, without namespacing; You are headed into trouble!

Another common “mistake” is using the same webresource for form events, and for ribbon commands. This is not a problem in itself, but you should be aware that the scripts will be loaded twice, and that it might make debugging more confusing. While form web resources are loaded as normal included scripts during form initialization, command code for ribbon buttons are loaded as unnamed dynamic script blocks when the ribbon loads. This means that you cannot set a breakpoint in the ribbon code before clicking a button the first time (because the script is not yet loaded). It also means that if you use the same web resource for the form and the ribbon, the ribbon dynamic script will overload the form script, and thus you will loose the ability to debug the loaded form script (The dynamic script block can still be debugged if you can locate it…).

This post is an attempt to share some best practices that can prevent these issues, based on my experience. Code examples are found in the bottom.

General JavaScript development

Switch to TypeScript

Do your coding in TypeScript to have better intellisense and compile time error checking. ReSharper 9+ have TypeScript support, to help you code even better TypeScripts

NB: To really be flying with TypeScripts, you should use third party libraries that have TypeScript definition files. Luckily there are Typescript Definition files for jQuery, AngularJS, XrmServiceToolkit, Xrm.Page API, etc. See more at http://definitelytyped.org/

Follow general best practices

Follow general best practices for JavaScript that also applies to TypeScript to some extent: http://www.w3schools.com/js/js_best_practices.asp

There are also many online resources to improve your TypeScript style and code quality. Read up on the official documentation and guides like https://github.com/panuhorsmalahti/typescript-style-guide

Here are also two good CRM specific links, from JoeCRM:

CRM JavaScript Best Practices

JavaScript Best Practices for Microsoft Dynamics CRM 2015

Handle dependency conflicts

Be aware that CRM and certain managed solutions uses common third party libraries like jQuery. If you are using different versions of these libs, you might run into problems were something become not compatible as the last loaded version overloads the others. jQuery has a noConflict function that can be used to load several versions of jQuery at once, and this is facilitated by dependency injection frameworks like requireJS (http://requirejs.org/)

Optimize your scripts for good performance

If you have a lot of script files, and it is hurting load time, consider bundling and minifying scripts. This can be done with for example Gulp (http://gulpjs.com/)

General CRM Scripting

Namespaces

ALWAYS use namespaces in CRM JavaScript. Especially if you divide your logic in multiple scripts. It will make it a lot easier to maintain and read if you can see exactly what is being referenced. Also, you should introduce namespaces from the start, mostly because it is a pain to change all the references later on.

I prefer to use a hierarchical namespace structure, with the following naming convention:

[Orgname].CRM.[EntityLogicalName].[Area]

For example: Contoso.CRM.Account.Form and Contoso.CRM.Account.Ribbon

I also prefer to have a separate namespace for internal functions and variables, so that the only thing exposed through the main namespace is the functions registered as commands or event handlers in the forms

For example: Contoso.CRM.Account.Form._privateMembers

Prepare for re-use

Avoid having duplicate code in your Form JS and Ribbon JS. Place common code in a third library instead if possible, like Contoso.CRM.Account.Common.js. This will not only make the code re-usable, but also easier to maintain as changes will only have to me made in one place.

Use Virtual Folders for webresources

To make it easier to keep you scripts organized, group them in folders on your development computer, and make the name webresource name reflect that path. This is also a recommended practice from Microsoft (https://msdn.microsoft.com/en-us/library/gg309473.aspx)

For example: A file C:\ContosoCRM\Webresources\JavaScript\Account\AccountForm.js could be registered as “new_/Account/AccountForm.js”, with “new_” being the CRM publisher prefix.

Ribbons / Command bar

Use an editor

Use a ribbon editor like Ribbon Workbench (https://ribbonworkbench.uservoice.com/). There are others, but Ribbon Workbench is the one I have had best experiences with.

Load the actual logic in the form when appropriate

If the command/logic is only to be used on the form ribbon/command bar, consider having the actual logic loaded by the form itself, instead of as dynamic script in the ribbon. This will make debugging easier, and promotes reuse of the logic for form events. Also, making changes to web resources is a lot faster if you don’t have to change the command definition in the ribbon at the same time (i.e. changed function signature).

For commands that should be available on the Entity home page and in related views, all required scripts should be loaded by the ribbon itself.

Debugging Ribbon Commands

If you have trouble debugging the dynamic script blocks, you can try the “Breakpoints in Dynamic JavaScript” feature in Chrome. This is described along with other debugging approaches in this post: http://blogs.msdn.com/b/crm/archive/2015/11/29/debugging-custom-javascript-code-in-crm-using-browser-developer-tools.aspx

Loading dependencies in Ribbons / Command bars

If you need to load other JavaScripts in a ribbon command before executing your function (i.e. jQuery), load them by calling the function “isNaN”. The reason for this is that a javascript action in ribbon XML requires a webresource and a function name. isNaN() is part of the ecma javascript definition, and will therefore always be declared. It will also always execute without errors, and has a very little overhead.

Add a Keyboard Shortcut to your button

I think it is a good idea to add keyboard shortcuts for command bar buttons in forms. I have successfully registered keyboard shortcuts in the form’s onLoad event using this library: http://www.webreference.com/programming/javascript-keyboard-shortcuts/2.html
Add a shortcut hint as a tooltip on the button in the ribbon, so you won’t forget.

Code examples

Ribbon command script (JavaScript)

This example uses a minimal implementation, where the actual logic is in a separate file that is used both by the ribbon and form events.

var Contoso = Contoso || {};
Contoso.CRM = Contoso.CRM || {};
Contoso.CRM.Account = Contoso.CRM.Account || {};

Contoso.CRM.Account.Ribbon = {
    helloWorldCommand: function () {
        Contoso.CRM.Account.Common.helloWorldCommand();
    }
}

Ribbon command script (TypeScript)

This example uses a minimal implementation, where the actual logic is in a separate file that is used both by the ribbon and form events.

module Contoso.CRM.Account.Ribbon {
    export function helloWorldCommand() {
        Common.helloWorldCommand();
    }
}

Common code used by Ribbon and Form (JavaScript)

var Contoso = Contoso || {};
Contoso.CRM = Contoso.CRM || {};
Contoso.CRM.Account = Contoso.CRM.Account || {};

Contoso.CRM.Account.Common = {
    helloWorldCommand: function () {
        alert("Hello World!");
    }
}

Common code used by Ribbon and Form (TypeScript)

module Contoso.CRM.Account.Common {
    export function helloWorldCommand() {
        alert("Hello World!");
    }
}

Form script (JavaScript)

Adds a keyboard shortcut in the form, to the ribbon button.

Contoso.CRM.Account.Form = {
    _privateMembers: {
        initialize: function() {
            //...
        }
    },

    onLoad: function() {
        // Register shortcuts using http://www.webreference.com/programming/javascript-keyboard-shortcuts/2.html
        shortcut.add("Ctrl+H", Contoso.CRM.Account.Common.helloWorldCommand, { 'type': 'keydown', 'propagate': true, 'target': document });

        Contoso.CRM.Account.Form._privateMembers.initialize();
    }
}

Form script (TypeScript)

Adds a keyboard shortcut in the form, to the ribbon button.

module Contoso.CRM.Account.Form {
    
    // private members
    function initialize() {
        //...
    }

    // public/exported members
    export function onLoad() {
        // Register shortcuts using http://www.webreference.com/programming/javascript-keyboard-shortcuts/2.html
        shortcut.add("Ctrl+H", Contoso.CRM.Account.Common.helloWorldCommand, { 'type': 'keydown', 'propagate': true, 'target': document });

        initialize();
    }
}
Advertisements