Defining and Extending JavaScript Modules

After writing a few modules you will find yourself reusing the same parts of code such as:

- initiating a function with a prototype
- getting a reference to the dom element
- merging options with defaults

These are duplicated in each Module Class you write. here is a simple example of code duplication:

Panel.js
function Panel(id, options) {
this.el = document.getElementById(id);
if (!this.options) { this.options = {}; }
for (var item in options) { this.options[item] = options[item]; }
this.init();
}

Panel.prototype = {

init: function() {
this.el.innerHTML = 'options: <pre>'+JSON.stringify(this.options)+'</pre>';
}
}

Item.js
function Item(id, options) {
this.el = document.getElementById(id);
if (!this.options) { this.options = {}; }
for (var item in options) { this.options[item] = options[item]; }
this.init();
}

Item.prototype = {

init: function() {
this.el.innerHTML = 'A new item!';
}
}

Obviously we can improve on these 25 lines of code as these modules share the same init code. So here's a way to improve it using define as a global function name:

Main.js
function define(name, module, unique) {
function Module(id, options) {
this.el = Utils.getEl(id);
if (!this.options) { this.options = {}; }
for (var item in options) { this.options[item] = options[item]; }
this.init();
}
Module.prototype = module;
if (!window[name]) { window[name] = Module; }
}

Panel.js
define('Panel', {
init: function() {
this.el.innerHTML = 'options: <pre>'+JSON.stringify(this.options)+'</pre>';
}
});

Item.js
define('Item', {
init: function() {
this.el.innerHTML = 'A new item!';
}
});

Now it is 22 lines of code and we will save many more for every new module created. The added benefit of this is that we can now update the module base in one place for all modules at once. Here is an example where i've added the ability to define a singleton module that will only exist once ever on a page:

Main.js
function define(name, module, unique) {
if (unique == true) {
if (!window[name]) { window[name] = module; }
}
else {
function Module(id, options) {
this.el = Utils.getEl(id);
if (!this.options) { this.options = {}; }
for (var item in options) { this.options[item] = options[item]; }
this.init();
}
Module.prototype = module;
if (!window[name]) { window[name] = Module; }
}
}

Utils.js
define('Utils', {
elements: {},
getEl: function(id) {
if (this.elements[id]) {
return this.elements[id];
}
else {
this.elements[id] = document.getElementById(id);
return this.elements[id];
}
}
}, true);

So Utils is now defined as a singleton instead of a function with a prototype. But what if we need to extend modules off each other? Well this becomes simpler now as we can save and merge modules within our define function.


var modules = {};

Main.js
modules[name] = module;
if (module.extend) {
module.superclass = modules[module.extend];
module = Utils.extend(Utils.clone(module.superclass), module);
}


First we are saving all module prototypes to a list we can reference by their name. Then if a module contains an extend property we will merge the two modules together and set a property called superclass to reference the parent module at any later date.

The Utils class then is updated with the additional clone and extend functions:

Utils.js

define('Utils', {
elements: {},
getEl: function(id) {
if (this.elements[id]) {
return this.elements[id];
}
else {
this.elements[id] = document.getElementById(id);
return this.elements[id];
}
},
clone: function(o) {
var a = {};
for (var i in o) {
if (o.hasOwnProperty(i)) { a[i] = o[i]; }
}
return a;
},
extend: function(o, o2) {
for (var p in o2) {
try {
if (o2[p].constructor == Object) { o[p] = this.extend(o[p], o2[p]); }
else { o[p] = o2[p]; }
}
catch (e) { o[p] = o2[p]; }
}
return o;
}
}, true);


If you would like to see a real-world example of these modules in action, you can look at the source of my portfolio:
http://kimturley.co.uk/debug.html

http://kimturley.co.uk/js/Main.js
http://kimturley.co.uk/js/modules/Utils.js
http://kimturley.co.uk/js/modules/Events.js
http://kimturley.co.uk/js/modules/Template.js
http://kimturley.co.uk/js/modules/List.js