Monday, July 21, 2008

Object Creation in Ext JS

Ext JS has a useful object creation pattern. Most constructors can be passed a hash of configuration parameters. This can be used instead of the prototype creation pattern (not to be confused with Javascript's prototypical inheritance). Rather than create new objects that are copies of an existing object, you create objects based on a set of configuration data.

You want an example? OK. Today, I was creating context menus for items in a tree. There is a global pool of possible actions, and each node responds to a different subset of them. When the user right-clicks on a node, I need to

  1. Create a Menu instance
  2. Add all of the appropriate menu items to it
  3. Show the menu
I had hoped that I could create one menu item instance per possible action (20 or so), and then re-use them in different Menu instances.
var actionMenuMap = {
addChild: new Ext.menu.Item({text:"Add child", icon:"add.png"}),
delete: new Ext.menu.Item({text:"Delete", icon:"delete.png"}),
fireZeMissiles: new Ext.menu.Item({text:"Fire ze Missiles!", icon:"fire.png"})
}

var nodeActions = ["addChild", "delete"]; //in practice, this would come from the node itself

//dangerous nesting ahead!
new Menu({
items: nodeActions.map(function(a) {
return actionMenuMap[a];
})
}).showAt(event.getXY());

That didn't work - it seemed like I couldn't share a menu item instance between menu instances. I might be able to get it to work by removing menu items after the menu is dismissed, but I don't actually need to. I can simply hold on to the configuration information.

var actionMenuMap = {
addChild: {text:"Add child", icon:"add.png"},
delete: {text:"Delete", icon:"delete.png"},
fireZeMissiles: {text:"Fire ze Missiles!", icon:"fire.png"}
}

var nodeActions = ["addChild", "delete"]; //in practice, this would come from the node itself

//now we're cooking with functional programming!
new Menu({
items: nodeActions.map(function(a) {
return new Ext.menu.Item(actionMenuMap[a]);
})
}).showAt(event.getXY());

In case you are not familiar, map is a function that is present in every functional language and many dynamic languages. It does not exist in Javascript natively, but is added by Prototype, jQuery, dojo, Mochikit, and probably every other Javascript framework. Here's a sample implementation for reference:

Array.prototype.map = function(f) {
if (typeof(f) !== "function") {
throw new Error("map takes a function");
}

var result = new Array(this.length);
for (var i = 0; i < this.length; ++i) {
result[i] = f(this[i]);
}
return result;
}

What could possibly make this better? In addition to taking a hash, allow the constructor to take a function. That function manipulates the object after the rest of the construction runs, allowing you to add children or manipulate settings or calculate values. This would be pure icing, of course. Factory functions also feel a lot more lightweight to me than factory objects. See also Rails' version of the K combinator.

Saturday, July 12, 2008

Please encode the patterns that you see

As my software career has advanced, I've steadily become more cynical and bitter. This isn't good, and it's something that I'm trying to work on. It's hard to go a day without having one of those "you've got to be kidding me" moments. "I can't believe they did it like that." "What were they thinking?" "Hasn't anybody ever tried to do this before?" "There has to be an easier way."

Case in point: I recently read that SWFObject was THE way to embed swf files in your HTML. Fine, I though. I'll give it a try. Within minutes, I ran into something simple that it couldn't do. When you call the function that embeds the swf, it might not actually do the operation immediately - it might wait until the page initialization has progressed beyond a certain point, and then inject the code. I needed to run some code AFTER the swf had been embedded in the page (for sure). There's no way that I could see to do this with the stock SWFobject. Despite the fact that SWFObject inherently depends on event dispatch to work, it doesn't dispatch enough of its own events to be useful to me.

As I started to look at the code to see if I could work it in, I stumbled across the following snippet:

var att = {};
if (attObj && typeof attObj === OBJECT) {
for (var i in attObj) {
if (attObj[i] != Object.prototype[i]) { // Filter out prototype additions from other potential libraries
att[i] = attObj[i];
}
}
}

"Wow!" I thought. "That looks like it would be useful to lots of people." As it turns out, it is very useful - even to the SWFObject developers, who used the same pattern twice more in the same 58-line function:

var par = {}; 
if (parObj && typeof parObj === OBJECT) {
for (var j in parObj) {
if (parObj[j] != Object.prototype[j]) { // Filter out prototype additions from other potential libraries
par[j] = parObj[j];
}
}
}
if (flashvarsObj && typeof flashvarsObj === OBJECT) {
for (var k in flashvarsObj) {
if (flashvarsObj[k] != Object.prototype[k]) { // Filter out prototype additions from other potential libraries
if (typeof par.flashvars != UNDEF) {
par.flashvars += "&" + k + "=" + flashvarsObj[k];
}
else {
par.flashvars = k + "=" + flashvarsObj[k];
}
}
}
}
That general pattern appears at least 9 times in the code. It's a good thing they put it in a reusable functio... oh wait, they didn't. Instead, they dutifully pasted the code (comment and all) 8 times.

OK, something's going on. Maybe I've become a much better programmer in the past year; maybe everybody else is getting worse; maybe I'm off my rocker; maybe there's some strange performance or compatibility tweak that prevented them from doing what seems plainly obvious to me. Removing the obvious duplication would have simplified their code, making it more readable (and possibly obviating the need for the accompanying comment).

It's the little things like this that make me wonder if we're actually moving the state-of-the-art forward at all. If we can't see (or don't care about) the duplication at the micro scale, how will we re-use software at any broader level?