Dissecting Dijit

By on November 13, 2007 11:05 am

Dissecting Dijit

The Dojo Toolkit is now an official release and we’re talking about new and changed features. In my previous post, I demonstrated Dijit’s brand new ability to construct widgets in markup. Now we’ll return to the more familiar Dijit Widget and see the culmination of the previous Dojo Toolkit betas.

Let’s start with a bare bones HTML document that includes the Dojo Core, a widget require statement, and a div to hang our widget. dojo.parser is here to parse any widgets in the HTML, and parseOnLoad=true enables automated widget parsing. An addOnLoad attachment ensures script execution only after the DOM is ready:





	

Here’s an example of, arguably, the simplest widget possible:

dojo.declare(
	"Simple",
	[dijit._Widget], {
		postCreate: function(){
			this.domNode.innerHTML = "Simple Widget"
        }
    }
);

So what is dojo.declare, anyway? Entire articles on dojo.declare define it in great detail. In essence, it builds a constructor that we can later turn into an object. The previous object just happened to be a widget that displays HTML. In fact, if you did this:

dojo.declare(
	"Simple",
	null,
	{ }
);

var obj = new Simple();

Here you would have created a JavaScript object, albeit with special Dojo goodness, like inheritance and extension syntactic sugar. For you Java guys, it’s a PODO or “Plain Old Dojo Object”. I was thinking of calling it a Definition Of a Dojo Object, but I digress.

So dojo.declare boils down to its class name (“Simple”), and any classes it may be extending. Our widget is extending the dijit._Widget class (the super-simple example used “null”, meaning it didn’t extend any classes). Our Simple widget is then followed by a mixed-in object of properties and methods which is where postCreate resides.

Notice we’re not using namespaces here. You may have seen the “acme.widget.Thinger” Dojo demos. It’s not explicitly required, but in doing it this way, we’ve created our widget constructor in the global namespace, which is considered poor form. This also doesn’t utilize Dojo’s packaging system. But it’s sufficient for the purpose of a few simple lines of code.

Now, let’s insert the dojoType in the markup and assign it to our widget:

Because we’ve required dojo.parser, and set parseOnLoad to true, the Dojo parser scans the document, finds the dojoType keyword, and converts that DOM element into a widget. The output is as expected:

Simple Widget

The same results happen if you create it programmatically. Note that if there were only programmatic widgets on the page, you wouldn’t need to require dojo.parser, nor set parseOnLoad in djConfig. A widget constructor takes two arguments; the first is a properties object, and the second is the DOM node that will be converted to the widget:

dojo.addOnLoad(function(){
	var obj = new Simple({}, dojo.byId("main"));
}

There’s an important change to Dijit from the 0.4.x release. Previously, you could specify any node, even document.body, and provide a third argument of “last” or “first”, and it would insert the widget in that node. This functionality is no longer supported, and instead the specified node gets replaced. If you wish to insert a widget in the body, say for a dialog window, you do as follows:

dojo.addOnLoad(function(){
	var obj = new Simple({});
	document.body.appendChild(obj.domNode);
}

In order, here are the methods that fire upon creation of a widget:

preamble()
Originating in dojo.declare, preamble is a new Dojo Core feature. It’s a pre-constructor accessory. By analogy, preamble is to constructor as postMixInProperties is to postCreate. Since preamble gets the same arguments as the constructor, you may extend another object, and jump in front of the constructor and change the arguments.
constructor()
Originating in dojo.declare, constructor has a new usage pattern. Previously, it fired last, which I didn’t find particularly useful nor accurate. It now fires early in the widget lifecycle, allowing early initialization with the arguments passed into the object. While more common in use, it’s not exactly necessary, as Dijit handles the job of converting your arguments into object properties.
postMixInProperties()
Originating in dijit._Widget, postMixInProperties is used more commonly by widget developers. That said, some of its duties are superseded by the addition of constructor and preamble. Its main purpose is firing after the properties have been set, but before the widget has been parsed and created. Pre-creation work on widget properties is typically done in this method.
postCreate()
Originating in dijit._Widget, postCreate is the “heavy lifter” of Dijit. This fires after creation, but before the widget is rendered to the page. At this time in the widget lifecycle, you have access to the widget’s nodes, so additional parsing, connections, styling, or even attaching more widgets is possible.
startup()
Originating in dijit._Widget, startup is somewhat misunderstood. startup doesn’t fire unless the widget is a child of another widget. And then it only fires after it, and all of its siblings have been created. Then they all fire together.

Knowing this, let’s create a widget to test its creation methods firing order.

dojo.declare(
	"Simple",
	[dijit._Widget], {
		preamble: function(){
			console.log("preamble - args:", arguments);
			arguments[0].name = arguments[0].prefix 
				+ arguments[0].suffix;
		},
		constructor: function(){
			console.log("constructor (", 
				this.name, ") - args:", arguments);
		},
		postMixInProperties: function(){
			console.log("postMixInProperties (", 
				this.name, ")");
		},
		postCreate: function(){
			console.log("postCreate");
			this.domNode.innerHTML = 
				this.name + " Simple Widget"
		},
		startup: function(){
			console.log("startup");
		},
	}
);
dojo.addOnLoad(function(){
	w = new Simple({prefix:"Foo", 
		suffix:"Bar"}, dojo.byId("main"));
})

Firebug Output

Note what’s happening here. Preamble occurs first, grabs the arguments object, edits it, and inserts a name key which the constructor acquires when grabbing the arguments. When constructor fires, this.name as a property has not been set, although it is set and available for the next two methods. Also, as stated earlier, startup was never fired.

Let’s do the same thing with the same widget in markup. In markup, you pass the parameters into the constructor as attributes in the DOM node.

Firebug Output

Whoa! What happened here? The properties didn’t get set. But after looking at all of the tests, this is how it’s done.

Time to put your Java hat on. The Dojo parser is only looking for class properties that you’ve preset. We need to “declare” them:

dojo.declare(
	"Simple",
	[dijit._Widget], {	
		prefix:"",
		suffix:"",
		preamble: function(){
			...other methods...

Firebug Output

Now we’re back on track. A word of caution on your “declared” variables. The Dojo parser is doing type-checking too. Currently, prefix:"" is being changed to the string “Foo” giving us the following output:

FooBar Simple Widget

Change it to “prefix:false”, and here are your results:

trueBar Simple Widget

Finally, let’s use some HTML in the widget. Keeping it simple, we’ll add this line:

templateString: "
${name}Widget
"

dojo.parser reads in this text. It catches instances of “${…}” and replaces it with the corresponding property. This is another change from Dojo 0.4.x. Notice the use of name in the template doesn’t include the keyword this!

To enable dojo.parser within a widget, we need to mixin the Dijit Template class, making the final code look like this:

dojo.declare(
	"Simple",
	[dijit._Widget, dijit._Templated], {	
		templateString: "
${name}Widget
" prefix: "", suffix:"", preamble: function(){ console.log("preamble - args:", arguments); arguments[0].name = arguments[0].prefix + arguments[0].suffix; }, constructor: function(){ console.log("constructor (", this.name, ") - args:", arguments); }, postMixInProperties: function(){ console.log("postMixInProperties (", this.name, ")"); }, postCreate: function(){ console.log("postCreate"); } } );

Firebug Output

With Dojo Toolkit 1.0, Dojo Core, and the new Dijit, widgets are now simpler and more powerful than ever. Demystifying them helps you get past the “how to do Dojo” and into “how to write cool widgets” mode.

Comments

  • I disagree slightly on your assessment of the dijit._Wiget.startup function. It is only automatically called by dojo.parser. If you create a widget programmatically, then you *should* call w.startup() yourself after creating it. You can see examples of this in the Dijit tests: dijit/tests/_programaticTest.html, dijit/tests/layout/test_LayoutCode.html, etc..

    Maybe the Dijit developers can chime in on this personally?

  • Pingback: Ajaxian » Dissecting Dijit: Dojo Widgets()

  • Hi David,

    I looked at the two tests you mention – the programmatic unfortunately is not working at the moment, so I looked at the layout test. I commented out the w.startup() line, and the test still worked.

    I’m not a Dijit commiter, so I can’t say exactly what the intention of this method is other than going by my own tests and the code comments.

    In the layout test, there is in fact a comment asking if this is how startup() should work. In the _Widget.js, the comment seems more confident: “Called after a widget’s children, and other widgets on the page, have been created.” And I confirmed this functionality in my tests. Unfortunately, for the sake of keeping my post relatively succinct, I edited out my examples using children. (I will possibly include those tests in later blog). And it did work as stated – without children, it didn’t fire, with children it did.

    I recently took over some Dojo code from a client, who was using startup to “start the widget”. It caused problems when a widget had children, because the startup would be natively called, then programmatically called – calling it twice, and at times creating duplicate code.

    But you’re right, there does seem to be a bit of a mixed message among the Dijit commiters involving startup().

  • For programmatic widgets/Dijits, my understanding is that startup should be called manually for anything that contains other widgets or has children widgets. This should be done on the parent/container widget, after all children are added, and is a performance optimization to prevent Dojo from calling it on each widget individually.

  • Hi,

    Thanks for this informative article. One of the problems I had while writing a new Dojo widget was how do I make it load properly? I am using Dojo 1.0 from AOL CDN, and I wrote a tooltip widget (http://nileshbansal.blogspot.com/2007/09/enhanced-ajaxified-tooltips-using-dojo.html). But

    dojo.require(“my.new.Tooltip”)

    failed to load the code for the Tooltip properly, and I always got error that the package “my.new.Tooltip” new found. It would be helpful if you discussed those issues as well!

    thanks

  • Hi Nilesh,

    This topic was covering some fundamentals and the new changes and features. I do have plans for a future blog that covers extending widgets, child widgets and packages.

    Troubleshooting your problem may be best for the Dojo IRC or forums, but briefly, I would say to make sure you’ve included the module path to your files:
    dojo.registerModulePath(“my_js_files”, “../js”);

    Then include a folder for your widgets in the “js” directory called “widgets”.

    And if you put a tooltip file there it would be accessed:

    dojo.require(“my_js_files.widgets.Tooltip”)

    I would be careful about using the word “new” in your code or file path.

  • Bill Keese

    Hi all,

    Yah, startup() is the hardest one to understand. The reason that method exists at all is that sometimes a widget has some code that needs to run after other widgets on the page have finished being created. For example TabContainer needs to communicate with it’s child widgets.

    When you call startup() on (for example) a TabContainer it will trickle down to all the children of the !TabContainer, and to their children, etc. startup() needs to be called on each “top level” widget, where “top level” technically means that the widget doesn’t have a “parent”, where parent is used in the strict sense of _Container/_Contained relationship, not in the loose sense where a ContentPane has some random HTML that somewhere inside of it contains another widget.

    Bill