Chapter 12. Dijit Anatomy and Lifecycle
Like object-oriented concepts from any other programming paradigm, Dojo widgets—dijits—follow particular patterns for lifecycle events such as creation and destruction, are composed according to a particular an anatomical style, and are described by a somewhat specialized vocabulary. This chapter provides a summary of these topics with an extended discussion on the fundamentals of custom dijit design.
Dijit Anatomy
Although you already know that dijit is short for "Dojo widget,"
it's helpful to elaborate just a bit before we proceed any further. To
be precise, a dijit is any Dojo class that inherits from a
foundational class from Dijit called _Widget. This class is part of the toolkit's
foundational dijit module, so the
fully qualified name of the class is dijit._Widget. There are several other
foundational classes from Dijit that you'll learn about, but _Widget is the one that provides the primary
ancestor for any dijit.
As you learned in Chapter 10, Simulated Classes and Inheritance, dojo.declare saves you from writing a lot of
mundane boilerplate; dijits follow suit by tucking away a lot of
complexity in classes like _Widget.
As you're about to see, there are a number of method stubs that you
can override to achieve custom behavior, as opposed to engineering
your own boilerplate.
Tip
You may be wondering why the _Widget class is prefixed with a leading
underscore. When used in relation to dijits, the leading underscore
almost always means that it should not be instantiated on its own.
Rather, it is usually used as a superclass for an inheritance
relationship.
Let's start out our discussion on dijits with the familiar
constructs that define a dijit on a physical level—the HTML, CSS,
JavaScript, and other static resources that you've been developing
with all along. Then, with both feet firmly planted, we'll dig deeper
into the dijit lifecycle model by building upon your knowledge of
dojo.declare and work through a
number of increasingly complex code examples that involve the design
of a custom dijit.
Web Development Review
As anyone who's ever touched a computer knows, HTML is the de facto standard for displaying information in a web browser. You can standardize headings, paragraph division, form fields, and generally provide just about any kind of markup that's useful for displaying textual information and images. Still, HTML alone isn't very pleasing to the eye: there's no nice formatting involved, and the content is static. The overall structure of a page is quite simple, and it doesn't change in response to user interaction. Given what we've come to expect over the years, the web would be intolerably boring with HTML alone.
Bring in a dash of CSS, however, and the scene changes significantly. Suddenly, the aesthetic nature of the page improves. Whereas HTML alone provides content with very little visual appeal, CSS adds value by improving a page's layout and typesetting. But style alone still results in a static page that leaves the inherent dynamism of human interaction longing for something with a little more life. You could create some nicely typeset pages with HTML and CSS, but that's about it.
JavaScript provided the dynamism that styled HTML so sorely lacked and gave rise to DHTML, which fueled the increasingly interactive online experience that blossomed into this modern era of rich Internet applications. JavaScript brings a web page to life and enables that sheer contentment we enjoy when a simple mouse click, selection in a combo box, or casual keystroke makes you wonder if the computer is reading your mind.
Although we've all come to know a well-designed, interactive web page when we see one, the experience itself can still be quite difficult to achieve; the JavaScript that controls the HTML and CSS can often be quite complex, and even the cleverest of implementations may not be maintainable or especially noteworthy. The central issue at stake is that the HTML, CSS, and JavaScript can be difficult to integrate into a single, cohesive framework. With little cohesion amongst them in ad-hoc designs, synergy is difficult to achieve, and massive amounts of energy is lost in implementing the not-so-interesting boilerplate. Unfortunately, this laborious process can quickly drain motivation and creativity from the parts of the application that really matter.
Dijits to the Rescue
Fortunately, dijits make matters much easier by providing the foundation upon which you can build a more complex design. They unite the HTML, CSS, and JavaScript into a unified medium, and although not perfect, dijits allow you to think in an object-oriented context much more quickly than you would without them: the end result is that you can quickly get to the heart of your own application before so much of the energy and creativity dries up. Whereas you previously had to provide your own means of uniting the HTML, CSS, and JavaScript, Dojo now does that tiresome work for you, and leaves you to get to the good stuff more quickly.
Just like standalone classes, dijits are self-contained inside of a single directory that corresponds to a namespace. In addition to a single JavaScript file, however, the directory also contains dependencies such as image file and stylesheets. The inherent familiarity of a directory structure provides an innate portability that makes it trivial to share, deploy, and upgrade dijits. Maintenance is also eased because there are no binary formats to unravel, and each component of a dijit can be checked into a version control system like Subversion as a separate file.
While all of the resources that compose a dijit could just be thrown into a single directory in the one-big-pair-of-clown-pants approach, Figure 12.1, “Anatomy of a dijit on disk” displays a common convention for laying out a dijit on disk. Basically, the convention is to just compartmentalize the various facets into subdirectories to make things more manageable.
Dijits unite the HTML, CSS, and JavaScript that are so very central to any web development effort and provide you with a single, unified means of structuring the creativity required of your own application. In the end, you'll save time, effort, and likely obtain a more efficient design. Note that the layout for a minimal dijit that doesn't include a template or CSS is simply a directory with a JavaScript file.
The layout shown in Figure 12.1, “Anatomy of a dijit on disk” shows the template contained in its own separate HTML file, and this setup is typical during the development cycle because it allows members of the development team to work on the template, CSS, and JavaScript files separately.
Fetching the template requires the JavaScript engine to issue a synchronous call back to the server; however, Dojo provides a wonderful way to optimize that synchronous call out the picture entirely: you can include the template as an inline string that's inside of the JavaScript file. Plenty of examples are coming up that illustrate how simple it is to make this happen.
Dijit Lifecycle Methods
Let's now turn our attention to the central dijit lifecycle
methods that _Widget provides. As
you're about to see, _Widget packs
a lot of power with only minimal effort required on your part. By
simply including it as the primary superclass ancestor in the
inheritance hierarchy, your subclass has immediate access to the
standard dijit lifecycle methods it provides, and you may override any
of these method stubs to produce custom behavior during the
construction and destruction of the dijit.
For example, _Widget provides
stubs to override before a dijit appears on screen, immediately after
a dijit becomes visible, and when a dijit is just about to be
destroyed. Each of these choke points can be immensely valuable times
to synchronize with the server-side model, explicitly destroy objects
(so as to avoid well-known memory leaks), or do some tactical DOM
manipulation. Regardless of the particulars for each and every
situation, this is boilerplate that you don't have to write; it's
already in place, and you can use it if and when you need it.
To introduce what _Widget
offers, Example 12.1, “Subclassing from _Widget” shows a
simple class that defines a class inheriting from _Widget and overriding the key methods
involved in construction and destruction to produce debugging messages
in the Firebug console. As you know from the last chapter, this file
would be named Foo.js, and would be located in a
directory named after the module—nothing more than a class mapped to a
namespace.
The key point to observe in this example is that you override
the inherited methods from _Widget
just like you would expect. Take a look, and then we'll review each of
these methods in more detail.
Example 12.1. Subclassing from _Widget
dojo.require("dijit._Widget");
dojo.addOnLoad(function( ) {
dojo.declare(
"dtdg.Foo", // the subclass
dijit._Widget, // the superclass
{
/* Common construction methods in chronological order */
constructor : function( ) {console.log("constructor");},
postMixInProperties : function( ) {console.log("postMixInProperties") ;},
postCreate : function( ) {console.log("postCreate");},
/* Your clever logic goes here */
talk : function( ) {console.log("I'm alive!");},
/* Canonical destructor, implicitly called via destoryRecursive( ) */
uninitialize : function( ) {console.log("uninitialize");}
}
);
});
foo = new dtdg.Foo( );
foo.talk( );
foo.destroyRecursive( ); /* Calls uninitialize, among other things */When you run that example, you should notice the following output in the Firebug console:
constructor postMixInProperties postCreate I'm alive! uninitialize
The _Widget Lifecycle
To come full circle to the discussion about the creation
pattern dojo.declare provides
from back in Chapter 10, Simulated Classes and Inheritance,
here's how the _Widget lifecycle
plugs in:
preamble(/*Object*/ params, /*DOMNode*/node)
//precursor to constructor; can manipulate superclass constructor args
constructor(/*Object*/ params, /*DOMNode*/node)
// fire any superclass constructors
// fire off any mixin constrctors
// fire off the local class constructor, if provided
postscript(/*Object*/ params, /*DOMNode*/node)
//_Widget implements postscript to kick off the create method...
_Widget.create(/*Object*/params, /*DOMNode*/node)
_Widget.postMixInProperties( )
_Widget.buildRendering( )
_Widget.postCreate( )The take away is two-fold:
_Widgetbuilds right on top of whatdojo.declarealready provides and hooks into thepostscriptmethod in order to fire off thecreatemethod that systematically calls_Widgetspecific lifecycle methods.A widget, as an ancestor of
_Widget, is a bona fide JavaScript Function object. Sure, there's a lot of flare and pizzazz involved, but in the end, it comes right back to the basics.
Lifecycle methods
A flattened version of the lifecycle follows along with a
short synopsis of what each _Widget lifecycle method accomplishes.
It's flattened out and starts with preamble because it's quite
uncommon to override postscript
or the create method yourself
(although you could if you wanted to devise
your own widget lifecycle methods instead of using the standard
ones). Expanded examples that more thoroughly cover each method
appear later in this chapter.
- preamble (originating from
dojo.declare) Preambleprovides an opportunity to manipulate arguments beforeconstructorreceives them. If you overridepreamble, know that the same arguments that would normally be passed toconstructorare passed topreambleand whateverpreamblereturns is what gets passed intoconstructor. This method is somewhat of an advanced feature and used infrequently compared to other lifecycle methods such as, for example,postCreate.- constructor (originating from
dojo.declare) This is the first method that you can override to perform custom behavior during dijit construction. There are two particularly common operations that are performed in
constructor. One is including the initialization of dijit properties that are not primitive types. (Recall from Chapter 10, Simulated Classes and Inheritance that declaring a complex type like an object or list inline as an object property causes it to be shared by all object instances.) Another common operation is adding any additional properties that are relied upon by other lifecycle methods downstream.- postMixInProperties (originating
from
dijit._Widget) This method is called just after Dojo has walked the inheritance hierarchy and mixed all of the ancestors into the class. Thus, the name
postMixInPropertiesliterally refers to the time at which all a widget's properties have been mixed into the particular object instance. Therefore, by the time this method executes, your class has full access to those inherited properties and can manipulate them before the dijit visibly appears on the screen. As we'll soon see in an example that illustrates dijits that derive from a template, this method is typically the place where you'll modify or derive placeholders (indicated by${someWidgetProperty}style notation) that appear in the template's markup.- buildRendering (originating from
dijit._Widget) In
_Widget's implementation, this method simply sets the internal_Widget.domNodeproperty to an actual DOM element so that the dijit physically becomes a part of the page. Given that this method fires directly afterpostMixInProperties, it should now be all the more apparent whypostMixInPropertiesis the canonical location for modifying a widget's template.As you'll soon learn, another foundational Dijit class,
_Templated, overrides this method to perform all of the myriad details that are involved in fetching and instantiating a dijit's template. Finally, note that just afterbuildRenderingis called, the dijit itself is added to Dojo's dijit manager object so that the dijit can be properly destroyed during explicit destructor methods and/or when the page is unloaded. Some browsers do have well-known memory leaks that become relevant for long-running applications, and tracking widgets through a centralized registry is Dojo's way of helping to alleviate that problem. It is quite uncommon to override this method; you'll normally use the default implementation from_Widgetor_Templated.- postCreate (originating from
dijit._Widget) This method executes once the dijit has been created and visibly placed in the page, so you can use it to perform any actions that might not otherwise be possible or prudent until that time. Take special care to perform actions that affect things such as a dijit's style or placement on the screen in
postMixInPropertiesso that they occur before the dijit becomes visible. Performing those actions inpostCreatemay sometimes cause intermittent display "jerks" because you're manipulating the already visible dijit in this method; these issues can be difficult to locate and fix if you've forgotten the fundamental differences betweenpostMixInPropertiesandpostCreate. Additionally, note that if your dijit contains any child dijits, these children are not safely accessible here. To safely access child dijits, use the lifecycle methodstartupinstead. To safely access other nonchild widgets, wait until the page has loaded via usingdojo.addOnLoad.- startup (originating from
dijit._Widget) For child widgets declared in markup, this method automatically fires once the widget and all child widgets have been created. As such, this is the first safe place that a child widget could safely reference a child. As simple as it sounds, this task is often attempted in
postCreate, which can lead to inconsistent behavior that can is difficult to detect and repair. For programmatically created widgets that contain other child widgets as part of a has-a relationship, you'll need to manually callstartupyourself when you're sure that all child widgets have been created. The reason that you need to call it yourself for programmatically created widgets containing children is because it wouldn't make sense to proceed with sizing and rendering unless all child widgets have been added. (Otherwise, there could very well be lots of false starts.) This method is the final method stub that you can override for custom behavior to occur during dijit construction.- destroyRecursive (originating from
dijit._Widget) This method is the generic destructor to call in order to cleanly do away with a dijit and any of its child dijits. In the processing of destructing a dijit, this method calls
uninitialize, which is the primary stub method that you can override to perform custom tear down operations. Do not overridedestroyRecursive. Provide custom tear-down operations inuninitializeand call this method (it does not get automatically called), which takes care of the rest for you.- uninitialize (originating from
dijit._Widget) Override this method to implement custom tear-down behavior when a dijit is destroyed. For example, you might initiate a callback to the server to save a session, or you might explicitly clean up DOM references. This is the canonical location that all dijits should use for these destruction operations.
Warning
Knowing the intricacies that distinguish the various lifecycle methods from one another is absolutely essential. Take special care to remember what type of behavior should be occurring in each method.
Especially common mistakes include:
Trying to manipulate a template after
postMixInPropertieshas been calledModifying a widget's initial appearance after
postMixInPropertieshas been calledTrying to access child widgets in
postMixInPropertiesinstead ofstartupForgetting to perform any necessary destruction in
uninitializeCalling
uninitializeinstead ofdestroyRecursive
Essential properties
In addition to the _Widget methods just described, there
are also some especially notable properties. Just like dijit
methods, you can reference these properties with dot notation.
You'll generally treat these properties as read-only:
-
id This value provides a unique identifier that is assigned to the dijit. If none is provided, Dojo automatically assigns one. You should never manipulate this value, and in most circumstances, you won't want to use it at all.
-
lang Dojo supports features for internationalization, and this value can be used to customize features such as the language used to display the dijit. By default, this value is defined to match the browser's setting, which usually matches that of the operating system.
-
srcNodeRef If provided, this value populates the widget's
domNodewith the contents of thesrcNodeRefand sets thedomNode's id value to the id of thesrcNodeRef.-
domNode This property provides a reference to the dijit's most top-level node. This property is the canonical node that is the visible representation of the dijit on the screen, and although you'll probably want to avoid direct manipulation of this property if using the
_Templatedmixin, it is helpful for some debugging scenarios. As previously mentioned,_Widget's default implementation ofbuildRenderingsets this property, and any methods that overridebuildRenderingshould assume this responsibility or else strange, mysterious things may happen._Widget's default implementation ofbuildRenderingsetsdomNodeto either the value ofsrcNodeRef(if provided) or an empty DIV element.
Just in case you're wondering, here's a simple code snippet
that you could run to inspect these properties in the Firebug
console. Again, all of the properties are inherited from _Widget and are available via this, when this refers to the context of the
associative array that is the third argument to dojo.declare:
dojo.require("dijit._Widget");
dojo.addOnLoad(function( ) {
dojo.declare(
"dtdg.Foo",
dijit._Widget,
{
talk( ) : function( ) {
console.log("id:", this.id);
console.log("lang:", this.lang);
console.log("dir:", this.dir);
console.log("domNode:", this.domNode);
}
}
);
});
foo = new dtdg.Foo( );
foo.talk( );Mixing in _Templated
While _Widget provides the
foundational stub methods that you can override for creation and
destruction events that occur during the lifecycle, _Templated is the previously alluded-to
ancestor that actually provides the basis for defining a widget's
template in markup and using substitutions and attach points to add
functionality to it. Overall, it's a nice separation that lends
itself to tooling and separates designer tasks from
coding.
The vast majority of _Templated 's work involves parsing and
substituting into a template file. An important part of this work
entails overriding _Widget 's
buildRendering method, which is
where all of the template mangling takes place. Three very important
concepts for templates include:
- Substitution
Dijit uses the
dojo.stringmodule to perform substitutions into templates using the${xxx}styledojo.stringsyntax. This is handy for taking widgets attributes that are passed in on construction and using them to customize templates.- Attach points
When the special
dojoAttachPointattribute is used in a template node, it provides the ability to directly reference the node via the attribute value. For example, if a node such as<span dojoAttachPoint="foo">...</span>appears in a template, you could directly reference the node asthis.foo(inpostCreateor later).- Event points
Similar to attach points, you can use the special
dojoAttachEventattribute to create a relationship between a DOM event for a node in a template and a widget method that should be called when in response to the DOM event. For example, if a node were defined, such as<span dojoAttachEvent="onclick:foo">...</span>, the widget'sfoomethod would be called each time a click occurred on the node. You can define multiple events by separating them with commas.
Like _Widget, _Templated is given more thorough coverage
with some isolated examples in just a moment. You're being exposed
to it now so that you have a general idea of its overall
purpose.
Lifecycle methods
The most notable effect of mixing in _Templated is that it results in
overriding _Widget 's buildRendering method. Here's a synopsis
of buildRendering :
- buildRendering
While
_Widgetprovides this method,_Templatedoverrides it to handle the messy details associated with fetching and instantiating a dijit's template file for on screen display. Generally speaking, you probably won't implement your ownbuildRenderingmethod. If you ever do override this method, however, ensure that you fully understand_Templated's implementation first.
Essential properties
Here are _Templated 's
essential properties:
-
templatePath Provides the relative path to the template file for the dijit, which is simply some HTML. Note that fetching the template for a dijit requires a synchronous network call, although Dojo will cache the template string after it is initially fetched. A discussion of producing a custom build of your dijits with tools from Util so that all template strings are interned is included in Chapter 16, Build Tools, Testing, and Production Considerations.
-
templateString For dijits that have been designed or built to have their template strings interned inside of the JavaScript file, this value represents the template. If both
templatePathandtemplateStringare defined,templateStringtakes precedence.-
widgetsInTemplate If dijits are defined inside of the template (either path or string), this value should be explicitly set to
trueso that the Dojo parser will know to search for and instantiate those dijits. This value isfalseby default. Including dijits inside of other dijit templates can be quite useful. A common mechanism for passing values into child widgets that appear in a parent widget's template is via the${someWidgetProperty}notation that is used for substitution.-
containerNode This value refers to the DOM element that maps to the
dojoAttachPointtag in the web page that contains your dijit. It also specifies the element where new children will be added to the dijit if your dijit is acting as a container for a list of child dijits. (Dijits that act as containers multiply inherit from the Dijit_Containerclass, and the dijits that are contained inherit from the Dijit class_Contained.)
Your First Dijit: HelloWorld
After all of that narrative, you're no doubt ready to see some code in action. This section provides a series of increasingly complex "HelloWorld" examples that demonstrate fundamental concepts involved in custom dijit design.
Let's build a canonical HelloWorld dijit and take a closer look at some of the issues we've discussed. Although this section focuses exclusively on what seems like such a simple dijit, you'll find that there are several intricacies that we'll highlight that are common to developing any dijit.
Figure 12.2, “Basic layout of a minimalist HelloWorld dijit” illustrates the basic layout of the HelloWorld dijit as it appears on disk. There are no tricks involved; this is a direct instantiation of the generic layout presented earlier.
HelloWorld Dijit (Take 1: Bare Bones)
The first take on the HelloWorld dijit provides the full body
of each component. For brevity and clarity, subsequent iterations
provide only relevant portions of components that are directly
affected from changes. As far as on disk layout, these examples
assume that the main HTML file that includes the widgets is located
alongside a dtdg module
directory that contains the widget code.
HTML page
First, let's take a look at the HTML page that will contain the dijit, shown in Example 12.2, “HelloWorld (Take 1)”. Verbose commenting is inline and should be fairly self-explanatory.
Example 12.2. HelloWorld (Take 1)
<html>
<head>
<title>Hello World, Take 1</title>
<!--
Because Dojo is being served from AOL's server, we have to provide a
couple of extra configuration options in djConfig as the XDomain
build (dojo.xd.js) gets loaded.
Thus, we associate the "dtdg" namespace w/ a particular relative path
on disk by specifying a baseUrl along with a collection of namespace mappings.
If we were using a local copy of Dojo, we could simply stick the
dtdg directory beside the dojo directory and it would have been
found automatically.
Specifying that dijits on the page should be parsed on page load
is normally standard for any situation in which you have dojoType tags in the page.
-->
<script
type="text/javascript"
src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"
djConfig=isDebug:true,parseOnLoad:true,baseUrl:'./',modulePaths:{dtdg:'dtdg'}">
</script>
<!--
You'll normally include the dojo.css file, followed by
any of your own specific style sheets. Remember that if you're not using
AOL's XDomain build, you'll want to point to your own local dojo.css file.
-->
<link
rel="stylesheet"
type="text/css"
href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css">
</link>
<link
rel="stylesheet"
type="text/css"
href="dtdg/themes/hello/hello.css">
</link>
<script type="text/javascript">
dojo.require("dojo.parser");
//Tell Dojo to fetch a dijit called HelloWorld that's associated
//with the dtdg namespace so that we can use it in the body.
//Dojo will use the values in djConfig.modulePaths to look up the location.
dojo.require("dtdg.HelloWorld");
</script>
</head>
<body>
<!--
This is where the Dojo parser swaps in the dijit from the
dojo.require statement based on our parseOnLoad:true option.
Any styles applied to the dijit are provided by the style sheets imported.
-->
<div dojoType="dtdg.HelloWorld"></div>
</body>
</html>What you just saw is almost the bare minimum that would
appear in any page that contains a dijit. There is a token
reference to any relevant style sheets that are spiffing up the
dijits, the customary reference to Base that bootstraps Dojo, and
then we explicitly dojo.require
in the parser and HelloWorld dijit we're using in the body of the
page. The only remotely tricky thing about any of these things is
properly mapping the dtdg
module to its path on disk in djConfig.modulePaths.
CSS
A widget's style consists of ordinary CSS and any static
support that may be necessary, such as images. The neat thing,
however, is that the actual style for the dijit is reflected in
the dijit template—not in the DOM element where the dojoType tag is specified. This is
particularly elegant because it essentially makes your dijits
skinnable, or in Dojo parlance, you can define
themes for your dijits and change these
themes by swapping out stylesheets.
In our example dijit, the style for an individual DIV element is purely pedagogical but
does illustrate how you could style your own dijits. Our
HelloWorld theme consists of a single CSS file with nothing more
than the following style in it:
div.hello_class {
color: #009900;
}Template
Just like the style, our HTML template for the HelloWorld is
minimal. We're simply telling Dojo to take the DIV tag that was specified in our HTML
page and swap it out with whatever our template supplies—in this
case, our template just happens to supply another DIV element with some style and inner
text that says "Hello World".
Our actual template file contains nothing more than the following line of HTML:
<div class="hello_class">Hello World</div>
JavaScript
Although it looks like there's an awful lot going on in the JavaScript, most of the substance is simply extra-verbose commenting. We're still dealing with the basic constructs that have already been reviewed, and you'll see that it's actually pretty simple. Go ahead and have a look, and then we'll recap on the other end. As you'll notice, the JavaScript file is just a standard module:
//Summary: An example HelloWorld dijit that illustrates Dojo's basic dijit
//design pattern
//The first line of any module file should have exactly one dojo.provide
//specifying the resource and any membership in parent modules. The name
//of the resource should be the same as the .js file.
dojo.provide("dtdg.HelloWorld");
//Always require resources before you try to use them. We're requiring these
//two resources because they're part of our dijit's inheritance hierarchy.
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
//The feature rich constructor that allows us to declare Dojo "classes".
dojo.declare(
"dtdg.HelloWorld",
//dijit._Widget is the prototypical ancestor that provides important method
//stubs like the ones below.
//dijit._Templated is then mixed in and overrides dijit._Widget's
//buildRendering method, which constructs the UI for the dijit from
//a template.
[dijit._Widget, dijit._Templated],
{
//Path to the template of this dijit. dijit._Templated uses this to
//snatch the template from the named file via a synchronous call.
templatePath: dojo.moduleUrl("dtdg", "templates/HelloWorld.html")
}
);In the inheritance chain, _Widget provides the prototypical
ancestor that our dijit inherits from to become a dijit. Because
this first example is minimalist, we didn't need to override any
of _Widget 's lifecycle
methods, but examples that override these methods are coming up.
The mixin ancestor, _Templated,
provides functionality that pulls in the template by overriding
_Widget.buildRendering. The
actual template was located via the templatePath property. Although using
templatePath instead of
templateString incurred the
overhead of a synchronous call back to the server, the template
gets cached after it has been retrieved. Therefore, another
synchronous call would not be necessary if another HelloWorld
dijit came to exist in the same page.
Tip
The first time Dojo fetches a template file for a dijit, the overhead of a synchronous call back to the server is incurred. Afterward, the template gets cached.
Although this example entails your screen simply displaying
a message to the screen, there's a lot more than a print statement behind the scenes that
makes this happen. Moreover, the effort involved in HelloWorld is
pretty much the minimal amount of effort that would ever be
required of any dijit.
Let's solidify your understanding a bit more by filling in some of the method stubs to enhance the dijit. Only instead of taking the direct route, we'll take a few detours. After all, what better way to learn?
HelloWorld Dijit (Take 2: Modifying The Template)
Suppose you want your dijit to be a little less generic.
Instead of displaying the same static message every time the page is
loaded, a good first step might be to make the custom message that
is displayed dynamic. One of the wonderful mechanisms that Dojo
employs for keeping the logical concept of a dijit cohesive is that
you can reference dijit properties that are defined in your
JavaScript source file inside the template. Although referencing
dijit properties from inside the template is only useful prior to
_Templated 's buildRendering method executing, you'll
find that initializing some portion of a dijit's display before it
appears on the screen is a very common operation.
Referencing a dijit property from inside of the template file is simple. Consider the following revision to the HelloWorld template file:
<div class="hello_class">${greeting}
</div>In short, you can refer to any property of the dijit that
exists from inside of the template file and use it to manipulate the
initial display, style, etc. However, there is a small but
incredibly important catch: you have to do it at the right time. In
particular, dijit properties that are referenced in templates are
almost always most appropriately manipulated in the postMixInProperties method. Recall that
postMixInProperties is called
before buildRendering, which is
the point at which your dijit gets inserted into the DOM and becomes
visible.
Warning
Recall that the canonical location to manipulate template
strings is within the dijit lifecycle method postMixInProperties, which is inherited
from _Widget. Manipulating
template strings after this point may produce undesirable
intermittent display twitches.
Without further ado, Example 12.3, “HelloWorld (Take 2: postMixInProperties)” shows how the dijit's JavaScript file should appear if we want to manipulate the properties in the template to display a custom greeting.
Example 12.3. HelloWorld (Take 2: postMixInProperties)
//An example of properly manipulating a dijit property referenced
//in a template string via postMixInProperties
dojo.provide("dtdg.HelloWorld");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare(
"dtdg.HelloWorld",
[dijit._Widget, dijit._Templated],
{
greeting : "",
templatePath: dojo.moduleUrl(
"dtdg",
"templates/HelloWorld.html"
),
postMixInProperties: function( ) {
//Proper manipulation of properties referenced in templates.
this.greeting = "Hello World"; //supply as static greeting.
}
}
);HelloWorld Dijit (Take 3: Interning the Template)
As alluded to earlier, you can save a synchronous call back to the server by specifying the template string directly inside of your JavaScript file. The next variation on the HelloWorld in Example 12.4, “HelloWorld (Take 3: templateString)” demonstrates just how easy this is to do manually, but keep in mind that the Dojo build scripts found in Util can automate this process for all of your dijits as part of a deployment routine.
Example 12.4. HelloWorld (Take 3: templateString)
dojo.provide("dtdg.HelloWorld");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare(
"dtdg.HelloWorld",
[dijit._Widget, dijit._Templated],
{
greeting : "",
//Provide the template string inline like so...
templateString : "<div class='hello_class'>${greeting}</div>",
postMixInProperties: function( ) {
console.log ("postMixInProperties");
//We can still manipulate the template string like usual
this.greeting = "Hello World";
}
}
);In this example, templateString provides the template
inline, so there's no need for a separate template file. This, in
turn, saves a synchronous call to the server. If you can imagine
lots of dijits with lots of template strings, it's pretty obvious
that baking the template strings into the dijit's JavaScript files
can significantly reduce the time it takes to load a page. For
production situations, you won't want to do without the Util's build
system (Chapter 16, Build Tools, Testing, and Production Considerations) to automate
these kinds of performance optimizations for you.
HelloWord Dijit (Take 4: Passing in Parameters)
As yet another improvement to our HelloWorld dijit, let's
learn how to pass in custom parameters to dijits through the
template. Given the previous example, let's suppose that we want to
supply the custom greeting that is to appear in our widget from its
markup that appears alongside the dojoType tag. Easy; just pass it in like
so:
<div dojoType="dtdg.HelloWorld" greeting="Hello World"
></div>Passing in the parameter for a widget that is programmatically created is just as simple:
var hw = new dtdg.HelloWorld({greeting : "Hello World"
}, theWidgetsDomNode);Of course, you are not limited to passing in values that are
reflected in the template. You can pass in other parameters that are
used in other ways as well. Consider the following DIV element containing a reference to your
HelloWorld dijit that specifies two extra key/value pairs:
<div foo="bar" baz="quux" dojoType="dtdg.HelloWorld"></div>
Wouldn't it be handy to be able to pass in custom data to dijits like that so that they can use it for initialization purposes—allowing application-level developers to not even have to so much as even peek at the source code and only hack on the template a bit? Well, ladies and gentlemen, you can, and the JavaScript file in Example 12.5, “HelloWorld (Take 4: custom parameters)” illustrates just how to do it.
Example 12.5. HelloWorld (Take 4: custom parameters)
dojo.provide("dtdg.HelloWorld");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare(
"dtdg.HelloWorld",
[dijit._Widget, dijit._Templated],
{
templateString : "<div class='hello_class'>Hello World</div>",
foo : "",
//you can't set dijit properties that don't exist
//baz : "",
//tags specified in the element that supplies the dojoType tag
//are passed into the constructor only if they're defined as
//a dijit property a priori. Thus, the baz="quux" has no effect
//in this example because the dijit has no property named baz
constructor: function( ) {
console.log("constructor: foo=" , this.foo);
console.log("constructor: baz=" , this.baz);
}
}
);As you might have noticed, there's an emphasis on making the
point that you can only pass in values for dijit
properties that exist ; you cannot create new dijit
properties by tossing in whatever you feel like into the element
that contains the dojoType
placeholder tag. If you run the previous code example and examine
the Firebug console, you'll see the following console output:
constructor: foo=bar constructor: baz=undefined
While passing in string values to dijits is useful, string values alone are of limited utility because life is usually just not that simple—but not to worry: Dojo allows you to pass in lists and associative arrays to dijits as well. All that is required is that you define dijit properties as the appropriate type in the JavaScript file, and Dojo takes care of the rest.
The following example illustrates how to pass lists and associative arrays into the dijit through the template.
Including the parameters in the element containing the
dojoType tag is
straightforward:
<div
foo="[0,20,40]"
bar="[60,80,100]"
baz="{'a':'b', 'c':'d'}"
dojoType="dtdg.HelloWorld"
></div>And the JavaScript file is just as predictable:
dojo.provide("dtdg.HelloWorld");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare(
"dtdg.HelloWorld",
[dijit._Widget, dijit._Templated],
{
templateString : "<div class='hello_class'>Hello World</div>",
foo : [], //cast the value as an array
bar : "", //cast the value as a String
baz : {}, //cast the value as an object
postMixInProperties: function( ) {
console.log("postMixInProperties: foo[1]=" , this.foo[1]);
console.log("postMixInProperties: bar[1]=" , this.bar[1]);
console.log("postMixInProperties: baz['a']=", this.baz['a']);
}
}
);Here's the output in the Firebug console:
postMixInProperties: foo[1]=20 postMixInProperties: bar[1]=6 postMixInProperties: baz['a']=b
Note that even though the value associated with the dijit's
property bar
appears to be a list in the page that includes
the template, it is defined as a string value in the JavaScript
file. Thus, Dojo treats it as a string, and it gets sliced as a
string. In general, the parser tries to interpret values into the
corresponding types by introspecting them via duck typing.
Warning
Take extra-special care not to incorrectly define parameter types in the JavaScript file or it may cost you some debugging time!
HelloWorld Dijit (Take 5: Associating Events with Dijits)
As yet another variation on our HelloWorld dijit, consider the
utility in associating a DOM event such as a mouse click or mouse
hover with the dijit. Dojo makes associating events with dijits
easy. You simply specify key/value pairs of the form DOMEvent: dijitMethod inside of a dojoAttachEvent tag that appears as a part
of your template. You may specify multiple key/value pairs or more
than one kind of native DOM event by separating them with a
comma.
Let's illustrate how to use dojoAttachEvent by applying a particular
style that's defined as a class in a stylesheet whenever a mouseover event occurs and remove the
style whenever a mouseout event
occurs. Because DIV elements span
the width of the frame, we'll modify it to be an inline SPAN, so that the mouse event is triggered
only when the cursor is directly over the text. Let's apply the
pointer style to the
cursor.
The changes to the style are simple. We change the reference
to an inline SPAN instead of a
DIV and change the mouse cursor
to a pointer:
span.hello_class {
cursor: pointer;
color: #009900;
}The JavaScript file in Example 12.6, “HelloWorld (Take 5: dojoAttachEvent)” includes the updated
template string, illustrating that the use of dojoAttachEvent is fairly straightforward
as well.
Example 12.6. HelloWorld (Take 5: dojoAttachEvent)
dojo.provide("dtdg.HelloWorld");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare(
"dtdg.HelloWorld",
[dijit._Widget, dijit._Templated],
{
templateString :
"<span class='hello_class' dojoAttachEvent='onmouseover:onMouseOver,
onmouseout:
onMouseOut'>Hello World</span>",
onMouseOver : function(evt) {
dojo.addClass(this.domNode, 'hello_class');
console.log("applied hello_class...");
console.log(evt);
},
onMouseOut : function(evt) {
dojo.removeClass(this.domNode, 'hello_class');
console.log("removed hello_class...");
console.log(evt);
}
}
);See how easy that was? When you trigger an onmouseover event over the text in the
SPAN element, style is applied
with the dojo.addClass function,
which is defined in Base. Then, when you trigger an onmouseout event, the style is removed.
Neat stuff!
Did you also notice that the event handling methods included
an evt parameter that passes in
highly relevant event information? As you might have guessed,
internally, dojo.connect is at
work standardizing the event object for you. Here's the Firebug
output that appears when you run the code, which also illustrates
the event information that gets passed into your dijit's event
handlers:
applied hello_class... mouseover clientX=64, clientY=11 removed hello_class clientX=65, clientY=16 mouseover clientX=65, clientY=16
Warning
Take care not to misspell the names of native DOM events,
and ensure that native DOM event names stay in all lowercase. For
example, using ONMOUSEOVER or
onMouseOver won't work for the
onmouseover DOM event, and
unfortunately, Firebug can't give you any indication that anything
is wrong. Because you can name your dijit event handling methods
whatever you want (with whatever capitalization you want), this
can sometimes be easy to forget.
To be perfectly clear, note that the previous example's
mapping of onmouseover to
onMouseOver and onmouseout to onMouseOut is purely a simple convention,
although it does make good sense and results in highly readable
code. Also, it is important to note that events such as onmouseover and onmouseout are DOM
events, while onMouseOver and onMouseOut are methods
associated with a particular dijit. The distinction may
not immediately be clear because the naming reads the same, but it
is an important concept that you'll need to internalize during your
quest for Dijit mastery. The semantics between the two are similar
and different in various respects.
Parent-Child Relationships with _Container and _Contained
After you've been rolling with _Widget and _Templated for a while, it won't be long
before you find that it's convenient to have a widget that contains
some children widgets. The "has-a relationship" pattern is quite
common in programming and it is no different with Dojo. The _Container and _Contained mixins are designed to facilitate
the referencing back and forth between parents and children that often
needs to happen. Table 12.1, “_Container and _Contained mixins” summarizes the
API.
Table 12.1. _Container and _Contained mixins
|
Name |
Comment |
|---|---|
|
|
Removes the child widget from the parent. (Silently fails if the widget is not a child or if the container does not have any children.) |
|
Adds a child widget to
the parent, optionally using the |
|
|
Allows a child to reference its parent. Returns a dijit instance. |
|
|
Allows a parent to
conveniently enumerate each of its children dijits. Returns an
|
|
|
Allows a child widget to reference its previous sibling, i.e., the one "to the left." Returns a dijit instance. |
|
|
Allows a child widget to reference its next sibling, i.e., the one "to the right." Returns a dijit instance. |
You'll see these mixins used extensively when you learn about the layout dijits. Next, we'll look at an example.
Rapidly Prototyping Widgets in Markup
Now that you have a feel for exactly how the widget lifecycle
works and have seen plenty of examples, it's time to demonstrate a
tool that you can use for quick, lightweight prototyping. Declaration is a Dijit resource that allows
you to declare a widget in markup without resorting to a separate
JavaScript file; this approach can be a tremendous boon during a
development cycle when you need to rapidly capture or test out an
idea.
Example 12.7, “HelloWorld (Take 6: Declaration)” shows our
very first HelloWorld widget using Declaration to create a widget in a
completely self-contained page.
Example 12.7. HelloWorld (Take 6: Declaration)
<html>
<head>
<title>Hello World, Take 6</title>
<script
type="text/javascript"
src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"
djConfig="isDebug:true,parseOnLoad:true">
</script>
<link
rel="stylesheet"
type="text/css"
href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css">
</link>
<!-- define your CSS inline -->
<style type="text/css">
span.hello_class {
color: #009900;
cursor: pointer;
}
</style>
<script type="text/javascript">
dojo.require("dijit.Declaration");
dojo.require("dojo.parser");
</script>
</head>
<body>
<!-- delcare the widget completely in markup -->
<div
dojoType="dijit.Declaration"
widgetClass="dtdg.HelloWorld"
defaults="{greeting:'Hello World'}">
<span class="hello_class"
dojoAttachEvent='onmouseover:onMouseOver, onmouseout:onMouseOut'>
${greeting}
</span>
<script type="dojo/method" event="onMouseOver" args="evt">
dojo.addClass(this.domNode, 'hello_class');
console.log("applied hello_class...");
console.log(evt);
</script>
<script type="dojo/method" event="onMouseOut" args="evt">
dojo.removeClass(this.domNode, 'hello_class');
console.log("removed hello_class...");
console.log(evt);
</script>
</div>
<!-- now include it into the page like usual -->
<div dojoType="dtdg.HelloWorld"></div>
</body>
</html>Hopefully you made the immediate connection that Declaration is terrific
for quickly working up an example with no hassle. There's no switching
between and keeping track of multiple files, declaring module paths,
and otherwise spending time on anything except the core task—so you
can stay focused on the task at hand and get your work done as
effectively as possible. Table 12.2, “Attributes of Declaration”
shows the Declaration API.
Table 12.2. Attributes of Declaration
|
Attribute |
Comment |
|---|---|
|
|
The widget's class |
|
|
Attribute values that you'd normally pass in as parameters for construction |
|
|
An |
Warning
The mixins attribute for a Declaration declared in markup must be an
Array. This is different from
dojo.declare, which allows for
the possibility of either an Object ancestor or an Array of Object
ancestors.
You'll generally want to refactor the work you do with Declaration after your idea settles, but
there's really no faster way to mock-up a good idea in a hurry.
Summary
After reading this chapter, you should:
Be able to explain how dijits encapsulate the HTML, CSS, and JavaScript into a standalone, portable unit of code
Understand the key lifecycle events that
_Widgetprovides with stub methods, including the order they execute and the stubs they provideUnderstand how
_Templatedacts as a mixin ancestor for_Widgetand provides supplemental functionality for adding template support to dijitsUnderstand the differences and trade-offs between using
templatePathandtemplateStringin templated dijitsBe able to successfully manipulate a dijit's template before it is displayed on screen
Be able to pass in parameters to dijits through their templates
Be able to programmatically create a widget and place it into the page
Know how to add support for DOM events such as
onmouseoverin your dijitsBe able to use
Declarationto rapidly prototype in markup
A discussion of form widgets is next.







Add a comment



Add a comment