Saturday, December 22, 2007

Object Oriented Programming with Javascript

What?
As Joseph Smarr at Plaxo said in his YUI theatre talk, Javascript is a malleable language and can be bent to do what you want it to. However, the best thing to do is simply let it be what it wants to be.

Object Oriented Programming has proved an accurate pattern, as far as it makes us architect our abstract information structures in humanly comprehendible manners. As javascript has arguably become the most used programming language and declared by many as the next big Language it becomes increasingly important for us to learn how to use it properly (which many of us do not - as Douglas Crockford [more on him later] points out, it is also the worlds most misunderstood language)

Why?
Hacks serve a purpose - they solve problems without project overhead - but any code that is to be built upon and maintained must be understandable. It would seem that much of software's strength comes from its hierarchical structurability (The work you did yesterday I can use as a foundation for my work today), but this requires intuitive usability (APIs).

How?
In javascript, each class has a prototype-object that all other objects of the class refer to. When an object wants to use a method this.doSomething(), it calls the prototype.doSomething() function.

To make this work, we have to get around two novelties: classes are declared as functions, and the methods of the class are declared outside of the class body.

/**
* Our MissManners application
*/
var MissManners = {
nextUniqueId : 0
};

/**
* A miss manners guest
* @constructor
* @param {Object} params The parameters object. All other parameters are named properties of this object
* @param {String} name The guest name
* @param {String} gender The guest gender. 'male' or 'female'
* @param {String} interest The guest interest. A single string. You should give guests interests so that some match up - this is what the rule engine bases their placemenet on.
* @param {String} color Hexadecimal value of the color to represent the guest with.
*/
MissManners.Guest = function(params) {
this.name = params.name || this.Default.name;
this.gender = params.gender ? params.gender.toLowerCase() : this.Default.gender;
this.interest = params.interest ? params.interest.toLowerCase() : this.Default.interest;
this.color = params.color || this.Default.color;
this.id = 'MMGuest-' + MissManners.nextUniqueId++;
}

/**
* Generate and Html snippet to represent the guest.
*/
MissManners.Guest.prototype.toHtml = function() {
var result = '';
result += '<div class="missMannersGuest" id="'+this.color+'" style="background-color:'+this.color+'">';
result += '<ul>';
result += '<li>Name: '+this.name;
result += '<li>Gender: '+this.gender;
result += '<li>Interest: '+this.interest;
result += '</ul>';
result += '</div>';
}

/**
* The default values for a guest.
*/
MissManners.Guest.prototype.Default = {
name : 'Joan Doe',
gender : 'female',
interest : 'weather',
color : '#def'
}
We first initialize our MissManners object - this will contain our classes. Then we declare the Guest class, and at the same time define its initializer. This initializer defaults to return the this object.

Every function you create is given a prototype property, so that you can reference MissManners.Guest.prototype and build on it with the toHtml function.

We then go ahead and use our class:
function createGuest(divId) {
var guest = new MissManners.Guest({
name : 'Marcus Westin',
gender : 'Male',
interest : 'Flow',
color : '#cde'
});
document.getElementById(divId).innerHTML = guest.toHtml();
}
At this point, when we call guest.toHtml() it first checks to see if the newly created guest object has a function called toHtml(). If not, it then goes to its prototype, MissManners.Guest.prototype and checks if it has something called toHtml. If it does, it calls that function, and the this variable in toHtml gets bound to guest, such that this.name in toHtml is the same as guest.name in createGuest. If that prototype does not have the property or function, javascript goes to its prototype, and so on until it bottoms out in Object.prototype.

To view this code in action, as well as view the code in its entirety, see the demo. This entry builds on excerpt code of the Miss Manners demo app I wrote yesterday.

No comments: