Part 3: "The Dojo Programming Model"

Using Dojo to add dynamic capbilities to your web applications can be a little daunting at first.

Let's look at the programming model in more detail to better understand how to use Dojo to build some really cool apps. The programming model is object-oriented inspired and includes "classes" with methods and multi-level inheritance coupled with aspect-oriented event model famous in JavaScript. You will find the API doc quite useful to determine the methods and properties that are inherited from the parent "classes".



Thanks to Eugene Lazutkin and Bill Keese for help on this chapter.

Declarative vs Programmatic model

Dojo supports two programming models, declarative and programmatic. Which one you use depends on what you are doing. In most cases, the declarative model may be easier to use as it is markup but there are times when you will use the programmatic model. You can, of course, intermix the models on the same page as needed.

The best way to show the models is through the use of widgets which can be used either declaratively or programmatically. Although both models are available we will mainly use the declarative model through out this book when both are an option.

The tutorial used the declarative model to create a button on the page using the code below.

<BUTTON widgetId="helloButton" dojoType="Button">Hello World!</BUTTON>

The following declarative formats are equivalent.

	<?xml:namespace prefix = dojo /><dojo:widget></dojo:widget>

	<DIV dojoType="widget">

	<DIV class=dojo-widget></DIV></DIV>

We will discuss the declarative model in more detail shortly.

You can also declare widgets programmatically using the dojo.widget.create API as follows. When declaring widgets programmatically, the API returns the widget id which you use to call the appropriate methods.

var myTabPane= dojo.widget.createWidget("TabPane", {id: "myTabPane"}, srcDiv); 

Which approach to use is discussed later in the book but for now we want to introduce the idea that you have different options.

Infrastructure

Before we get into the declarative model let's look at the widget infrastructure to better understand what methods are available when using widgets in your pages.

  • Lifecycle methods are called by the infrastructure when a widget is created. They are not meant to be called by the user.
  • Internal methods are called by the widget class code for supporting functionality and are "private" and not meant to be called by the application developer. Unfortunately there is no easy way to identify the lifecycle or internal methods. We are working on conventions that will help identify these methods in future releases.
  • "on" methods begin with the string "on" and are available to application developers. These are called by the widget infrastructure based on an action or event. Application developers can provide application specific code for these methods and those that application specific code will be called automatically when the related event or action is triggered.
  • Developer methods are the rest of the methods in the widget. They are provided for use in the programmatic model.

In addtion to methods, each class has parameters that are useful. There are two types of parameters listed here.

  • init parms are set at initialization time and then are readonly
  • rest of the list should be in the API doc

Declarative model

dojoType instructs Dojo how to process the element when the page is loading. Keep in mind that Dojo manipulates the DOM as it renders the page so you must use the Dojo APIs to access the widget ids. Dojo keeps a reference of all widgets it has created that can be accessed with the dojo.widget.byId function - providing you specify either the widgetId or id attribute in your markup. Also you can use dojoAttachEvent using this method.

Namespace Details

In order to make namespaces practical and easy to use, Dojo has a concept of modules and resources. This concept was introduced in Part 2 Modules, Resources and Widget Namespaces when we first saw the HelloWorld tutorial. Resources are used to define a namespace and can be dynamically loaded on demand.

In order to load a resource you should request it using dojo.require(). It takes a string of text, which denotes a downloadable component. The content of this string is interpreted and a subject of Dojo conventions. First, it is traced to a single JavaScript file on disk (on web server). There are ways to affect this interpretation but out of the box it has a very sane behavior:



dojo.xxx => dojo/src/xxx.js

dojo.xxx.yyy => dojo/src/xxx/yyy.js

dojo.xxx.yyy.zzz => dojo/src/xxx/yyy/zzz.js



and so on. For example if you see a statement like that:



dojo.require("dojo.json");



It will load a file named dojo/src/json.js. If you don't know what it contains, you can go and look it up. To sum it up: a namespace hierarchy essentially reflects a file system hierarchy. By convention, if you see somebody using dojo.foo.bar.baz(), you can find it's definition in dojo/src/foo/bar.js, or, if it is not there, in one of files of dojo/src/foo/ (more on that case later), or in dojo/src/foo.js.



Dojo allows you to define your own resources and modules. For your custom resource you define your own top-level object. I will use "example" in my examples. If the Dojo loader sees that the first component is not "dojo", it applies following rule:



example.xxx => dojo/../example/xxx.js

example.xxx.yyy => dojo/../example/xxx/yyy.js

example.xxx.yyy.zzz => dojo/../example/xxx/yyy/zzz.js



Be sure to put the dojo.provide() call in your module resource, so that Dojo knows it found the right file. For instance, xxx/yyy/zzz.js should have dojo.provide("xxx.yyy.zzz") in it.



Essentially it means that on your web server next to the "dojo" folder there is an "example" folder, which files are interpreted in the same way.

There is more information about the namespace and how it is used in the Widgets chapter.

Object Oriented concepts and inheritance

JavaScript is at its heart an object oriented language, but it is a prototype based object oriented language which does not have the same structure as class based languages like Java.This concept can be hard for new programmers who are not familiar with its construct. Dojo brings the object orientedness into a more familiar domain by modeling concepts that can be followed from Java and letting the toolkit handle the prototyping, inheritance and odd procedures JavaScript requires to make it work. Because of this, it not only allows people to get programming in object oriented JavaScript quicker, but it makes it faster to program because you can let the toolkit handle all of the odd procedures JavaScript requires to make it work. It all begins with a simple dojo.declare() function.

Simple declaration

Classes in Dojo are declared with a declare statement and assigning it a Class Name.Within the body can be variables, methods and constructors (know in Dojo as an initializer).

dojo.declare("ClassName",null, {
//class body
});

(Note: ClassName is the basic name, but to avoid naming conflicts, use package names like my.class.ClassName. For simplicity sake, we will start out with using just the simple name.)

Let's add some more content to our class by giving it a name and showing what the initilizer can do.Following is a persons class with an initializer and a moveToNewCity() function:

dojo.declare("Person", null, {
		//acts like a java constructor
		initializer: function(name, age, currentResidence){
		this.name=name;
		this.age=age;
		this.currentResidence=currentResidence;
	},
	moveToNewCity: function(newState) 
	{
		this.currentResidence=newState;
	} 

});

To create an object of this class you use the new keyword:

//create an instance of a new person

var matt= new Person('Matt', 25, 'New Mexico');

The initializer function is called once the object is created and the arguments are passed to it initializing the object.Our Matt object who is 25 currently lives in New Mexico, but let's say he moves a little further west to California.We can set his new currentResidence with the Person class method moveToNewCity(): matt.moveToNewCity('California');

Now the current value of matt.currentResidence shows that he now lives in California.

Inheritance

A person can only do so much, so let's create an Employee class that extends the Person class.The second argument in the dojo.declare() function is for extending subclasses.

dojo.declare("Employee",Person, {
	//acts like a constructor
	initializer:function(name, age, currentResidence, position)
	{
		Employee.superclass.initializer(name, age, currentResidence);
		this.password="";
		this.position=position;
	},

	login: function()
	{
		if(this.password!="" && this.password!=null){
		alert('you have successfully loged in with the password '+this.password);
	}
	else
	{
		alert('please ask the administrator for your password');
	}
}});

The first line in the initializer calls Employee.superclass.initializer, the Person class constructor. Dojo handles all of the requirements for setting up the inheritance chain.Methods or variables can be overridden by setting the name to the same as it is in the parent class. The Employee class can override the Person class moveToNewCity(), perhaps by letting the company pay for moving expenses.

You initialize the sub class the same as the Person class with the new keyword.

var kathryn=new Employee(' Kathryn ', 26, 'Minnesota', 'Designer');

The Employee class passes the first three arguments down to the Person class, and sets the position.Kathryn has access to the login() function found in the Employee class, and also the moveToNewCity() function by calling kathryn.moveToNewCity(‘Texas’); Matt on the other hand, does not have access to the Employee login() function.

matt.login() // ERROR can’t log in because he is not an Employee

Array/Object Declarations

If your class contains arrays or other complex objects, they should be declared in the initializer, due to some subtleties of object inheritance in javascript. Note that simple types (strings, numbers) are fine to declare in the class directly.

dojo.declare("my.classes.bar", my.classes.foo, {
	// coupledObjects: [1, 2, 3, 4]	- doesn't do what I want; 
	//                                      ends up being like a static!!
	numItem : 5,        // one per bar
	strItem : "string", // one per bar

	 initializer: function() {
		this.coupledObjects = [ ]; // each bar should have it's own array
		this.expensiveResource = new expensiveResource(); // one per bar
	}
});

Statics

On the other hand, if you want an object or array to be static (shared between all instances of my.classes.bar), then you should do something like this:

dojo.declare("my.classes.bar", my.classes.foo, {
	initializer: function() {
		dojo.debug("this is bar object # " + this.statics.counter++);
	},

	statics: { counter: 0, somethingElse: "hello" }
});

Mixins

The example below inherits from my.classes.foo and then mixes in "my.mixin". This is similar to multiple inheritance but there are some subtle differences, namely that this.inherited can only reference my.classes.foo, not my.mixin.

dojo.declare("my.classes.bar", [my.classes.foo, my.mixin], { initializer: function() { my.mixin.call(this /*, args*/); // invoke some mixin constructor // (note: my.mixin.prototype is ignored) }, valueForPrototype: 3, methodForPrototype: function() { } });

Design Notes

Constructor vs Initializer

In non-trivial cases, constructor definitions contain code. The constructor code performs initialization tasks and defines instance-only properties (properties whose values belong to a particular object, as opposed to prototype-properties which are shared by all objects using the prototype).

However, there is an issue with respect to constructor code in any simple JavaScript inheritence system. The constructor function of an object must be executed to create a prototype object for an inheritor. Therefore the constructor function must serve as both prototype-initializer and instance-initializer. The double duty of inherited constructors can be non-obvious and lead to subtle bugs.

Given the constructor above, when a bar object is created to use as a prototype, the mixin properties and properties created in the constructor become members of the inherited object's prototype. Almost always these properties are not intended to be part of a prototype.

As a practical matter, the extra prototypical properties are usually ignored as matching instance properties are created at object-instantiation time. However, for example, having an extra expensiveResource can be costly. And errors can result if the environment is not ready to create an expensiveResource at inherits-time. Errors caused by these conditions can be hard to track down, especially if the developer is not aware of how constructors are used when inheriting prototypes.

Separating instance-initializer tasks from prototype-initializer tasks eliminates these concerns. Therefore dojo.declare creates a standard, controlled constructor and separates instance-initialization tasks into a separate, optional initializer method.

Note: dojo.declare cannot inherit from an object that has a non-trivial constructor because dojo.declare does not allow constructors to also perform instance initialization.However, you can inherit from a dojo.declare created constructor without restriction

Calling Inherited (Ancestor) Methods

Sometimes one wants to invoke a method on an object from an ancestor prototype. JavaScript allows any function to call any other function in any context via the call and apply built-ins. So there are techniques like:

my.classes.bar.prototype.someMethod == function() {
	// invoke any function in our context
	anyFunction.call(this);
	 // invoke inherited version of this method in our context
	my.classes.foo.someMethod.apply(this, arguments);
}

(Note: in these examples, the == indicates an assertion that the named property is equivalent to the function shown. Actual assignment is done via dojo.lang.extend or dojo.declare.)

As a convenience, dojo.inherits puts a reference to the ancestor prototype into the descendent constructor, and a reference to the descendent constructor into the descendent prototype. These extra references allow a great deal of extra flexibility in general, and also allow calling ancestor methods without explicitly naming the ancestor:

// invoke inherited version of this method in our context this.constructor.superclass.someMethod.apply(this, arguments);

However, the above technique will cause an infinte loop if someMethod is once removed. E.g., if we have foo -> bar -> zot, you can run into an issue like this:

my.classes.foo.prototype.identify == function() { return "I'm a foo"; }

my.classes.bar.prototype.identify == function() { return "I'm a bar and " + this.constructor.superclass.identify.apply(this, arguments); }

my.classes.zot.prototype.identify == function() { return "I'm a zot and " + this.constructor.superclass.identify.apply(this, arguments); } bar = new my.classes.bar(); alert(bar.identify()); // "I'm a bar and I'm a foo" zot = new my.classes.zot(); alert(zot.identify()); // stack overflow

The error results because this.constructor.superclass referenced in bar's identify function refers to zot's superclass (causing bar.identify to call itself).

To resolve these issues, objects created from dojo.declare constructors include a function called inherited that safely invokes an ancestor method.

inherited: function(methodName /*string*/, arguments /* arrayLike */)

inherited correctly handles the problem scenario above:

my.classes.foo.prototype.identify == function() { return "I'm a foo"; } my.classes.bar.prototype.identify == function() { return "I'm a bar and " + this.inherited('identify', arguments); } my.classes.zot.prototype.identify == function() { return "I'm a zot and " + this.inherited('identify', arguments); } bar = new my.classes.bar(); alert(bar.identify()); // "I'm a bar and I'm a foo" zot = new my.classes.zot(); alert(zot.identify()); // "I'm a zot and I'm a bar and I'm a foo"

Syntax

The syntax is:

dojo.declare(className /*string */, superClass /*function*/ [, initializer /* function*/]);

or

dojo.declare(className /*string */, [superClass /*function*/, mixin /* function */, ...] [, initializer /* function*/]);

Including the target className in the argument list allows the object path to be created automatically (i.e. intermediate namespaces are created as needed). Also, dojo.declare stores className in an eponymous property in the created object's prototype (e.g. my.classes.foo.prototype.className == "my.classes.foo").

Naming

Technically speaking, JavaScript does not have classes: object construction is based on prototypes. For this reason reference to the term class has been (mostly) avoided above.

It seems that the difference is not of great practical importance. It's true that constructor functions in JavaScript are actual objects, but they operate like classes in the sense that they generally have no other purpose than as a mold for object instantiation. It is noted that classes are typically compile-time (or at least meta-) constructs and JavaScript constructors exist as Objects at runtime and contain actual data.

The name dojo.declare was chosen after much debate. The name is vague but easy to remember, read, and type. The method name inherited is lifted (at least) from ObjectPascal.ÂÂ

Order matters

In general, a script should do the following in the ... section:

  1. (Optional) set the djConfig options
  2. Load the Dojo script
  3. Call dojo.require(...) for all libraries used in the page
  4. (Optional) define initialization functions and call addOnLoad
As in this example:

<!-- Step 1 (Optional) Set djConfig -->
<SCRIPT type=text/javascript>
	djConfig = {
	  debug: true
	};
</SCRIPT>

<!-- Step 2: Load dojo -->

<SCRIPT src="js/dojo/dojo.js" type=text/javascript></SCRIPT>
<!-- Step 3: call dojo.require -->
<SCRIPT>
   dojo.require("dojo.book.myWidget.*");
   <!-- Step 4 (Optional): define initialization functions -->
   function initMyStuff() {
      ...
   }
   dojo.addOnLoad(initMyStuff);
</SCRIPT>

The order is important! If you do the steps out of order, dojo may not initialize properly, and your page will be a mess.

This script element is responsible for loading the base Dojo script that provides access to all the other Dojo functionality. Following this we add the requires statements which pulls in functionality needed by the application.

Use the dojo.addOnLoad to call functions which use the widget ids because Dojo must completely load the page and finish parsing the HTML before a reference can be made to the id. So, for example, the following will not work:

<BUTTON widgetId="helloButton" dojoType="Button">Hello World!</BUTTON>
<SCRIPT>
// ILLEGAL!!  helloButton does not exist yet
dojo.byId("helloButton").width2height = 0.5;
</SCRIPT>

Instead, place the script in an initialization function:

   
    <SCRIPT src="js/dojo/dojo.js" type=text/javascript></SCRIPT>
    <SCRIPT>
       function initMyStuff() {
          dojo.byId("helloButton").width2height = 0.5;
       }
       dojo.addOnLoad(initMyStuff);
    </SCRIPT>
<BUTTON widgetId="helloButton" dojoType="Button">Hello World!</BUTTON>

The global Dojo Objects

Dojo defines a global object called "dojo" which serves as an umbrella for everything Dojo-related. It simulates a namespace and was created to prevent clashes in the global JavaScript? namespace between the code in Dojo and other toolkits or user supplied code. Unfortunately it cannot be used during a bootstrap process, so special global variables should be used. All of them are prefixed with "dj".

You will need to use exactly two top-level Dojo-defined objects: "dojo", which serves as a namespace, and "djConfig", which is used to supply initialization parameters to Dojo, and should be created before Dojo's bootstrap.

For example, to turn off global widget searching, add these lines just *before* you include dojo.js:
<script type="text/javascript">

djConfig = {

parseWidgets: false

};

</script>