Click here to Skip to main content
11,411,241 members (64,190 online)
Click here to Skip to main content

jModulizer - How to Modularize and Decouple your JavaScript Code

, 9 Jun 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
jModulizer - How to modularize and decouple your JavaScript code.

Introduction 

jModulizer is a framework which is developed for the purpose of an existing project and this assisted to develop maintainable JavaScript code. In this article, I will share some of the pattern implementations in JavaScript and how we can overcome some problems using those patterns. Additionally, you can learn "How to develop your own JavaScript library".

This article is not for JavaScript beginners. This is targeted at professionals, who want to improve their knowledge about some of the JavaScript patterns. Knowledge of JavaScript basics is very important to understand this article.

I developed this framework to deal with today’s web applications problems. Some of these common problems faced by developers are set out below;

  1. Difficult to maintain due to large number of code lines.
  2. JavaScript is presently used as a mainstream web development language. There can be 1500 – 3000 code lines, even in a small project.

  3. Not well structured
  4. JavaScript is still not a very popular language among developers. As a result, different developers use different coding styles and it might lead to code maintenance problems.

  5. Lot of global variables
  6. Global variables are not suitable for web applications. The potential for naming collisions increases when the number of global variables and functions go up in an application, and this may result in overwriting already declared functions or variables. A function that depends on global variables is tightly coupled with the environment. If the environment changes, then the function is likely to break variables accidentally. The easiest code to maintain is code in which all of its variables are defined locally.

  7. Highly dependent with third party libraries such as jQuery, Kendo, etc.
  8. Number of third party libraries are being used in the present context. So, it is pertinent to identify a good way to incorporate any other library or plugin into the application. If those library functions are used everywhere, a replacement cannot be done easily.

  9. Performance issues
  10. Performance is a very important aspect when a large application is developed. Lack of JavaScript performance would destroy application usability.

  11. Poor dependency management makes unit testing difficult
  12. When there are number of dependencies in a module, testing the module cannot be done individually without the incorporation of other dependencies.

  13. Re-usability problem
  14. Re-usability is a vital feature in any programming language and is not well facilitated in much JavaScript code.

  15. Error handling problems
  16. This again is an important area since users will experience strange behavior when errors are not properly handled.

  17. Parallel loading problems
  18. Normally, script tags will block all other content downloading. If there are many scripts to be loaded, other contents will be blocked till all the scripts are downloaded.

jModulizer framework has been developed based on the following design patterns.

  1. Sandbox pattern
  2. Module pattern
  3. Publish/Subscribe Pattern
  4. Façade Pattern

Except for the Module and Sandbox patterns mentioned above, others are well known design patterns. It is important to draw the attention to some of the design patterns, before going to the Sandbox pattern.

Namespace Pattern

The Namespace pattern reduces the use of globals in our programs and at the same time helps to avoid naming collisions or excessive name prefixing. JavaScript does not have namespaces built into the language syntax, but this is a feature that is quite easy to achieve. Instead of polluting the global scope with many functions, objects, and other variables, one global object can be created for the application or library and can add all functions/properties to this global object.

JavaScript variables and functions can be declared as follows:

var
url = 'www.myurl.com';

function connect() {}
function process() {}
function disconnect() {}

In this code, there is one global variable and three global functions. This code has a problem; if the code appears in two or more places, there is a naming collision. By using the Namespace pattern, we can overcome this problem.

Here is how the above code can be refactored by using a namespace;

var jModulizer = jModulizer || {}

jModulizer.conManager = jModulizer.conManager || {};
 
jModulizer.conManager.url = 'www.myurl.com';
jModulizer.conManager.connect = function () { };
jModulizer.conManager.process = function () { };
jModulizer.conManger.disconnect = function () { };

Or can be declared with an object literal:

    jModulizer.conManager = {
        url: 'www.myurl.com',

    connect: function () { },
    process: function () { },
    disconnect: function () { }
}

There is only one global variable in the above code. The following code is used to access the connect() function;

jModulizer.conManager.connect();

Module Pattern

Module (definition):

"Interchangeable single parts of a larger system that can be easily re-used."

The module pattern is widely used since it provides structures and helps to organize your code as it grows. Unlike other languages, JavaScript doesn’t have special syntax for packages, but the module pattern provides the tools to create self-contained decoupled pieces of code, which can be treated as black boxes of functionality and added, replaced, or removed according to the (ever-changing) requirements of the software you are writing. The module pattern is a combination of several JavaScript design patterns such as Namespaces, Immediate functions, Private and privileged members.

Let’s create conManager module object;

    var jModulizer = jModulizer || {};
    jModulizer.conManager = {};

Now, conManager module’s logic can be developed as below:

jModulizer.conManager = (function
() {
    var self = this;
    self.url = 'www.myurl.com';
    self.connect = function () { };
    self.process = function () { };
    self.disconnect = function () { };
    return {
        url: self.url,
        self.connect,
        process: self.process,
        disconnect: self.disconnect
    };
} ());

This jModulizer.conManager has exposed URL property and connect, process and disconnect functions to the public.

Notice the "());" on the last line? This conManager is an immediate function.

Immediate Functions

The immediate function pattern is a syntax that enables you to execute a function as soon as it is defined.

Immediate Functions can be identified as follows:

(function () {
    /* Implementation goes here */
}());

You can also pass arguments to immediate functions, you can pass window and document objects to the function as follow;

(function (window, document) {
    /* Implementation goes here */
} (window, document);

This pattern is in essence just a function expression (either named or anonymous), which is executed right after its creation.

The pattern consists of the following parts:

  1. You define a function using a function expression. (A function declaration won’t work.)
  2. You add a set of parentheses at the end, which causes the function to be executed immediately.
  3. You wrap the whole function in parentheses (required only if you don’t assign the function to a variable).

The following alternative syntax is also common (note the placement of the closing parentheses):

(function () {
    /* Implementation goes here */
})();

This pattern is useful because it provides a scope sandbox for your initialization code.

Sandbox Pattern

The sandbox pattern solves the problems of the namespace pattern:

  • Reliance on a single global variable to be the application’s global.

In the namespace pattern, there is no way to have two versions of the same application or library run on the same page, because they both need the same global symbol name, for example, jModulizer.

  • Long, dotted names to type and resolve at runtime, for example.

jModulizer.conManager.url

As the name suggests, the sandbox pattern provides an environment for the modules to "play" without affecting other modules and their personal sandboxes. In the sandbox pattern, the single global is a constructor called AppSandbox(). You create objects using this constructor, and you also pass a callback function, which becomes the isolated sandboxed environment for your code.

new AppSandbox(function (box) {
     /* module logic goes here */
});

The object box will be like conManager in the namespacing example. It will have all the library functionality you need to make your code work.

The AppSandbox() constructor can accept an additional configuration argument (or arguments) specifying names of modules required for this object instance. We want the code to be modular, so most of the functionality AppSandbox() provides, will be contained in modules.

new AppSandbox(['creditor', 'debtor'], function (box) {



});

Now using "box" object, creditor and debtor modules can be accessed within the AppSandbox. This is also called Dependency Injection. When creating AppSandbox object, Sandbox’s constructor checks first argument and inject those dependencies through "box" object.

jModulizer Framework Overview

JModulizer framework was developed with the assistance of the Sandbox and the Module patterns. The framework architecture overview diagram is given below:

Figure 01: jModulizer Architecture Overview Diagram
  • Region A – Mediator Pattern Section
  • Region B – Module Pattern
  • Region C – Façade Pattern

According to the above architecture, core is in a position to control the sandbox. The sandbox can control modules since modules only know the sandbox and do not know any other external object other than its own objects.

jModulizer Core Implementation

This is the place where all the core objects exist. This core is responsible for registering/starting/stopping/loading modules and error handling.

jModulizer is the global variable of this framework which is similar to "jQuery" in jQuery. jQuery has two global variables, i.e., "jQuery" and "$" sign. We avoid naming collisions with jModulizer with the use of a single global variable. jModulizer global setup configuration is also handled inside the core. Separating configuration data from code is a good programming practice, because well-designed applications keep vital data outside the main source code.

jModulizer is a singleton object and is created when jModulizer.core.js is loaded.

(function (window, undefined) {
    var jModulizer = window.jModulizer = window.jModulizer || {};
    jModulizer.core = (function () {
/*jModulizer core functionality goes here*/ 

var self = this;

self.config = {
            DEBUG: false
        };
 
self.setup = function (config) {
            self.config = $.extend(self.config, config);
        };
 
        self.isDebug = function () {
            return self.config.DEBUG
        };
 
self.register = function (moduleId, Creator) {
            jModulizer.moduleData[moduleId] = {
                creator: Creator, instance: null, id: moduleId
            };
 };

self.start = function () {

var args = Array.prototype.slice.call(arguments), moduleId = args[0], 
    configuration = args[1] ? args[1] : null, module = jModulizer.moduleData[moduleId];
module.instance = module.creator(jModulizer.sandbox(self));
if (!self.isDebug())
                     self.errorHandler(module.instance);

module.instance.init(configuration);           
                };
               
                self.stop = function (moduleId) {
    var data = jModulizer.moduleData[moduleId];
    if (data.instance) {
        data.instance.dispose();
        data.instance = null;
    }
};
 
                self.jsLib = jQuery;
 
self.errorHandler = function (object) {
    var name, method;
    for (name in object) {
        method = object[name];
        if (typeof method == "function") {
            object[name] = function (name, method) {
                return function () {
                    try {
                        return method.apply(this, arguments);
                    } catch (ex) {
                        self.displayMsg({ method: name, message: ex.message });
                    }
                };
            } (name, method);
        }
    }
};

return {
                register: self.register,
                start: self.start,
                stop:self.stop,
                jsLib: self.jsLib                     
}  
 })();
window.jModulizer = jModulizer;
jModulizer.moduleData = {};
})(window);

Here, global error handler is pre-configurable one. We can configure this using jModulizer global setup function and can enable error handler only in production mode. Otherwise...

In here, having window as a local variable is marginally faster than a global variable. There isn't any value for the parameter named undefined. When you don't pass a value for a parameter, it will be set to the value undefined, so inside the function block the parameter named undefined will have the value undefined. The purpose of this is because the global identifier undefined wasn't a constant until recent versions of JavaScript, so in older browsers you can give the property undefined a value so that it doesn't represent undefined any more. (Note that the parameter without a value defined will get the actual value undefined, not the current value of the global property undefined.)

jModulizer Mediator

jModulizer mediator is responsible for communication between modules. Any module can publish messages with relevant data and other modules can respond if they subscribe to that message.

(function (window, undefined) {
    var jModulizer = window.jModulizer = window.jModulizer || {};
    jModulizer.com = function () {
        var handlers = {};
 
        return {
            subscribe: function (msg) {
                var type = msg.type;
                handlers[type] = handlers[type] || [];
                if (!this.subscribed(msg))
                    handlers[type].push({ context: msg.context, callback: msg.callback });
            },
 
            subscribed: function (msg) {
                var subscribers = handlers[msg.type], i;
                for (i = 0; i < subscribers.length; i++) {
                    if (subscribers[i].context.id === msg.context.id)
                        return true;
                }
                return false;
            },
 
            publish: function (msg) {
                if (!msg.target) {
                    msg.target = this;
                }
                var type = msg.type;
                if (handlers[type] instanceof Array) {
                    var msgList = handlers[type];
                    for (var i = 0, len = msgList.length; i < len; i++) {
                        msgList[i].callback.call(msgList[i].context, msg.data);
                    }
                }
            },
 
            remove: function (msg) {
                var type = msg.type, callback = msg.callback, handlersArray = handlers[type];
                if (handlersArray instanceof Array) {
                    for (var i = 0, len = handlersArray.length; i < len; i++) {
                        if (handlersArray[i].callback == callback) {
                            break;
                        }
                    }
                    handlers[type].splice(i, 1);
                }
            },
        };
    } ();
})(window);

Here, messages can be defined separately as follows:

var jModulizer = window.jModulizer = window.jModulizer || {};
jModulizer.messages = {
    SYS_ERROR: "SYS_ERROR",
    SAVED: "SAVED"   
}

jModulizer Sandbox Implementation

This consists of functionality which can be used in any module. If you carefully observe the core implementation above, the start function, and module starting, you would see the core object is passed to sandbox constructor. So, the sandbox can access core’s public properties and functions, and since the sandbox object is passed to the module, modules can access the sandbox’s public properties and functions.

(function (window, undefined) {
var jModulizer = window.jModulizer = window.jModulizer || {};
jModulizer.sandbox = function (core) {
var self = this;
 
self.publish = function (msg) {
            if (msg instanceof Array) {
                for (var i = msg.length - 1; i >= 0; i--) {
                    jModulizer.com.publish(msg[i]);
                }
            }
            else {
                jModulizer.com.publish(msg);
            }
        };
 
        self.subscribe = function (msg) {
            if (msg instanceof Array) {
                for (var i = msg.length - 1; i >= 0; i--) {
                    jModulizer.com.subscribe(msg[i]);
                }
            }
            else {
                jModulizer.com.subscribe(msg);
            }
        };
 
self.$ = function (selector) {
return new self.internalCls(selector);
};
 
self.internalCls = function (selector) {
this.elements = core.jsLib(selector);
};
 
self.internalCls.prototype = {
jModulizerTabs: function (options) {
return this.elements.tabs(options); //de-coupled jqueryui tabs
},
jModulizerGrid: function (options) {
return this.elements.kendoGrid(options); //de-coupled kendo grid
}
};
 
self.$.jModulizerAjax = function (options) {
core.jsLib.ajax(options); //de-coupled jquery ajax
}

            self.extend = function extend(defaults, options) {
return core.jsLib.extend(defaults, options);
};
 
return {
subscribe: self.subscribe,
publish: self.publish,
$: self.$,
extend: self.extend
};
};
})(window);

Here "jModulizer.com" is the jModulizer mediator. We can publish/subscribe messages as follows.

Publishing (Broadcasting)

sandbox.publish({ type: jModulizer.messages.SAVED,  data: { creditorId: 100 } });

Subscribing (Listening)

sandbox.subscribe({ type: jModulizer.messages.SAVED, callback: handler, context: this});
function handler(data){     /* Message handling logic goes here */ }

Here "SAVED" is the message name in jModulizer.messages.

The Sandbox can also be used to de-couple core libraries, such as jQuery, Underscore, etc. and the advantage of this is that Module has no idea as to what libraries that have been used in the application. Under these circumstances, if a requirement arises to change a core library (e.g., jQuery to Underscore) in a subsequent stage, that can be done without changing modules and with minor changes (writing small adaptor) to the Sandbox.

How this de-coupling works

Sandbox itself has its own symbol "$". This is not the same as jQuery $. The object $ in Sandbox provides core JavaScript library object to modules. In the above instance, the jQuery global object has been provided by $ in Sandbox. So module does not know as to which base library that has been used. Module only knows $ in Sandbox.

jModulizer Module Registration

jModulizer core has module registration functions. It accepts two arguments, firstly the module name and secondly, the module object.

Every module has to have two public methods, init() and destroy() because jModulizer core needs them to be used at the start and at the stop respectively. These two work as constructor and destructor of a module.

jModulizer.core.register("jModulizer.module.loanCalculator", function (sandbox) {
    var self = this;
 
    self.config = {
        loanAmount: 0,
        period: 0,
        interestRate: 0
    };
 
    self.init = function (options) {
        self.config = sandbox.extend(self.config, options);
        sandbox.$('#loanAmount').val(self.config.loanAmount);
        sandbox.$('#loanPeriod').val(self.config.period);
        sandbox.$('#loanInterestRate').val(self.config.interestRate);
        sandbox.$('#calculate').click(self.calculateLoanInstallment);
    };
 
    self.calculateLoanInstallment = function () {
        self.config.loanAmount = sandbox.$('#loanAmount').val();
        self.config.period = sandbox.$('#loanPeriod').val();
        self.config.interestRate = sandbox.$('#loanInterestRate').val();
 
        var monthlyInterestInstallment = 
            new Number((self.config.loanAmount * self.config.interestRate) / 
			(12 * self.config.period)),
            monthlyCapitalInstallment = new Number(self.config.loanAmount / 
			(12 * self.config.period)),
            monthlyTotalInstallment = new Number
		(monthlyInterestInstallment + monthlyCapitalInstallment);
        sandbox.$("#monthlyInterestInstallment").val(monthlyInterestInstallment);
        sandbox.$("#monthlyCapitalInstallment").val(monthlyCapitalInstallment);
        sandbox.$("#monthlyTotalInstallment").val(monthlyTotalInstallment);
    };
 
    self.dispose = function () {
    };
 
    return {
        init: self.init,
        destroy: self.dispose
    };
});

As you can see, this module has sandbox object access. We can start the module after registering in the following manner:

jModulizer.start("jModulizer.module.loanCalculator ");

or:

jModulizer.start("jModulizer.module.loanCalculator ", { /* Startup arguments */ });

You can see the behavior of start() function in the core. The first argument is the module name and the second argument is an optional startup argument into the module. For example, DOM element ids or class names can be passed as startup arguments.

When a module is started, its init() function is automatically called. To stop a module, call the stop function:

jModulizer.stop("jModulizer.module.loanCalculator");

This will call module’s destroy() function automatically. The destroy() function within the module can be used to flush objects, unbind events, etc.

How to test "loanCalculator" module with QUnit unit testing framework. This can be tested individually without any other module dependencies.

test("Test loan calculation output", 2, function () {
jModulizer.core.start("jModulizer.module.loanCalculator", {
    loanAmount: 100000,
    period: 2,
    interestRate: 0.10
});
 
var calculateButton = $("#calculate");
calculateButton.trigger('click');
 
var monthlyInterestInstallment = $("#monthlyInterestInstallment").val(),
    monthlyCapitalInstallment = $("#monthlyCapitalInstallment").val(),
    monthlyTotalInstallment = $("#monthlyTotalInstallment").val();

equal(monthlyInterestInstallment, 416.67, "monthly interest installment correctly calculated");
equal(monthlyCapitalInstallment, 4166.67, "monthly capital installment correctly calculated");
equal(monthlyTotalInstallment, 4583.34, "monthly total installment correctly calculated");
});

What have we learned from developing jModulizer:

  • Legacy JavaScript code of existing web applications can easily be adapted to this framework.
  • Almost all web applications contain non-independent classes and each class is coupled with other classes. By using this developed framework, these classes could easily be made into independent modules.
  • Every module of a web application can be tested individually and independently.
  • Modules created through this framework can be loaded in parallel, while script tags (<script> </script>) blocks the HTML contents downloading. You can use the requirejs or headJS library to load these modules in parallel.
  • This framework has inbuilt mediator to communicate with each other module. If you use requirejs or some other library to modularize JavaScript, you have to build your own communication mechanism.
  • Developers are provided with directions to follow programming guidelines and ultimate code would be a neat and clean code.
  • There is an automatic error handling mechanism and in the event the developer don’t handle an error, the framework automatically handles it. (This is a pre-configurable functionality, so, enable it only in production release).

Additionally, you can improve performance using some design patterns such as proxy, flyweight, etc.

The above article contains only an overview of the framework functionalities and its architecture.  

I am of the view that there are certain areas and best practices that still need to be developed and improved. However, this framework has practically been used and tested and thereby confirm the successful running of same.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sameera Millavithanachchi
Software Developer (Senior)
Sri Lanka Sri Lanka
No Biography provided

Comments and Discussions

 
Question[My vote of 2] Have a 2: jModulizer? jConnect? PinmemberDewey8-Jun-13 10:09 
AnswerRe: [My vote of 2] Have a 2: jModulizer? jConnect? PinmemberSam@Galle9-Jun-13 5:04 
GeneralMy vote of 4 Pinmembermbsmbs3-Jun-13 22:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150414.5 | Last Updated 9 Jun 2013
Article Copyright 2013 by Sameera Millavithanachchi
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid