Prototyping with dijit/Declaration

In this tutorial, you'll learn about the special dijit/Declaration module, which was designed to allow you to quickly prototype custom widgets—all using declarative syntax.

  • Difficulty: Intermediate
  • Dojo Version: 1.8

Getting Started

Have you ever had a need to develop a custom widget using Dijit's infrastructure? What if you wanted to quickly prototype something without having to go through infrastructure setup—such as creating your own namespace to develop in, setting up the correct directory structure, making sure the module paths are all correct, etc.?

What if you wanted to do all of it in a single HTML file, using declarative syntax?

Dijit has a solution for you, in the form of dijit/Declaration. This is kind of a meta widget; the sole purpose of it is to let you define new widgets quickly, using the same kind of declarative syntax you would use to put widget instances on an HTML page.

The Basic Concept

The concept behind dijit/Declaration is simple: in your markup, you define a widget, and then you can immediately create instances of that widget. Here's a simple example:

<script type="text/javascript">
	require(["dojo/parser", "dojo/domReady!"], function(parser){
		parser.parse();
	});
</script>

<div data-dojo-type="dijit/Declaration"
		data-dojo-props="widgetClass:'Employee', defaults:{ empid:0, name:'' }">
	<span>${name}</span>
	<a href="update.php?id=${empid}">update</a>
	<a href="delete.php?id=${empid}">delete</a>
</div>

<div data-dojo-type="Employee"
		data-dojo-props="empid: 100, name:'John Doe'">
</div>

We'd like to take the time here (and we'll continue to reinforce this throughout this tutorial) to remind you that you should never deploy something defined with dijit/Declaration in a production application. dijit/Declaration was intended to be used only to quickly prototype new widgets—when you are satisfied with your custom widget, you should refactor it into a traditional dojo/declare'd format. The dojo/declare format will make the end product more portable and will allow you to create builds that include the new widget.

Let's break down the code above.

Defining Your Widget

The first HTML block in the example above is our widget definition (using dijit/Declaration). It defines at least one required property (widgetClass), sets up a few default properties with values, and contains an embedded template. Anything defined using dijit/Declaration automatically assumes that you are inheriting from dijit/_WidgetBase, and mixing in dijit/_TemplatedMixin and dijit/_WidgetsInTemplateMixin.

The widgetClass property

Every declaration must have a widgetClass property defined. This is the resulting name of your widget class, and what you'll use when instantiating widgets based on your code. This name can be anything—including namespaced definitions (just make sure your namespace has been defined already).

The defaults object

Optionally, you can pass the declaration an object of default properties, called defaults. Not only will the declaration create those properties in your new class, but will set the default values of those properties when creating the initial definition of your widget.

The template

Finally, inside the declaration is your HTML template that will be used whenever a new instance of your custom widget is instantiated. This template follows the same rules as any widget created using the dijit/_TemplatedMixin and dijit/_WidgetsInTemplates mixins (see the Writing Templates section), including variable substitution, embedded widgets, attach points and events, and more.

dijit/Declaration assumes (unlike most widgets built on dijit/_TemplatedMixin) that you will be including widgets within your template (i.e. it mixes in dijit/_WidgetsInTemplateMixin).

Instantiating your custom widget

Once your custom widget is defined, you can create instances of it using the standard Dijit way—by using data-dojo-type and data-dojo-props attributes in your markup. The Dojo parser will instantiate your custom widget and use the included/embedded template to render your code, and the custom widget is immediately available after the declaration of that widget.

Using Attach Points and Event Attachments

Just like with any other _TemplatedMixin-based widget, both attach points and event attachments can be defined in your inline template. However, since you are defining your widget using declarative syntax you'll need some way to define methods inline as well. To handle this, the Dojo parser allows you to inline any script blocks in your code using a special syntax, like so:

<div data-dojo-type="dijit/Declaration"
		data-dojo-props="widgetClass:'CustomButton', defaults:{ counter:0 }">
	<button data-dojo-attach-event="onclick: myHandler"
			data-dojo-attach-point="containerNode"></button>
	<span data-dojo-attach-point="messageNode"></span>
	<script type="dojo/method" data-dojo-event="myHandler">
		this.inherited(arguments);
		this.messageNode.innerHTML = "You clicked me " + (++this.counter) + " times!";
	</script>
</div>

<button data-dojo-type="CustomButton">Click for a message</button>
View Demo

In our example, you'll notice the use of a script block with a special MIME type, called dojo/method. This is one of two ways of declaratively attaching code in your custom widget; using the corresponding data-dojo-event attribute (and an optional data-dojo-args attribute that defines the arguments to be passed to your method definition), you can create method definitions right in your template. The data-dojo-event attribute should be the name of the method you are writing.

At this point, we'd like to give you a friendly reminder that you should never deploy something defined with dijit/Declaration in a production application.

Building on Existing Widgets

One of the things you can also do is extend an existing widget using dijit/Declaration. You do this through the optional argument in your declaration called mixins, like so:

<div data-dojo-type="dijit/Declaration"
		data-dojo-props="widgetClass:'CustomButton', defaults:{ counter:0 }">
	<button data-dojo-attach-event="onclick: myHandler"
			data-dojo-attach-point="containerNode"></button>
	<span data-dojo-attach-point="messageNode"></span>
	<script type="dojo/method" data-dojo-event="myHandler">
		this.inherited(arguments);
		this.messageNode.innerHTML = "You clicked me " + (++this.counter) + " times!";
	</script>
</div>

<div data-dojo-type="dijit/Declaration"
		data-dojo-props="widgetClass: CustomButton2, mixins: ['CustomButton']">
	<button data-dojo-attach-event="onclick: myHandler"
			data-dojo-attach-point="containerNode"></button>
	<div data-dojo-attach-point="messageNode" style="font-weight:bold;"></div>
</div>

<button data-dojo-type="CustomButton2">Click me!</button>
View Demo

Here you'll note that all we wanted to do was change the template between our original CustomButton and our new CustomButton2, so that the message node is now a block element with a bold font. You'll also notice that we did not have to redefine our myHandler method; since CustomButton2 extends CustomButton, the CustomButton's version of myHandler is automatically picked up and used.

Connecting to Existing Methods

We mentioned before about the ability to include script blocks right within your template for your custom widgets, using the special dojo/method MIME type. But if you want to simply attach something for execution—say, against an existing method—you can use the other special MIME type the Dojo parser provides: dojo/connect. Any script block using this as the MIME type will have the corresponding code connected (via dojo/connect) to the given method, as opposed to overwriting it. This is particularly useful when you need to do something with one of the base methods of dijit/_Widget, such as the startup method—where you don't want to overwrite the existing method, but simply execute something when that method is executed. Here's an example:

<div data-dojo-type="dijit/Declaration" data-dojo-props="widgetClass:'foo'">
	<p data-dojo-attach-point="messageNode"></p>
	<script type="dojo/connect" data-dojo-event="startup">
		this.messageNode.innerHTML = "This message was created during startup.";
	</script>
</div>

<div data-dojo-type="foo"></div>
View Demo

Have we mentioned yet that you should never deploy something defined with dijit/Declaration in a production application?

Including Existing Widgets in Your Declaration

We alluded to the fact that unlike most programmatically-declared widgets, dijit/Declaration automatically assumes that you will be including other widgets in your own declarations. Adding in existing widgets is simple enough; simply define the widget just like you would in any other declarative definition, like so:

<script type="text/javascript">
	require(["dojo/parser", "dijit/ProgressBar", "dojo/domReady!"], function(parser){
		parser.parse();
	});
</script>

<div data-dojo-type="dijit/Declaration"
		data-dojo-props="widgetClass:'WithWidget', defaults: { progress:100 }">
	<p data-dojo-attach-point="firstTextNode"></p>
	<div data-dojo-type="dijit/ProgressBar"
			data-dojo-props="style:'width:400px', maximum:200, progress: ${progress}">
	</div>
	<p data-dojo-attach-point="lastTextNode"></p>

	<script type="dojo/method" data-dojo-event="postCreate">
		this.inherited(arguments);
		this.firstTextNode.innerHTML = "Text created on postCreate!";
	</script>
	<script type="dojo/connect" data-dojo-event="startup">
		this.lastTextNode.innerHTML = "Text created on startup!";
	</script>
</div>

<div data-dojo-type="WithWidget" data-dojo-props="progress: 150"></div>

Note though that the widgets that occur in templates must be pre-loaded (via a require() call or <script type="dojo/require">), as done above.

View Demo

Putting it All Together

Now that we've learned how dijit/Declaration works, and how to wire up custom code to a widget, let's put it all together. We're going to quickly prototype a complex widget that should do the following:

  1. Set up a basic layout using dijit/layout/BorderContainer
  2. Create a title for our widget
  3. Load up some data and display it in a grid
  4. Create an editor (dijit/Editor)
  5. When someone clicks on a row in the grid, display some data in the editor.

To begin with, let's load and run the parser:

require(["dojo/parser", "dojo/domReady!"], function(parser){
	parser.parse();
});

Next, we'll load all widgets used within the template, plus other modules referenced from <script> blocks:

<script type="dojo/require">
	lang: "dojo/_base/lang",
	ItemFileReadStore: "dojo/data/ItemFileReadStore",
	BorderContainer: "dijit/layout/BorderContainer",
	ContentPane: "dijit/layout/ContentPane",
	Grid: "dojox/grid/DataGrid",
	Editor: "dijit/Editor"
</script>

Next, we'll define our custom widget, complete with template and a couple of custom methods:

<div data-dojo-type="dijit/Declaration"
		data-dojo-props="widgetClass:'ComplexWidget', defaults:{ title:'A Complex Widget' }">
	<div data-dojo-type="dijit/layout/BorderContainer"
				data-dojo-props="liveSplitters: false, design: 'headline'"
				style="width:100%;height:640px;">
		<div data-dojo-attach-point="headerPane"
					data-dojo-type="dijit/layout/ContentPane"
					data-dojo-props="region:'top',splitter:false"
					style="background-color:#efefef;">
			<h2 style="margin:0;">${title}</h2>
		</div>
		<div data-dojo-attach-point="grid" data-dojo-type="dojox/grid/DataGrid"
					data-dojo-props="region:'center'"></div>
		<div data-dojo-attach-point="editorPane"
					data-dojo-type="dijit/layout/ContentPane"
					data-dojo-props="region:'bottom', splitter:true">
			<div data-dojo-attach-point="editor" data-dojo-type="dijit/Editor">
				Click on a row to edit the author's bio (does not save).
			</div>
		<div>
	<div>
	<script type="dojo/method" data-dojo-event="postCreate">
		//	set the structure of our grid, and set up the connection to it.
		this.inherited(arguments);
		this.grid.set('structure', this.layout);

		//	when someone clicks on a row, set the value of the
		//	editor to be the included bio.
		this.grid.on("rowClick", lang.hitch(this, function(evt){
			var idx = evt.rowIndex,
				item = this.grid.getItem(idx),
				value = this.store.getValue(item, "bio");

			this.editor.set("value", value);
		}));
	</script>
	<script type="dojo/connect" data-dojo-event="startup">
		//	we set the grid's data in our startup method, so
		//	that it has a chance to render properly.
		if(this.data){
			// set up our store with the data
			this.store = new ItemFileReadStore({ data: this.data });

			//	feed the grid with the store
			this.grid.setStore(this.store);
		}
	</script>
</div>

Next, we'll set up a couple of variables for our widget instance to use (note that we are not hard-coding it into the widget definition):

var data = {
	identifier: "id",
	label: "name",
	items: [
		//	items go here
	]
};

var layout = {
	defaultCell: {
		width: 10,
		editable: false,
		headerStyles: 'padding: 0',
		styles: 'text-align: center; padding: 2px;'
	},
	cells: [
		{ name: "ID", field: "id" },
		{ name: "Avatar", field: "avatar", formatter: function(value){
			return '';
		}},
		{ name: "Name", field: "name", styles: "text-align:left;", width: "100%"},
		{ name: "Company", field: "company", width: 14 }
	]
};

Finally, let's test it by creating an instance of the widget!

<div id="test" data-dojo-type="ComplexWidget"
	data-dojo-props="title: 'Dojo Tutorial Authors', data: data, layout: layout">
</div>
View Demo

As you can see, prototyping widgets with dijit/Declaration can be quite powerful; with just a few lines of code we were able to create a pretty complex widget by leveraging existing widgets in the Dojo Toolkit.

A final word of warning: you should never deploy something defined with dijit/Declaration in a production application! When you are satisfied with your custom widget, you should refactor it into a traditional dojo/declare'd format.

Conclusion

In this tutorial, we've learned how to quickly prototype custom widgets with dijit/Declaration, by leveraging existing pieces of the Dojo Toolkit—including Dijit's template system, custom blocks of code using Dojo's special script block approach, and including existing widgets in your definitions. We hope that you can use dijit/Declaration to work through your own widget development needs!

Error in the tutorial? Can’t find what you are looking for? Let us know!