Many Dojo widgets make use of client-side templating for generating the UI. Template HTML files are brought in to the page with dojo/text
, parsed and converted to DOM nodes, and placed on the page, allowing our code to make substitutions, instantiate widgets within the template, and hook up events and attach points. However, we may find ourselves dealing with markup generated on the server and delivered on the page that we want to enhance with Dojo. Luckily, it is quite simple to make a custom widget that uses its source node as the template, allowing us to use markup already on the page as the template for our widget.
Widgets can easily take advantage of existing source nodes to define how they might end up rendering. They might use the source nodes to define a data set. They could be widgets that manage a number of child widgets as is done with the various Dijit Layout widgets. However, under normal circumstances, a widget’s source node is replaced by the nodes generated from its template or the original source nodes are moved to a container node within your template. What we are looking for is a way to define a widget that can encapsulate behaviors and data, provides for dynamic template generation, and retains the utility of data-dojo-attach-point
and data-dojo-attach-event
from the templating
system.
The good news is that it is very easy to do. Let’s look at a normal templated widget declaration:
define([
'dojo/_base/declare',
'dijit/_WidgetBase',
'dijit/_TemplatedMixin'
], function (declare, _WidgetBase, _TemplatedMixin) {
return declare([_WidgetBase, _TemplatedMixin], {
templateString: '<div><div data-dojo-attach-point="container"></div><div data-dojo-attach-event="mouseover:mouseOver">Go</div></div>',
mouseOver: function () {
console.log('mouseover');
}
});
});
This is a simple widget that does not do much. As you can see, it will end up creating a div containing two child divs at its declared location on the page. The first of these children has a data-dojo-attach-point
attribute, which will create a reference to this DOM node at this.container
in the widget instance. the second div has a data-dojo-attach-event
attribute which will connect that DOM node’s mouseover
event to the widget’s this.mouseOver
method.
There are numerous examples of using templates inside of Dijit, but that’s not what we want as it is too static. Our templates will be defined as part of the instance declaration instead of the widget’s JavaScript declaration. To accomplish this goal, we simply need to declare a new widget that extends from _WidgetBase
and mixes in _AttachMixin
and _WidgetsInTemplateMixin
. The _AttachMixin
module is new with Dojo 1.9 and separates the DOM node attribute parsing functionality from template processing, enabling widgets to automatically process custom node attributes used by Dijit templates of arbitrary HTML (as opposed to just pre-created templates). This enables enhancement and widgetization of existing HTML (similar to directives in AngularJS).
By extending from _WidgetBase
, we get the expected widget life cycle, getters/setters, and overall structure of a common dijit. Adding the _AttachMixin
will automatically set the widget’s domNode
to the srcNodeRef
, the DOM node that contains the data-dojo-type
attribute and the root node of our widget. This will also parse the elements for data-dojo-attach-point
and data-dojo-attach-event
attributes and set them up accordingly. Finally, mixing in _WidgetsInTemplateMixin
adds support for parsing additional declarative child widgets within our srcNodeRef
.
It is worth mentioning that this approach does not make variable substitution available to us, meaning that strings like ${stringToReplace}
would not be interpolated. This may not be an issue, however, as variable replacement can easily be taken care of by the server prior to delivering the content to the client.
As an example, let’s say we have the following static markup in our page:
<div data-dojo-type="my/Widget">
<div data-dojo-attach-point="textNode"></div>
<button data-dojo-type="dijit/form/Button" data-dojo-attach-event="click:go">Go!</button>
<button data-dojo-type="dijit/form/Button" data-dojo-attach-event="click:stop">Stop!</button>
</div>
And the widget code would look like this:
define([
'dojo/_base/declare',
'dijit/_WidgetBase',
'dijit/_AttachMixin',
'dijit/_WidgetsInTemplateMixin',
// used by the template
'dijit/form/Button'
], function (declare, _WidgetBase, _AttachMixin, _WidgetsInTemplateMixin) {
return declare([_WidgetBase, _AttachMixin, _WidgetsInTemplateMixin], {
go: function () {
this._setText('Go! button clicked');
},
stop: function () {
this._setText('Stop! button clicked');
},
_setText: function (text) {
var node = document.createTextNode(text);
this.textNode.innerHTML = '';
this.textNode.appendChild(node);
}
});
});
The instance of my/Widget
will have a reference to the div at this.textNode
, and the buttons, when clicked will call the widget’s go()
and stop()
methods, respectively. As you can see, there isn’t really any difference between this and a normal template provided by an inline string or in a template HTML file. However, it’s easy for complex servers to generate a lot of HTML and then wrap it in a my/Widget
to maintain it.
Experiment with a live demo at JSFiddle (to run in the JSFiddle environment this demo uses the first parameter to dojo/declare
to declare a named global constructor – in normal development you would encapsulate the widget in its own module as shown above).