Here is a jQuery plugin that makes it easy to build up a tree of DOM nodes. It lets you write code like this:
var table =
$.
TABLE({ Class:
"MyTable" },
$.TBODY({},
$.
TR({ Class:
"MyTableRow" },
$.TD({ Class:"MyTableCol1" }, 'howdy' ),
$.
TD({ Class:
"MyTableCol2" },
'Link: ',
$.
A({ Class:
"MyLink", href:
"https://www.example.com" },
'example.com'
)
)
)
)
);
Basically, each function such as $.TABLE
creates a DOM node and takes the following arguments:
The first argument is an object that list any attributes to be set on the node. You can specify the className attribute in a few different ways depending on your taste:
className: text
Class: text
'class': text
Any additional arguments after the first one represent child nodes to be created and appended. These arguments can be DOM elements themselves (e.g. inline $.FOO
calls as above), or they can be numbers or strings which are converted to text nodes, or they can be arrays, in which case each element of the array is handled in this same way.
This interface is inspired by Bob Ippolito’s MochiKit DOM API, although it doesn’t implement all of the features of that one (yet).
The code predefines most of the common tags; you can add additional tags by calling:
$.defineTag( tagName ); // e.g. $.defineTag( 'dd' );
Or simply add the tag names to the tags list in the code.
One last definition is $.NBSP
which defines a non-breaking space (same as
in HTML).
$._createNode
is an internal helper function used by the $.FOO
functions. I would have hidden it away as a nested function, but I wanted to avoid any unnecessary closures.
This code doesn’t actually depend on any of the features of jQuery except for the presence of the $
function—and it uses $
only as a way to avoid cluttering the global namespace. I haven’t tested it with Prototype.js, but it should work equally well there. Or the code can be used with no library, by preceding it with:
Here is the source code, or you can download it:
// DOM element creator for jQuery and Prototype by Michael Geary
// https://mg.to/topics/programming/javascript/jquery
// Inspired by MochiKit.DOM by Bob Ippolito
// Free beer and free speech. Enjoy!
$.
defineTag =
function( tag
) {
$[tag.toUpperCase()] = function() {
return $._createNode
( tag, arguments
);
}
};
(function() {
var tags = [
'a',
'br',
'button',
'canvas',
'div',
'fieldset',
'form',
'h1', 'h2', 'h3', 'hr', 'img', 'input', 'label', 'legend',
'li',
'ol',
'optgroup',
'option',
'p',
'pre',
'select',
'span', 'strong', 'table', 'tbody', 'td', 'textarea',
'tfoot',
'th',
'thead',
'tr',
'tt',
'ul' ];
for( var i = tags.length - 1; i >= 0; i-- ) {
$.
defineTag( tags
[i
] );
}
})();
$.
NBSP =
'\u00a0';
$._createNode =
function( tag, args
) {
var fix = { 'class':'className', 'Class':'className' };
var e;
try {
var attrs = args
[0] ||
{};
e = document.createElement( tag );
for( var attr
in attrs
) {
var a = fix[attr] || attr;
e
[a
] = attrs
[attr
];
}
for( var i =
1; i < args.
length; i++
) {
var arg = args[i];
if( arg ==
null ) continue;
if( arg.constructor != Array ) append( arg );
else for( var j =
0; j < arg.
length; j++
)
append( arg[j] );
}
}
catch( ex
) {
alert( 'Cannot create <' + tag + '> element:\n' +
args.
toSource() +
'\n' + args
);
e = null;
}
function append
( arg
) {
if( arg == null ) return;
var c = arg.
constructor;
switch( typeof arg ) {
case 'number': arg =
'' + arg;
// fall through
case 'string': arg = document.createTextNode( arg );
}
e.appendChild( arg );
}
return e;
};
I read some funky Lisp articles this summer, and the goal of that article was to make some HTML output in a syntax that was a lot like this, and even more like Why the Lucky Stiff’s “Markaby”.
Go you, Mr Geary. This is most cool. I’ll keep my eyes peeled for more content at this space. Or rather, I’ll instruct my feed-reader to.
Hi Michael,
I wanted to report a problem with the plugin when one wants to create a
label
element with afor
attribute. As this is also a keyword in JavaScript I had to change a bit:1) Add that to the fix:
2)
e[a] = attrs[attr];
does not work, so I had to change that to this (ugly, ugly):So now I can create my desired label:
The following syntax also works with no code fix:
Sorry, this does not work (at least in Firefox). The value of the attribute is
null
. Can you provide a working example?In firefox (gecko) based browsers, you don’t need the “for” attribute, but the “htmlFor” attribute. Google on it, it is hard to find but i got it working.
Thanks, guys, good catch. Gilles, even better,
htmlFor
isn’t just for Firefox, it is the standard. So what if we try this:Or better yet, simplify it to this:
and change the assignment that uses
fix
to:I like
For
better thanforAttr
—it’s closer to what you’d write in HTML. Klaus, your example would read:Or you could use:
I have a feeling we won’t need the “ugly, ugly” :-) code that uses
setAttribute
. BecausehtmlFor
is the standard DOM property, my guess is that it will work with the simplee[a] =
assignment. Whoever tries it first, let us know if it works, OK? Thanks!Good news, bad news. Thanks for the hint with htmlFor. In general it works, but in IE assigning the attribute via
fails completely (The form doesn’t even render). I made the experience, that in IE some attributes must be assigned explicitly via
So now I resorted to this:
I don’t like eval, but it’s a little less ugly :-) Maybe there is another solution, but I haven’t got the time at the moment to test some more things.
The good news is, that the label element now works as expected in IE . Before I had to do some even more ugly stuff:
Thank you guys!
From another website:
So i decided to modify the script like this:
Haven’t tested it yet, but should work even though it’s pretty messy.. After all we are looking for a cross-browser sollution.
On the other hand, why not use the default jQuery way to do it? It has built in support for the “fix” so that can be removed from the code.
Change:
To:
Works fine! Thank you Gilles!
If you need to create simple text nodes, add the following snippet:
You can than easily create a single text node as well:
Thanks for this cool script! I was just playing with it and found myself in a situation where I was creating lots of elements that I didn’t need to pass any attributes to… I was ending up with code like:
So made a little modification to the script. I added this code in (the line before and the line after are included so you can see where it goes):
As you can see, if the first argument is a string it just prepends an empty object to the front of the arguments array. This isn’t as clean as it could be because the arguments array isn’t a real array and doesn’t support unshift() but the code seems to work and now I can simplify my code to:
Not sure if you think this is a useful modification? Seems to work fine on IE / FF WinXP and I don’t think it will have too much of a speed impact in most cases…
Cheers,
Kelvin :)
Similarly, if the first argument is not an attributes object, but another dom element, e.g.
You can do something like:
to test for it.
I’m new to jQuery so hopefully someone could improve on that….but it seems to be working ok for me.
This is a fix that allows the attr ({}) to be optional without requiring the first arg to be a string.
I wrote another "Easy DOM creation" method. I think it lets you write more understandable markup, and there is no need for tag definitions in the code:
Here is the jQuery function:
And here is an example:
Method returns a jQuery object so you can easily manipulate it after creation. For example:
Also I’ve coded a usable JSON template method, which uses the method above:
The power of this method:
and:
I think Olsows method is extremely powerful. The json based ‘template’ functionality is particularly neat. Olsow: If the content of the JSON data object contains html (or encoded html for example), the HTML is not parsed.
Thanks for the very creative solution, Olsow. It’s interesting to see a whole different approach to the problem.
A couple of thoughts…
The
instanceof
operator is available only in JavaScript 1.4 and higher. To support older browser versions the code would have to be changed to use other techniques.One thing about my code was that it’s designed to be independent of jQuery. The idea was to allow for a two-stage page load: First, a very small JavaScript file including the DOM creation code but no jQuery could render the dynamic content. Then, a larger JavaScript file including jQuery, behavioral methods, etc. would be loaded in the background.
As it happens, I took a different approach for my own project anyway. Instead of using DOM creation at all, my first stage page rendering code generates HTML code and uses
document.writeln()
- yes,document.writeln()
! - to render the content. This allows for completely smooth page loading, with the dynamic content appearing as part of the page when it first loads. That is impossible to achieve with DOM creation. More on this in a later post…First, I think Olsow method is great! If they ever incorporate a DOM Creation into jQuery I would want it to work like this.
Now my issue, when using this method to set the class, it adds both “classname” and “class”. When you use .removeClass() it only removes the “class” part. I didn’t know if this problem was due to your method or jQuery.
That’s a bug in jQuery itself. I think it’s fixed in the latest development version.
I like the way this works, but it stopped working when I switched to jQuery 1.0. So in case anyone cares, I’ve rewritten it slightly to work with current versions. Also, instead of returning jQuery objects (don’t see the point in that), these just return arrays of DOM elements that can be passed to jQuery methods like append()…
ive taken it further, to complete programmatic view and behavior definition.. cheers
Hi, I have difficulties getting $.lNPUT to work in IE (6.0). The fabulous Microsoft Script Debugger tells me “Object doesn’t support this property or method”. I don’t know if it has something to do with the “e[a] = attrs[attr];”, but I tried all the fixes suggested and nothing works. I’m fairly new at DOM scripting, so I’d be most thankful for help..
Kajsaw, could you post a code sample? I’d be happy to take a look at it. Kind of hard to guess the problem without seeing the actual code.
Sure! For example, if I want to create a text field, I can write $.INPUT({type:’text’},”) in FF, but in IE i have to use the (much uglier) $.TEXTAREA to get it to work.
Sorry if I seem dense, but if you could post an actual test page that I could try out, it would be easier for me to see the problem and experiment with it to try to find a solution.
You could either post a link to a test page, or include the actual HTML and JavaScript code in a comment. Enclose HTML code in a <geshi html4strict> tag, and JavaScript code in a <geshi javascript> tag. Or just post the whole thing inside a <pre> tag and I’ll clean up the formatting.
Thanks!
‘course, i was just lazy..:) Btw, I’m typing on a Mac I can’t handle, and couldn’t find the curly brackets, so I used the square ones instead… I don’t do that normally:) also, your server seemed to think it was suspicious to use scripttags so i’m just printing the javascript. put this inside scripttags in the htmlbody, and nothing else on the page (except the jquery-dom.js include in the head).
I took care of the curly braces for you. :-) I’ll take a look at the code later and see if I can find out anything.
Some code to add support for inline styles. (They’re only evil most of the time. :D)
Usage (same as before)
And here’s some code to make the style attribute work. If this doesn’t work quite right, it’s because I ported it from my own DOM creator w/o testing. Probably fat fingered a variable.
Probably easier to use an object for the style and take advantage of JQuery:
with this code (using a variation of the the “ultimate fix” above):
or, even easier (skip the whole
for (var attr in attrs)
loop:Just to let you know I had to add a tiny fix to allow the use of ‘colspan’ and ‘rowspan’ (it has to be ‘colSpan’ and ‘rowSpan’ in javascript):
I would love a simple generator app for this, where you paste in HTML and generate JS using this technique. Say you’re generating fieldsets on the fly; sure you only have to create the jquery-dom.js JS code one time, but if it’s like 4 fields with labels and all that it would be a pain.
Okay, back to work where I have to create the jquery-dom.js JS code for generating fieldsets on the fly with like 4 fields with labels and all that!
Thanks for this script. It has been an absolute joy to work with.
I made a simple tweak to return a jQuery object. Then I could benefit from method chaining.
Just change line 57 to this:
and line 60 to this:
Then you can do this:
Thanks for the kind words, Brandon.
Returning a jQuery object sounds like a great idea. I didn’t do it in my own code because I wanted to be able to use the DOM creation code standalone as well as with jQuery.
I have a question about the patch. The
return $(e);
makes sense, but what does the change to line 57 do? Given thatarg
is a DOM element, then$(arg).get(0)
should just returnarg
. So I would expect that these two statements would be equivalent:vs.
You know I had the same question but at the time I was more in a hurry to meet my deadline. After quickly looking back over the script and the patch it would seem that line 57 should not be changed but rather a new lined added after the check to see if arg is null in append (line 51). The new line should say:
Honestly I’m still a little confused how arg ends up being a jQuery object but it needs to be a regular dom node before being appended.
So I had to actually change the above line once again so that it would work for none DOM nodes.
The reason this is necessary is because each $.[tagName] returns a jQuery object.
Please provide a link to a working example for this.
Hi I tried attributes cellspacing and cellpadding and they are not working/visible in DOM.
There are some attributes that are capitalized differently in the DOM vs. HTML. If you use cellSpacing and cellPadding it should work OK.
After looking at JS, I followed bellow given comments. I think there are lots of updates (fix) that should be available in download able JS file. Right now I feel after download JS, I have to follow the thread and update script. Can anybody please post latest script!
Thanks
Here’s how I’d generate the same html table. It might seems more natural to “certain” people :-)
https://www.talrasha.com/jquery/jquery.easydom.js
This plugin is not working with IE for creating radio buttons.
It creates radio buttons but we can’t select that radio buttons. Is there any solution to solve this problem?
Hi, I found the work around for this issue. The issue is specific to creating elements in IE. In IE you have to create radio buttons by using
document.createElement("<input name='myradio' checked >")
as the IE doesn’t allow the name or many other attributes to be set on input elements. So for this I made following change in jquery-dom.js file to run it in IE also.Is this correct solution? Please suggest me if you have some better solution for the above issue.
—Vishakha
Thanks for chasing that down, Vishakha. I assume this code replaces the
e = document.createElement( tag );
in the original? I’ll take a look at it and see if there is any way to simplify it, but it does sound like you’re on the right track.Count on IE to make our lives more complicated! :-)
Notably missing is any support for the
<EM>
tag.