$E - a short cut for constructing dom elements

3 views
Skip to first unread message

Erik R.

unread,
May 6, 2008, 5:27:45 PM5/6/08
to Ruby on Rails: Spinoffs
Recently I've been using prototype's wonderful new DOM creation
syntax. But I found that it's still too verbose. Say I want to
create the following table:

<table id="myTable">
<thead>
<tr>
<th>Title A</th>
<th>Title B</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>B</td>
</tr>
</tbody>
</table>

Simple, right? But, as I understand the prototype.js DOM building, to
build this table, I'd have to do:

var table = new Element('table', {id:myTable});
var thead = new Element('thead');
table.appendChild(thead);
var theadRow = new Element('tr');
thead.appendChild(theadRow);
theadRow.appendChild(new Element('th').update('Title A'));
theadRow.appendChild(new Element('th').update('Title B'));
var tbody = new Element('tbody');
table.appendChild(tbody);
var tbodyRow = new Element('tr');
tbody.appendChild(tbodyRow);
tbodyRow.appendChild(new Element('td').update('A'));
tbodyRow.appendChild(new Element('td').update('B'));

Grossly verbose, I think you'll agree. Particularly, it's the saving
of the local variables that bothers me.

But what if we had a shortcut function? Just like $() is short for
document.getElementById(), I think we could benefit from a shortcut
element function. So I've written one: $E.

var $E = function(tagName, attributes, childrenVarArgs)
{
var element = new Element(tagName, attributes);
$A(arguments).flatten().each(function(child, i)
{
if (i > 1 && child)
element.appendChild(child);
});
return element;
};

It takes the tagName and attributes just like the Element constructor,
but it will also take other arguments that will be appended as
children. Look at the new code to create that same table:

var table = $E('table', {id:myTable},
$E('thead', null,
$E('tr', null,
$E('th').update('Title A'),
$E('th').update('Title B'))),
$E('tbody', null,
$E('tr', null,
$E('td').update('Title A'),
$E('td').update('Title B'))));

A little nicer, don't you think? Some intelligent argument parsing
might also be added to get rid of those null attribute parameters.

Anyway, I'm submitting this as a suggestion to be incorporated into
the next release of prototype.js. Let me know what you think.

Cheers,
Erik

Hector Virgen

unread,
May 6, 2008, 5:36:14 PM5/6/08
to rubyonrail...@googlegroups.com
Very nice! Is $E() short for Erik? :)

Erik R.

unread,
May 6, 2008, 5:43:57 PM5/6/08
to Ruby on Rails: Spinoffs
I was thinking "Element", but now that you mention it, "Erik" makes
more sense. :-)

kangax

unread,
May 6, 2008, 6:06:06 PM5/6/08
to Ruby on Rails: Spinoffs
So for every single one of those cells (that $E is called) there is a
"new Element" instantiation and 2 enumerable methods (that are being
called recursively) : )
Why not just use string interpolation?

- kangax

Hector Virgen

unread,
May 6, 2008, 6:43:02 PM5/6/08
to rubyonrail...@googlegroups.com
Maybe this can be optimized a little bit? I'm not sure if this helps, but it doesn't use any anonymous functions.

var $E = function(tagName, attributes, childrenVarArgs)
{
  var element = new Element(tagName, attributes);
  if (arguments.length < 3) return element;
  var args = $(arguments).flatten();
  var size = args.size();
  for (var i = 1; i <= size; i++) {
    element.appendChild(args.indexOf(i));
  }
  return element;
};

Erik R.

unread,
May 6, 2008, 7:03:52 PM5/6/08
to Ruby on Rails: Spinoffs
Sure. An old-school for loop is probably more efficient. But most of
the Enumerable methods already use each() with an anonymous function
anyway, so presumably they could be further optimized as well. I was
just sticking with the prototype.js style.

Jon L.

unread,
May 6, 2008, 7:18:17 PM5/6/08
to rubyonrail...@googlegroups.com
In theory (at least), couldn't it be simplified to something like:

var $E = function (/** tag, attr, child0, ..., childN **/) {
  var args = $A(arguments);
  var element = new Element(args.shift(), args.shift());
  args.each(element.appendChild.bind(element));
  return element;
};

No idea whether it helps with performance.
But, at minimal, it removes the anonymous function.

- Jon L.

Hector Virgen

unread,
May 6, 2008, 7:35:19 PM5/6/08
to rubyonrail...@googlegroups.com
Very cool! A quick test took 5 seconds to build 500 tables with 4 cells each. I guess that's reasonable, but could be better.

kangax

unread,
May 6, 2008, 8:49:28 PM5/6/08
to Ruby on Rails: Spinoffs
Erik,
It's not really about anonymous function, but rather such frequent
(for every element) invocation of a method which is quite "heavy".
This could of course be useful when plain string insertion is not the
best solution (though I don't see why it would be so).

I am sometimes using "makeElement" helper - its performance is a
constant (i.e. it does not depend on the size of an element being
created):

String.prototype.makeElement = function() {
var element = Element('div');
element.innerHTML = this;
return element.down();
}
...
var table = '<table><tr><td>...</td></tr></table>'.makeElement();

As a side note, appendChild is buggy with certain elements. #insert on
the other hand smoothes all these quirks.

Best,
kangax

Rod

unread,
May 6, 2008, 8:53:37 PM5/6/08
to Ruby on Rails: Spinoffs

I really like what Dan Webb has done in LowPro (http://www.danwebb.net/
lowpro) for making DOM building easier. His site isn't responding just
at the moment so I can't grab his exact code, but since I still prefer
scripty's Builder.node() I have this in my extensions that achieves
the same result:

("p|div|span|strong|em|img|table|tr|td|th|thead|tbody|tfoot|pre|
code|" +
"h1|h2|h3|h4|h5|h6|ul|ol|li|form|input|textarea|legend|fieldset|"
+
"select|option|blockquote|cite|br|hr|dd|dl|dt|address|a|button|abbr|
acronym|" +
"script|link|style|bdo|ins|del|object|param|col|colgroup|optgroup|
caption|" +
"label|dfn|kbd|samp|var").split("|").each(
function(el) {
window['$' + el] = function() {
return Element.extend(Builder.node.apply(Builder,
[el].concat(Array.prototype.slice.apply(arguments))));
};
});

And you end up with a $-prefixed function for each different type of
element, so you could do your above table something like this:

var table = $table({ id: 'myTable' }, {
$thead({
$tr({ $th('Title A'), $th('Title B') })
}),
$tbody({
$tr({ $td('Cell A'), $td('Cell B') })
})
});

Which I reckon makes it pretty readable. And with Builder.node()'s
flexibility with arguments you end up with a pretty powerful DOM
builder that requires a small amount of code and is easy to maintain.
That is of course if you're comfortable cluttering up the global
namespace with all these extra functions.

(btw, if you're going to use the above then you need to make sure it
is executed only after scripty's builder.js is loaded, so if you're
loading it the normal way you might need to do it after a dom:loaded
event).
-- Rod

Erik R.

unread,
May 7, 2008, 7:25:21 AM5/7/08
to Ruby on Rails: Spinoffs
Nice one, Rod. That code is LISPtastic!

RobG

unread,
May 7, 2008, 8:12:17 AM5/7/08
to Ruby on Rails: Spinoffs
Compared to the equivalent HTML:

<table id="myTable">
<thead>
<tr><th>Title A<th>Title B
<tbody>
<tr><td>A<td>B
</table>

It seems verbose.


--
Rob
Reply all
Reply to author
Forward
0 new messages