A recovering 7 foot tall cactus
dojo/parser
Ok, what is becoming a bit of a trend, I am adding a third to my series on forthcoming features in Dojo core in 1.8. I have already covered dojo/promise and dojo/request, now I am going to cover changes to dojo/parser. This is something I am quite passionate about, since I had my hand directly involved in the features that were added, with lots of help from Bill Keese and Ben Hockey (and others).
With the full conversion to AMD in 1.7, there were likely a few features that made the declarative syntax a bit more challenging to use, versus the programmatic syntax. While the programmatic approach to Dojo is usually a better way of creating applications, the declarative way is very popular with developers, great for prototyping and a quick way to get started with Dojo, especially when using the Dijit package of widgets.
For those not familiar with programmatic, versus declarative syntax, here is a quick example of creating a Dijit button and making it log something to the console when clicked. First here is how we would do it programatically:
require(["dijit/form/Button"], function (Button) {
var button = new Button({
label: "Click Me!",
}, "buttonNode");
button.on("click", function () {
console.log("I was clicked!");
});
});
Now the same HTML snippet done declaratively:
<button type="button" id="buttonNode" data-dojo-type="dijit.form.Button">
<span>Click Me!</span>
<script type="dojo/on" data-dojo-event="click">
console.log("I was clicked!");
</script>
</button>
The declarative syntax is then converted by the dojo/parser into instantiated widgets, essentially converting your decorated HTML code into programmatic code. It does the heavy lifting for you, so you don’t have to keep managing the nodes in your document for all your widgets.
Now for the improvements in 1.8. The first was that you could only specify the
class in declarative syntax using a global class name (the old way, dot
notation, e.g. dijit.form.Button
). With AMD and the deprecation of Dojo
declaring classes in the global namespace, this was starting to become a
problem. So now the dojo/parser accepts the Module ID (MID) as the type (e.g.
dijit/form/Button
). In fact this is now the preferred way of referring to a
object type in declarative markup. For example, our button from above should be:
<button type="button" id="buttonNode" data-dojo-type="dijit/form/Button">
<span>Click Me!</span>
<script type="dojo/on" data-dojo-event="click">
console.log("I was clicked!");
</script>
</button>
The second added feature is to bring the declarative scripting fully capable
with the introduction of dojo/aspect
as the preferred way of modifying the
behaviour of an instance in Dojo. dojo/aspect
supports the semantic concepts
of before, around and after which are part of Aspect Oriented Programming (AOP).
Now the declarative scripting supports those as well, so you can do the
following:
<div data-dojo-type=...>
<script type="dojo/aspect" data-dojo-advice="after" data-dojo-method="method1" data-dojo-args="e">
console.log("I ran after!");
</script>
<script type="dojo/aspect" data-dojo-advice="around" data-dojo-method="method2" data-dojo-args="origFn">
return function(){ // Have to act as a function factory
console.log("I ran before!");
origFn.call(this); // You have to call the original function
console.log("I ran after!");
};
</script>
<script type="dojo/aspect" data-dojo-advice="before" data-dojo-method="method3" data-dojo-args="i">
console.log("I ran before!");
i++; // Modifying argument
return [i]; // Returning modified arguments to be used with original function
</script>
</div>
Now for the “cool” feature, but which could easily cause quite unintended
consequences, is something called auto-require. This caused a lot of debate
within the Dojo developer community, mostly because you don’t have to be
explicit about your requirements with your declarative scripting. Basically if
the dojo/parser
encounters a MID that isn’t loaded, it will attempt to load it
for you. It makes things easier, but it can mask problems and may cause a lot of
negative impact on performance when using built layers that may not contain all
the right modules. At the end of the day though we thought it was useful enough
to be included. Without it, you would have had to require all your modules used
in your declarative markup before invoking the parser, which would have looked
something like this for our button:
require(
["dojo/ready", "dojo/parser", "dijit/form/Button"],
function (ready, parser) {
ready(function () {
parser.parse();
});
},
);
:information_source: It is still better, since 1.7 to manually invoke the
parser.parse()
instead of using parseOnLoad in dojoConfig.
Another new feature which fits well in the world of AMD is a declarative
require. This allows you to require in modules without having to do it within a
JavaScript code block in your code. What it essentially does, is allow you to
load modules and map them into the global namespace. A declarative require is a
<script>
block that contains a JavaScript object that defines the mapping of
the modules and would look like this:
<script type="dojo/require">
"app.on": "dojo/on",
myModule: "package/myModule"
</script>
Because of the auto-require and declarative require use Dojo’s loader (via
require()
) and require()
operates in an async fashion, it has meant that
dojo/parser now operates in a async fashion. Historically the .parse()
function returned an array of instantiated widgets. For backwards compatibility
reasons .parse()
still returns an array, but it also behaves like a promise
too. When the parser does not need to use auto-require or declarative require or
Dojo loader is running in sync mode, this array will be populated with the
instantiated objects. When it is running in an async fashion, the array will be
empty but the promise will be resolved with an array of objects.
For new development it is best to ensure you treat parser.parse()
as if it is
always running in an async fashion. So if you need access to the instantiated
objects or if you need to do something once the .parse()
is finished, you
should do something like this:
require(["dojo/parser", "dojo/ready"], function (parser, ready) {
ready(function () {
parser.parse().then(function (instances) {
// do something
});
});
});
One piece that I am still working as I write this is to get the Dojo builder to be able to analyse your HTML resources for a build, understand the dependencies in the marked up code and allow you to build that into your built Dojo or a layer. Hopefully this will help people avoid some of the issues that could occur if they just used features like auto-require without thinking about the consequences.
There is a lot of “good” stuff in the dojo/parser
in 1.8 that makes it more
“modern” like other parts of Dojo. Hopefully for those who use it, these
features will help you take advantage of a modern Dojo and feel “left behind”.