Module Pattern Template

So, as most of you know, I have been swamped with work lately. However, I promised myself that I would get you all a detailed article by the end of this week, regardless of how busy I am. In the near future I want to get back into comparing the different Frameworks, however, those articles take 5-10 hours to write and I just do not have that much time today. Instead, lets discuss the Module Pattern some more. In this article, I am going to show you my template for organizing my module patterns and discuss some details and gotchas about it.

First, why use the module pattern? The module pattern is best used with objects that are instantiated once (or very few times) per page. The module pattern does not leverage the power of prototype based OOP as the Functions are not being attached directly to the prototype of the Object. Instead you are instantiating a function and creating a closure thereby enclosing a self contain Object, which is more expensive in CPU cycles and memory when called frequently. I use the module pattern for 3 reasons, when writing: widgets, managers (like an Event Manager), and business logic for pages.

Here is an example of a module pattern that I use for managing the business logic of a page, which is the business need that I use the Module Pattern for most frequently:

Template.js

Core.Biz.PageName = function() {
	//	Object namespace; private variables go here
	var _this = null; // internal object to reference public self
	
	//	Module dom namespace; id references to the DOM and other DOM objects go here
	var dom = {

	};
	
	//	Module evt namespace; event callbacks go here
	var evt = {

	};
	
	//	Module req namespace; AJAX request and callbacks go here
	var req = {

	};
	
	//	Public methods and constants
	return {

		/**
		 *  The DOM selector class to identify example DOM elements
		 *  @property CLASS_EXAMPLE
		 *  @type String
		 */
		CLASS_EXAMPLE: example,

		/**
		 *	Some publicly available method
		 */
		publicDoSomething: function() {

		},

		/**
		 *  initialize the object, put elements here that use global object elements (like static variables)
		 */
		init: function() {
			// initialize DOM references that are not null
			for (var k in dom) {
				if (isString(dom[k])) {
					dom[k] = $(dom[k]);
				}
			}

			_this = Core.Biz.PageName;
		}
	};
}();

In this example Core.Biz.PageName will create this object:

Core.Biz.PageName = {
	CLASS_EXAMPLE: String,
	publicDoSomething: Function,
	init: Function
}

The object will be created when your page loads, but the internal objects that require the DOM should be put inside the init Function and that function should be called when the document is ready (for best performance) or when window loads (if you do not use a powerful enough Event management Toolkit). Inside the closure Function, I generally create 3 namespaces (unless one is not needed), which helps me organize and manage my Object, these are: dom, evt, and req.

The dom namespace will generally have the String IDs that I use to find elements on a page, or null, if I am going to initialize the DOM node reference through some other means in the init Function. That namespace might look something like this:

var dom = {
	body: projectBody,
	someAnchor: link1,
	someOtherElem: null
};

By putting this namespace first all other functions of the object can reference these elements. The init function will automatically call document.getElementById (or equivalent depending on your Framework) on any String you defined in the namespace, so thereafter the Object members will reference DOM nodes. I would define someOtherElem in the init Function as well, maybe by calling dom.someOtherElem = dom.body.getElementsByTagName(div)[0];.

The variable _this is an internal shortcut to reference the public object. Initially null, you assign the Core.Biz.PageName Object to it during the init Function. Since references to _this will not be evaluated until those Functions are called, it is safe to do use this Object as long as you run the init Function first. Using _this helps to prevent memory leaks or circular logic, and you do not have to worry about managing this. I also use it if I need to call an internal method that is not yet instantiated. For example you would not normally be able to access methods in the req namespace from evt because req is declared after evt. However, as shown in the evt namespace discussion below, you can circumnavigate this issue, by calling a public method on _this.

The evt namespace will contain all non-trivial event callback functions for events attached to DOM nodes. These events should be attached in the init Function after you have finished declaring your dom namespace. If using YUI it might look something like this:

var evt = {
	onAnchorClicked: function(e) {
		var targ = YAHOO.Util.Event.element(e);
		CBP.publicDoSomething();
	}
};

…

init: function() {
…
YAHOO.Util.Event.addListener(dom.someAnchor, click, evt.onAnchorClicked);
…
}

The req namespace will contain all AJAX request and callback functions. I find this namespace helpful, because I can organize all my AJAX logic in the same place. If you do not use AJAX in your application or the Object does not manage AJAX, then omit this namespace.

The init Function will be used to initialize elements of the Object that require the DOM. The first loop is included to do the automatic conversion of the dom namespace ID references into DOM nodes. I then declare the _this Object so that any internal references to the public Object are readily available. Thereafter, I will attach events, AJAX, and initialize other widgets used by the page. If you are using YUI I recommend the following line of code be included right after Core.Biz.PageName is initialized to fire the init Function as soon as the DOM is ready and before the window is completely loaded:

YAHOO.util.Event.onDOMReady(Core.Biz.PageName.init);

That about wraps things up. This is a complicated subject and if you have any questions, do not hesitate to ask. Also, I would be interested in hearing about how you organize your module pattern objects and any pointers you might have.