
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”.