Degrading Script Tags, an aside.
A few months ago John Resig published a 'degrading script tag' post, which outlined a very simple pattern for better utilizing a script element. I liked it, it has always bothered me a simple construct like:
<script src="foo.js"> foo(); // woohoo! </script>
doesn't "just work". It makes perfect sense: load foo.js and execute some code when the script is ready. If the script fails or doesn't exist, don't execute the code. It is the perfect pattern for progressive javascript.
This makes perfect sense for Dojo as well: load dojo.js and execute the content therein if everything is OK. It would be great to support a syntax like:
<script src="dojo/dojo.js"> dojo.require("dijit.layout.TabContainer"); dojo.addOnLoad(function(){ // make some tabs, and stuff. }); </script>
Not satisfied with John's scripts[scripts.length - 1] solution, and [at the time] lacking a better idea of my own, I dismissed the idea as novel. A few days ago it dawned on me: Dojo can do this already. I quickly threw together a hack. All we need is the script element.
Base Dojo is broken into several files (though works transparently between the "development" version and the "built" base, aka: dojo.js), and uses a sophisticated build system to make a custom deployment as simple as:
./build.sh profile=myStuff
My quick solution involves suggesting some incredibly simple changes into some deep bits of the Dojo loader. Some background: Dojo 1.x supports an optional "djConfig" parameter placed on the script element loading the library. eg:
<script src="dojo.js" djConfig="parseOnLoad:true"></script>
As a result of the lookup involved in having maintained this behavior for years, Dojo "knows" the element loading dojo.js (or dojo.xd.js), but only in a browser environment. We can add a single line to the hostenv_browser.js, saving a reference to our own script node:
// after we are in the regexp match looking for ourself: d._scriptElem = scripts[i];
Then, we create a new base module to process the content if it exists:
dojo.provide("dojo._base._loader.self"); // tell package/build system about this module/plugin (function(e){ // execute this content [if] found in the script tag for dojo.js e && e.innerHTML.length && dojo["eval"](e.innerHTML); delete dojo._scriptElem; // cleanup? })(dojo._scriptElem);
Hooking the two parts together is another one-liner: add a conditional require to the "base layer" file. As the last line to ensure it is the last piece of code executed by dojo.js:
dojo.requireIf(dojo.isBrowser, "dojo._base._loader.self");
If we are building for non-browser situations the code is ignored. We can go a step further and save a few bytes by excluding the whole module as an optional build parameter:
//>>excludeStart("noself", kwArgs.noSelf); dojo.requireIf(dojo.isBrowser, "dojo._base._loader.self"); //>>excludeEnd("noself");
This way, if we build dojo.js with noSelf=true, the code never even becomes a consideration for base, and the innerHTML of our degrading script tag is simply ignored.
So my solution/hack here fixes a couple of the issues raised in John's blog comments, such as XHR loading of the script, delayed or deferred loading of the script, and most importantly the assumption the script would be the last in the page. I've thrown together some simple tests double checking delayed loading and basic success, and made up a small shell script which will fetch Dojo, apply the patch, and run a build (giving you a dojo.js with these behaviors, though the 'development' dojo.js works identically). I don't agree with the assumption John made about the code being executed within a $(document).ready function, as there is a lot of Dojo functionality that doesn't require the DOM to operate (eg: dojo.require) so I omitted that bit.
I'm torn on if this is worthwhile for Dojo or not, as we try to avoid any ubermagic that goes against what trained developers are expecting ... but I agree with John: the syntax is quite sexy and ridiculously clear as to what is and should be happening.
My initial pass at this was slightly more sloppy, but misguided. I had a nasty bit of code in self.js like:
var how = "textContent" in e ? "textContent" : "text" in e ? "text" : "innerHTML" in e ? "innerHTML" : null; e && e[how] && dojo["eval"](e[how]);
But that was just me being neurotic from my experience of injecting raw code into a newly created script element. Turn's out .innerHTML works across browsers for reading the current content.
Interestingly, Opera stores the src="" value in .text and the actual innerHTML in .textContent, whereas IE only has .text. I even made a log from my findings (before I noticed John was only using .innerHTML, at which point I stopped populating my mini ppk-inspired-compatibility table)
... so if the use of eval() bothers you, I've worked up a small bit of code to create a script and safely inject some JavaScript within (where I copy/pasted my sloppy self.js lookup code from):
(function(d){ d.noteval = function(/* String */code){ // summary: Execute some javascript. if(!code) return; var e = d.create("script", { type:"text/javascript" }), // jump through the cross-browser hoops: how = "text" in e ? "text" : "textContent" in e ? "textContent" : "innerHTML" in e ? "innerHTML" : "appendChild" ; if(how == "appendChild"){ e[how](d.doc.createTextNode(code)); }else{ e[how] = code; } return d.doc.getElementsByTagName("head")[0].appendChild(e); }; })(dojo);
I've used this in a slightly more verbose version to support a very sloppy injection of JavaScript in XHR content via the dojoType attribute and utilizing the dojo.parser. It could probably be reduced. I should make a table.
Webmaster:
Please e-mail me your contacts. I have a question webmaster@spottovo.ru” rel=”nofollow”>……
Thank you!!!…
10 June 2010, 3:12 amNATHAN:
Buy:Cialis.Tramadol.Viagra.Levitra.Viagra Professional.Cialis Soft Tabs.Viagra Soft Tabs.Propecia.Soma.Super Active ED Pack.Cialis Professional.Zithromax.VPXL.Viagra Super Force.Cialis Super Active+.Maxaman.Viagra Super Active+….
21 July 2010, 10:50 am