Using the XML DOM Without Writing 15,0000 Lines of Code
I’m a pretty big fan of using the XML DOM rather than innerHTML, simply because it seems to me to be better programming style. I hate hate hate, however, that it takes 15 lines of code to create one node. Today, I came up with an idea to solve the problem. I created a wrapper function for document.createElement() that takes either a string or an object literal as an argument. Then, based on the data in this argument, I create the needed element(s). Here’s a simple example that creates a text node:
var node = createElements("Hello World!");
document.body.appendChild(node);
Ok, so the above example is pretty useless, but what if you wanted to make that text into a link. the createElements() function allows us to pass an object literal with four items: tag, attributes, style, and children. “Tag” is, of course, the type of HTMLElement you want to create. “Attributes” is another object literal containing the attributes for the html tag (src, href, etc…). “Style” is an object literal containing the style attributes you want applied to the element, and last but certainly not least, “children” allows you to nest elements. It is an array of either strings or object literals, just like the ones you would pass into this function. Each of these children will be recursively created and appended to the main element. Here’s an example that creates a link to google:
var node = createElements({
tag: "a",
attributes: {
href: "http://www.google.com"
},
style {
textDecoration: "none"
},
children: [ "Click Here!" ]
});
document.body.appendChild(node);
And here’s a more complex example that really shows off the nesting power of this javascript function:
var node = createElements({
tag: "div",
attributes: {
id: "google_link"
},
children: [
{
tag: "a",
attributes: {
href: "http://www.google.com"
},
style: {
textDecoration: "none",
fontWeight: "bold"
},
children: [
{
tag: "img",
attributes: {
src: "http://www.google.com/intl/en_ALL/images/logo.gif",
alt: "Google!",
title: "Google!",
border: "0"
},
style: {
width: "200px",
height: "200px"
}
},
{
tag: "br"
},
"Click here to go to Google!"
]
}
]
});
document.body.appendChild(node);
The above example creates the following code:
<div id="google_link"> <a style="text-decoration: none; font-weight: bold;" href="http://www.google.com"> <img style="width: 200px; height: 200px;" title="Google!" alt="Google!" src="http://www.google.com/intl/en_ALL/images/logo.gif" border="0"> <br /> Click here to go to Google! </a> </div>
As you can see, it’s quite a bit easier to use than typing mynode.setAttribute 20 times. Here’s the source code:
function createElements( args ) {
var el;
if( typeof(args) == "string" ) {
el = document.createTextNode(args);
} else if ( typeof(args) == "object" ) {
el = document.createElement( args.tag );
if ( args.attributes ) {
for ( i in args.attributes ) {
el.setAttribute(i, args.attributes[i]);
}
}
if ( args.style ) {
for ( i in args.style ) {
el.style[i] = args.style[i];
}
}
if ( args.children ) {
for ( var i = 0; i < args.children.length; i++ ) {
el.appendChild( createElements( args.children[i] ) );
}
}
}
return el;
}
Comments
Comment from NelsoN
Time: February 23, 2008, 10:50 pm
Due to several bugs in IE, your (otherwise very nice method) will fail on a bunch of the attributes you pass it..
http://webbugtrack.blogspot.com/2007/08/bug-242-setattribute-doesnt-always-work.html
E.g. if you try to create form elements (with a name) it will fail… setting the style will fail,… setting any event handlers will fail etc.
The good news is that only IE will fail. All other browsers are based on standards. :-)
Comment from spudly
Time: February 24, 2008, 8:51 am
Wow thanks for the info. I’ll have to give this another look, and I’ll post an update soon.
Comment from Jim
Time: February 29, 2008, 5:20 pm
I really like this. I don’t think it saves a lot of typing or lines of code, but it is MUCH more semantic (and I love anything that improves semantics).
As for IE, well, you just have to know to use all the proper attribute names that IE expects for when you execute in IE, and all the stuff Fx expects when in Fx, and all the stuff Opera expects when in Opera–you get the point. It has nothing to do with your code and has everything to do with the problem we have all faced in web dev–browser compatibility.
Well done,
Jim
Comment from spudly
Time: February 29, 2008, 6:49 pm
I’m actually working on a new version that works a little better with IE. It uses a proprietary IE technique for creating objects. I’m having a little trouble with table elements but other than that it is almost ready. Before I post it I’ll make sure to test it with all sorts of element types in multiple browsers. I think I’m pretty close tho…
Pingback from createElements() una función para ahorrarte mucho código | aNieto2K
Time: March 3, 2008, 1:17 am
[...] especial empeño en mejorar este punto ya que es algo que cada vez más usamos en nuestros scripts. createElements() es una función que facilita esta tarea sin necesidad de depender de un framework, 30 líneas que [...]
Comment from krypton
Time: March 3, 2008, 3:08 am
Good work! Still share the opinion of Jim, it seems to me very clean and organized at the level of semantics, but not write less code. But still saves work. It continues the good work.
Comment from Jim
Time: March 3, 2008, 7:53 am
IRT Tables, just remember that IE and Fx vary between each other, and with themselves between raw HTML and DOM building, when it comes to the requirements for thead, tbody, and tfoot elements. It is ALWAYS safe, however, to ENSURE all TRs are within one of those blocks. So while this adds complication to your simple logic, you could look to see if the TR currently being built is inside one of them or not and put it there if it isn’t (I would default them to tbody myself).
Comment from Serkan Karaarslan
Time: May 13, 2008, 2:40 pm
i’m really impressed, nice code excerpt and usefull.
Comment from montana
Time: June 22, 2008, 3:59 am
bummer all that awesomeness and ur using inline stlyes and ? tags?… just omit that part and trim up a little more and this could be a pretty rad approach (yes, I’m still using the word rad)
generic class functions help this:
// Simple CSS Functions
ClassFX = {
setClass:function(loElement, lsClass) { //hard set to single class
if (!loElement){return;}
loElement.className = lsClass;
return(true);
},
addClass:function(loElement, lsClass) {
if (!loElement){return;}
if(!this.hasClass(loElement, lsClass)) {
loElement.className += ‘ ‘ + lsClass;
}
},
hasClass:function(loElement, lsClass) {
if (!loElement){return;}
var loRE = new RegExp(’\\b’+lsClass+’\\b’);
return(loRE.test(loElement.className));
},
flipClass:function(loElement, lsClass) { //toggle…
if (!loElement){return;}
if(this.hasClass(loElement, lsClass)) {
this.removeClass(loElement,lsClass);
} else {
this.addClass(loElement,lsClass);
}
},
removeClass:function(loElement, lsClass) {
var loRE = new RegExp(’\\b’+lsClass+’\\b’, ‘g’);
if (!loElement){return(true);}
loElement.className = loElement.className.replace(loRE, ”);
return(true);
}
}
//*****************************************
//Just rewriting the ugly html above following some kind of a RAD pattern you can actually get some quite nice, nuggetized looking code
//of course no one is sick enough to name an anchor variable ‘a’
//I would strangle mutilate and behead anyone on my team doing so, then they would rewrite the code.
var oDiv = document.createElement(”div”);
var oAnchor = document.createElement(”a”);
var oImg = document.createElement(”img”);
var oText = document.createTextNode(”Click here to go to Google!”);
var lsImgMeta = ‘Google!’
var lsLinkURI = “http://www.google.com”
var liLinkID = “google_link”;
var lsIMGURI = “http://www.google.com/intl/en_ALL/images/logo.gif”
oAnchor.setAttribute(”id”, liLinkID);
oAnchor.setAttribute(”href”, lsLinkURI);
//ClassFX.addClass(oAnchor, ‘linkClass’); //should actually inherit from the diiv container so omit this…
oImg.setAttribute(”src”, lsIMGURI);
oImg.setAttribute(”alt”, lsImgMeta);
oImg.setAttribute(”title”, lsImgMeta);
ClassFX.addClass(oImg, ‘imgClass’); //in case there is specific formatting for image…
oAnchor.appendChild(oImg);
oAnchor.appendChild(oText);
oDiv.appendChild(oAnchor);
ClassFX.addClass(oDiv,’navigationContainerClass’); //set all default classes for img’s and anchors here instead of even using classes on individual DOM Elements
Comment from KRWETATNT
Time: June 22, 2008, 1:13 pm
I will try it .
thanks
Comment from Fredrik
Time: July 13, 2008, 12:20 pm
Agreed, something like you envisioned is almost a must to be able to work with the DOM in a sane way in JS.
Similar constructs can be found here also:
http://www.mochikit.com/doc/html/MochiKit/DOM.html#fn-createdom
http://www.prototypejs.org/api/element
Comment from greg
Time: September 11, 2008, 1:14 pm
getting “htmlfile: type mismatch” on line ” el.appendChild( createElements( args.children[i] ) ); ” in IE 6 and 7… could be that those DOM methods aren’t exposed to docs with my DTD. who knows.
just FYI :(
Comment from spudly
Time: September 11, 2008, 6:35 pm
greg - what code are you using to call the function? I imagine you’re probably running into a problem with readonly properties. For example, in IE the ‘type’ attribute is readonly for input elements. As of now this function has some definite browser compatibility issues (purely IE problems to the best of my knowledge). There are some IE proprietary workarounds that I plan to explore some time in the future, and this function will have to be rewritten to include those changes. I just haven’t gotten around to it yet.
Comment from greg
Time: October 28, 2008, 10:19 am
don’t recall what code i was using anymore :) just came back to tell you I ran into another problem with IE (devil take it) and creating radio buttons on the fly. Since the name attribute cannot be set on the fly the radio buttons were unselectable. found that this worked for me:
try{
tagString = ”;
el = document.createElement(tagString);
}catch(e){
el = document.createElement(args.tag);
}
you could likely do something similar with the type attribute problem as well.
Comment from dave
Time: November 19, 2008, 5:05 pm
I was first and foremost shocked to learn that people actually still use innerHTML. And then more shocked to see you using inline style. Abstracting the dynamic creation of DOM Elements is a very good goal, but sticking with attributes for style doesn’t really help much.
Pingback from links for 2008-12-08
Time: December 8, 2008, 7:03 pm
[...] spudly.shuoink.com » Using the XML DOM Without Writing 15,0000 Lines of Code Tags: Links [...]
Comment from dr3
Time: March 13, 2009, 8:47 pm
thank you nice and usfull info
thank you
Comment from spudly
Time: February 21, 2008, 8:02 am
Just a side note - If you were to write the above example without my method, you would have to write the code below, which is horribly ugly:
var div = document.createElement("div"); node.setAttribute("id", "google_link"); var a = document.createElement("a"); a.setAttribute("href", "http://www.google.com"); a.style.textDecoration = "none"; a.style.fontWeight = "bold"; var img = document.createElement("img"); img.setAttribute("src", "http://www.google.com/intl/en_ALL/images/logo.gif"); img.setAttribute("alt", "Google!"); img.setAttribute("title", "Google!"); img.setAttribute("boder", 0); img.style.width = "200px"; img.style.height = "200px"; a.appendChild(img); var br = document.createElement("br"); a.appendChild(br); var text = document.createTextNode("Click here to go to Google!"); a.appendChild(text); div.appendChild(a);… whereas the example I gave could be condensed to:
var node = createElements({ tag: "div", attributes: { id: "google_link" }, children: [ { tag: "a", attributes: {href: "http://www.google.com"}, style: {textDecoration: "none",fontWeight: "bold"}, children: [ { tag: "img", attributes: { src: "http://www.google.com/intl/en_ALL/images/logo.gif", alt: "Google!", title: "Google!", border: "0" }, style: { width: "200px", height: "200px" } }, { tag: "br" }, "Click here to go to Google!" ] } ] });As you can see, there is a lot less typing involved…