Modular JavaScript Design with Sitefinity and RequireJS

What if I were to tell you that you can build modular client-side applications in Sitefinity and also not worry about JavaScript dependencies or conflicts? This should be music to your ears if you do any JavaScript development in Sitefinity.

First thing is you need to have solid understanding of what the Asynchronous Module Definition (AMD) is. Essentially, it is a standard for defining self-contained modules that loads dependencies asynchronously on demand. I suggest you take 10 minutes to read my blog post about this with examples. This will permanently change the way you think about JavaScript development. Once it clicks in your mind, I guarantee you will be amazed and even somewhat relieved.

So are you back? I'm serious. I will give you a second chance to read it…

Wasn't it worth it? Now let's use these fresh concepts in Sitefinity and build a client-side architecture. Usually the best place to start is the file structure. We will be using our theme location to store our JavaScript files:


The "libs" folder is the one that sticks out. It is where all our JavaScript frameworks, libraries, plugins and utilities files will go. I have a nice collection I use frequently across projects so I placed them all in there. We will not be using all of them, but only what we need on demand. The only exception is RequireJS which we will be using on all pages. Our entire client-side architecture will be built using RequireJS. For this, add require.js onto your site template so it is used across all pages. Add it to the bottom of the page:


Next thing to notice in the file structure is App.js and main.js in the root of the JS folder. Let me first discuss main.js. This is the content used to configure and initialize RequireJS. Normally, you would load this file asynchronously using the data-main attribute on the RequireJS script tag, but we want to make sure the RequireJS configuration is called before any of the sub pages even think about using RequireJS. For this reason, add main.js underneath require.js on your Sitefinity template:



This file contains all the configurations and dependency rules for all the JavaScript files. Here is what it looks like:

//DETERMINE BASE URL FROM CURRENT SCRIPT PATH
var scripts = document.getElementsByTagName("script");
var src = scripts[scripts.length-1].src;
var baseUrl = src.substring(src.indexOf(document.location.pathname), src.lastIndexOf('/'));
 
//CONFIGURE LIBRARIES AND DEPENDENCIES VIA REQUIREJS
require.config({
    baseUrl: baseUrl,
    //ASSIGN SHORTCUTS FOR EASY LOADING AND VERSION ABSTRACTION
    paths: {
        jquery: [
          //LOAD FROM THIS LOCATION IF THE CDN LOCATION FAILS
          'libs/jquery-1.8.1.min'
        ],
        underscore: 'libs/underscore-min',
        underscorestring: 'libs/underscore.string.min',
        can: 'libs/canjs/can.jquery.min',
        canfixture: 'libs/canjs/can.fixture',
        bootstrap: 'libs/bootstrap/js/bootstrap.min',
        kendoall: 'libs/kendoui/js/kendo.all.min',
        kendoweb: 'libs/kendoui/js/kendo.web.min',
        kendomobile: 'libs/kendoui/js/kendo.mobile.min',
        kendodataviz: 'libs/kendoui/js/kendo.dataviz.min',
        jquerymobile: 'libs/jquerymobile/jquery.mobile-1.2.0-beta.1.min',
        qtip: 'libs/qtip/jquery.qtip.min',
        blockUI: 'libs/jquery.blockUI',
        ajaxForm: 'libs/jquery.form',
        moment: 'libs/moment.min',
        notifier: 'libs/notifier.mod',
        html5placeholder: 'libs/html5placeholder.mod',
        nailthumb: 'libs/nailthumb/jquery.nailthumb.1.1.min',
        onebyone: 'libs/onebyone/jquery.onebyone',
        touchwipe: 'libs/jquery.touchwipe.min',
        browserselector: 'libs/css_browser_selector'
    },
    //DECLARE NON-AMD COMPLIANT JS AND DEPENDENCIES
    shim: {
        underscore: {
            deps: ['jquery'],
            exports: '_'
        },
        can: {
            deps: ['jquery'],
            exports: 'can'
        },
        kendoall: {
            deps: ['jquery'],
            exports: 'kendo'
        },
        kendoweb: {
            deps: ['jquery'],
            exports: 'kendo'
        },
        kendomobile: {
            deps: ['jquery'],
            exports: 'kendo'
        },
        kendodataviz: {
            deps: ['jquery'],
            exports: 'kendo'
        },
        moment: {
            deps: ['jquery'],
            exports: 'moment'
        },
        notifier: {
            deps: ['jquery'],
            exports: 'Notifier'
        },
        underscorestring: ['underscore'],
        canfixture: ['can'],
        qtip: ['jquery'],
        bootstrap: ['jquery'],
        blockUI: ['jquery'],
        ajaxForm: ['jquery'],
        html5placeholder: ['jquery'],
        onebyone: ['jquery'],
        touchwipe: ['jquery'],
        nailthumb: ['jquery']
    }
});
 
//INITIALIZE APP
require([ 'App' ], function (App) {
    App.init();
});

This deserves some explaining so we will take it piece by piece:
  1. The baseUrl is being determined based on where the main.js file is located. From this point on, any paths you specify will be relative from the main.js file.
  2. The paths section is used for defining shortcut names. Notice the paths are relative to the main.js because of what we put in the baseUrl property. Also there is never a ".js" extension on the files. This is part of the RequireJS pattern. You do not have explicitly write ".js" because RequireJS assumes that you are working with JavaScript files. There are a couple of benefits of defining the paths section. One is that it is more convenient to reference "jquery" instead of "libs/jquery-1.8.1.min" in your modules (we will get to that in a minute). Secondly, if you upgrade your libraries which usually have version numbers appended to the file name, that it abstracted away so you will not have to update the paths in your modules. They continue to just use "jquery". Also notice here that you can define a CDN location. If the CDN location fails, it falls back to the local file. Nice! The default wait period is 7 seconds, but this is also configurable if needed.
  3. Next is the shim section (which means "smell" in Arabic by the way :). This section is used to explicitly declare non-AMD compliant JavaScript files. Generally, a module is wrapped with "define(function () { ... })" to make it AMD complaint. Starting from jQuery 1.7, it joins the rank of AMD so you do not have to tell RequireJS's shim about it. However, many JavaScript libraries and plugins are still not. So a way around this is to define them here any time you add a new JavaScript file that is non-AMD complaint. Yes, it is an inconvenient, but you are gaining much convenience from using RequireJS in the first place so it is a small price to pay. Notice I am telling RequireJS that some of these files have a dependency on jQuery. This way, when I reference these files from my modules, RequireJS handles the dependency for me and loads jQuery automatically when needed!
  4. Lastly is the actual start up code that is executed. The first parameter of "require" is "App", which is saying that it would like RequireJS to automatically load the dependency of App.js (remember, you should not add the ".js" extension on the files). The App.js is relative to main.js so there is no folder... just "App". Get it? Then comes the callback function as the second parameter. Here you pass App as a parameter to the callback and work with that instance as you like. You can call the parameter name anything, but for consistency, it is a good idea to call it the same as the file name with a caps ("best practices" states that caps signify a JavaScript class name). This is a peek into how to use RequireJS. It will become more clear when explaining App.js.
Now let's look at App.js:

define([
    'require',
    'jquery',
    'notifier',
    'bootstrap',
    'blockUI',
    'browserselector',
    'html5placeholder'
], function (require, $, Notifier) {
    //PRIVATE PROPERTIES
    var baseUrl = require.toUrl('./');
    var baseServiceUrl = '~/api';
  
    //PUBLIC PROPERTIES
    return {
        init: function () {
            console.log('App initialized!');
        },
  
        toUrl: function (url) {
            if (url.indexOf('~/') === 0) {
                url = baseUrl + url.substring(2);
            }
            return url;
        },
  
        toRestUrl: function (restUrl) {
            return this.toUrl(baseServiceUrl + restUrl);
        },
 
        notifySuccess: function (message, title) {
            Notifier.success(message, title);
        },
 
        notifyError: function (message, title) {
            Notifier.error(message, title);
        },
 
        notify: function (message, title, icon, timeout) {
            Notifier.notify(message, title, icon, timeout);
        },
  
        convertToBoolean: function (value) {
            //VALIDATE INPUT
            if (!this.isDefined(value)) return false;
  
            //DETERMINE BOOLEAN VALUE FROM STRING
            if (typeof value === 'string') {
                switch (value.toLowerCase()) {
                    case 'true':
                    case 'yes':
                    case '1':
                        return true;
                    case 'false':
                    case 'no':
                    case '0':
                        return false;
                }
            }
  
            //RETURN DEFAULT HANDLER
            return Boolean(value);
        }
    };
});

This is a JavaScript module! It takes two parameters. The first parameter is an array of dependencies (it is using the shortcut names to the JavaScript files declared in main.js). This is telling RequireJS to load up jQuery, Notifier, Bootstrap, BlockUI and some other libraries when my site loads. Remember, this App.js file was being called by main.js as a dependency, so it will load App.js, then it will load App.js's dependencies, then load up the dependencies for each of those dependencies, and so forth. Do you see what is happening here? It is recursively loading dependencies up the tree. This handles dependencies but also makes each module self contained!

The second parameter of define is a function that returns a list of properties and methods... a JavaScript class in other words. You can put your own goodies in here. What is important here is the parameters of the function. It matches up to the dependencies array. That is how you use the dependencies! And they are scoped within your functions so you do not have to worry about conflicting with another JavaScript class. Note that some dependencies return a class, such as jQuery returns $ or App.js return App. Other times, you just need to load the dependencies without needing a class to use. For example, I am loading HtmlPlaceHolder which adds placeholder support for form inputs for browsers that do not support placeholders. Another example is Bootstrap, which is an awesome library by the way. I just need the JavaScript loaded up but do not need to use it as a utility in my function logic.

There you have it... the client-side architecture is in place. Pheww, all that hard work for what? Well, now in pages all you have to do is ask RequireJS for the dependencies and do what you like in the callback functions. Gone are the days of adding tons of JavaScript files onto the Sitefinity page, or juggaling the order of the files, or thinking of putting them on the template or the page level, or worrying about JavaScript conflicts. This is what you do from your Sitefinity pages:


This piece of JavaScript code is telling RequireJS to load up: jQuery, my App.js file and the OneByOne jQuery plugin. Then I list the dependencies as parameters to the function, then start my logic. Very elegant huh? Welcome to JavaScript AMD and Sitefinity.

Happy Coding!!
 
comments powered by Disqus