Posted in Dojo Cookies
What is a _Widget
There has never been a more generic term than “widget”. As a teen in economics class our theoretical compaines sold widgets, and almost anything on earth without description falls into a “thinger” category … I’m here to give API stability to the word “widget”, and poke around the idea of “template” in Dojo. I will call it Dijit.
Torrey Rice recently explored Safari 3.1’s new CSS Animation support on the SitePen blog. While this kind of upcoming technology is wonderful, it doesn’t help to make my pages sexy now. I want simple FishEye effects today — using my existing technology — and it has to perform well everywhere. Even Internet Explorer.
Thankfully, I’d already done this, and Dojo made it really easy. I’m going to walk through my FisheyeLite DojoX component as an example use of dijit._Widget. You can look at the full source of the FisheyeLite as reference, but we’ll be generating most of the code along the way.
Start by setting up your module. There is no namespacing cookie just yet, but a slightly outdated _Widget tutorial in the Dojo Book explains the process pretty well.
This file lives in dojox/widget/FisheyeLite.js, so we’ll setup our provides and declaration to reflect that:
dojo.provide("dojox.widget.FisheyeLite"); // stuff needed: dojo.require("dijit._Widget"); dojo.require("dojox.fx.easing"); // declare it: dojo.declare("dojox.widget.FisheyeLite", dijit._Widget, // inherit from _Widget { postCreate: function(){ // summary: my widget setup this.inherited(arguments); } });
We can tie into any _Widget method by simply defining a function, and calling this.inherited(arguments) to execute the inherted method. By using postCreate we’re tying into a vital step in the Dijit lifecycle, and are injecting custom code.
At this point we can safely var fisheye = new dojox.widget.FisheyeLite({},node);
, though nothing will happen. We need to setup some behavior, and add code to do what we need. We’ll assume all the rest of the code is contained within the dojo.declare
object properties (the third argument).
The most convenient thing that comes from using _Widget as a base class: this.connect()
. When called, it executes dojo.connect
automatically hitched to the widget instance and stores the handle. When _Widget.destroy() is called, the connections are automatically cleaned up for us. In postCreate we’ll add a few connections to the node we used to create the widget, and add some methods to run when the events fire.
{ postCreate: function(){ this.inherited(arguments); this.connect(this.domNode,"onmouseover","show"); this.connect(this.domNode,"onmouseout","hide"); this.connect(this.domNode,"onclick","onClick"); }, show: function(e){ // show the effect }, hide: function(e){ // remove the effect }, onClick: function(e){ // stub to override or hook into for user code } }
A cavet of using this.connect
is you are unable to manually disconnect the connection. If you need to start and stop your connections, you’d need to use dojo.connect directly:
this._savedHandle = dojo.connect(this.domNode,"onclick",this,"onClick"); // and later: dojo.disconnect(this._savedHandle);
The variable this.domNode
is another benefit / side-effect of _Widget. It’s the top-level node used to create the widget instance. It’s just a pointer to the domNode, so we can use it as such (connect to it’s dom Events, appendChild, etc). This concept will be expanded on when we get into dijit._Templated. this.domNode
is created if no node was passed the the creation, which is why after making a new _Widget (dijit) you have you place it somewhere if it wasn’t in the dom already:
// create a thinger, and append it to <body> tag var foo = new my.Thinger({ prop:"erty" }); dojo.body().appendChild(foo.domNode);
At this point, we have a clean widget. A basic “getting started” custom Dijit example. If we want to allow a user to set options when they instantiate the Fisheye from markup we need to define those properites in our class. Dijit typically lists them out before the first piece of real code in a class:
dojo.declare("dojox.widget.FisheyeLite", dijit._Widget,{ easeIn: "dojox.fx.easing.elasticOut", easeOut: "dojox.fx.easing.bounceOut", durationIn: 1200, durationOut: 500 // ... snip, postCreate et al come next: });
This allows us to set some default properties and variables to reuse, and allows the user to override those settings during instantiaion.
in markup:
<div id="foo" dojoType="dojox.widget.FisheyeLite" easeIn="dojox.fx.easing.bounceIn">Foo!</div>
from code:
var foo = new dojox.widget.FisheyeLite({ durationOut: 1200, easeIn: "dojox.fx.easing.easeOut" },"foo");
or from code the new fun 1.1 / dojo.query way:
dojo.query("#foo") .instantiate(dojox.widget.FisheyeList,{ easeIn: "dojox.fx.easing.bounceIn", durationOut: "1000" });
Inside our widget code, we can reference these settings anywhere. Calling _Widget.destroy() on our instance will cleanup any this.connect
’s, and instance variables we created.
For the sake of simplicity in this article, I’ll spare you the walkthrough of the _makeAnims
function in the FisheyeLite. Though if you look at the code, _makeAnims creates two animations based on the passed properties:
parameter, and the behavioral .connect
’s we created in postCreate call play()
on whichever animation is needed. Its really simple, just beyond the “basics of _Widget”. The FisheyeLite is an excellent example of a very lightweight _Widget, with one heavy function only ever called once.
Now that we have the basics of _Widget worked out, we’ll move on into dojo.declare
’s mixin. Above, we created a class based on _Widget, which provided us with some great convenience functionality. Changing the baseclass (above: _Widget) to an array of objects causes declare to use the first object as the baseclass and each subsequent object as a mixin
. Each mixin
is merged into the base class in order in the array. The most common occurance of this in Dijit looks a lot like:
dojo.provide("my.TemplatedThinger"); dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); dojo.declare("my.TemplatedThinger", [dijit._Widget, dijit._Templated], { templatePath: dojo.moduleUrl("my","templates/Thinger.html") });
Neil already covered why templatePath:
is cool, and what happens when you use it, so I’ll skip right on into “what mixing dijit._Templated actually does for us”. The crash course of Dijit Templating.
When the parser runs across the dom (using our my.Thinger class above):
<div dojoType="my.Thinger">Show foo.jpg</div>
it’s left in tact — Meaning the resulting dom is just a div with a .innerHTML of “Show foo.jpg” though our connections and whatnot have been applied.
When the parser runs across the dom:
<div dojoType="my.TemplatedThinger">Show foo.jpg</div>
we see something different happen. That div is replaced with the contents of templates/Thinger.html
, and all sorts of other magic happens along the way. The magic is in the template markup. The most important rule about templates is we’re only allowed one top-level node. This node is what is placed in the dom in leui of the source node (the one with the dojoType, or the node we passed via new Thinger({},node);
), so it only makes sense we’re only allowed one. We can, however, put as much content as we can imagine within that node and use some really powerful automation to make it even easier.
Let’s start with a simple node-replacement template:
<div class="myThinger"> <div class="thingerHeader">Yep.</div> <div dojoAttachPoint="containerNode"></div> </div>
Anytime we make a TemplatedThinger
, a div with class=”myThinger” is created, and the resulting dom is replicated for all instances. Without any styling, all the above template will produce is “Yep” and “Show foo.jpg” on two lines. Our very most basic template introduces the first of _Templated magic, the “dojoAttachPoint”. Setting a dojoAttachPoint will create an instance variable pointing to the domNode the attribute was found on. We can access this.containerNode
from anywhere within our widget code, and point to that node in the template.
“containerNode” is a very special node, much like “domNode”. In the above template, the node with class=”myThinger” is accessible as this.domNode
by default. In _Templated, “containerNode” is use by the parser as a place to put content found in the original dom of a source node. “Show foo.jpg” (being the only content in the original dom) is placed in this.containerNode
, resulting in “Yep”,”Show …” on two lines.
We can invent attachPoints, and name them what we like:
<div class="myThinger"> <div dojoAttachPoint="wrapperNode"> <div> <h1 dojoAttachPoint="headerNode">${title}</h1> </div> <div dojoAttachPoint="containerNode"></div> </div> </div>
Now, this.wrapperNode
is a reference to the overall wrapper around our containerNode
and a new heading element accessible as this.headerNode
. Same stuff, and really convenient when you get to actual coding. But wait! I see bling$.
_Templated does basic string replacements using a ${varname}
syntax. Considering the two instances of my.TemplatedThinger:
<div dojoType="my.TemplatedThinger" title="Hello Templates">Same thinger one</div> <div dojoType="my.TemplatedThinger" title="Okay I Get it, Move on">Another thinger</div>
each will have it’s title=”" attribute parsed, and injected into the template. ${title}
is replaced by any content in the attribute. the key is: the attribute must exist in the one of the mixins or your own custom code either as a default, or a null value:
dojo.declare("a.Thinger", [dijit._Widget, dijit._Templated],{ foo:"A default foo", bar:"", templateString:'<div><h1>${foo}</h1><div>${bar}</div>' });
this.foo will default to “A default foo” for all instances, unless overridden. bar will be blank unless given a value. The lack of a dojoAttachPoint="containerNode"
in the small template above will cause any content inside the original dom to be orphaned, and will cause weird things to happen. In a pinch, I just create a hidden containerNode:
<div> <div dojoAttachPoint="headerNode">${title}</div> <div dojoAttachPoint="containerNode" style="display:none"></div> </div>
You get the added convenience of having access to the original content, and never have to show it to the client.
The last big secret about _Templated is dojoAttachEvent
. This attribute, when specified in a template, will automatically connect and scope an event to itself. For example, a template named Foo.html
:
<div> <h1 dojoAttachPoint="headerNode" dojoAttachEvent="onclick: _click">${title}</h1> <div dojoAttachPoint="containerNode"></div> </div>
And some widget code:
dojo.declare("some.Thinger", // aka: "A templated widget" [dijit._Widget, dijit._Templated], { title:"", // after build, this is templateString: templatePath: dojo.module("some","templates/Foo.html"), _click: function(e){ // the node this.headerNode got an onclick event if(this.headerNode == e.target){ console.log("yep."); } } });
Here, the dojoAttachEvent is the equilivant of calling:
this.connect(this.headerNode,"onclick","_click");
in some method of your Thinger class, though is done automatically for you, and eventually translates to less bytes on the wire by using the attachEvent method. As mentioned about this.connect, called thinger.destroy() will cleanup any listening events before removing the nodes created by the template.
That’s the basic’s on _Widget and _Templated, hope this helps. Here’s to making some really awesome widgets!
One important note about Dojo / declare / mixins / templates which is worth reading but not noting in this cookie: Understand objects and arrays and how they relate to Class prototypes.
Here’s a sample of a new “templated widget” I created, though it’s not fully ready for public consumption. Behold, the Image Skewer
Tags: Dijit, dojo.declare, DOM, templates, Widgets