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.

Comments

  • Good article, but I’m still a little unclear about a few points.

    My flyweight I assume you mean the flyweight design pattern? If so I’m not sure I understand your interpretation of the pattern here. Do you mean that this warper flyweight widget does the mouse event capturing for the sub-widgets (as it seem in the example download)?

    What does the “[widgetId]” do/mean in the above dojo.query, I’ve never seen that before? I’m assuming from your code comments that it somehow “find all the child nodes that are widgets”?

    I like the design approach here and it was insightful as to the inner workings of the dijit._Template widget system in Dojo.

  • By Flyweight I didn’t mean anything quite as specific as that design pattern. I guess my usage is somewhat in correct, though I’ve heard others refer to adding behaviors like this to the existing dom as a flyweight widget. I could probabably argue and rationalize the similarities between what this does and the actual design pattern, but doesn’t seem like it will get anyone anywhere, so I won’t. :)

    The “[widgetId]” query looks for domNodes that have the “widgetid” attribute on them (which all instantiated widgets do/will have).

  • Leelakrishna

    Hello,

    I am new to Dojo and trying to develop an front end design for my project and i am using accordian menu for my application.I saw the site “http://www.dojocampus.org/explorer” and i need to get that left side menus like{“data”,drag n drop,etc..} like my menus are {“payslips-sublinks are ‘Jun-08’,’jul-08′”,”TaxDeclaration-sublink is:’taxdeclarationsheet'”,etc..}
    I am not getting that left side menu exactly like labels…
    Can any body help..

    Thanks In Advance,
    Leelakrishnad.

  • Laura_

    How would this be done on Dojo 1.4?

    The approach in this post, although working great for 1.3, no longer seems to do the trick.

  • Navtej

    Laura_,
    The reason this pattern doesn’t work in dojo 1.4 is that _Templated’s buildRendering method does additional setup that is not done in the code in this blog post. The buildRendering method supplied above never calls this.inherited, so the additional setup isn’t done.
    IMHO, a better way to achieve the goals of this pattern would be to simply set the templateString of the widget to be equal to the HTML of the srcNodeRef in the constructor (or as soon as srcNodeRef is available), and then let the parent classes of _Widget and _Templated deal with the rest.

  • neil

    Navtej,

    How would that work? I’m trying to create a ‘dynamic’ template widget, but when I look at ‘this.srcNodeRef’ in ‘constructor’, it’s showing as null.

  • neil

    You know what? It doesn’t matter, I’ve figured out another way. If you pass the template in as an argument when instantiating the widget programmatically, you can get it in the constructor.

    Not sure if this will work with HTML declaration of the widget, but it’s all I need right now.

  • @neil: constructor is too early in the widget lifecycle. Buildrendering is where you can mess with the inlined template. It is available on this.srcRefNode

  • httpete

    For dojo 1.7, this works:

    buildRendering: function(){
    this._attachTemplateNodes(this.srcNodeRef, function(n,p){ return n.getAttribute(p); });
    },

  • Pingback: Creating Dojo Widgets with Inline Templates | Blog | SitePen()