Wednesday, January 13, 2010

DOM Manipulation in JavaScript, a Utility

In my latest addition to the q12 JavaScript library, I have added a simple function to construct a nested document structure. I grew tired of writing repetitive and difficult to follow code like this
var div = document.createElement('div');
div['id'] = 'outer-div';
var link = document.createElement('a');
link.href = 'http://blog.jeffscudder.com';
link.appendChild(document.createTextNode('my blog'));
div.appendChild(link);
Using the new tree function this would become:
var div = tree([
'div', {
id: 'outer-div'}, [
'a', {
href: 'http://blog.jeffscudder.com'}, [
'my blog']]]);
In the process of writing this utility function, I came across another small oddity in editing HTML using JavaScript related to the style attribute. Most HTML attributes are simple text properties, however when editing the CSS style of a node, you cannot set the full content of the style attribute.
// You cannot do this:
div.style = "color:red; font-weight:bold";
Instead, you set each sub item in the style attribute:
// This works:
div.style.color = 'red';
div.style['font-weight'] = 'bold';
The tree function that I've written handles this nested style structure as well. Here's an example:
['div', {
'id': 'foo',
'style': {'color': 'red', 'font-weight': 'bold'}},
'This works too!']
Now that you have an idea of how to use this utility, here's the code. Minus the comments it's only about twenty-one lines.
/**
* Creates a DOM tree from a simple list.
* The structure of the tree passed in is as follows:
* ['elementTag',
* {attribute1: value,
* attribute2: value,
* style: {property1: value,
* property2: value}},
* 'child text node',
* ['elementTag',
* {property: value},
* 'grandchild text node'],
* 'third node']
* The above will result in a DOM node which has three child nodes, the
* first and third will be text nodes because the values were strings.
* The second child node will be a DOM node as well.
*
* @param {Array} t The tree's structure as a collection of strings, lists,
* and simple objects. The structure is as follows
* ['elementTag', {attributes}, child, child, child, ...]
* @return {Element} Returns a new DOM element.
*/
function tree(t) {
// Create the node using the tag which is first in the list.
var domNode = document.createElement(t[0]);
// Add all HTML attributes to the node.
for (var key in t[1]) {
// The style attributes get special treatment.
if (key == 'style') {
for (var styleAttribute in t[1].style) {
domNode.style[styleAttribute] = t[1].style[styleAttribute];
}
} else {
domNode[key] = t[1][key];
}
}
// Iterate over all child nodes, converting them to either text or HTML
// nodes.
for (var index = 2, child; child = t[index]; index++) {
if (typeof(child) == 'string') {
domNode.appendChild(document.createTextNode(child));
} else {
// Buid recursively.
domNode.appendChild(tree(child));
}
}
return domNode;
}
What do you think, is there anything you would add?

On a realted note, if you're interested in a more powerful templating system, you might want to try Closure Templates which were recently open sourced.

1 comment:

Jeff Scudder said...

It's funny, these days so much of the comment activity actually happens over on twitter. Here's what people have said so far:

Ikai said: @jscud does it create radio buttons and checkboxes in IE that work?

I replied: @ikai great question, we should try it. How do they usually break?

Ikai: @jscud in IE if you dynamically create radio buttons and checkboxes, they don't do anything when you click. You need to hack it to work

Ikai: @jscud may have been fixed in ie8. Not sure off the top of my head. And yes, I spent at least a day on this nonsense #iemustdie

me: @ikai bummer, what kind of hack? I'm guessing innerHTML? Or is JavaScript right out.

Ikai: @jscud Check it http://cf-bill.blogspot.com/2006/03/another-ie-gotcha-dynamiclly-created.html

me: @ikai awesome! or would terrible be a better word... In any case thanks for the details.

Ikai: @jscud Hopefully it works for you. This was even harder for me to debug in an opensocial iframe with OSML (IE can't debug in iframes)

Then xirzec jumped into the mix. I'm not sure if he'd be OK with me posting his reply since his account is marked private. So here's a link instead.

I replied: @xirzec glad you like it. Yeah you can do some pretty impressive stuff pretty tersely in jQuery.

Whew. It's times like this that I really wish all of these social endpoints used the Salmon protocol.