Understanding _Widget

In this tutorial, you'll learn what Dijit's _Widget and _WidgetBase objects are and how they serve as the foundation for all widgets in the Dojo Toolkit.

  • Difficulty: Intermediate
  • Dojo Version: 1.6

Getting Started

The foundation of Dijit, and the ability to create your own widgets, relies on two base class definitions: dijit._WidgetBase and dijit._Widget. While there are a few other key pieces that most rely on for developing web applications with the Dojo Toolkit (such as the Dojo parser and the Dijit Template system, detailed in different tutorials), these two class declarations are the key to creating any kind of custom widget using the Dojo Toolkit. In this tutorial, you'll learn how Dijit's widget infrastructure works.

dijit._Widget inherits from dijit._WidgetBase; when defining your own custom widgets, you should always inherit from dijit._Widget (or one of the widgets available in Dijit), and not from dijit._WidgetBase.

The most important thing to understand about Dijit's system is the lifecycle of a widget. This lifecycle is primarily concerned with the inception of a widget—in other words, from when a widget is conceived to when it is fully useable by your application—through the destruction of a widget, and the widget's associated DOM elements.

If you are wondering why there is an "_" in front of both "Widget" and "WidgetBase", it is because neither definition is intended to be used directly; instead, they are intended to be built upon using the Dojo Toolkit's "declare" mechanisms.

To do this, dijit._WidgetBase defines two concept lines: a set of methods that are called in succession during the process of creation, and a way of getting/setting fields with minimal data binding while the widget lives in the application. Let us take a look at the first mechanism: Dijit's widget lifecycle.

The Dijit Lifecycle

Each widget declared with _Widget as its base will run through several methods during instantiation:

([widget].constructor());
[widget].postscript();
[widget].create();
[widget].postMixInProperties();
[widget].buildRendering();
[widget].postCreate();  // this is the most important one!
[widget].startup();
View Demo

These methods are there to handle a number of things: when initial values are defined, how the visual representation of those values are created, where those visualizations are placed, and how to deal with things (such as DOM node measurements) that are dependant on the browser itself.

[widget].postCreate()

By far, the most important method to keep in mind when creating your own widgets is the .postCreate method. This is fired after all properties of a widget are defined, and the document fragment representing the widget is created—but before the fragment itself is added to the main document. The reason why we consider this the most important method is because it is the main place where you, the developer, get a chance to do any last minute modifications before the widget is presented to the user, including setting any kind of custom attributes, etc. Think of it like the place just before you draw a curtain on a play; it is where you can make last minute adjustments to costumes and sets before they are actually seen by your users. When developing a custom widget, most (if not all) of your customization will go here.

[widget].startup()

Probably the second-most important method in the Dijit lifecycle is the startup method. This method is designed to handle processing after any DOM fragments have been actually added to the document; it is not fired until after any potential children widgets have been created and started as well. This is useful for composite widgets as well as layout widgets.

When instantiating a widget programmatically, always call the widget's startup() method. It's a common error to create widgets programmatically and then forget to call startup, leaving you scratching your head as to why your widget isn't showing up properly.

Tear-down methods

In addition to the instantiation methods, dijit._WidgetBase also defines a number of destruction methods:

[widget].destroy();
[widget].destroyDescendants();
[widget].destroyRecursive();
[widget].destroyRendering();
[widget].uninitialize();

When writing your own widget, any kind of custom tear-down behavior you need to do should be defined using [widget].uninitialize. Dijit itself takes care of node and most object management for you already (using the destroy methods mentioned), so you don't have to worry about writing custom versions of these methods.

Node references

A widget is generally some sort of user interface, and it would not be complete without some sort of DOM representation. _WidgetBase defines a standard property called domNode, which will be a reference to the overall parent node of the widget itself. You can always get a reference to this node programmatically if you need to do something (like move the entire widget around in a document), and it is available by the time the postCreate method is called.

In addition to the domNode property, some widgets will also define a containerNode property. This is a reference to a child node in a widget that will contain any other widgets that are defined outside of your widget definition.

We'll discuss the importance of the containerNode property in another tutorial; for now, be aware that the property exists on all widgets, regardless of whether it is actually used.

Getters and Setters

In addition to the startup and tear-down infrastructure, _WidgetBase also provides not only a number of pre-defined properties that all widgets need, but also a way of letting you define custom getters and setters that will work with the standard get and set methods, pre-defined on all widgets. It does this by asking you to define custom "private" methods in your code using the following standard:

// for the field "foo" in your widget:

//	custom getter
_getFooAttr: function(){ /* do something */ },

// 	custom setter
_setFooAttr: function(value){ /* do something */ }

If you define custom method pairs using that standard, _WidgetBase's infrastructure will then allow you, when using an instance of a widget, to just use the standard get() and set(), already pre-defined. For instance, given the above example, you could do this:

//	assume that the widget instance is "myWidget":

//	get the value of "foo":
var value = myWidget.get("foo");

//	set the value of "foo":
myWidget.set("foo", someValue);

This standard allows other widgets and controlling code to interact with a widget in a consistent way, while giving you the ability to do custom things when a field is accessed (such as modifications to a DOM fragment, etc.), as well as allowing you to fire off any other methods (such as an event handler or a notification). For example, say your widget has a custom value, and you want to be able to notify anyone that that value has changed (possibly via an onChange method you've defined):

//	assume our field is called "value":

_setValueAttr: function(value){
	this.onChange(this.value, value);
	this._set("value", value);
},

//	a function designed to work with dojo.connect
onChange: function(oldValue, newValue){ }

As you can see, this gives us a convenient way to be able to customize getter and setter behavior within our own widgets.

When defining your own widgets, you should always create custom getter and setter methods; when using your own widgets, you should always use the get() and set() methods for field access. In addition, when defining your custom setter methods, you should always use the internal _set method, as this mechanism allows for the new watch functionality from dojo.Stateful, which all widgets now inherit from.

Connections and Subscriptions

The _WidgetBase infrastructure provides custom implementations/wrappings for two of Dojo's most important event mechanisms: connect() and subscribe(). These methods are defined partially for convenience sake, and partially to faciliate accurate setup and tear-down of widgets. You don't have to define these yourself—it comes magically with your widget.

Using both .connect() and .subscribe() is simple:

// 	assume we have a button widget called "btn",
// 	and we want the button to listen to foo.bar():

btn.connect(foo, "bar", function(){
	//	note that "bar" is executed in the scope of btn!
	this.set("something", somethingFromFoo);
});

Topic subscriptions (i.e. Dojo's pub/sub system) are done in a similar fashion.

The advantage of defining versions of connect and subscribe in the widget infrastructure is that internally, the widget can keep track of all the connections and subscriptions it has, and make sure everything is disconnected and/or unsubscribed when the widget is destroyed—preventing any kind of memory leaks.

Pre-defined Properties and Events

Finally, the _Widget infrastructure provides a set of pre-defined properties:

id,			//	a unique string
lang,		//	a rarely used string that can override the default Dojo locale
dir,		//	bi-directional support
class,		//	HTML class attribute for the widget's domNode
style,		//	HTML style attribute for the widget's domNode
title,		//	HTML title attribute for native tooltips
tooltip,	//	optional dijit.Tooltip reference
baseClass,	//	root CSS class of the widget
srcNodeRef	//	the original DOM node that existed before it was "widgetfied"

...as well as a set of pre-defined events that you can connect to or override for custom functionality:

//	mouse-based events
onClick,
onDblClick,
onMouseMove,
onMouseDown,
onMouseOut,
onMouseOver,
onMouseLeave,
onMouseEnter,
onMouseUp,

//	key-based events
onKeyDown, 
onKeyPress, 
onKeyUp,

//	additional events
onFocus,
onBlur,
onShow,
onHide,
onClose

Conclusion

As you can see, Dijit's _Widget infrastructure provides a solid foundation on which to create and use widgets; all aspects of a widget (lifecycle, DOM node references, getters and setters, pre-defined properties and events) are covered out of the box. We've seen how a widget's .postCreate() method is the most important lifecycle method when developing custom widgets, and how vital calling .startup() is when instantiating widgets programmatically. We've also covered Dijit's getter/setter infrastructure, as well as the importance of a widget's domNode property.

Colophon


Error in the tutorial? Can’t find what you are looking for? Let us know!