Enhancing scalability & maintainability of your JavaScript/jQuery code – JS Design patterns

Are you building a huge, jQuery-ASP.NET web application, which is subjected to a lot of global code changes in your client script? If yes, you should probably consider the below design pattern, which would help you have a better control on code execution.

Maintainability problem with large chunks of JavaScript code :

Imagine a web application having ~400 .js files. Each .js file would have code for AJAX calls/DOM manipulations for the respective .aspx page. But there would be some code which is redundant and can be moved to a common global function, which can be accessed by all .js files. With respect to ASP.NET paradigm, this can be compared to - your code behind partial class inheriting a base class. So whenever you have a global change, you can just change the base class and all .aspx files will have the changes.

However, there is a huge difference in the above comparison. ASP.NET has a powerful page life cycle which strictly defines which piece of code should be executed at what event. i.e., Pre Init, Init, Page Load, Pre Render etc. In the case of JavaScript, it is just dependent on ‘where you place your code’. i.e., even if you call a global function on line 1, developers can always add lines above and below it, which will mess up your code execution sequence.  This pattern is an attempt to have control on JS code execution, having Pre and Post events.

Solution- Using wrapper which can provide pre/post events:

Let’s say for every page, we need to prevent post back on click of any button. i.e., writing “e.preventDefault()” on button click events. I call this as a “Pre Event”, since this is to be executed before any click events are written.

Similarly, let’s say, for every page, we need to disable/show/hide certain controls, based on few conditions. I call this as a “Post Event”, since this code has to be executed at the end (similar to pre render event of asp.net model). So here is how we can try to enforce this.

Enhancing our chainable JavaScript library:

The following description is based on my previous article, building a chainable JavaScript library. For the global “Pre” and “Post” events, I’m going to extend the chainable JavaScript library with the “execute” method, like this:

(function(){
    var value='Hello world';
    var preInit=function(){
        console.log('pre Init');
    }   
    var preRender=function(){
        console.log('pre Render');
    }
    var mySpace=function(){
        return new PrivateSpace();
    }
    var PrivateSpace=function(){
 
    };    
    PrivateSpace.prototype={
        init:function(){
            console.log('init this:', this);
            return this;
        },
        ajax:function(){
            console.log('make ajax calls here');
            return this;
        },
        cache:function(){
            console.log('cache selectors here');
            return this;
        },
        setValue: function(newValue){
            value=newValue;
            return this;    
        },
        getValue: function(callbackFunc){
            callbackFunc.call(this,value);
            return this;    
        },
        execute:function(oldClass){
            preInit();
            var obj=new oldClass();
            obj.init.call(this);
            preRender();
        }
    }
    window.my$=mySpace();
})(); 

To explain the execute method, let’s consider the below code:

var CommonSearch=function(){
    var pageLoad=function(){
        console.log("page load operations here...");
    }
    return{
        init: pageLoad
    }
}

The above code is what resides in your ‘.js’ file. It is written in revealing module pattern. Here, CommonSearch is a constructor function and to execute it, we should say:

$(document).ready(function(){
    var objCommonSearch=new CommonSearch();
    objCommonSearch.init();
});

This does not have “Pre” and “Post” events. So if we need some global changes, we are lost! We have to manually change all 400 “.js” files. To troubleshoot this problem, if we use our my$.execute method, we can say:

$(document).ready(function(){
    my$.execute(CommonSearch);
});

Note that in the above snippet, we are not creating instance of CommonSearch constructor function. Instead, we are passing it to my$.execute, which internally calls the private “PreInit()” method first, then executes “init()” of  CommonSearch, and calls the private “preRender()” method. So, we are enforcing a particular sequence of code execution, which solves our problem.

[Note: As far as I know, 100% enforcement is impossible in JavaScript. If you are a JS Ninja, you can easily bypass whatever enforcement is set. That is the beauty of the language!!]

How this is different from using a common global function in document.ready, which can do the same functionality as my$.execute? Simple…we have good name spacing and chainability in our my$ function, which prevents conflicts with other modules.

If you are a JavaScript newbie, this article might sound a bit weird., but actually it is not so. I’m no JS guru to say this is the best approach. But this approach helped me a lot in scaling up easily and maintaining huge .js files, without any issues. If you can think of a better approach, please suggest. I’m waiting to learn..

Happy coding Smile