DojoX DTL (Django Template Language)

The Django Template Language is one part of Django, a "high-level Python Web framework that encourages rapid development and clean, pragmatic design." Django is the preferred web framework for several of the Dojo commiters.

The DojoX implementation implements the full infrastructure of the Django Template Language. This language, as implemented in the Django Project is limited to text, since it only deals with page serving. While Dojo's implementation also works with text, it has an additional layer that allows us to dynamically render blocks of HTML.

Existing templates should work without fuss using Dojo's implementation. There are some additional abilities in an HTML environment, obviously, but you can add those as you go.

Learning the Markup

Since Dojo implements markup just as it is in Django's implementation, the best place to visit would be their excellent book or their excellent documentation

But in case you are just itching to know what it looks like, it's made up of some simple parts: {% tags %}, {{ variables }}, and {{ variables|filtered }} and {{ variables|more:"advanced"|filtering }}. Sometimes tags have a start and an end tag, sometimes they work alone.

The Base Constructors: Templated and HtmlTemplated

We have 2 base constructors when using DTL outside of a widget: dojox.dtl.Template and dojox.dtl.HtmlTemplate.

Template works only with text. What this means is that you can't use it to do DOM manipulation, you can only use it to generate text that you might use to set a node's innerHTML.

HtmlTemplate is an extension to Templated, which means in terms of size, that it's the full size of Template plus some more code. But using HtmlTemplate, you can do direct DOM manipulation. This means that if a node is inside of an /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.javascript .imp {font-weight: bold; color: red;}
.javascript .kw1 {color: #000066; font-weight: bold;}
.javascript .kw2 {color: #003366; font-weight: bold;}
.javascript .kw3 {color: #000066;}
.javascript .co1 {color: #009900; font-style: italic;}
.javascript .coMULTI {color: #009900; font-style: italic;}
.javascript .es0 {color: #000099; font-weight: bold;}
.javascript .br0 {color: #66cc66;}
.javascript .st0 {color: #3366CC;}
.javascript .nu0 {color: #CC0000;}
.javascript .me1 {color: #006600;}
.javascript .re0 {color: #0066FF;}
{% if %} block, it will be removed from DOM if the logic is false, and will be added to DOM if the logic is true.

Both of these constructors can take either plain text, or a URL. HtmlTemplate can also accept a node.

Creating and Rendering a Template in Plain Text

Creating a new instance

As mentioned in the previous chapter, you can create a new instance of dojox.dtl.Template using either a URL or a string.

Once you've created an instance of this object (which now contains a compiled version of your template that you can render as many times as you want), you have a few options:

The update function

Every dojox.dtl.Template instance will have an update function. This function will change the innerHTML of a node, or a list of nodes. This function accepts a node, a node ID, or a dojo.query result as its first parameter, and an object or URL as its second parameter, to be used as a context.

The render function

This works exactly the same as rendering a template in Django.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #000066; font-weight: bold;}
.geshifilter .kw2 {color: #003366; font-weight: bold;}
.geshifilter .kw3 {color: #000066;}
.geshifilter .co1 {color: #009900; font-style: italic;}
.geshifilter .coMULTI {color: #009900; font-style: italic;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #66cc66;}
.geshifilter .st0 {color: #3366CC;}
.geshifilter .nu0 {color: #CC0000;}
.geshifilter .me1 {color: #006600;}
.geshifilter .re0 {color: #0066FF;}

dojo.require("dojox.dtl");
dojo.require("dojox.dtl.Context");

var template = new dojox.dtl.Template("Hello {{ place }}!");
var context = new dojox.dtl.Context({
  place: "World"
});
console.debug(template.render(context)); //1

Use dojo.query

With the dojo.query extension, you don't even need to create a template instance. What this means is that repeatedly rendering a template will be slightly slower, but your code will be more compact.

To use, make sure you require the dojox.dtl.ext-dojo.NodeList module, which adds the dtl function. It accepts a string or URL as its first parameter, and an object or URL as its second parameter. Like the update function above, it will change the innerHTML values of all nodes in the dojo.query result, using the first parameter as its template and the second parameter as its context.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #000066; font-weight: bold;}
.geshifilter .kw2 {color: #003366; font-weight: bold;}
.geshifilter .kw3 {color: #000066;}
.geshifilter .co1 {color: #009900; font-style: italic;}
.geshifilter .coMULTI {color: #009900; font-style: italic;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #66cc66;}
.geshifilter .st0 {color: #3366CC;}
.geshifilter .nu0 {color: #CC0000;}
.geshifilter .me1 {color: #006600;}
.geshifilter .re0 {color: #0066FF;}

dojo.require("dojox.dtl.ext-dojo.NodeList");

dojo.query(".fruit").dtl("Fruit is: {{ fruit }}", { fruit: "apple" });

Writing a Widget

We'll bypass how to use the raw HtmlTemplate object (we'll get into that later) and explain how to write a widget using Dojo's implementation of the Django Template Language.

Both of the solutions covered here work almost exactly like dijit._Templated, which is covered elsewhere in the book. To use the text version, mix in dojox.dtl._Templated and to use the HTML version, mix in dojox.dtl._HtmlTemplated.

These objects will use templatePath, templateString, and use the dojoAttachPoint and dojoAttachEvent node attributes.

They add a single function: render. This function is to be used in the event of re-rendering. One of the main reasons for re-render would be if not all data was available during instantiation. The template will be rendered during creation even if you don't call the render function.

The template will be rendered using the widget object as its context. If you don't want this behavior, you can pass your own Context object to the render function.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #000066; font-weight: bold;}
.geshifilter .kw2 {color: #003366; font-weight: bold;}
.geshifilter .kw3 {color: #000066;}
.geshifilter .co1 {color: #009900; font-style: italic;}
.geshifilter .coMULTI {color: #009900; font-style: italic;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #66cc66;}
.geshifilter .st0 {color: #3366CC;}
.geshifilter .nu0 {color: #CC0000;}
.geshifilter .me1 {color: #006600;}
.geshifilter .re0 {color: #0066FF;}

dojo.require("dojox.dtl._Templated");

dojo.declare("demo", [dojox.dtl._Widget, dojox.dtl._Templated] {
  templateString: "<div>I like eating {{ fruit }}</div>",
  postCreate: function(){
    this.fruit = "apple";
    this.render();
  }
});

New "Contributed" Tags and Filters for a the Browser and Dojo

To Use

These tag/filter sets can be included with either dojo.require, or the /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.javascript .imp {font-weight: bold; color: red;}
.javascript .kw1 {color: #000066; font-weight: bold;}
.javascript .kw2 {color: #003366; font-weight: bold;}
.javascript .kw3 {color: #000066;}
.javascript .co1 {color: #009900; font-style: italic;}
.javascript .coMULTI {color: #009900; font-style: italic;}
.javascript .es0 {color: #000099; font-weight: bold;}
.javascript .br0 {color: #66cc66;}
.javascript .st0 {color: #3366CC;}
.javascript .nu0 {color: #CC0000;}
.javascript .me1 {color: #006600;}
.javascript .re0 {color: #0066FF;}
{% load %} tag.

Attribute Tags

Before we get to the list, it's important to note that HtmlTemplate considers attributes to be a full-fledged part of the template system. What this means is that there are new tags that have been introduced that use named attributes in order perform new actions. For the curious tag author, this is accomplished by registering a named tag with "attr:" in front of it

dojox.dtl.contrib.dijit

These tags are only applicable to the HTML version of the template system. If you want the same behavior in a text-based environment, you have to use the dojox.dtl._Templated mixin on a widget. These tags will work even if they are used outside of dojox.dtl._HtmlTemplated.

  • Node Attribute: dojoType: If widgetsInTemplate is set on a widget, or if the global dojox.dtl.contrib.dijit.widgetsInTemplate is set, the node will be instantiated as a widget of that type.
  • Node Attribute: dojoAttachPoint: Works exactly the same as the Dijit templated equivalent
  • Node Attribute: attach/dojoAttachPoint: Works exactly the same as the Dijit templated equivalent
  • Node Attribute: dojoAttachEvent: Works exactly the same as the Dijit templated equivalent
  • Note Attribute: onclick/onmouseover/etc: The string you have in its value is the name of the function to call when that action is performed. eg onclick="goHome"

dojox.dtl.contrib.html

These are also only applicable to the HTML version. The html tag is especially important because, without it, the variable contents are always rendered as text.

  • html: If you want to render a variable as HTML, you have to use {% html varName %}
  • Node Attribute: tstyle: Use if you want to change style attributes during rendering. eg tstyle="top: {{ y }}px;"

dojox.dtl.contrib.data

This allows you to take a given data store, a given list of items, and assign them to a single variable that can be used as if each item was a normal variable.

In the example below, /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.javascript .imp {font-weight: bold; color: red;}
.javascript .kw1 {color: #000066; font-weight: bold;}
.javascript .kw2 {color: #003366; font-weight: bold;}
.javascript .kw3 {color: #000066;}
.javascript .co1 {color: #009900; font-style: italic;}
.javascript .coMULTI {color: #009900; font-style: italic;}
.javascript .es0 {color: #000099; font-weight: bold;}
.javascript .br0 {color: #66cc66;}
.javascript .st0 {color: #3366CC;}
.javascript .nu0 {color: #CC0000;}
.javascript .me1 {color: #006600;}
.javascript .re0 {color: #0066FF;}
{{ item.title }} will be the equivalent of calling: /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.javascript .imp {font-weight: bold; color: red;}
.javascript .kw1 {color: #000066; font-weight: bold;}
.javascript .kw2 {color: #003366; font-weight: bold;}
.javascript .kw3 {color: #000066;}
.javascript .co1 {color: #009900; font-style: italic;}
.javascript .coMULTI {color: #009900; font-style: italic;}
.javascript .es0 {color: #000099; font-weight: bold;}
.javascript .br0 {color: #66cc66;}
.javascript .st0 {color: #3366CC;}
.javascript .nu0 {color: #CC0000;}
.javascript .me1 {color: #006600;}
.javascript .re0 {color: #0066FF;}
store.getValue(item, "title"};

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #000066; font-weight: bold;}
.geshifilter .kw2 {color: #003366; font-weight: bold;}
.geshifilter .kw3 {color: #000066;}
.geshifilter .co1 {color: #009900; font-style: italic;}
.geshifilter .coMULTI {color: #009900; font-style: italic;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #66cc66;}
.geshifilter .st0 {color: #3366CC;}
.geshifilter .nu0 {color: #CC0000;}
.geshifilter .me1 {color: #006600;}
.geshifilter .re0 {color: #0066FF;}

{% bind_data items to store as flickrItems %}
{% for item in flickrItems %}{{ item.title }}: {{ item.description }}{% endfor %}

You can also use: /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.javascript .imp {font-weight: bold; color: red;}
.javascript .kw1 {color: #000066; font-weight: bold;}
.javascript .kw2 {color: #003366; font-weight: bold;}
.javascript .kw3 {color: #000066;}
.javascript .co1 {color: #009900; font-style: italic;}
.javascript .coMULTI {color: #009900; font-style: italic;}
.javascript .es0 {color: #000099; font-weight: bold;}
.javascript .br0 {color: #66cc66;}
.javascript .st0 {color: #3366CC;}
.javascript .nu0 {color: #CC0000;}
.javascript .me1 {color: #006600;}
.javascript .re0 {color: #0066FF;}
{{ item.getLabel }}, {{ item.getAttributes }}, and {{ item.getIdentity }}

Note that there's a little weirdness with getValue and getValues. If you want to be sure that getValues is called, just add an extra "s" to the end of your variable name.

Using the Extends Tag

In Django, the extends tag looks through the installed applications until it finds the named template. In a browser environment, we don't want to have to go searching for templates, so there has to be a way to reference a specific file, while not changing the markup style of the extends tag.

The "easiest" way to do this is to put an explicit reference to the template. This means that you need to specify a URL in relation to your root page. But doing it like this creates a problem if you want to move around your directory structure, or if a page in a different directory wants to use the template.

Django helps us out by allowing a variable name to be used in the extends tag. What we can do with this, then, is set a variable in the Context using dojox.moduleUrl.

If we're using the extends tag in an HTML environment, there's another factor to consider. Let's say we have a blog and there are two ways of viewing the page: a list view, and a detail view. Both of these views use a parent node that contains the page header, a menu, and a sidebar. We don't want the template system to have to redraw the DOM for their parent template, but how do we indicate that? There are two ways, one which is significantly better than the other.

The first is to use a string in the extends tag, outlined in the "easiest" way at the top. Putting "shared:" at the beginning of the string tells the extends tag to reuse the nodes between all other children that also want to share the parent.

The significantly better way is partly outlined in the section above on moduleUrl. You can use a variable containing a moduleUrl, but how do you tell the extends tag that you want to share the parent? Instead of just passing a moduleUrl call, when we have an extends tag that looks like {% extends parent %}, we can use an object that looks like this:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
new dojox.dtl.Context({
  parent: {
    url: dojo.moduleUrl("mymodule", "templates/template.html"),
    shared: true
  }
});

New Context Object Abilities

Unlike the page serving model of Django, we can keep our Context objects around between each template render. What we want to be able to do is quickly clone an existing context, and either reduce, or add to, the data in the object. To do this, there are two new functions:

  • filter: Just put the keys you want to key in its arguments to get only those keys back in a cloned Context.
  • extend: Just pass it an object to get a cloned Context containing all of the old keys, plus the new ones.

Some new functions are added to allow tags to communicate with the rendering object.

  • setThis: Sets the object on which to perform operations. Used by the attach attribute tag, for example.
  • getThis: Used by tags, gets the currently set this object.

Advanced Topics

While Django provides some documentation about how their compiler works, which you should definitely read before continuing this section, there are some areas in which things are done differently, such as tag and filter registration.

The other major difference in Dojo's implementation is the new HTML compiler. This section will go over how much of what we've done was accomplished, and will provide information to those curious about how the normal text-based compiler works.

Writing and Registering Tags and Filters

The actual filter functions are identical in terms of structure to the way they are implemented in Django. What this means is that Django's documentation is great for learning how to write a filter, and doesn't need to be rewritten here

Tags work in the following way: a registered tag is passed the parser object, and the tag string, and is tasked with returning an object that obeys a specific interface. This is how they are implemented in Django, so once again, their documentation will go a long way toward understanding how to write a tag.

The one distinct difference between the two system is rendering a NodeList. The expected behavior would be to pass it the context and buffer objects that the render function accepts, but you must also pass its own instance (this) as the third parameter. Without this, there could be unexpected behavior when rendering in an HTML environment. You should also clone nodelists and then render them, rather than taking the re-rending approach of Django's normal system. Because the rendering in HTML results in a DOM tree, re-rendering without cloning would change the same nodes over and over.

As mentioned earlier, there is a new tag type in the HTML version of the compiler. If you've registered an attribute-based tag, you will be passed a null parser object, and the string contents of the attribute tag are prefixed with the attribute name. What this means is that, for example, in the case of an attach attribute that looks like attach="varName", the string that is passed is "attach varName". This way, you can register your function with a regular expression, but see exactly what was matched.

The only piece of the interface you have to implement for the text-based version is the render function. If your tag will appear in an HTML environment, you must add an unrender and clone function. The unrender function is tasked with removing a node from the DOM if one was previously added, or to tell the "swallowed" nodes to unrender. The clone function should duplicate the object. Note that the same tags are used in both the text and HTML versions of the compiler, so your functions should be written with this in mind.

Registering tags/filters looks like this:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */
.geshifilter {font-family: monospace;}
.geshifilter .imp {font-weight: bold; color: red;}
.geshifilter .kw1 {color: #000066; font-weight: bold;}
.geshifilter .kw2 {color: #003366; font-weight: bold;}
.geshifilter .kw3 {color: #000066;}
.geshifilter .co1 {color: #009900; font-style: italic;}
.geshifilter .coMULTI {color: #009900; font-style: italic;}
.geshifilter .es0 {color: #000099; font-weight: bold;}
.geshifilter .br0 {color: #66cc66;}
.geshifilter .st0 {color: #3366CC;}
.geshifilter .nu0 {color: #CC0000;}
.geshifilter .me1 {color: #006600;}
.geshifilter .re0 {color: #0066FF;}

dojox.dtl.register.filter("my.module", "my.module.obj", ["fn", "anotherFn", [/RegExp/, "fnForRegex"]]);

Obviously, registering a tag would use dojox.dtl.register.tag. The first parameter is the require statement to call if we encounter one of these tags or filters. The second parameter is the base object on which these functions are stored. And the final parameter is an array of function names, or RegExp/function name pairs. Since some words are "reserved words", if we can't find the function on your object, we check for the function name with an underscore on the end of it.

If you write a custom set of tags and filters, the way to use them is exactly the same as in Django: the load tag. At the top of your template, add {% load namespace.tags namespace.filters etc %}. You should register your tags and filters in the same file as you declare them, and where you put the registration code doesn't matter, it can be before or after your object declarations.

How HTML Templates are Compiled

A quick note before we begin: If tags and filters within code are wrapped in comment tags, as in /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .html4strict .imp {font-weight: bold; color: red;} .html4strict .kw1 {color: #b1b100;} .html4strict .kw2 {color: #000000; font-weight: bold;} .html4strict .kw3 {color: #000066;} .html4strict .coMULTI {color: #808080; font-style: italic;} .html4strict .es0 {color: #000099; font-weight: bold;} .html4strict .br0 {color: #66cc66;} .html4strict .st0 {color: #ff0000;} .html4strict .nu0 {color: #cc66cc;} .html4strict .sc0 {color: #00bbdd;} .html4strict .sc1 {color: #ddbb00;} .html4strict .sc2 {color: #009900;} <!--{% tag %}--> then some browsers treat them as full nodes and will help a lot with node traversal. Just something worth knowing.

There are two steps to compiling a template: tokenizing a template, and parsing the tokens. The types of tokens can be found in dojox.dtl.text.types as well as dojox.dtl.html.types. They are: tag, variable, text, change, attribute, and element. Tags and variables are of course the foundation of our markup, text is just plain text that has no real bearing on markup, a change is when the parent node changes, an attribute is an HTML attribute, and an element is a DOMNode. During tokenizing, we actually disassemble the tree structure so that when the tokenization is done, none of the Nodes have a parent Node. The first Node encountered is the "root" Node. This is what we will use to insert our template into our document.

We build these tokens by traversing the Node tree, and when we reach a text node, tokenizing the contents if it has any tags or filters (note: this is where, if you use comments, this will never have to happen). After we're done tokenizing, we move on to parsing.

During parsing, all of the tokens become objects. Many of the tokens are always replaced with the same object. For example, the change type always becomes a dojox.dtl.html.ChangeNode, the element type always becomes a dojox.dtl.html.Node, a variable always becomes a dojox.dtl.html.VarNode, and text always becomes a dojox.dtl.html.TextNode. This leaves us with the attribute and tag types. An attribute token can go in two directions: If there is a tag registered for the name of that attribute, we call that function and the returned object replaces that token. If not, the token becomes a dojox.dtl.html.AttributeNode.

The major player here is the tag token. When the registered tag function is called, the tag doesn't always simply replace the token, it can actually "swallow" tokens until it finds a tag that it's looking for. A good example of this is the if/else/endif structure, where the function parses tokens until it reaches and endif tag. This is how nesting tags is possible. In the case of the if tag, while the first if tag is parsing tokens, looking for an else or endif tag, the parser might run into another if tag. This if tag now the one parsing tokens, looking until it finds the endif tag. Once it's done, that object replaces a whole range of tokens in the content that first if tag has been building. The parent if tag, when finished, returns an object to the parser that replaces a range of tokens, all of which are now in either one of the new objects returned by the if tags. No tokens ever disappear, they can all be accounted for in the various objects.

What we end up with is a very linear set of instructions. We have an array of objects, all our objects have a render function, and many of the objects contain lists of objects, and when their render function is called, they will call the render function on the objects they have swallowed. We have what's basically, a tree that always executes linearly, and is always called in the same order.

The Importance of our Buffer Objects

The render/unrender/clone functions used in registered tag objects all accept a buffer. Since we are using the same tag objects for both text and HTML, and we absolutely need a buffer to render an HTML template (we'll get to that in a second), a buffer will be both accepted and returned by every render/unrender function. When we render a text-based template, we use dojox.string.Builder to ensure the fastest string concatenation possible. But in the HTML version of the compiler, we have a very special Buffer.

Let's propose a situation that happens all the time when writing a template. It looks something like this:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div>
  <h1>Title</h1>
  {% if name %}
  <div>Hello {{ name }}</div>
  {% else %}
  <div><a onclick="login">Please log in </a></div>
  {% endif %}
  < p>Welcome to our wonderful site</ p>
</div>

Now when someone uses this page, we have a situation where, when they first use the application, they are given a link to log in, but once their name is set, they're given a "Hello" message. While we didn't have to write it this way, we've created a situation now where only one of two sibling divs can be shown at a time. For those of you familiar with DOM, you know how complicated it is to insert a node if it's at neither the beginning nor the end of a parent. The HTML buffer allows us to overcome this problem with ease.

As a template is rendered, the setParent function is called on the buffer object, which changes the Node that is considered to be the current parent. Calling concat on the buffer uses this parent to insert the passed node beneath the current parent. Where to place it involves a little bit of magic. As shown in the example above, if we just use appendChild, the second time we render the template, the div in the else tag will end up after the paragraph tag, which is not where it should go.

What we do, then, is check to see if the node we're inserting exists in DOM (by looking at its parentNode). If it doesn't exist in DOM, we check to see if the buffer's currently set parent has any childNodes. If the parent has no child nodes, we can just use appendChild. If there are existing child nodes, we store the child in a cache. When a node arrives that has the same parentNode as the buffer's currently set parent, we take the cache and append it before this node.

Finally, when setParent is called, we get the cache from the old parent, and make sure all the nodes are appended to the end of it, before changing the parent.

The reason this works is that every time we render, every node calls the concat function. While this may seem like a speed hit, it's not only necessary to allow us to fill in those cached nodes, the function does no other work. So in the example above we start out with the opening div set as the current parent, and no value for name. The buffer's concat function is called with the h1, the div in the else block, and finally the p tag. The h1 is added using appendChild, since it's the first node, and the div and p are cached until setParent is called at the end of the render (in code, we actually call setParent twice at the end on the root node so that the cache gets flushed).

But now, if we re-render after giving name a value, the following happens: The h1 is passed to concat and nothing happens since it has a parentNode, and the cache is empty, then the div in the if block gets added to the cache since it has no parentNode, the div in the else block gets removed from the DOM by the unrender function of the if tag, and when the p tag is passed to concat, we see that it has a parentNode, and use insertBefore to place the items in the cache (our div) before the p tag.

Using this method of buffering, we don't need to worry at all about how the DOM is organized. Simply switching the parent and calling concat manages all the complex interactions we would otherwise have to account for.

Using the HtmlTemplate

Another quick note: This page is more to explain how this object works. Using the dojox.dtl.render.html.Render object to display your templates is a cleaner and more error-proof way of rendering and inserting your template into DOM.

Really the only thing you'll be doing with this object is creating an instance of it. You can either pass it a string to use as your template, or an object that has a toString method (we encourage the use of dojo.moduleUrl). Once you have an instance, you should pass it to the dojox.dtl._Widget.render function or the dojox.dtl.render.html.Render.render function.

Ultimately, even the widget's render function utilizes dojox.dtl.render.html.Render, so looking at the code for that will give you a great idea of how these functions are used. Let's actually display some of that code here:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
buffer = buffer || tpl.getBuffer();

You can pass whatever buffer you want to the HtmlTemplate's render function. What this means is that you could either subclass the current HtmlBuffer, or implement your own (not recommended) if you really want to do something crazy with the template architecture. But the HtmlTemplate object provides the getBuffer function so that you really don't need to worry about what Buffer object you should use.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
if(this._tpl && this._tpl !== tpl){
  this._tpl.unrender(context, buffer);
}
this._tpl = tpl;

The HtmlTemplate object also provides the unrender function to unrender the template. If we want to replace a template, we should call this function on the current template before we call render on the new one.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
var frag = tpl.render(context, buffer).getParent();

The render function builds a Node tree that's not necessarily in DOM. The getParent call here is actually on the HtmlBuffer, not on the HtmlTemplate. The HtmlTemplate provides a getRootNode function, which returns the first (and should be the only) top-level node in the rendered template. It is unlikely you'll need to use it, and one of the reasons we use it is to batch DOM changes (basically, checking to see if what's in the DOM is the root node, or whether we've removed it from DOM.

The HTML Renderer

This is a component that is completely unique to the Dojo implementation of the Django Template Language and is basically unrelated to the language itself. It is an object that is designed to render templates and make sure they are inserted into the DOM properly. It also add an important component for large templates, the ability to make sure that changes in the DOM are done in batches, though this ability is turned off by default, and even when turned on, can be done at a variety of levels

This is the object to use to make sure that rendering/unrendering happens properly, and what you want to use if you want to render a template, but don't want to use a widget to do it. All users are strongly encourage to use this to render a template if they are not using dojox.dtl._Widget to render their templates.

The object can be found at dojox.dtl.render.html.Render. It has two functions: setAttachPoint and render. It must have an attach point, which can be set in the constructor, or through the setAttachPoint function.

The setAttachPoint function simply sets the node we want to use to append the rendered template to.

The render function does several nice things:

  • Unrenders the previous template if we're now using a new template.
  • Creates a buffer if one hasn't been passed.
  • Manages the batch change functionality discussed above.
  • Inserts the rendered template into the document.