Wednesday, March 29th, 2006

Dollar E: A document.createElement Wrapper

Category: JavaScript, Prototype

<>p>Prototype is quickly becoming one of the most widely used Javascript libraries on the web, and with good reason. It sets up a simple interface making common Javascript tasks as simple as a single line of code. Of course, there are some things that Prototype still hasn’t gotten around to yet – such as this handy bit of code to create elements in your page.

The primary new feature ( from my perspective ;-) ) was a brand new UI that made use of AJAX, which I built on top of the Prototype JavaScript library. There was a lot of dynamic element creation based on the data coming back from the AJAX calls, and thus a lot of document.createElement calls, along with the appendChild and insertBefore and the element-property-setting statements. It’s very verbose.

At one point early on, I decided to write a wrapper function for document.createElement to avoid typing that out so often, and ended up realizing I should reduce more typing by figuring out a way to pass in attributes like the class and ID to be set. And then I realized, and it feels obvious in retrospect, that it should be able to handle creating a whole tree. I’m sure I’m not the first person to think of this, or even to write such a function. But I have found this to be very useful, and so after getting permission to do so from my company, I came here to release it as open source.

The document.createElement code creates a “$E()” function as an add-on to the power that Prototype already presents. Given the data of the object to create (tag, className, id), dropping a new element into the page is quick and painless. Best of all, it supports adding children at the same time, cutting dramatically down on the multiple calls.

The post includes a description of the add-on, the code to drop into you installation and a sample showing how to add a DIV tag with other child DIVs (one including an anchor tag).

Related Content:

Posted by Chris Cornutt at 7:38 pm
38 Comments

+++--
3.9 rating from 92 votes

38 Comments »

Comments feed TrackBack URI

See also: Builder in script.aculo.us, a similar solution.

Comment by Michael Daines — March 29, 2006

Global namespace pollution like this is hideous.

Comment by Alex Russell — March 29, 2006

What makes this more ‘hideous’ than the $() shortcut ? Doesn’t that ‘pollute’ the global namespace?

Comment by Mario — March 29, 2006

For completeness, it’s worth noting that Mochikit’s DOM API has something similar…

Comment by Brendan O'Connor — March 29, 2006

@Mario: yes.

Comment by Jeremy Dunck — March 29, 2006

From the description, I think domEl() from PaweÅ‚ Knapik does a better job, and is easier in use. $E() has childrens, though… Well, whatever one likes.

Comment by Datrio — March 29, 2006

Actually this has existed in scriptaculous (highly related to prototype) for quite some time now..

http://wiki.script.aculo.us/scriptaculous/show/Builder

Comment by Alexander — March 30, 2006

The sample code given in the $E article seems very complicated. I posted a comparison between the $E code and the same code written using my jQuery/Prototype DOM creation plugin (loosely patterned after MochiKit.DOM). There is quite a difference, both in complexity and flexibility.

Comment by Michael Geary — March 30, 2006

Rostislav, Builder from Scriptaculous does very well know how to work around the name attribute problem in IE.

innerHTML is faster. And you can use foo.innerHTML = [ "..", "..", ...].join() instead of string concatenation to gain some ms.

But if you create complicated structures, this is a maintainace nightmare. Furthermore, you have to be cautious of the elements for which IE does not support innerHTML.

Comment by Martin Bialasinski — March 30, 2006

Why not directly jump to the last station?
In my opinion all these efforts tends to a comprehensive DOM creation wrapper using the same (X)HTML syntax you would use to achieve the same result with innerHTML. Something like:

$DOM('<div id="myid" class="someclass" onmousedown="someFunc()" onkeyup="someFunc()" onmouseover="otherFunc(this,1)" onmouseout="otherFunc(this,0)"><img src="img/img.gif" />Some text here</div>');

The function will parse the arg string to do the right things (events will naturally be added as listeners), create, set and correctly nest the elements.
This is the current trend, the same (mutatis mutandis) has been done for the DOM elements selector classes that just emulate the powerful CSS syntax.
Anyone willing to challenge such implementation?

Comment by Andrea Martines — March 30, 2006

function createHtmlFragment(html)
{
var tmp = document.createElement(“div”);
tmp.innerHTML = html;
return tmp.firstChild;
}

Something like that?

Comment by Lon — March 30, 2006

I think he meant something that would actually parse up the HTML fragments and create the DOM structure it would implement. It would have to walk across the fragment and create structure as it went…

So


$DOM('<div id="myid">my <b>BOLD</b> text</div>');

would be exactly equivalent to:


var div = document.createElement("div");
div.id = "myid";
var text = document.createTextNode("my ");
div.appendChild(text);
var bold = document.createElement("b");
text = document.createElement("bold");
bold.appendChild(text);
div.appendChild(bold);
text = document.createTextNode(" text");
div.appendChild(text);

-John.

Comment by John Leavitt — March 30, 2006

@John: exactly. I meant something NOT using innerHTML, but allowing DOM creation with the same argument string (a markup fragment) you would use for the equivalent innerHTML injection.
The two main goals of this approach would be:
- making complex DOM injections as easy as HTML syntax, encouraging even dummiest developers to use them;
- make the two approaches easily switchable (you could even choose to degrade to the innerHTML strategy just for browsers with uneffective implementation of DOM creation)

Comment by Andrea Martines — March 30, 2006

Andrea: Why build your own parser, when you can use the tried and tested built-in parser of the browser? Lons code does exactly what you want! (I.e. it does not set the innerHTML of an existing element, but of a temporary element.)

Comment by Sjoerd Visscher — March 30, 2006

@Andrea: Why build your own parser, when you can use the tried and tested built-in parser of the browser? Lons code does exactly what you want! (I.e. it does not set the innerHTML of an existing element, but of a temporary element.)

Comment by Sjoerd Visscher — March 30, 2006

Hey Alex,

I undestand that Dojo uses namespaces to avoid the “global namespace polution”. For those interested in travelling a better namespace route, as I am, do you have a couple of good links explaining some best practices in how to accomplish it quickly and easily?

Comment by Danilo Celic — March 30, 2006

Danilo, there’s no rocket science involved here. Alex is simply talking about the practice of adding only a single, hopefully unique, name to the global namespace, and nesting all of your other objects and functions inside that.

You can accomplish this with any of the usual JavaScript coding techniques. For example:

DaniloCelic = {
myObject: { test: 123 },
myFunction: function() { alert( DaniloCelic.myObject.test ); }
};

Instead of adding myObject and myFunction as globals, you’ve added only one global name, with everything else inside it.

Comment by Michael Geary — March 30, 2006

Geary’s plugin rocks.

Comment by sup — March 30, 2006

Thanks Michael.

I am aware of that style of creating namespaces, I guess I should have specified, I was asking about how best to implement nested namespaces such that multiple namespaces don’t overwrite one another when they are being instantiated/constructed. Such as the dojo.io and dojo.lang.

I haven’t delved into dojo’s code as yet, so I’m hoping there may be a couple of links that talks about how to set up such a nested system of namespaces so that I don’t have to specifically worry about execution order for namespace creation.

Comment by Danilo Celic — March 30, 2006

Danilo: Michael Geary covered it pretty well, but you could quite easily put all of these functions into some sort of “proto” object like this:

if(!window["proto"]){ // avoid re-definition
var proto = {};
proto.$E = function(){ /* … */ };
}

Or declare it all as one object:

var proto = {
$E: function(){
/* … */
}
};

Or you could use the singleton syntax if you’re enamored of having private variables:

var proto = new function(){
var _somePrivateVar = “foo”;
this.$E = function(){
/* … */
}
};

In all of these cases, you can get to the new “$E” function via proto.$E()

All quality toolkits use some variety of these techniques to avoid fucking their users over with hard-to-debug namespace collision issues. Why Prototype continues to inhabit the dark ages (and why the Ajaxian editors seem to think it’s fine!?) is well beyond me.

The JavaScript community collectively moved on from this kind of brain damage *years* ago.

Regards

Comment by Alex Russell — March 30, 2006

I have to disagree on the global namespace pollution bit. Certainly it is bad to put random seldomly used functions in the global namespace but functions which are to be used as essentially core functionality ($, $E, $A replace commonly used language features/DOM features). It makes sense to put these things in the global namespace for the same reason it makes sense to add intrinsic regex support to the language rather than making the programmer call RegEx.parse(str) every time to create a regex (well actually this might be a bad example..I don’t use regexs that much but you get the point).

Of course what you really want is a feature in javascript like that in real languages that lets you include a namespace/modulo/whatever letting you avoid pulling things into the global namespace when you don’t want but easily adding them when you do. Thankfully I believe the namespace support in JS 2.0 (ECMAscript ?4?) will allow one to do just this now we just need to wait till it gets released.

Theoretically one could acheive a similar result now by wrapping all your code in a with statement when you want to pull something into the global namespace and then coding your entire library inside an object. However, given what I have heard about the with statement, (performance reliability issues) I’m not sure if you really want to do this and besides it is ugly.

Besides prototype has to screw with core object prototypes to do what it does. So it would be misleading for it to look like it can be loaded in a safe namespace (all functions are called like Ptype.$ unless you load it) when it may cause subtle incompatibility problems like this in the background (I’ve yet to run into this though…is it really a common issue when combining things with prototype?). Luckily this too seems set up to be fixed in JS 2.0

Finally by putting things in the global namespace it makes it easy to treat them as a standard part of the language. If I want $() to also add some custom information to the dom element it returns I can implement this easily enough just by redefining $. Other libraries can adopt $() as a standard. If it was hidden in ab obj I would have to go muck in the Proto object to change anything about $ and Proto.$ certainly couldn’t become a common cross library idiom.

So yes use the global namespace carefully but this sort of function does seem appropriate.

Also was the motivation of the innerHTML replacement code just religious belief in standard’s compliance? As far as I am concerned innerHTML is part of the defacto standard and it is the W3C who ought to change their standard and include it rather than us who ought to follow the standard. Usually this isn’t the case but by leaving out innerHTML the W3C people left the essential feature of the browser inaccesible by script (taking strings of html and turning them into DOM trees/web pages). Or is there some other goal I am missing?

Comment by logicnazi — March 30, 2006

logicnazi,

I can’t argue against placing often-used functions in short, well-known locations, but carving out the most precious locations in the global namespace for one implementation by one toolkit of a non-standarized behavior is both asinine and preventable. We considered the addition of a “$” function in Dojo and decided that since Prototype has gone the brain-damaged route and decided to allocate some portion of the global namespace to itself and that since there would be no way of knowing that our implementation would be compatible with any and every version of Prototype that Dojo must co-exist with in the wild, that we simply couldn’t use the same name. We settled on “dojo.byId” instead.

This is the difference between being a toolkit author and being an application author. As toolkit authors we must ensure that our tools are inert when inserted into foreign environments and clearly document the cases where this is not the case.

You might be able to make a strong case for addition of a function like “$” in a future JavaScript spec, but I hardly think you can defend the actions of toolkit authors who must live in today’s browsers and environments by the same logic.

As for your question about whether or not Prototype’s bad behavior is a problem in the real world, I can only answer that many users of Dojo have picked it after having been burnt by Prototype’s shenanigans. We wouldn’t go to the effort of namespacing everything if it weren’t really a problem. It’s a lot of work to ensure that nothing ever “leaks” into the global namespace.

Prototype continues to do the JavaScript community a disservice.

Comment by Alex Russell — March 30, 2006

Prototype takes the approach of being the basement of your application and you build on thop of it, you have control about what you build, so there is no problem when it liberally adds to the global object to make for easier access.

Dojo wants to get out of the way of any existing script you find somewhere and decide to insert into your application as is. Therefore the necessary dojo.long.object.java.like.chains.

> As toolkit authors we must ensure that our tools are inert
> when inserted into foreign environments

That describes the Dojo way. If one wants this, Dojo is something to look into.

The Prototype answer is: Prototype is the foundation of your application. you do not insert it into foreign environments, you build on top of it, the Prototype space IS the environment. Prototype is not concerned with “inserted into foreign environments”, it is not part of the “value proposition”. Short version: We do not care, this is not what we are into. This is similar to the answer Rails gives to request for XML configuration files and similar request for turing it into a Java clone.

Two different philosophies for different usage patterns, BOTH have advantages and disadvantages, so the user has to weight them depending on his own situation. NEITHER is better in all aspects.

Comment by Martin Bialasinski — March 30, 2006

@Alex Russell: can you cool it with the harshness toward Prototype. I mean ‘brain damaged’, ‘hideous’, ‘pollute’, am I in “spin alleyâ€? at a political convention?

Prototype is used in a lot of really cool feature rich scripts and frameworks like Prado, Lightbox, Scriptaculous…

It also helps inspire people, push Ruby/Rails, and makes JavaScript fun and easy again. I think that a little bit of global use like that of the $ is alright.

I mean sure, Application.io.framework.element.retreaveById() is all fine in good but give me good ol’ $() anyday.

Don’t want to Flame but gosh is it getting smug in here?

Comment by Mario — March 30, 2006

sup, thanks for the kind words. :-)

Martin, there is a serious problem that you and the other Prototype fans are ignoring.

It’s fine and dandy for Prototype to take over the JavaScript language as it does, if there is no “foreign” JavaScript code that must run in the same page.

So you’ve built your app with Prototype, it works great, and now you decide you’d like to incorporate some third-party widgets. Maybe Google Maps or one of the new Yahoo goodies, or perhaps one of the embeddable widgets I’m working on.

Now you have a problem. And so does the author of the widget you’d like to use. Because that third-party widget is likely to break unless the author has been very, very careful to avoid using standard JavaScript idioms that Prototype has broken.

Remember the Object.prototype.extend fiasco? Thanks to Prototype, the for( i in object ) was completely broken! That is fixed, but there are still other things that Prototype has broken such as for( i in array ).

The real bummer for those of us who write embeddable widgets is the way Prototype is becoming so entrenched thanks to its integration into Rails. All of that goodness that Rails and Prototype fans get to have fun with? Not only do we miss out on the fun, but it has made our lives much more difficult.

Of course, making embeddable widgets that will be compatible with any variety of embedding web page is a difficult problem to begin with. There are all sorts of pitfalls we have to watch out for. But Prototype has made the problem more difficult than it needed to be.

Comment by Michael Geary — March 30, 2006

I just had this problem for the first time tonight, actually. I was trying to incorporate the css3multi-column module from ALA and wasn’t able to do so on my prototype-infused site because prototype overrode for.

Comment by Paul Payne — March 31, 2006

> Remember the Object.prototype.extend fiasco?

Beating a dead horse from a past century. This was a major mistake, it was fixed.

> there are still other things that Prototype has broken such
> as for( i in array ).

I argue, that any code that iterates over Array not by numeric indices is broken, as an Array is a collection of values sorted by its numerical index. I do not want to reuse code blindly that mistakes Array for a hash, the author might be mistaken about other aspects of Javascript as well.

To use Flanagan’s words:


Chapter 8 documented the JavaScript object type -- a composite data type that holds named values. This chapter documents arrays -- a composite data type that holds numbered values. Note that the arrays we'll discuss in this chapter are different from the associative arrays described in the previous chapter. Associative arrays associate values with strings. The arrays described in this chapter are just regular numeric arrays; they associate values with non-negative integers.

> very careful to avoid using standard JavaScript idioms
> that Prototype has broken

Which one are you talking about?

> All of that goodness that Rails and Prototype fans get to have
> fun with?

Well, I use PHP.

Comment by Martin Bialasinski — March 31, 2006

If you use PHP try the Prado (http://www.xisc.com/) its a PHP Framework that separates the html from php and javascript. It uses Prototype as its base JS class and builds a very advanced framework on top of it. the for(i in array) is a very weak argument (Wow. Got bitter framework developers?). Use what works. Prototype works and its simple.

Comment by Mario — March 31, 2006

I wrote something identical to this 2 years ago. The only difference is the exact syntax and the fact that mine can return an object with references to any created element (so you don’t have to then travers the created doc fragment with elem.childNodes…..)

http://www.jslibrary.org/snips/domcreate.asp

Comment by CWolves — March 31, 2006

> the for(i in array) is a very weak argument (Wow. Got bitter framework developers?). Use what works. Prototype works and its simple.

Don’t worry, I’m not a framework developer and I’m not bitter. :-)

I am a bit annoyed, though. One reason is that I can’t use Prototype for my own code. Remember that I am building embeddable widgets, not standalone websites. There’s no way I could responsibly use Prototype for this application, because I can’t guarantee that it won’t break existing code in my customer’s site.

Yes, that could be for( i in array ). I am not about to tell my customer that they can’t use a perfectly valid JavaScript construct that can have a useful purpose. Martin, you’re right that coders usually should use a numeric loop, but consider this sparse array:


var a = [];
a[0] = 'a';
a[1] = 'b';
a[999] = 'c';

Now I have a choice of two loops that do different things. An a.length numeric loop will iterate 1000 times. A “for in” loop will iterate three times, with values 0, 1, and 999 (not necessarily in order).

Why not make “a” an object in this case? Maybe I want a.length and a.push() to work. Maybe some other reason. The point is that it’s not my code I’m talking about here, it’s my customer’s code, and I’m extremely reluctant to tell my customers that they have to change their own code to keep mine from breaking it.

Next, what happens if I use Prototype in my embeddable widget and my customers use it in their sites too? Do we have the same version of Prototype? Do we have different versions that will conflict with each other? What if they have customized their version and mine steps on it?

As a Windows developer for 20 years, I spent a lot of time in “DLL Hell”, where one application’s Dynamic Link Libraries overwrite those used by another application. I have no desire to go back to those kinds of conflicts, and I certainly will if I use Prototype.

So, what do I use in my embedded widgets? jQuery. The stock version of jQuery does step on some globals, notably providing its own version of the “$” function which it tries to keep compatible with Prototype. But because of the way jQuery is coded, it is simple to wrap the whole thing along with my widgets inside a container function that turns jQuery’s “$” and other functions into local variables instead of global.

That doesn’t solve every compatibility issue, but it does avoid a lot of the problems I’d have using a framework like Prototype whose philosophy is that it owns the JavaScript world.

Comment by Michael Geary — March 31, 2006

@Alex: Thanks for the code sample. I’ll play around with that a bit. Maybe time to delve intot he Dojo code. :-)

@Michael: I agree with martin in that iterating over arrays with for-in. IMO the fact that it works in some situations that way is lucky at best, and broken in several other cases. Especially considering that properties can be added to object by the language itself, not even considering the actions of a library developer or a developer integrating JavaScript into their applciation. For example, the RegExp object exec() method returns an array with two additional properties on it: index and input. Consider the folowing code:
myRe=/Array/g;
str = “JavaScript Arrays”;
myArray = myRe.exec(str);
alert(myArray[0]);
alert(myArray.length);
alert(myArray.index);
alert(myArray.input);

this alerts:
Array
1
11
JavaScript Arrays

Of course not everyone will be iterating over RegExp arrays, but given the fact that for-in is easily broken for array iteration, and the “regular” for loop just plain works regardless of the extra properties or not, trying to save a few keystrokes just isn’t worth it IMO.

Anyway, back on topic, I wrote some code similar to the $E code so that I could take a JSON object (which happened to be an array of objects), or an evaled JSON string in the “proper” format and convert it into DOM nodes that I could either do further process or drop into the document at the proper location(s). Worked quite well for most uses I could come up with.

Comment by Danilo Celic — March 31, 2006

@Michael Geary: you make a very good point for your purposes. In my situation though I am using Prado, everything is made to work with and evolve from Prototype. So I use it. I am making ‘widgets’ to work with prototype and its underlying framework and you make ‘widgets’ to work for everyones things. Its coo 8)

Comment by Mario — March 31, 2006

@Danilo: Good point about the exec() return value. My only real problem is that, wise or not, my customers may be using for..in on arrays, and I don’t want to be the one to tell them that they have to change their code. More likely they will tell me to take a hike. :-)

@Mario: Indeed, different situations have different issues and constraints. Prototype is not a good choice for general purpose embedded widgets like mine, but if you know your code is going into a Prototype environment, then it’s perfect.

Comment by Michael Geary — March 31, 2006

Personally my problem with prototype’s array prototyping is that it’s completely unnecessary. You can easily have the same functions not prototyped onto an array: instead of

array.each(function(){})

you can have:

each(array, function(){})

I personally map any “prototype” functions directly onto the Array object:

Array.each(array, function(){})

so that it doesn’t pullute the global namespace or mess with Array.prototype

Comment by CWolves — March 31, 2006

Danilo: If you read the ECMA 262-3 spec, you’ll see references to “[DontEnum]” all over the place. This internal flag allows the interpreter to provide a “clean surface” for objects like RegExs and their results to be iterated over. The fact that they have the properties that you list in no way implies that they break for(.. in ..) iteration.

The huge disadvantage that those of use scripting in the JS “userland” have is that we can’t mark our own names as [DontEnum] and therefore more discipline is required to avoid walking on each other’s toes. Prototype simply ignores any situation in which it is not placed in some exhaulted position and just tells everyone else to suck it up. It’s a type of arrogance that I last saw out of the Java community and something that I hope dies quickly in the JS world.

Regards

Comment by Alex Russell — April 3, 2006

@Alex,
Not having read the spec, I’ll trust you that the for-in should not include those extra properties; however, in my testing, in IE6 and Firefox 1.5.0.1 as well as within the Rhino engine that Dreamweaver uses internally, those extra properties do appear within a for-in loop on an array returned by RegExp.exec().

Trust me, I’m not at all trying to argue that anyone should be adding properties to any object.

Comment by Danilo Celic — April 4, 2006

[...] Installation. 1. You need OutputComponent, HeadHelper from this blog 2. dollar_e.js from http://ajaxian.com/archives/dollar-e-a-documentcreateelement-wrapper 3. Prototype >= 1.4.0 4. Copy the following to /app/controller/components/cajax.php [...]

Pingback by RosSoft » Cajax Component v2 — May 22, 2006

how do you wrap a in a createElement() tag to produce a dynamic calendar upon adding rows?

Comment by chiponium — October 6, 2006

Leave a comment

You must be logged in to post a comment.