Sunday, November 29, 2009

XML To HTML Snippet

This John Resig post about nodeName demonstrates once again how trustfulness are edge cases in JavaScript.
I must agree 100% with @jdalton: frameworks or selector libraries should not be concerned about these cases.

First of all there is no universal solution so whatever effort able to slow down libraries won't be perfect, then why bother?

Secondly, I cannot even understand why on earth somebody could need to adopt XML nodes in that way.
Agreed that importNode or adoptNode should not be that buggy, but at the same time I have always used XSL(T) to inject XML into HTML and I have never had problems.

Different Worlds

In an XML document a tag is just a tag. It does not matter which name we chose or which JavaScript event we attached, XML is simply a data protocol, or transporter, and nothing else.
A link, a div, an head, the html node itself, does not mean anything different in XML so again: why do we need to import in that way?
In Internet Explorer we have the xml property which is, for HTML represented inside XML docs, the fastest/simplest way to move that node and what it contains inside an element and via innerHTML.
Moreover, namespaces are a problem, we cannot easily represent them into an HTML document so, in any case, we need to be sure about represented data.

A Better Import

Rather than ask every selector library to handle these edge cases we could simply adopt a better import strategy.
This XML to HTML transformer is already a valid alternative, but we can write something a bit better or more suitable for common cases.
As example, a truly common case in these kind of transformations is a CDATA section inside a script node.
With CDATA we can put almost whatever we want inside the node but 99.9% of the time what we need is a JavaScript code, rather than a comment.
Another thing to consider is that if we need to import or adopt an XML node, 99.9% of the time we need to import its full content and not an empty node (otherwise we should ask us why we are representing data like that, no?).
I bet the deep variable will be basically true by default so, here there is my alternative proposal which should be a bit faster, avoiding implicit boolean cast for each attribute or node, and considering what I have said few lines ago:

function XML2HTML(xml){
// WebReflection Suggestion - MIT Style License
for(var
nodeName = xml.nodeName.toUpperCase(),
html = document.createElement(nodeName),
attributes = xml.attributes || [],
i = 0, length = attributes.length,
tmp;
i < length; ++i
)
html.setAttribute((tmp = attributes[i]).name, tmp.value)
;
for(var
childNodes = xml.childNodes,
i = 0, length = childNodes.length;
i < length; ++i
){
switch((tmp = childNodes[i]).nodeType){
case 1:
html.appendChild(XML2HTML(tmp));
break;
case 3:
html.appendChild(document.createTextNode(tmp.nodeValue));
break;
case 4:
case 8:
// assuming .text works in every browser
nodeName === "SCRIPT" ?
html.text = tmp.nodeValue :
html.appendChild(document.createComment(tmp.nodeValue))
;
break;
}
};
return html
};

I have tried to post above snippet into John post as well but for some reason it is still not there (maybe waiting to be approved)
We can test above snippet via this piece of code:

var data = '<section data="123"><script><![CDATA[\nalert(123)\n]]></script><option>value</option></section>';
try{
var xml = new ActiveXObject("Microsoft.XMLDOM");
xml.loadXML(data);
}catch(e){
var xml = new DOMParser().parseFromString(data, "text/xml");
}
var section = xml.getElementsByTagName("section")[0];
onload = function(){
document.body.appendChild(XML2HTML(section));
alert(document.body.innerHTML);
};

We can use latest snippet to test the other function as well and as soon as I can I will try to compare solutions to provide some benchmark.

Monday, November 23, 2009

On element.dataset And data-* Attribute

Why on earth? I mean why we should put a data-whatever attribute into our layout? Where is the good old MVC? How can you consider data-* more semantic? more semantic than what? Why we would like to kill truly semantic pages and graceful enhancements? Why we need JavaScript redundant info inside nodes attributes but we cannot understand a script tag in the middle of the page? Why in the performances matter era we would like to let users download stuff that they will probably never use?

What Am I Talking About

We can find the "magic" data attribute description in the W3C Semantics, structure, and APIs for HTML documents page. These days there is a page that is going around even too much ... finally somebody realized that this data-thingy is nothing different than what we could have always used since ages: XML.

Why We Are Doing Wrong

If we are enthusiast about a custom attribute able to bring whatever information, we should ask ourself why on earth we are not using simply XML plus XLS to transform nodes with custom attributes. Each node can be easily and quickly transformed, in the client side as well and in a cross browser way, via one or more cached XSLT able to create runtime whatever we need.
We need to sort the music file list via data-length attribute? No problems at all, we can reorder via DOM every node we need and put the XML fragment transformed via XSLT into a single HTML node in that page. Moreover, we can use modular XSL to transform branches or specific cases ... but that is too clean and professional, isn't it?

Data Used Via Selectors

Let's say we have 3 songs with the same length, taking the example from W3 page, OK?

<ol>
<li data-length="2m11s">Beyond The Sea</li>
<li data-length="2m11s">Beside The Sea</li>
<li data-length="2m11s">Be The Sea</li>
</ol>

First of all that example is quite hilarious, to make a sort as fast as possible that kind of representation is not ideal, is it?
It does not matter, the point is that as soon as this stuff will be implemented, all jQuery users will start to think so semantic that everything will become a:

$("@data-whatever=whatevervalue").each(...)

Seriously ... if we want to make the web the worst semantic place ever, just put in medium skilled developers this kind of weapon, and we'll scream in few months HTML5 Failed!
Who will care anymore about the appropriate element when everything can be easily validated in the W3 for totally dirty and useless, for non JS users, layouts?

The Namespace War Has Started Already

I can imagine lots of new devs starting to use the data as if it is their own attribute, ignoring conflicts problem we've always had with the global namespace. Move this problem into the DOM, and the Cactus Jam is ready to eat.
On the other hand how best practice will be a DOM loads of nodes with multiple data attributes?

<div
data-jquery-pluginname-good-stuff="$(this).whatever()"
data-dojo-loadsync="thisNodeFile"
data-prototype-valueof="Object.prototype.valueOf=this"
/>

... I mean ... seriously, is this the HTML5 we are talking about? An empty element there just to make the anti semantic magic happen?

Dozens Of Different Ways

First of all querySelectorAll.
The document method we all were waiting for is finally 90% here and rather than use semantic and logic selectors to retrieve what we need and when we need, we prefer to make the DOM that dirty? Are we truly so monkeys that we cannot spot a list of songs and order them by their length that will be 99.9% of the time part of that song info and accordingly present in the DOM as context?
Where are classes? Where are external resources totally ignored by those users which aim is simply the one to surf quickly, or to crawl info? data-whatever?

Why We Don't Need data-*

First of all, classes. If we have a list of songs the class songlist in the single outer UL or OL node is everything we need to retrieve that list and order by title, duration, everything else present in that list, since in whatever grid we have ever used, we order by columns where we know the column content.
How can a user even decide to order by length if this length is not displayed?
How can he order by something not even showed?
It's like me in a shop asking to order by palm trees a list of sounds systems ... I think they'll look at me like a mad person and they would be right, don't you agree?
So, the semantic part of this attribute does not exist. The same example showed in the W3 draft is ridiculous for the reason I have already said. If the song length info is already in the DOM and properly showed we don't need redundant info for every user, we just need a good sort function for that list of songs and nothing else.

$.fn.sort = function(){
// proof of concept, not even tested
// written directly here in the blogger textarea
// don't try at home but please try to understand the logic
var li = Array.prototype.slice.call(this);
li.sort(function(a, b){
return
$(a).find(".length").text() <
$(b).find(".length").text() ?
1 : -1
});
return $(li).each(function(i, li){
li.parentNode.appendChild(li);
});
};
$(".songs li").sort();

Is above proof of concept that different from a $(".songs @data-length").sort() ? It's even shorter!

Map The DOM If Necessary

If we are struggling that much with this "so much missed data-*" we can still use the class to map common info ... how?

<ol class="songs">
<li class="map-0">Beyond The Sea</li>
<li class="map-0">Beside The Sea</li>
<li class="map-1">Be The Sea</li>
</ol>

If we need to attach properties into a DOM node we can still use the class attribute.
Semantic, classifying the node as a mapped one, not redundant, letting us provide more than once same kind of info for different nodes, and lightweight, avoiding data description provided by the mapped object.
In few words, and always via JavaScript, we can attach a file with this content:

var map=[{
length:"3m21s",
artist:"Nature"
},{
length:"2m59s",
artist:"You"
}];

For each node, when necessary, we could simply retrieve the associated map in this way:

function nodeInfo(node){
return map[
(
/(?:^|\s)map-(\d+)(?:\s|$)/.exec(node.className) ||
[0,-1]
)[1]
];
};

And that's it. Each mapped node will return an object or undefined when nodeInfo function is called. Again, all this stuff is a proof of concept, but can we agree that data-* is just the wrong response to solve a JavaScript problem that should be solved everywhere except into the HTML?

Tuesday, November 17, 2009

195 Chars To Help Lazy Loading

Update
removed named function expression

Update
I wrote events letters in the wrong place, please use the latest script

We have talked many times about performances, and not only here.
One common technique to speed up a generic online page is the lazy loading.
For lazy loading I mean:
  • runtime dependencies resolution via script injection, preferably including all dependencies we need in a shot rather than script after script
  • Google comments style, where code is evaluated when required but the size is still there
  • namespaced strings, an example I have explained via code in Ajaxian
  • whatever else, because sometimes we need this or that library only in certain conditions


What Is The Problem

If we would like to widely adopt this lazy load pattern, we should be aware about a "little detail": Firefox < 3.6 does not support document.readyState, an historically valid Internet Explorer feature adopted in HTML5 and present in basically every other browsers.
We can find the readyState description and the related bug in Mozilla domain.
Reading suggestions, I found quite pointless the long diatribe about:

Should it be "loading" or "interactive" before it will switch to "complete" ?

IMHO, who cares, as long as we can rely in the final statement: complete

Affected Libraries

I have no idea but for sure jQuery! The event.js file shows at line 857 this check for the bindReady event:

// Catch cases where $(document).ready() is called after the
// browser event has already occurred.
if ( document.readyState === "complete" ) {
return jQuery.ready();
}

The code goes on with common DOMContentLoaded checks and emulations.
The problem is that with such library where basically everything starts with an ready event:

$(function(){
// the ready event we are theoretically
// sure will be fired even if the
// code has been loaded after
});

every Firefox < 3.6 user will miss that code, plug-in, extension, whatever.
I have already informed jQuery dev ML about this problem but obviously they already know. John Resig told me that there is no guarantee the ready event will be fired if the page has been loaded.
Fair enough, I can perfectly understand John point which is: all jQuery supported browsers may not support document.readyState.
AFAIK, even if this is a good reason to avoid some obtrusive code, we all would expect consistency from a framework so if something worked even in IE I can't even think about Firefox problems.

The Solution


This missed FF feature could affect different libraries, not only jQuery.
We, as developers, could help every library author adding 195 uncompressed bytes, even less once deflated, as first stand alone piece of code ever in our page:

// WebReflection Solution
(function(h,a,c,k){if(h[a]==null&&h[c]){h[a]="loading";h[c](k,c=function(){h[a]="complete";h.removeEventListener(k,c,!1)},!1)}})(document,"readyState","addEventListener","DOMContentLoaded");

// NOTE: IE will never consider false s[o]==null

Since Firefox is usually updated automatically, all we need to do once we are sure people are surfing with version 3.6 or greater is simply remove above little line of code.

Explained Solution


// verify that document.readyState is undefined
// verify that document.addEventListener is there
// these two conditions are basically telling us
// we are using Firefox < 3.6
if(document.readyState == null && document.addEventListener){
// on DOMContentLoaded event, supported since ages
document.addEventListener("DOMContentLoaded", function DOMContentLoaded(){
// remove the listener itself
document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
// assign readyState as complete
document.readyState = "complete";
}, false);
// set readyState = loading or interactive
// it does not really matter for this purpose
document.readyState = "loading";
}


Conclusion

Being this a browser problem and not directly libraries related, it does not probably make sense to put this fix just for few months until next FF release. At the same time we can guarantee for Firefox users, and only if the library does not sniff the browser via this read only property, that lazy loaded stuff a la $(rockNroll) will work into our favorite browser too or we could simply rely and with our code into a readyState "complete" check to decide what to do (this is the real reason I have investigated more this problem, but this is another story coming soon).