Zach’s ugly mug (his face)

Zach Leatherman

DOMDom, easy DOM Element Creation

07 Jul 2007 Read in about 11 minutes

Generally when approaching a complex problem involving web programming in JavaScript, the first question I ask myself is ‘What would Jesus do?’ As much as I am entertained by a mental picture of Mr. Jesus himself sitting on his Jesux Distro appending children (and parents) into his DOM, I am instead distracted by an intense “passionate” hatred for Mel Gibson swelling in my chest. It’s actually quite distracting to the problem I am having, but I calm myself by punching my 4 foot tall inflatable Mad Max and wonder how much time I waste doing this. I figure it happens about twice an hour. (Digression)

The DOM. Arch-nemesis of web developers attempting to support the ultimately tiny (I think it’s down to about 5 or 8% now?) sliver of browser market share that Internet Explorer currently entails. Let’s make it easier on ourselves and make a little package to do it for us. Run a function, pass in an argument with a description of the DOM node(s) we wish to create, and have the package auto-correct any bugs we would have encountered during manual creation, and maybe even have it inserted or appended for us.

But wait, why are we doing this when there are literally 80 billion other DOM element creation classes already out there? It’s all about syntax. The existing packages are incredibly verbose, focusing too much on a complex object structure to describe the nodes, in some cases even having separate objects for attributes inside a single node. Why not use the syntax we’ve already come to love in the various DOM query libraries that are available? Why not use DOMDom? Let’s see a few examples:

Single Node String

'div'

<div></div>

Single Node String with ID and Classes

'div#id1.class1.class2'

<div id="id1" class="class1 class2"></div>

Single Node String for a Form Element

'input[name="myTextBox",type="text",maxlength="5"]'

<input name="myTextBox" maxlength="5" type="text"/>

Single Node String with Style Syntax

'div{height=80px,color=#f90,border=1px solid #000}'

<div style="border: 1px solid #000; height: 80px; color: #f90"></div>

Complex Single Node String with ID, multiple classes, Style, and Namespaced Attribute)

'div#id1.class1.class2[style="width:60px;color:#f90",@class="class4",@att="true",@namespace:att="false"]'

<div id="id1" att="true" namespace:att="false" class="class1 class2 class4" style="width: 60px; color: #f90;"></div>

Multiple Node String: Linear (Parents with one Child)

'div span div'

<div>
     <span>
          <div></div>
     </span>
</div>

Multiple Node String: Non-Linear (Parent with more than one Child)

{ 'div': [ 'span', 'span' ] }

<div>
     <span></span>
     <span></span>
</div>

How Does It Work?

By default, it’s set up to do HTML Fragments (innerHTML) because they are much speedier than the manual DOM element creation (createElement). But if you desire, you can toggle a boolean in the code and it will switch back to DOM element creation. When in DOM element creation mode, it will account for the following browser bugs:

  • (IE6) Standardized for attribute representation (pointer to htmlFor)
  • (IE6) Standardized case for accesskey, usemap, maxlength, and frameborder attributes.
  • (IE6) Standardized checked attribute for radio and checkboxes.
  • (IE6) Special consideration for dynamic handling of name and type attributes (on form elements).
  • (Firefox) Works with whitespace that is treated as a node.

Syntax

You should already be able to tell how to create a node from the examples above. Here are some more things you might not have guessed:

Creating a node with an id

'div#myId'

Creating a node with CSS classes

'div.class1.class2'

Creating two nodes at the same level

[ 'div', 'div' ]

Creating a node with two children

{ 'div': [ 'div', 'div' ] }

You can mix the {} and [] syntax wherever you like, but if you want a node to have non-linear children, you have to use the {} object notation.

Creating a text node (start the node declaration with a #, you can change this to another non-conflicting character in the code if you like)

'#Any Text Here'

Creating a node with attributes (the @ is optional)

'div[class="class3",style="width:60px;color:#f90",@att="true",@namespace:att="false"]'

Creating a node with a Style Shortcut (mixing with a style attribute is handled properly)

'div{color=#f90,border=1px solid #000}[style="height:80px;background:#fff"]'

A few notes on attributes. Quotes are required on attributes (single or double but be consistent), but are not required in the style shortcut declaration. Quotes are not allowed to be nested inside of attributes (a single quote cannot be inside of a double quote and vice versa).

And of course, all of the above can be mixed together

'div#myId.class1.class2{color=#f90}[customAttr="true",@customAttr2="false"] div#child1 div#child2'

Usage

Appending at the end of a parent’s children:

DOMDom.append( 'div', yourParentNode );

Replacing the children of a parent:

DOMDom.replace( 'div', yourParentNode );

**Unshifting **(inserting at the beginning of a parent’s children):

DOMDom.unshift( 'div', yourParentNode );

Inserting before a certain integer index of a parent’s children:

// must have at least 3 children, the index is 0 based, if index is null with unshift by default
DOMDom.insert( 'div', yourParentNode, 2 );

Templates

Use <$var> to indicate a variable, in this example <$test>

// "Compile" the template
var str = DOMDom.compile( { 'div.test span': '#Test <$test>' } );

// Use your template in some context, notice the test variable being set.
for( var j = 0; j < 1000; j++ )
{
    // knows we're using a compiled template since we're passing in variables as a third argument.
    DOMDom.append( str, d, { test: j } );
}

Benchmarks

(If you have Firebug open, make sure it's not on the HTML tab, this will slow down the benchmark significantly)

Most of my work here has been inspired by the DomQuery and DomHelper classes written by JavaScript rock star Jack Slocum (the guy's initials are J.S. for God's sake), so I modeled my benchmark after his benchmark hosted on his website to test the DomHelper class. I'm running the same nodes he's testing on his website, so the results should be comparable. You can test my benchmark for DOMDom here. Here are some results, reporting the average of 3 results with the format of an uncompiled element first and the compiled template in square brackets.

DOMDom Results

Internet Explorer 6: 666 ms [328 ms]
Firefox 2.0.0.4: 1880 ms [666 ms]
Safari 3.0.2 [Windows]: 546 ms [151 ms]
Opera 9.21: 343 ms [140 ms]

Comparative numbers from Jack Slocum's DomHelper

Internet Explorer 6: 2458 ms [677 ms]
Firefox 2.0.0.4: 672 ms [458 ms]
Safari 3.0.2 [Windows]: 291 ms [119 ms]
Opera 9.21: 370 ms [166 ms]

The thing to take away from this is the question of why Satan is haunting my benchmarks? Two 666 averages? Anyway, DOMDom is quite a bit faster in the most popular browser, Internet Explorer, although I haven't tested it on IE7 yet. In Firefox, the opposite is true, with DomHelper taking the lead. Opera is comparable and Safari is faster in DomHelper as well. You can run your own tests using the links above.

Dependencies

This library was built to work with Yahoo User Interface (YUI), but could be trivially ported to another library by changing the function dependencies listed in the ADAPTER variable in the code.

var ADAPTER = {
  setStyle: YAHOO.util.Dom.setStyle,
  addClass: YAHOO.util.Dom.addClass,
  isString: YAHOO.lang.isString,
  isArray: YAHOO.lang.isArray,
  isNumber: YAHOO.lang.isNumber,
  isObject: YAHOO.lang.isObject,
  get: YAHOO.util.Dom.get
}; // to port, change these references

If you're still reading this encyclopedia, here are some links:

-dom suffix denoting condition or state, as in freedom, wisdom, or DOMDom

Update: changed the variable syntax to allow variables inside of nodes (not just text).

9 Comments

➡ Load Disqus to Leave a Comment ⬅

Stefan Schuster

12 Jul 2007 at 02:40PM

Great Work!

DOMDom Dojo 0.9 Port if anybody is interested:

var ADAPTER = {
setStyle: dojo.style,
addClass: dojo.addClass,
isString: dojo.isString,
isArray: dojo.isArray,
isNumber: isFinite,
isObject: dojo.isObject,
get: dojo.byId
};

Hi Zach,

As noted on Ajaxian, DomHelper has a less verbose syntax available if you don't like the default.

Ext.DomHelper.append( myDiv, {
style: 'width:100%;border:1px solid blue;',
cls: 'testClass',
cn: {tag: 'a', href: 'http://www.google.com/', cn: {tag: 'span', html: 'Google' }}
});

Zach,

using selectors for DOM creation is really slick!

We were discussing a port to jQuery and here are some ideas. For attribute creation I'd stick to correct CSS selector syntax:

'input[name="myTextBox"][type="text"][maxlength="5"]'

instead of

'input[name="myTextBox",type="text",maxlength="5"]'

And for inline styles why not using (seems to be more consistent to me):

'div[style="height: 80px; color: #f90; border: 1px solid #000;"]'

instead of

'div{height=80px,color=#f90,border=1px solid #000}' ?

Pure CSS selectors could also be used for creating child elements:

div > span + span

instead of

{ 'div': [ 'span', 'span' ] }

Just a few ideas... The huge advantage I see with that is that one literally wouldn't have to remember anything special using the DOM creator if already familiar with CSS selectors. And you would have to write less documentation :-)

By the way, one of your examples is not very well chosen, the one with 'div span div'. A span element may not contain block level elements, thus the resulting DOM would not look like in the given example, but like the following due to browser tag soup parsers closing the span before the div starts:

<div>
<span></span>
<div></div>
</div>

Great work!

Zach Leatherman

14 Jul 2007 at 02:51PM

@Stefan: thanks for the Dojo Port!

@Jack: Yeah, that syntax is less verbose, but I guess the intention of this class was to provide a syntax that minimized the amount of proprietary keywords in it. Less documentation, as Klaus says.

@Klaus: Good suggestions! I will definitely take those to heart. I suppose the attribute list separated by comments is a leftover from my XPath days. I see the W3 spec definitely agrees with you.

The style attribute is supported both ways you have listed there, and you can mix and match if you so choose (but I doubt anyone would)

With the child selectors, how you would you have siblings that would each have children? I don't see anything in the CSS selector spec on nesting or parenthetical grouping.

div > (span a) + (span a)

<div><span><a/></span><span><a/></span></div>

and not

div > span a + span a

<div><span><a/><span><a/></span></span></div>

As a note to anyone reading, div span div does output as predicted in both DOM and HTML Fragment modes:

<div><span><div></div></span></div>

Nonetheless it is invalid HTML. Scripting on top of an invalid DOM won't make your life easier and I don't see the point in promoting bad practice!

That's the standards advocate in me speaking ;-)

DomHelper has special code for table broken implementation on IE. IS that covered

Zach Leatherman

25 Jul 2007 at 10:58PM

Hey vikas:

Unfortunately, I have not yet built that into DOMDom, but will certainly tackle it when I also complete some of the changes Klaus suggested. Look for the next release soon!

Thanks, Zach

Surprised noone else ran into this. I wanted to pass a sring as the parent to DOMDom.append when the template is compiled but compiledExecute does not handle that. So on line 381 I added this block of code taken from elementExecute():


if( parent )
{
if( ADAPTER.isString( parent ) ) parent = ADAPTER.get( parent );
if( mode == MODE_REPLACE )
parent.innerHTML = '';
}

And then I could pass the parent as a string and the world was whole again. :)