- The Book of Dojo
- Quick Installation
- Hello World
- Debugging Tutorial
- Introduction
- Part 1: Life With Dojo
- Part 2: Dijit
- Part 3: JavaScript With Dojo and Dijit
- Part 4: Testing, Tuning and Debugging
- Part 5: DojoX
- The Dojo Book, 0.4
Understanding The Parser
Submitted by alex on Thu, 08/30/2007 - 10:09.
One of the most common things to see in a Dojo page is a djConfig block like djConfig="parseOnLoad: true"
and later dojo.require("dojo.parser");
. Together these lines include and enable Dojo's page parsing infrastructure. This machinery layers on top of dojo.query()
to provide a way to declare instances of any class via markup in your page. To understand what we mean by that, let's take a simple example page which consists of a single tree backed by a JSON data store:
<script type="text/javascript" src="http://o.aolcdn.com/dojo/0.9.0/dojo/dojo.xd.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.data.JsonItemStore");
dojo.require("dijit.Tree");
dojo.require("dojo.parser");
var countries = new dojo.data.JsonItemStore({ url: "countries.json" });
</script>
</head>
<body class="tundra">
<div dojoType="dijit.Tree" store="countries" labelAttr="name" typeAttr="type"
query="{type:'continent'}" ></div>
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.data.JsonItemStore");
dojo.require("dijit.Tree");
dojo.require("dojo.parser");
var countries = new dojo.data.JsonItemStore({ url: "countries.json" });
</script>
</head>
<body class="tundra">
<div dojoType="dijit.Tree" store="countries" labelAttr="name" typeAttr="type"
query="{type:'continent'}" ></div>
In this case, we see the familiar use of the dojoType attribute to denote where an instance of our widget should be created. This is the functional equivalent of writing:
<script type="text/javascript" src="http://o.aolcdn.com/dojo/0.9.0/dojo/dojo.xd.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.data.JsonItemStore");
dojo.require("dijit.Tree");
dojo.require("dojo.parser");
dojo.addOnLoad(
var countries = new dojo.data.JsonItemStore({ url: "countries.json" });
var tree = new dijit.Tree({
store: countries,
labelAttr: "name",
typeAttr: "type",
query: {type: "continent"}
}, dojo.byId("treePlaceHolder"));
});
</script>
</head>
<body class="tundra">
<div id="treePlaceHolder"></div>
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.data.JsonItemStore");
dojo.require("dijit.Tree");
dojo.require("dojo.parser");
dojo.addOnLoad(
var countries = new dojo.data.JsonItemStore({ url: "countries.json" });
var tree = new dijit.Tree({
store: countries,
labelAttr: "name",
typeAttr: "type",
query: {type: "continent"}
}, dojo.byId("treePlaceHolder"));
});
</script>
</head>
<body class="tundra">
<div id="treePlaceHolder"></div>
In fact, they're identical. The only difference is that in the first example, the work of locating and creating the widget instance is handed off to the Dojo parser. Fundamentally, this means that the parser is:
- locating the nodes with
dojoType
attributes in the page - taking the attributes assigned to them and passing them into the constructor as properties on the configuration object
- passing the source node for the widget as the second parameter to the constructor
There's nothing about this process (except perhaps the passing of the node) which should be specific to widgets, and indeed the Dojo parser is equipped to create instances of any class. That means that we can revise our first example to be fully markup-driven:
<script type="text/javascript" src="http://o.aolcdn.com/dojo/0.9.0/dojo/dojo.xd.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.data.JsonItemStore");
dojo.require("dijit.Tree");
dojo.require("dojo.parser");
</script>
</head>
<body class="tundra">
<div dojoType="dojo.data.JsonItemStore" url="countries.json" jsId="countries"></div>
<div dojoType="dijit.Tree" store="countries" labelAttr="name" typeAttr="type"
query="{type:'continent'}" ></div>
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.data.JsonItemStore");
dojo.require("dijit.Tree");
dojo.require("dojo.parser");
</script>
</head>
<body class="tundra">
<div dojoType="dojo.data.JsonItemStore" url="countries.json" jsId="countries"></div>
<div dojoType="dijit.Tree" store="countries" labelAttr="name" typeAttr="type"
query="{type:'continent'}" ></div>
So how does this work? What happens to the source node in the resulting page when what we're creating isn't a widget? And what about the seemingly magical properties dojoType
and jsId
?
The Parsing Algorithm
To fully understand the parser, it's important to understand it's operation in broad terms. The parser operates by:
- Searching the document for elements with a
dojoType
attribute. This search is linear and nodes are returned in document order. - Iterating over all matching nodes, attempting to match the declared type with an available class to instantiate.
- If a class is found, the parser iterates over attributes of the class's prototype and populates the arguments object from the values of attributes on the source node of the same name. Lightweight type conversion is performed.
- If a markup factory is found for the class, it is used to create a new instance to return
- If no markup factory is found, the class is constructed using the
new
operator. The arguments to the constructor are assumed to be in the formnew someClass(argumentsObj, sourceNode);
- Events from markup are attached (although some may have been handled earlier)
The above process alludes to some features of the parser which we haven't seen in action yet. Here's a more sophisticated example. We create a class called "example.Class". The parser runs and creates an instance of this class (the div dojoType="example.Class"), which you can then access through the global variable "thinger."
<script type="text/javascript" src="http://o.aolcdn.com/dojo/0.9.0/dojo/dojo.xd.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.declare("example.Class", null, {
constructor: function(args, node){
// this class constructor is designed for the
// parser's "args, node" convention
dojo.mixin(this, args);
},
});
</script>
</head>
<body class="tundra">
<div dojoType="example.Class" foo="bar" jsId="thinger">
<script type="dojo/method">
// this block is executed as the class is created but
// after the class constructor is finished
console.debug(this.foo); // Prints "bar"
</script>
</div>
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.declare("example.Class", null, {
constructor: function(args, node){
// this class constructor is designed for the
// parser's "args, node" convention
dojo.mixin(this, args);
},
});
</script>
</head>
<body class="tundra">
<div dojoType="example.Class" foo="bar" jsId="thinger">
<script type="dojo/method">
// this block is executed as the class is created but
// after the class constructor is finished
console.debug(this.foo); // Prints "bar"
</script>
</div>
Each script of type "dojo/method" is executed after the constructor runs. We saw examples of this in Part 1, Example 2.. Finally, the class constructor uses a mixin to copy the attributes from tag to properties in the instance. Thus thinger is created by calling the constructor with args = {foo: "bar"}
and node as the div node itself. Foo is created as a property by mixin.
Type Conversions
Lightweight type conversions are done on the attribute values. The types are based on the property types used in the definition of the class. For example:
<script type="text/javascript" src="http://o.aolcdn.com/dojo/0.9.0/dojo/dojo.xd.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.declare("example.Class", null, {
constructor: function(args, node){
// this class constructor is designed for the
// parser's "args, node" convention
dojo.mixin(this, args);
},
title: "Hello",
isEnabled: true,
dayCount: 45,
onClick: function(){},
names: ["Monday", "Tuesday", "Wednesday"],
startDate: new Date()
});
</script>
</head>
<body class="tundra">
<div dojoType="example.Class" title="Good Morning" isEnabled="false" dayCount="4" onClick="alert(thinger.dayCount)" names="Thursday, Friday" startDate="2008-01-01" jsId="thinger">
<script type="dojo/method">
// this block is executed as the class is created but
// after the class constructor is finished
console.debug(this.foo); // Prints "bar"
</script>
</div>
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.declare("example.Class", null, {
constructor: function(args, node){
// this class constructor is designed for the
// parser's "args, node" convention
dojo.mixin(this, args);
},
title: "Hello",
isEnabled: true,
dayCount: 45,
onClick: function(){},
names: ["Monday", "Tuesday", "Wednesday"],
startDate: new Date()
});
</script>
</head>
<body class="tundra">
<div dojoType="example.Class" title="Good Morning" isEnabled="false" dayCount="4" onClick="alert(thinger.dayCount)" names="Thursday, Friday" startDate="2008-01-01" jsId="thinger">
<script type="dojo/method">
// this block is executed as the class is created but
// after the class constructor is finished
console.debug(this.foo); // Prints "bar"
</script>
</div>
In this example, the attributes will be converted to their correponding types that were used in the definition of example.Class:
- title: String
- isEnabled: Boolean
- dayCount: Number
- onClick: Function. Specify the function body in the attribute.
- names: Array. Separate the array members by commas. Array members are assumed to be strings.
- startDate: Date. "now" can be used to get the current date, otherwise, dojo.date.stamp.fromISOString() will be used to convert the text string to a Date object.
If the property type does not match one of the types listed above, then dojo.fromJson() will be used to convert the attribute value.
Markup Factory
If the class declares a method with the name markupFactory, that function will be used to create the object instance, instead of the constructor. This is useful if the class has special initialization for instances created via markup, versus instances created in script via the class constructor. An example class that defines a markupFactory method:
<script type="text/javascript" src="http://o.aolcdn.com/dojo/0.9.0/dojo/dojo.xd.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.declare("example.Class", null, {
constructor: function(args, node){
// this class constructor is designed for the
// parser's "args, node" convention
dojo.mixin(this, args);
},
title: "Hello",
isEnabled: true,
dayCount: 45,
onClick: function(){},
names: ["Monday", "Tuesday", "Wednesday"],
startDate: new Date(),
markupFactory: function(params, domNode, constructorFunction){
//params: object that contains the markup attribute values,
//with type conversion already completed.
//domNode: the DOM node (the div in the code below)
//constructorFunction: The constructor function matching
//the dojoType in markup. In this example, example.Class
var instance = new constructorFunction(params, domNode);
//Do special initialization intialization here
return instance;
}
});
</script>
</head>
<body class="tundra">
<div dojoType="example.Class" title="Good Morning" isEnabled="false" dayCount="4" onClick="alert(thinger.dayCount)" names="Thursday, Friday" startDate="2008-01-01" jsId="thinger">
<script type="dojo/method">
// this block is executed as the class is created but
// after the class constructor is finished
console.debug(this.foo); // Prints "bar"
</script>
</div>
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.declare("example.Class", null, {
constructor: function(args, node){
// this class constructor is designed for the
// parser's "args, node" convention
dojo.mixin(this, args);
},
title: "Hello",
isEnabled: true,
dayCount: 45,
onClick: function(){},
names: ["Monday", "Tuesday", "Wednesday"],
startDate: new Date(),
markupFactory: function(params, domNode, constructorFunction){
//params: object that contains the markup attribute values,
//with type conversion already completed.
//domNode: the DOM node (the div in the code below)
//constructorFunction: The constructor function matching
//the dojoType in markup. In this example, example.Class
var instance = new constructorFunction(params, domNode);
//Do special initialization intialization here
return instance;
}
});
</script>
</head>
<body class="tundra">
<div dojoType="example.Class" title="Good Morning" isEnabled="false" dayCount="4" onClick="alert(thinger.dayCount)" names="Thursday, Friday" startDate="2008-01-01" jsId="thinger">
<script type="dojo/method">
// this block is executed as the class is created but
// after the class constructor is finished
console.debug(this.foo); // Prints "bar"
</script>
</div>
- Printer-friendly version
- Login or register to post comments
- Unsubscribe post
jsId
So what is the jsId attribute for?
in the example, it attaches
in the example, it attaches the store to the global ref defined by the attribute. jsId = "conuntryStore" means the dojo.data store is available as countryStore.fetch()/etc
Args and event attributes for script type="dojo/method"
In order to override a method in markup you can write:
console.debug(param1, param2);
console.debug(arguments[0], arguments[1]);
</script>
Event-attribute names the method to be overridden. Args-attribute enables you to give names for arguments. Instead of using arguments-array to access parameters you can give them a names, just like in a standard function definition:
console.debug(param1, param2);
console.debug(arguments[0], arguments[1]);
}
friendly reminder on using markup attributes
as specified in the parser algorithm, your should have your corresponding properties in place in your class definition before using the markup in your code, e.g.
class definition:
use markup in your HTML:
Markup factory enables detailed creation of objects
Here's my explanation to a question about markupFactory:
When creating widget instances by parsing markup, if markup factory exists, it is used instead of constructor. The corresponding block of code from the parser:
if(!markupFactory && clazz["prototype"]){
markupFactory = clazz.prototype["markupFactory"];
}
// create the instance
var instance = markupFactory ? markupFactory(params, node, clazz) : new clazz(params, node);
Explained: If the class (or it's prototype) has function called "markupFactory", then it's used (at least expected) to return an instance of the class. The function markupFactory is executed in the same scope as the parser, because the object doesn't exist yet.
MarkupFactory isn't exactly a constructor - it's a factory. Basically, markupFactory is like a constructor - it creates objects. It just lets you define how: you can choose the class you want to instantiate, or you can prepare and reorder the parameters as the constructor expects them. A simple markupFactory passes the parameters to the standard constructor of current class:
var instance = new constructorFunction(params, domNode);
return instance;
}
More complex markupFactory in DojoData-class chooses to instantiate a certain class and sets the parameters for its constructor:
return new dojox.grid.data.DojoData(null, null, args);
},
(However, see issue 5503 - in this particular case hard-coding the class name wouldn't be necessary, as it could use constructorFunction parameter.)
Wikipedia page on factory method pattern gives some more examples and Design Patterns gives you the exact definition.
Use of object member variables is confusing.
I'm confused by what happens with these two member variables when you define multiple instances of the same class....
names: ["Monday", "Tuesday", "Wednesday"],
startDate: new Date()
});
Since these member variables are objects, and not simple types, won't they get overwritten if you define multiple instances of the same class?
<div dojoType="example.Class" names="Saturday, Sunday" jsId="thinger2"></div>
<script>console.debug(dojo.byId("thinger1").names);// Prints "Saturday, Sunday"?
</script>
Or am I misunderstanding from three pages back (Arrays and Objects as Member Variables).