One of the common patterns in Web UI development is where we try to specify and manipulate a tree of elements, without coupling Javascript code to the specific HTML. In the following, I describe some of the techniques I use when developing reusable Mootools components that are uncoupled from HTML produced by a designer.
The Problem
As an example, to generate a menu such as:
<ul class="menu">
<li class="menu-item">Item 0</li>
<li class="menu-item">Item 1</li>
<li class="menu-item">Item 2</li>
</ul>
... we can use the following Mootools Code
var menu = new Element('ul', {
'class': 'menu'
});
3.times(function(idx) {
var item = new Element('li', {
'class': 'menu-item',
'text': 'Item ' + idx,
'events': {
'click': function(ev) {
itemClicked(this);
}
});
item.inject(menu, 'bottom');
});
Suppose we had to add rounded corners on our fixed width ul.menu, we can use the sliding doors technique:
<div class="menu-wrapper">
<ul class="menu">
<li class="menu-item">Item 0</li>
<li class="menu-item">Item 1</li>
<li class="menu-item">Item 2</li>
</ul>
<div class="menu-bottom"></div>
</div>
Similiarly, if the design requires a fixed-height rounded box around each menu item:
<div class="menu-wrapper">
<ul class="menu">
<li class="menu-item">
<span class="left">Item 0</span><span class="right"></span>
</li>
<li class="menu-item">
<span class="left">Item 1</span><span class="right"></span>
</li>
<li class="menu-item">
<span class="left">Item 2</span><span class="right"></span>
</li>
</ul>
<div class="menu-bottom"></div>
</div>
As you can see, the HTML structure gets more complicated, and as a result, the corresponding javascript code would also be daunting to write and maintain, and it wouldn't be resuable in other projects where the required structure may be different.
In order to handle this in a reusable fashion, it would be much better if we could use some templating system on the client-side. There are a number of complex templating solutions available for Javascript, and one very good one (SubtleTemplates) is also available as a mootools plugin.
But for most cases, a simple API provided by Mootools Core is all thats needed. This is the String.substitute method.
What is String.substitute
String.subsitute implements a String interpolation operation. It is very a simple counterpart to server side templating languages.
It substitutes strings of the form {variablename} with its value from a context, which is an object with a key called variablename.
For e.g.,
var context = { 'variablename': 100 };
var template = "{variablename}";
template.substitute(context) == "100";
String.substitute can only do interpolation. It does not have any of the other niceties of templating, such as loops, etc, but we can still make good use of it using various techniques.
Using String.substitute
As we are looking for an HTML templating system, one immediate plugin becomes very useful. It should take a given string, and produce HTML elements from it. To do this, we can use the following simple piece of code:
String.implement({
template: function(context) {
context = context || {};
var el = new Element('div', { 'html': this.substitute(context)});
var children = el.getChildren();
return children.length == 1 ? children[0] : children;
},
});
Here's an example of its use:
var CustomMenu = new Class({
createMenu: function(items) {
var menuTemplate = '<div class="menu-wrapper"><ul class="menu"></ul><div class="menu-bottom"></div></div>';
var menu = menuTemplate.template();
menu.setStyles({ top: 10, left: 10 });
menu.inject(document.body, "bottom");
menu = menu.getElement('ul.menu');
items.each(function(item) {
var itemTemplate = '<li class="menu-item"><span class="left">{title}</span><span class="right"></span></li>';
var menuItem = itemTemplate.template({title: item.title});
menuItem.inject(menu, "bottom");
menuItem.addEvent(...);
menuItem.store("dataValue", item.value);
});
}
});
You can see above a simplistic way to use interpolation to achieve a specific design. Notably, we use the template String extension to create our elements. The advantage is ease of editing, without much loss in functionality.
Let us make it more resuable.
Element.implement({
thisOrElement: function(selector) {
return this.match(selector) ? this : this.getElement(selector);
}
});
var CustomMenu = new Class({
options : {
menuTemplate : '<ul class="menu"></ul>',
itemTemplate : '<li class="menu-item">{title}</li>',
},
initialize: function(items, options) {
this.setOptions(options);
this.createMenu(items);
},
createMenu: function(items) {
var menu = this.options.menuTemplate.template();
menu.setStyles({ top: 10, left: 10 });
menu.inject(document.body, "bottom");
menu = menu.thisOrElement('.menu');
items.each(function(item) {
var menuItem = this.options.itemTemplate.template(item);
menuItem.inject(menu, "bottom");
menuItem = menuItem.thisOrElement('.menu-item');
menuItem.addEvent(...);
menuItem.store("dataValue", item.value);
});
}
});
var items = [
{
'css': 'even',
'title': 'Item 0',
'value': '0'
},
{
'css': 'odd',
'title': 'Item 1',
'value': '1'
},
{
'css': 'even',
'title': 'Item 2',
'value': '2'
},
];
var menu = new CustomMenu(items, {
menuTemplate : '<div class="menu-wrapper"><ul class="menu"></ul><div class="menu-bottom"></div></div>',
itemTemplate : '<li class="menu-item {css}"><span class="left">{title}</span><span class="right"></span></li>',
});
Reusability can be achieved by using Class Options thus allowing users of your class the ability to change the default templates.
Of course, the default options included in the class must be the minimal or simplest HTML structure that works with your code. Styling can be achieved by overriding these options when creating a new object of that class.
However, one problem is that we may need to post-process specific elements, and there should be a standard way to retrieve them from the generated tree. As can be seen in the above example, in order to create menu items as per the required HTML, they need to be injected into the ul.menu.
If the default template is used, then ul.menu is the same element that is generated by templating. But, if we change this template, it is possible that the ul.menu might be a descedent element, e.g., it may be a child of div.menu-wrapper.
In order to handle this situation, we need to do two things:
- Designer and Developer must decide on specific selectors (or to be even more reusable, specify class options which provide selectors ), which acts as a pointer to the element we need, and
- we can create a simple Element extension: a method thisOrElement, which behaves exactly as Element.getElement but it first checks if the current element matches the selector and returns it if it does.
Thus, after deciding on the selector .menu the code menu.thisOrElement('.menu') would return the right element with either of the templates in the example above.
To take the reusability up a notch, we also allow provide a route by which the user can manipulate the context passed to templates.
In the above case, we achieve zebra striping/coloring for menu items, by providing a new template for menu items and providing the CSS class in the context.
Cooperating with a Designer
Some important problems when working with templates are:
- our template HTML might itself be produced by a server side template, and/or
- when the javascript is fully externalized, designers will have to look at two different files in order to implement UI elements, and/or
- in some cases, our template may be repeated twice (in order to support browsers which may not have Javascript enabled), and/or
- we want nice highlighting when editing HTML, and our text editor may not support nested contexts.
In order to mitigate these problems, I use a technique to embed the template within the HTML file.
<textarea id="menu-template" style="display: none">
<div class="menu-wrapper"><ul class="menu"></ul><div class="menu-bottom"></div></div>
</textarea>
<textarea id="menu-item-template" style="display: none">
<li class="menu-item {css}"><span class="left">{title}</span><span class="right"></span></li>
</textarea>
The advantage of a text area is that the textarea value is interpreted as text, and not HTML. The advantages are:
- The contents need not be valid HTML, and,
- The contents need not be escaped with entities.
So the following is possible:
<textarea id="some-template" style="display: none">
<{elementTag} {attributes}>{title}</{elementTag}>
</textarea>
... which if we had used a div, for e.g., would be:
<div id="some-template" style="display: none">
<{elementTag} {attributes}>{title}</{elementTag}>
</div>
If you are using XHTML, remember that the contents of a textarea cannot contain certain characters such as < and >. So you must place the template within a CDATA section
In conjunction with the following Element extension, templating becomes easier:
Element.implement({
template: function(context) {
return (this.get('tag') == 'textarea' ? this.get('value') : this.get('text')).template(context);
}
});
var menu = new CustomMenu(items, {
menuTemplate : $('menu_template'),
itemTemplate : $('menu_item_template'),
});
Final Thoughts
As you might have noticed, the problem that we are trying to solve stems from the fact that HTML is not a pure Model in the MVC paradigm. Rather, it behaves as a composite Model and View: changes to presentation such as adding rounded boxes, need HTML changes. Successive CSS improvements such as CSS3 takes us closer to the world where all presentation could be done seperate from the HTML (Model).
The techniques mentioned above are useful even when using more featureful templating solutions.
From the point of view of the MVC pattern, we have removed the template from our javascript (Controller), and put it where it belongs: in the HTML (View).

Reply to this entry