Update: DomHelper vs. Scriptaculous Builder vs. MochiKit DOM
Jack Slocum: 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. (October 6, 2006) Translation:in Chinese(中文)
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 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.
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 Ext.DomHelper.
var dh = Ext.DomHelper;
Let's look at a simple example:
// The context element 'my-div' can either be the id or the actual node 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:
Here's a list of the insertion methods supported:
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:
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 Ext.DomHelper.createTemplate(html); tpl.append('blog-roll', ['link1', 'http://www.extjs.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 Ext.DomHelper.createTemplate(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" });
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 Ext.DomHelper.createTemplate(html); tpl.compile(); //... use template like normal
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 = 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 |