Shorter DOMscript via cloning vs. generating elements
I just looked through some of my older scripts and other people’s code and realised that a lot of time we bloat our methods by repeating the same functionality.
This is especially true when it comes to generating a lot of HTML via the DOM.
We keep repeating the same document.createElement and setAttribute lines over and over. Wouldn’t it be more efficient to create those once when initialising our script and then cloning them?
Say for example we need to generate three links that are dependent on JavaScript (and we should generate these via JS).
A normal method to do this would be:
// 30 lines of code
createLinks:function(){
var newul,newli,newa;
newul=document.createElement('ul');
newli=document.createElement('li')
// sheep #1
newa=document.createElement('a');
newa.appendChild(document.createTextNode('baa#1'));
newa.href='#';
dolly.addEvent(newa,'click',dolly.baa1);
newa.onlick=function(){return false;} // Hello Safari
newli.appendChild(newa);
newul.appendChild(newli);
// sheep #2
newli=document.createElement('li')
newa=document.createElement('a');
newa.appendChild(document.createTextNode('baa#2'));
newa.href='#';
dolly.addEvent(newa,'click',dolly.baa2);
newa.onlick=function(){return false;} // Hello Safari
newli.appendChild(newa);
newul.appendChild(newli);
// sheep #3
newli=document.createElement('li')
newa=document.createElement('a');
newa.appendChild(document.createTextNode('baa#3'));
newa.href='#';
dolly.addEvent(newa,'click',dolly.baa3);
newa.onlick=function(){return false;} // Hello Safari
newli.appendChild(newa);
newul.appendChild(newli);
// herd them
document.body.appendChild(newul);
},
Now, if part of the initialisation method would be to generate a dummy link inside an LI, we could reuse by cloning it:
makeClones:function(){
dolly.li=document.createElement('li');
dolly.a=document.createElement('a');
dolly.a.appendChild(document.createTextNode('baa!'));
dolly.a.setAttribute('href','#');
dolly.li.appendChild(dolly.a);
},
By cloning this dummy li and referencing the link via firstChild and the link text via firstChild.firstChild we can save 9 lines of code, which still means a 4 line win if add the createClones method:
// 21 lines + 5 lines createClones
createFlock:function(){
var newli,newa,newul;
newul=document.createElement('ul');
// sheep #1
newli=dolly.li.cloneNode(true);
dolly.addEvent(newli.firstChild,'click',dolly.baa1);
newli.firstChild.onlick=function(){return false;} // Hello Safari
newli.firstChild.firstChild.nodeValue='baa#1';
newul.appendChild(newli);
// sheep #2
newli=dolly.li.cloneNode(true);
dolly.addEvent(newli.firstChild,'click',dolly.baa2);
newli.firstChild.onlick=function(){return false;} // Hello Safari
newli.firstChild.firstChild.nodeValue='baa#2';
newul.appendChild(newli);
// sheep #3
newli=dolly.li.cloneNode(true);
dolly.addEvent(newli.firstChild,'click',dolly.baa3);
newli.firstChild.onlick=function(){return false;} // Hello Safari
newli.firstChild.firstChild.nodeValue='baa#3';
newul.appendChild(newli);
// herd them
document.body.appendChild(newul);
},
Further reduction could be achieved by moving the event handling and text changing to another helper method:
dressClone:function(clone,func,name){
dolly.addEvent(clone.firstChild,'click',func);
clone.firstChild.onlick=function(){return false;} // Hello Safari
clone.firstChild.firstChild.nodeValue=name;
},
Which means the main method for generating the links will be 23 lines in total (including the helper methods):
// 15 lines + 5 lines + 3 lines
createWoolyFlock:function(){
var newli,newa,newul;
newul=document.createElement('ul');
// sheep #1
newli=dolly.li.cloneNode(true);
dolly.dressClone(newli,dolly.baa1,'baa#1');
newul.appendChild(newli);
// sheep #2
newli=dolly.li.cloneNode(true);
dolly.dressClone(newli,dolly.baa2,'baa#2');
newul.appendChild(newli);
// sheep #3
newli=dolly.li.cloneNode(true);
dolly.dressClone(newli,dolly.baa3,'baa#3');
newul.appendChild(newli);
// herd them
document.body.appendChild(newul);
},
The benefits are the following:
- We have one centralised spot for defining the links initially – if for example we needed to add a class at a later state we’d do it in one method instead of scanning the whole object of where to apply the changes
- Generating a new LI is now 3 lines of code instead of 7, which multiplies with the amount of links
- We don’t need to re-define a lot of variables or create new ones every time we add a link.
The “example script of cloning vs. generating”:http://icant.co.uk/sandbox/cloning-vs-generating-nodes/ works on MSIE 6, Firefox, Opera 8 and Safari.
Is there any problem or bad effect I am overlooking? How is the performance of cloning vs. generating anew every time we need a link?


April 26th, 2006 at 4:04 am
May 10th, 2006 at 7:02 am
January 6th, 2006 at 1:15 pm
This is silly. Creating functions for repeating code makes code shorter, cloneNode doesn’t. I’m not sure how you count the lines of code, but if you replace the 3 blocks of 8 lines in createLinks with a function call, you win about 3*8 – 3 – 8 = 13 lines, keeping the total below 20 lines.
January 6th, 2006 at 1:56 pm
I have to agree with Sjoerd:
function insertLi(targetUL,elName,textN,lnk) {
var lN = document.createElement('li');
var lil = document.createElement(elName);
lil.appendChild(document.createTextNode(textN));
lil.href = lnk;
lN.appendChild(lil); targetUL.appendChild(lN);
return lN;
}
Then everytime you’d just say:
insertLi(myUL,’a',’my link text’,'my href’)
and so on…
And you could possibly add different elements too.
January 6th, 2006 at 2:13 pm
I am aware of the option of doing that in a function, but the question still stands: Is the cloneNode() method not a better way as it just clones an existing object rather than creating a new one?
The benefit of a method instead of cloning is that you could set the event handlers in the method, too. Cloned nodes don’t clone event handlers it seems.
However, I am not a big fan of methods or functions with lots of parameters.
January 8th, 2006 at 4:08 pm
if you are creating a massive number of nodes of the same type, you should use cloneNode();
it’s not a matter of lines, it’s a matter of performance. and since javascript is run by the client, thinking of their performace is a nice thing to do.
January 9th, 2006 at 12:25 pm
That sparked an idea in my head. Thanks! I’ve used this idea to improve the performance of my own newEl() function:
http://verens.com/archives/2006/01/09/clonenode-caching/
January 12th, 2006 at 3:58 pm
This kind of thing is something I’ve been thinking about recently. I’ve always found creating elements and attaching attributes to be really tedious for all but the most simple cases. You just end up with lots setAttributes and appendChilds repeated over and over again so I’ve been investigating ways to generate node structures really easily and (althought its in a pretty raw state I’ve come up with this so far:
http://svn.vivabit.net/external/codedump/javascript/DomBuilder/
It’s straight out of my svn code dump with a small test page. See if you can work it out from the code. I think it’s really promising. Still I’ll post about it soon on vivabit.com/bollocks.
Just thought you might be interested. I do think this is an area worth alot of investigation though so nice one.
January 24th, 2006 at 4:11 pm
I too am “…not a big fan of methods or functions with lots of parameters”. A good design pattern for avoiding that particular code smell is Parameter Object. I’ll have to write something up about using that pattern in JS… when I get the time.
September 29th, 2006 at 4:12 pm
I have found this method (cloneNode) to be a life saver when dealing with multirow tables that need add/remove functionality. The columns can get quite complex with nested tables/complex layouts etc. and doing DOM construction in javascript is simply unmanageable.
The cloneNode functionality allows me to draw up a “template” row at design time using a WYSIWYG editor and my AddRow function simply clones that node at run-time.
It saved me hours and hours in coding and maintenance. Some of my other colleagues are still using the old method, and they battle all the time.