Feb 22, 2012 Udpate:
I’m embracing the Backbone.js client-side MVC model, including Backbone.Controller pattern. in conjunction with the self-authored anti-templating framework idom for server-driven Javascript apps (Backbone in its current state needs major modifications to work for exclusively client-side apps)
The feature-oriented encapsulation pattern below is for exclusively client side apps and can be extended with jQuery $.Callbacks()/$.Deferred() based pub-sub bus to connect the feature objects together. Any DOM rendering objects used here can be replaced with or upgraded to use idom.
Version: 0.5
Updated: Nov 4, 2011
Required familiarity:
Aspect Oriented Programming, Separation of Concerns, Object Oriented Javascript, Prototypal Inheritance, Anonymous Functions
The question:
How do you create and separate features in JS using Object Oriented Programming (OOP) techniques?
The requirement is that each Feature is encapsulated within an anonymous function scope and all that’s exposed is an object literal with public methods, including methods for getting/setting properties.
Also, objects must operate exclusively in the window they’re defined in, so you don’t end up with features being defined across many windows.
This model also assumes:
1) window._globals object literal defined in the main app window that contains global variables that can be accessed by all features in the same window (using window._globals.someVar) as well as features within iframes (using partent.window._globals.someVar). Applications with completely separate windows would simply implement this model separately per each window, including iframes in each window.
2) presentation logic as well as business/computational logic is decomposed into distinct Features and implemented using multi-instance objects (which may use dynamic event binding/unbinding in case of DOM/SVG elements) that provide an OO interface to the DOM/SVG elements and the Canvas within the given Feature and only expose methods to the outside on the returned object literal, including the methods for get/set properties.
3) coordination logic that is not part of any distinct Feature is not part of the model
Overall Goal:
To show that it is possible to construct features in a way that allows developers to maintain them as separate modules without having to resort to much more fancy frameworks/client-side-middleware that don’t work as well for feature separation and performance.
Here is the basic homegrown AOP method for OO Javascript:
window.someFeature = (function(){
/*
The properties variable below is an object literal used for storing properties and their values that are available to actual objects within this anonymous function. See the bottom of this anonymous function for the returned object literal, which exposes public methods (accessed as window.someFeature.publicMethodName), including setProperties and getProperties.
*/
var properties = {}
properties.property1 = 0;
properties.property2 = "some initial value";
...
properties.propertyN = window._globals.someVar; // or parent.window._globas.someVar if this window happens to be an ifarme
/*
a basic private object that implements some computation or business logic. It can be referenced anywhere within the self executing anonymous function, including in the private objects and publicly exposed methods.
*/
var somePrivateObject = function () {
this.initialize.apply(this, arguments);
}
somePrivateObject.prototype = {
initialize: function(args){
// initialized upon instantiation
// initialize code goes here
},
someMethod: function(args) {
// do something
// return something
},
...
anotherMethod: function(args) {
// do something
// return something
}
}
/*
a private object that operates on a DOM element (as container). It can be referenced anywhere within the self executing anonymous function, including in the private objects and publicly exposed methods.
*/
var somePrivateDOMObject = function () {
this.initialize.apply(this, arguments);
}
somePrivateDOMObject.prototype = {
initialize: function(someDiv){
// this is the constructor method (implemented on the prototype) that populates someDiv container with some child elements and attaches event handlers to them
// setup_event would be a prototype method on this object and is covered in the article linked at the bottom of this page
},
destroy: function(someDiv){
// not sure if all browsers garbage-collect event handlers immediately when elements are removed so, just in case,
// call detach_event to detach event handlers assigned by this object
// detach_event would be a prototype method on this object and is covered in the article linked at the bottom of this page
// then find out if element supports innerHTML and if so nullify content. Else, adjust strategy as necessary
},
someMethod: function(args) {
// do something
// return something
}
}
/*
some private clone-able object that operates on DOM and/or implements some computation or business logic and can be referenced anywhere within the self executing anonymous function, including in the private objects and publicly exposed methods.
Actual implementation would be for DOM or no DOM (no need to combine both modes into one object)
*/
var somePrivateCloneableObject = function () {
this.initialize.apply(this, arguments);
}
somePrivateCloneableObject.prototype = {
initialize: function(){
// this is the constructor method (implemented on the prototype) for a clone-able object where
// arguments[0] ... arguments[n] contain state data or arguments[0] contains the parent
// DOM element from which state data can be copied, e.g. innerHTML, style, etc
// Actual implementation would be for DOM or no DOM (no need to combine both modes into one object)
},
// here is some method
someMethod: function(args){
// do something
// update state data (if not using DOM element as argument for cloning)
// return something
},
...
// here is a method for cloning the given instance
clone: function(){
// copy the state data of the existing instance of somePrivateCloneableObject (i.e. this.var1 ... this.var4)
// or get the actual DOM element somePrivateCloneableObject operates on
// and pass it to the constructor for this clone-able object
// Actual implementation would be for DOM or no DOM (no need to combine both modes into one object)
var objInstance = new somePrivateCloneableObject(args);
// return the cloned instance
return objInstance;
},
destroy: function(domCleanup){
// called with (true) if object operates on DOM element and not implementing just computation/business logic
// Actual implementation would be for DOM or no DOM (no need to combine both modes into one object)
if (domCleanup) {
// not sure if all browsers automatically remove event handlers immediately when elements are removed so, just in case,
// call detach_event to detach event handlers assigned by this object
// detach_event would be a prototype method on this object and is covered in the article linked at the bottom of this page
// then nullify the element's innerHTML to clean the DOM.
}
},
// initialize object state data
var1:0,
var2:"something",
var3:100,
var4:{}
...
}
// methods exposed publicly thru the returned object literal
var someExposedMethod1 = function(args) {
// this method may create new instances of the previously defined private objects (which can access the properties object) and call methods on each of them
};
var someExposedMethod2 = function(args) {
// this method may create new instances of the previously defined private objects (which can access the properties object) and call methods on each of them
};
// properties set/get methods accessible thru returned object
var set = function(propObject){
if (!propObject) return null;
// sets properties per the object literal propObject, accessed as window.someFeature.setProperties({propert1: value1, ... , propertyN: valueN} Assigns existing values for properties missing from propObject
properties.property1 = propObject.property1 || properties.property1;
properties.property2 = propObject.property2 || properties.property2;
properties.propertyN = propObject.propertyN || properties.propertyN;
}
var get = function(){
if (!arguments) return null;
// gets properties, accessed as window.someFeature.getProperties("property1", ..., "propertyN") which return the object literal containing properties and their values
var propObj = {};
for (n = 0; n < arguments.length; n++) {
// returns undefined if property doesn't exist or null if any of property names is not a string
if (typeof arguments[n] != 'string') return null;
propObj[arguments[n]] = properties[arguments[n]];
}
return propObj;
}
return propObj;
}
// the returned object exposes public methods including set/get properties
return {
"somePublicMethod1": someExposedMethod1,
"somePublicMethod2": someExposedMethod2,
"setProperties":set,
"getProperties":get
};
}());
Also Relevant:
Dynamic Event Handling In Object-Oriented Javascript: A Little Help From JQuery