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 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, 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:

  • _Widget builds right on top of what dojo.declare already provides and hooks into the postscript method in order to fire off the create method that systematically calls _Widget specific 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)

Preamble provides an opportunity to manipulate arguments before constructor receives them. If you override preamble, know that the same arguments that would normally be passed to constructor are passed to preamble and whatever preamble returns is what gets passed into constructor. 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 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 postMixInProperties literally 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.domNode property to an actual DOM element so that the dijit physically becomes a part of the page. Given that this method fires directly after postMixInProperties, it should now be all the more apparent why postMixInProperties is 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 after buildRendering is 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 _Widget or _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 postMixInProperties so that they occur before the dijit becomes visible. Performing those actions in postCreate may 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 between postMixInProperties and postCreate. 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 method startup instead. To safely access other nonchild widgets, wait until the page has loaded via using dojo.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 call startup yourself 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 override destroyRecursive. Provide custom tear-down operations in uninitialize and 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 postMixInProperties has been called

  • Modifying a widget's initial appearance after postMixInProperties has been called

  • Trying to access child widgets in postMixInProperties instead of startup

  • Forgetting to perform any necessary destruction in uninitialize

  • Calling uninitialize instead of destroyRecursive

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 domNode with the contents of the srcNodeRef and sets the domNode's id value to the id of the srcNodeRef.

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 _Templated mixin, it is helpful for some debugging scenarios. As previously mentioned, _Widget 's default implementation of buildRendering sets this property, and any methods that override buildRendering should assume this responsibility or else strange, mysterious things may happen. _Widget's default implementation of buildRendering sets domNode to either the value of srcNodeRef (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.string module to perform substitutions into templates using the ${xxx} style dojo.string syntax. This is handy for taking widgets attributes that are passed in on construction and using them to customize templates.

Attach points

When the special dojoAttachPoint attribute 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 as this.foo (in postCreate or later).

Event points

Similar to attach points, you can use the special dojoAttachEvent attribute 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's foo method 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 _Widget provides this method, _Templated overrides 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 own buildRendering method. 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.

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 templatePath and templateString are defined, templateString takes precedence.

widgetsInTemplate

If dijits are defined inside of the template (either path or string), this value should be explicitly set to true so that the Dojo parser will know to search for and instantiate those dijits. This value is false by 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 dojoAttachPoint tag 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 _Container class, and the dijits that are contained inherit from the Dijit class _Contained.)

Get Dojo: The Definitive Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.