JavaScript Modules: Building Maintainable JavaScript

The JavaScript Module Pattern is used to make code cleaner, easier to understand, easier to reuse, and easier to test.

We’re going to look at some of the basic concepts of this pattern along with some examples and how this pattern can help improve JavaScript code in DDX.

Anonymous Closures

Anonymous Closures are the backbone of the module pattern. Closures allow us to keep everything in our module in the local scope while exposing only the parts we want to the outside world.

(function() {
    "use strict";

    //your module code
})();

Importing Global Variables

There are times when you need to access variables from outside your module. It is easy to import global variables, libraries, or other modules into your module.

(function($, someModule) {
    "use strict";

    var el = $('#myElement'),             //do stuff with jQuery
        result = someModule.someMethod(); //use imported module
})(jQuery, someModule);

Exporting The Module

Once you have finished your module you need to export it to make it accessible to other parts of your application.

You could export an object or a class.

//export object
var MODULE = (function() {
    "use strict";

    return {
        //properties
        one: 'ONE',
        two: 'TWO',

        //methods
        output: function(prop) {
          console.log(prop);
        }
    };
})();

//use module
MODULE.output(MODULE.one); //=> ONE

//export class
var CLASS = (function() {
    "use strict";

    function C(name) {
        this.name = name;
    }

    C.prototype.output = function() {
        console.log('My name is ' + this.name);
    };

    return C;
})();

//use module
var c = new CLASS('John');
c.output(); //=> My name is John

Cloning And Inheritance

Cloning and inheritance allow us to define a base module and then extend it in difference ways.

//base module
var BASEMODULE = (function() {
    "use strict";

    return {
        name: 'Base Module',

        output: function(message) {
          console.log(message);
        }
    };
})();

var MODULE = (function(baseModule) {
    "use strict";

    var module = {},
        key;

    //clone BASE module
    for (key in baseModule) {
    		if (baseModule.hasOwnProperty(key)) {
	         module[key] = baseModule[key];
    		}
  	}

    //override base property
    var baseName = baseModule.name;

    module.name = 'Inherited Module';

    //override base method
    var baseOutput = baseModule.output;

    module.output = function() {
        var message = 'I am the ' + module.name + '. I am inherited from ' + baseName + '.';

        baseOutput(message);
    };

    return module;
})(BASEMODULE);

//use module
MODULE.output(); //=> I am the Inherited Module. I am inherited from Base Module.

//base class
var BASECLASS = (function() {
    "use strict";

    function C() {
        this.name = 'Base Class';
    }

    C.prototype.output = function() {
        console.log('My name is ' + this.name);
    };

    return C;
})();

var CLASS = (function(baseClass) {
    "use strict";

    function C() {
        baseClass.call(this);
        this.baseName = this.name;
        //overwrite name
        this.name = 'Inherited Class';
    }

    C.prototype = Object.create(baseClass.prototype);
    C.prototype.constructor = C;

    C.prototype.output = function() {
        console.log('My name is ' + this.name + '. I have a base type of  ' + this.baseName + '.');
    };

    return C;
})(BASECLASS);


//use module
var c = new CLASS();
c.output(); //=> My name is Inherited Class. I have a base type of Base Class.

Improving JavaScript In DDX

So now that we’ve looked at the basics of JavaScript modules, how can they help us improve our code in DDX?

We already write basic modules in DDX that usually match the view that we are working on but those modules are not easily reusable. There are many areas that overlap and could be improved by using smaller, more reusable modules.

The Message component is a good example of a small module that we can reuse in many of our other modules.

First we load the Message component before we load our new module.

$this->Loader(false)->JS = 'ddxmessages';
$this->Loader()->JS = 'our_new_module';

Then we can use the Message component in our new module like this.

(function() {
    var page = DDX.namespace('our.new.module'),
        Message = DDX.components.Message,
        ErrorMessage = DDX.components.ErrorMessage,
        NoteMessage = DDX.components.NoteMessage;

    /**
     * page configuration object
     */
    page.config = {

    };

    /**
     * page constructor
     */
    page.init = function(config) {
        // pull in userland defined config options
        $.extend(page.config, config);

        // init document ready handler
        $(document).ready(page.documentInit);
    };

    /**
     * Stuff to do on jquery document ready
     */
    page.documentInit = function() {
        Date.dateFromatZend = page.config.date.format;

        var view = new View();

        view.init({
            //options
        });
    };

    /**
     * view class
     *
     * @param options
     * @constructor
     */
    function View() {

    }

    /**
     * initialize view
     *
     * @param options
     */
    View.prototype.init = function(options) {
        //do something and use Message component
        new ErrorMessage(Message.TARGET_DIALOG)
          .render('This is a sample error message');
    };
})();

Conclusion

Small reusable modules like this help eliminate code duplication and brings a consistency of functionality and appearance.

Other small areas that we could easily create reusable modules for are text formatting, number manipulation and formatting, currency formatting, initializing and working with jQuery UI dialogs, and tooth notation.

Some larger areas that would have benefited from better modularization are invoicing and task management.

Improving the way we write and organize our JavaScript code needs to be the first step we take which will allow us to start writing JavaScript unit test.

Share Comments
comments powered by Disqus