Creating Dojo Widgets with Inline Templates

By on June 24, 2008 2:59 pm

I recently came across a situation where I needed to manage a set of nodes and widgets to perform a number of visual operations as well as manage some data between the client and a server back end. While this was a custom operation, it was common to a number of different places within the app. Initially, things were quite simple and I managed with a few functions and connections primarily using dojo.query(). In the end, I ended up with a simple way to make a dijit._Templated widget that uses its source node as a template instead.

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 flyweight widget that can encapsulate behaviors and data, provides for dynamic template generation, and retains the utility of dojoAttachPoints and dojoAttachEvents from the templating system.

The good news is that it is very easy to do. Let’s look at a normal templated widget declaration:

dojo.declare("my.widget",[dijit._Widget,dijit._Templated],{
	templateString: '<div><div dojoAttachPoint="container"></div>
		<div dojoAttachEvent="onMouseOver:mouseOver">Go</div></div></div>"',
	postCreate: function(){
		// do something after I'm ready. Note: My children might not be 
		// ready yet, so dont' access them here!
	},
	startup: function(){
		// This fires after my children are available
	}
});

This is 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 dojoAttachtPoint attribute, which will create a reference to this DOM node at this.container in the widget instance. The second div has a dojoAttachEvent attribute which will connect that DOM node’s onMouseOver event to the widgets this.mouseOver() method.

There are numerous examples of using templates inside of Dijit and plenty of articles over at Dojo Campus or even here at the SitePen blog! 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 widgets JavaScript declaration. To accomplish this goal, we simply need to declare the widget, and then override the buildRendering() and startup() methods provided by the dijit.Templated widget. We want to do something like this on our page:

<div dojoType="my.fly">
	<div dojoAttachPoint="container">Some Stuff</div>
	<button dojoType="dijit.form.button" dojoAttachEvent="onClick: go">
		Go!
	</button>
	<button dojoType="dijit.form.button" dojoAttachEvent="onClick: stop">
		Stop!
	</button>
</div>

So now we have a template:

Some Stuff

The instance of my.fly will have a reference to the div at this.container, and the buttons, when clicked will call the fly’s go() or stop() methods respectively. As you can see there isn’t really any difference between this and a normal template provided by string or in a template HTML file. However, it’s easy for complex servers to generate a bunch of HTML and then wrap it with a fly widget to maintain it.

Let’s look inside my.fly to see how it ticks.

dojo.declare("my.fly", [dijit._Widget, dijit._Templated],{
	buildRendering: function(){
		// we already have the srcNodeRef, so lets just
                // keep it and use it as the domNode
		this.domNode = this.srcNodeRef;

		// call this._attachTemplateNodes to parse the template,
                // which is actually just the srcnode
		// this method is provided by the _Templated mixin
		this._attachTemplateNodes(this.domNode);
	},

	startup: function(){
		var node=this.domNode;
		this._supportingWidgets = [];

		// use a dojo.query to find all the child nodes that are widgets
		// and then get references to those widgets.  The widgets are
		// collected into this._supportingWidgets

		dojo.query("[widgetId]", node).forEach(function(n){
			if(n==node){return;}
			var id = dojo.attr(n,"widgetId");
			if (!id){return;}
			this._supportingWidgets.push(dijit.byId(id));
		}, this);

		// parse the widgets for dojoAttachPoints and dojoAttachEvents. 
		// Unlike the parse done in buildRendering, we're only going 
		// through the array of widgets in this._supportingWidgets;

		this._attachTemplateNodes(this._supportingWidgets, function(n,p){
			return n[p];
		});
	},

	go: function(){
		// do someting
	},

	stop: function(){
		//stop doing whatever it was go made me do.
	}
});

This flyweight mixin provides the above buildRendering() and startup() methods for easy reuse among a variety of flyweight widgets. Also included in this download are an example widget and test file, making it extremely easy to try this out with your application.