From:
DomHelper - Create Elements using DOM, HTML fragments and Templates
Update: DomHelper vs. Scriptaculous Builder vs. MochiKit DOM
I had some time to fool around tonight so I created this page with live head to head benchmarks against prototype+Scriptaculous and MochiKit's excellent DOM library. Check it out and see for yourself. The results are unbelievable.
If you are doing JavaScript development, then at some point or another you have probably had to use DOM to create elements. Like everything else with DOM, creating elements can be extremely verbose, leading to bloated code and spending too much time doing something that should be simple.
Creating elements using DOM can also have poor performance, especially in Internet Explorer. To maximize performance and maintain standards, any DOM creation classes should support both DOM and HTML fragments. HTML fragments are extremely fast in Internet Explorer, sometimes as much as 300% faster.
The DomHelper class in the 0.32.2 release provides a layer of abstraction from DOM and transparently supports creating elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates from your DOM building code.
Simple Examples
To save me from typing it out every time, the examples below are going to assume that a shorthand variable dh has being defined for YAHOO.ext.DomHelper.
var dh = YAHOO.ext.DomHelper;
Let's look at a simple example:
var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
Since DomHelper transparently uses DOM or HTML fragments, it manages appending the elements as well. This is a nice feature since it will save you from having to manually append everything after creating it.
Let's expand the example:
var list = dh.append('my-div', {
tag: 'ul', cls: 'my-list', children: [
{tag: 'li', id: 'item0', html: 'List Item 0'},
{tag: 'li', id: 'item1', html: 'List Item 1'},
{tag: 'li', id: 'item2', html: 'List Item 2'},
{tag: 'li', id: 'item3', html: 'List Item 3'},
{tag: 'li', id: 'item4', html: 'List Item 4'}
]
});
All attributes on the passed object "o" are assumed to be element attributes, except for 4 special attributes:
- tag - The tag name of the element
- children - An array of the same kind of element definition objects to be created and appended. These can be nested as deep as you want.
- cls - The class attribute of the element. "class" is a reserved word and className is too long. This will end up being either the "class" attribute on a HTML fragment or className for a DOM node, depending on whether DomHelper is using fragments or DOM.
- html - The innerHTML for the element.
Here's a list of the insertion methods supported:
- append(<HTMLElement/String> el, <Object> o) - Creates new DOM element(s) defined by o and appends them to el
- insertBefore(<HTMLElement/String> el, <Object> o) - Creates new DOM element(s) defined by o and inserts them before el
- insertAfter(<HTMLElement/String> el, <Object> o) - Creates new DOM element(s) defined by o and inserts them after el
- overwrite(<HTMLElement/String> el, <Object> o) - Creates new DOM element(s) defined by o and overwrites the contents of el with them
- createTemplate(<Object> o) - Creates a template from the DOM element(s) defined by o (See below)
- insertHtml(<String> where, <HTMLElement> el, <String> html) - Inserts fragments into the DOM. It supports four insertion points related to el (which are named after the same points in IE's insertAdjacentHTML): beforeBegin, afterBegin, beforeEnd, afterEnd.
OK, I've seen this before. What's so special about this implementation?
Under the hood when you are creating your elements, DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead of DOM can significantly boost performance. If you don't like performance boosts and you want to force it to use DOM, you can:
YAHOO.ext.DomHelper.useDom = true;
The real power is in the built-in templating. The "o" parameter for createTemplate() has the same syntax as all the other methods only it doesn't create or append any elements. Instead it returns a Template object which can be used over and over to insert new elements. Let's revisit the example above, and use a template this time:
var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
for(var i = 0; i < 5, i++){
tpl.append(list, [i]);
}
Template objects support the same insertion methods of DomHelper. The 2nd parameter to the insertion methods is either an array (for numeric template parameters, like the above example) or an object (for named parameters).
The Template object can also be used on it's own:
var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
var tpl = new YAHOO.ext.DomHelper.Template(html);
tpl.append('blog-roll', ['link1', 'http://www.jackslocum.com/', "Jack's Site"]);
tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin's Site"]);
Here's the same example using named parameters:
var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
var tpl = new YAHOO.ext.DomHelper.Template(html);
tpl.append('blog-roll', {
id: 'link1',
url: 'http://www.jackslocum.com/',
text: "Jack's Site"
});
tpl.append('blog-roll', {
id: 'link2',
url: 'http://www.dustindiaz.com/',
text: "Dustin's Site"
});
Compiling Templates
It's probably not shocking that the templates are applied using regular expressions. The performance is great, but if you are adding a bunch of DOM elements using the same template, you can increase performance even further by "compiling" the template. The way "compile()" works is the template is parsed and broken up at the different variable points and a dynamic function is created and eval'ed. The generated function performs string concatenation of these parts and the passed variables instead of using regular expressions.
var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
var tpl = new YAHOO.ext.DomHelper.Template(html);
tpl.compile();
Benchmarks
I did some benchmarking while creating these classes to see how different things would perform. I executed the code below in a loop 1000 times, which creates and appends 3000 new elements. I also had a non-template version which built the exact same thing.
var spec = {
tag: 'div',
style: 'width:100%;border:1px solid blue;',
cls: 'wtf',
children: [{
tag: 'a',
href: '{url}',
children: [{
tag: 'span',
html: '{text}'
}]
}]
};
var template = YAHOO.ext.DomHelper.createTemplate(spec);
Below are the average times by browser. Notice the HUGE difference in IE6, 1.35 to .300. The DOM implemenation in IE6 must be written in VB with that performance
. Opera 9's performance creating nodes is amazing as usual.
Insertion Method |
IE7 beta 2 |
IE6 |
FF 1.5 |
Opera 9 |
DOM |
.730 |
1.35 |
.420 |
.280 |
HTML Fragments |
.360 |
.380 |
.400 |
.260 |
Template |
.320 |
.335 |
.385 |
.220 |
Compiled Template |
.295 |
.300 |
.350 |
.210 |
The overall performance improvement of using DomHelper alone was good enough to merit the work. I can't wait to replace the Grid component's row building code with compiled templates.