Dojo 0.9 Power Tools: <script type=”dojo/method”>

September 21st, 2007 - by Alex

Dojo 0.9 is a different beast than any Dojo before it. It’s smaller, it’s (much) faster, and it “feels” different in many ways. As Jon Sykes noted, “everything is familiar yet strangely different”. I could go on for hours about why dojo.query() is completely indispensable or how having dojo.behavior as a part of Core changes everything, but there’s some fun stuff up the stack too. In particular, new parser bears mention, and a bit of explanation. In addition to now letting you build instances of any class (not just widgets), it also lets you configure their behavior as well as their properties.

For a long time systems like Flex and Laszlo have had a corner on naturally mixing behavior with markup. HTML’s native <script> tag doesn’t provide any context, and worse, can’t provide a way to be triggered by a particular event or action (outside of proprietary extensions). Several use-cases are important:

  • Scoped execution
  • Event-driven execution
  • Attachment vs. replacement

In Dojo 0.9 we took a hard look at them and devised <script type="dojo/method"> and <script type="dojo/connect">. Lets take a quick tour and show how it makes writing event handlers natural, building new widgets a snap, and over-riding built-in behavior trivial.

Scoped, Event-Driven Execution

In regular-old HTML script tags, the keyword this has no relationship whatsoever with the location of the <script> tag to it’s parent elements in the document. This makes some tortured sense, but when we’re building large component hierarchies, trying to find elements by id (or CSS selector) and then tying some customized behavior to them can seem like more trouble than it’s worth. In Dojo 0.4.x, you might have done something like this:

<div dojoType="dojo:ContentPane" widgetId="cp"
    onDownloadStart="dojo.debug(’starting…’);"
    onDownloadEnd="dojo.debug(’…ended’);">
 

Which worked fine for one or two expressions in reaction to an event, but what about when you wanted to do something more complicated? The inline style kinda falls apart and you were forced to wire things up more manually (still using 0.4.x APIs):

<script type="text/javascript">
   dojo.addOnLoad(function(){
      var cp = dojo.widget.byId("cp");
      dojo.event.connect(cp, "onDownloadStart",
           function(){
               dojo.debug("started downloading:", cp.href);
               // advanced behavior here
           }
       );

       // more configuration…
   });
</script>
<div dojoType="dojo:ContentPane" widgetId="cp">

 

Do this multiple times and you’ll quickly start looking for the manual to figure out how to build widgets programmatically so that you don’t have to try to match up when your script executes with when the widgets are available. Also, by moving to that manual style of wiring, the this keyword now points to the “wrong” place. In the inline example this mapped to the widget, but without extra work, this in the second example points to the global object. Not really what we want. In 0.9 we can write it much more naturally:

<div dojoType="dijit.layout.ContentPane">
    <script type="dojo/connect" event="onDownloadStart">
        console.debug("started downloading:", this.href);
        // advanced behavior here
    </script>
    …
</div>
 

The type="dojo/connect" lets us delay execution of the script block because the browser will skip it when it executes scripts of types it understands (like text/javascript). When the parser creates our widgets, however, it knows to look for scripts of this type and converts it into an event which is attached to the default behavior of onDownloadStart. We can also note that this now points to the widget instance directly. No more boilerplate to manage ids or look up widget instances. The nesting of the <script> block inside of the widgets definition on the page is enough to set up the relationship.

The new event property is also interesting. Instead of having to manually call dojo.connect() to wire a function to a behavior, we can specify on the <script> itself what the function we’re defining should do. In fact, thinking of these kinds of <script> blocks as defining functions is perhaps the best way to visualize what they do and how they work.

If all we want to do is log out the href property, Dojo 0.9 still lets us do that with the inline style we saw first:

<div dojoType="dijit.layout.ContentPane"
    onDownloadStart="console.debug(this.href);">

 

Looking at that harder, though, it’s not clear just by looking at it whether or not specifying a handler as an attribute replaces (as with regular value attributes) or connects to an existing behavior. Using the new <script> types, we can also clarify that too.

Connect Or Replace?

The difference between &tl;script type="dojo/method"> and <script type="dojo/connect">, simply stated, is that dojo/method replaces the specified event handler whereas dojo/connect is added as a listener to the original behavior. Simple.

We can then take our earlier example and change what this particular widget returns from its onDownloadStart method by specifying our handler as a method:

<div dojoType="dijit.layout.ContentPane">
    <script type="dojo/method" event="onDownloadStart">
        console.debug("started downloading:", this.href); // log it as before
        // previously returned the uber-boring this.loadingMessage
        return "abandon all hope ye who enter here!";
    </script>
    …
</div>
 

Seems straightforward. But what about if we don’t specify an event? Glad you asked. Luckily, the new system treats this as though you are executing code just after the instance is created, analogous to dojo.connect(this, "constructor", ...), assuming that were even valid (which it’s not). This means we can use these new <script> types as de-facto instance configuration blocks.

Lastly, to head off the “but what about performance?” questions at the pass, the new Dojo parser is much much faster than the 0.4.x system and as a result adding methods and connections this way often falls into the noise. There is some overhead to locate and create function objects out of the script bodies, but it’s far outweighed by the usability advantages and general 0.9 improvements in parser performance.

Happy hacking!

Bookmark and Share

7 Responses to “Dojo 0.9 Power Tools: <script type=”dojo/method”>”

  1. [...] Alex has written about some new script tag support added in Dojo 0.9: [...]

  2. [...] Alex has written about some new script tag support added in Dojo 0.9: [...]

  3. Gino says:

    Alex,

    It is great to see this feature in Dojo. It took me back to when I first started playing with LZX (laszlo). I loved the way you could override and add new methods right on the instance of the widget.

  4. [...] Both examples create an equivalent widget, and every property that can be set via markup is also available to be passed via the configuration property bag that every widget expects when calling its constructor function via the “new” keyword. This lightweight convention, in conjunction with the alternate script types, allows for full feature parity between markup and programmatic construction of widgets. Both are first-class citizens in Dojo 0.9/1.0 and you can build your entire app with programmatic construction if you so choose. [...]

  5. [...] We’re already familiar with Dijit’s capabilities to render widgets in markup. This is a powerful way of providing widgets in your page with about the same effort as styling with CSS. Recently, Alex Russell blogged about new Dojo alternate script types – a way of attaching code via markup through a creative use of the script tag’s type attribute. One of the newest features in the Dojo Toolkit, dijit.Declaration, extends that approach by giving us a way to define widgets in markup. [...]

  6. [...] Many, groundbreaking changes have gone into the Dojo .9 & 1.0 release cycle ranging from complete reorganization of the toolkit & major performance increases to innovative new ways to approach AJAX programming itself. [...]

  7. Jing Zhang says:

    Very helpful Alex! I did sth like this in my code:

    dojo.byId(updateLinkIdHolder).innerHTML = dojo.byId(’contactResponse’).innerHTML;

Leave a Reply