6. JavaScript Theming – Drupal 6 JavaScript and jQuery

Chapter 6. JavaScript Theming

In this chapter, we will discuss the last major component of the drupal.js JavaScript library. In the previous chapter we looked at the translation capabilities. Before that, in Chapter 4, we covered Drupal behaviors and utility functions. In this chapter, we will focus on the JavaScript theme system.

We will cover the following:

  • The difference between theming in PHP and theming in JavaScript

  • The Drupal JavaScript theming function

  • Implementing custom themes in JavaScript

  • Using the JavaScript theming module

  • Creating a JavaScript template engine

The projects in this chapter will be focused on improving JavaScript-generated user interfaces by using the JavaScript theme system.

Theming in PHP, theming in JavaScript

In Chapter 1, we had an overview of the server-side Drupal theming system. In Chapter 2, we created the Frobnitz theme, complete with its own PHP template, a stylesheet, and some JavaScript. Since then, we've steadily added to that theme. The theme system that we have been working with runs on a server. It is written in PHP which, when executed, prepares results that are sent to a browser.

This PHP-based theme system is very powerful and complex (and, in fact, Packt Publishing has published two books on Drupal theming). However, there is a limitation to this system, namely, all of the theming must be done before the data is sent to the client.

As we have already seen, when the page is delivered and loaded, JavaScript can start doing its thing. It can rearrange the page, add new data, hide old data, and create an interactive environment in which the user's input can change what is displayed.

The server's theme engine is responsible for taking Drupal data and laying it out for display. Once loaded on the client side, JavaScript can change that display. This means even JavaScrip can become responsible for generating the look and feel of a page. Therefore, JavaScript must be able to generate and manipulate HTML and CSS.

How does JavaScript handle this task? In the past, it handled it in one of the two not-very-graceful ways.

The first way was to embed strings inside of application logic. This led to code that looked something like this:

function calculateSum() {
var out = '<p>';
var total = 0;
if (arguments.length == 0) {
out += 'Error: No data given.';
}
else if (arguments.length == 1) {
out += 'Error: Cannot sum only one number.';
}
else {
out += 'The sum is: <span '
+ 'style="color: red; font-weight: bold">';
for(var i = 0; i < arguments.length; ++i) {
total += arguments[i];
}
out += new String(total) + '</span>';
}
out += '</p>';
document.getElementById('sum').innerHTML = out;
return total;
}

Simply speaking, the purpose of this demonstration function is to sum all of the arguments passed to it, and then place the result inside of the element with the id sum.

Note

Passing an arbitrary number of arguments in JavaScript

JavaScript supports passing a function, a variable number of arguments (sometimes referred to as varargs). Every function has access to a variable called arguments, which is an array-like object containing all of the parameters (in order) that were passed into the function.

For example, calling calculateSum(1, 2, 3, 4) would find the element with ID sum, and insert the HTML<p>The sum is: <div style="color: red; font-weight: bold">10</div></p>.

What we are interested in is how this function builds the information it inserts into the document.

Here, the HTML and CSS are embedded in strings, which are nested inside of the programming logic. The strings are concatenated (using the + or += operators) and the result is injected into the appropriate element.

The method exhibited here strikes many programmers as ugly. The string concatenation code is written alongside the programming logic (the actual summing of the numbers), and the result looks jumbled.

One response to this has been to get rid of the "ugly" string building code and use DOM objects. In this way, everything is essentially treated as programming logic as seen here:

function calculateSum2() {
var pEle = document.createElement('p');
var total = 0;
if (arguments.length == 0) {
var pcdata = document.createTextNode(
'Error: No data given.'
);
pEle.appendChild(txt);
}
else if (arguments.length == 1) {
var pcdata = document.createTextNode(
'Error: Cannot sum only one number'
);
pEle.appendChild(txt);
}
else {
var pcdata1 = document.createTextNode('The sum is:');
var spanEle = document.createElement('span');
spanEle.setAttribute(
'style',
'color: red; font-weight: bold'
);
for(var i = 0; i < arguments.length; ++i) {
total += arguments[i];
}
var pcdata2 = document.createTextNode(' ' + total);
pEle.appendChild(pcdata1);
pEle.appendChild(spanEle);
spanEle.appendChild(pcdata2);
}
var sumEle = document.getElementById('sum');
sumEle.appendChild(pEle);
return total;
}

Instead of incrementally appending text and data to strings, this method uses the browser's DOM API to programmatically build up elements and then insert them into the document.

Note

Don't worry if this code doesn't make sense. Thanks to jQuery, we never have to write code like this again. The jQuery library provides a much more concise alternative to the DOM API.

In many ways, this second example code is more visually appealing. It just looks like a series of function calls. It all looks like serious programming logic instead of string building.

However, we're not going to use either of these methods of building a user interface. Here's why.

While there's nothing wrong with either example, they both suffer from a major drawback: The layout information is coupled with the functional aspects of the function.

A few months from now, what if somebody needs to come back and change the layout of our code? What if they want to change the<p></p> tags to<div></div> tags? In both cases, they will need to wade through the programming logic to find all of the right places to change the tags. In a sizeable JavaScript application, if all of the layout code is buried in programming logic, HTML changes can become a nightmare.

Drupal developers have already addressed the problem of separating logic and layout by developing a server-side theme system. Therefore, when they addressed the problem in JavaScript, their solution was (unsurprisingly) to implement a version of the theme system in JavaScript.

The purpose of the JavaScript theming system is to separate the display code out into separate functions, and then use a standard method of calling those functions. By separating the theming functions from the rest of the code, layout information can be isolated and easily changed. But the developers didn't stop there. Like the server-side system, the JavaScript version supports an overriding mechanism. Here, a default theme can be assigned, but another theme can be declared that overrides that default. In a moment we will see, how that works (and why it is a good thing).

The Drupal.theme() function

On the server side, the PHP theming system is remarkably sophisticated. There are PHP template files, theming functions, and preprocessing functions. There is a .info file and a complex system of theme inheritance.

The JavaScript side of things is considerably simpler. The drupal.js library in Drupal 6 only has two theme-related JavaScript functions: Drupal.theme() (which is a paltry seven lines of code) and Drupal.theme.prototype.placeholder() (with only three lines of code).

As you may have guessed, the Drupal.theme() function is the more important of the two. The Drupal.theme() function is responsible for invoking the correct theme and passing it data to be themed. Those familiar with Drupal's theme() PHP function should already have an accurate idea of how this works.

Drupal.theme() takes at least one argumentthe name of the theme function that should be called. Any number of additional arguments may be passed in, too. All additional arguments will simply be passed on to the theming function as well.

An example will help clarify all of this.

There is one built-in theme that is included with Drupal 6: the placeholder theme. A few paragraphs ago, I mentioned that there were only two theme-related functions. Well, the second of these functions is the one that themes placeholders: Drupal.theme.prototype.placeholder().

But we don't call that function directly. Instead, we use Drupal.theme()as seen here:

Drupal.theme('placeholder', 'Hello World');

The first argument, the string placeholder, tells Drupal to use the placeholder theme function. Any subsequent arguments are just passed into the placeholder function. It is as if we called the placeholder function like this:

Drupal.theme.prototype.placeholder('Hello World');

The placeholder theme function is responsible for formatting the passed-in string. The code would return the string<em>Hello World</em>.

There are two questions we might ask at this point. First, why use Drupal.theme() if all it does is call the longer function? Secondly, what if we don't want our placeholder text to be returned as emphasized text? (For example, what if we want it to be returned as bold text?)

These two questions are closely related, for the answer is that the Drupal.theme() function can do more than just call that particular function. We can define an alternate theming function to override the default behavior and themes in any way we want. And the Drupal.theme() function will use our overriding function instead of using the default placeholder function.

All we have to do to override the default function is create a new function in a different namespace. If the original function is Drupal.theme.prototype.placeholder(), we will define Drupal.theme.placeholder(). Then, all we do is drop the prototype property from the definition.

Note

New tools are sometimes rough around the edges. Drupal's theming system misuses the prototype object, which has a very special purpose in the JavaScript object model. The prototype property should not be used as a generic namespace. Hopefully, this will be fixed in a future Drupal release.

We can easily create our own placeholder function to wrap placeholders in<strong></strong> tags instead of the<em></em> tags used by default. In fact, we can do it in a couple of ways.

Here is one way we could do it:

Drupal.theme.placeholder = function (str) {
return '<strong>' + Drupal.checkPlain(str) + '</strong>';
};

This simply creates a new string, concatenating the strong tags to the original string. Note that we use Drupal.checkPlain() to do any necessary escaping on the string passed in.

Note

The Drupal.checkPlain() function was discussed in Chapter 4.

We are still using the kind of string concatenation that some developers find offensively ugly. However, since we have abstracted the theme information into a separate function, at least it remains separate from the main programming logic.

Another way to write a function using jQuery that is functionally equivalent to the previous one, and perhaps a little less ugly to some programmers, is:

Drupal.theme.placeholder = function (str) {
return $('<strong></strong>').text(str).parent().html();
};

Here, the string concatenation code has been replaced with a jQuery chain that builds a new DOM element from the<strong></strong> tags, and then adds str as its text. The jQuery text() function handles all of the escaping of the text. So there's no need to call Drupal.checkPlain() on str.

Getting the new element back as a string is little tricky. This is done by getting the parent element (the root of this new DOM) and then calling the html() function to get the HTML as a string.

Note

The jQuery function is doing more work "under the hood," and so it will be slower. Rarely will the speed difference be noticeable. jQuery's extra work pays off. More sophisticated HTML can be built with little effort. Sometimes, jQuery will catch and clean mistakes in the HTML.

Note

Later in this chapter, we will see how to make the most of jQuery features by moving from string concatenation to HTML templates. This will give us PHP Template like functionality from the client side.

Either of these methods will achieve the same result. Now, when Drupal.theme('placeholder', 'Hello World') is called, the theme function will return<strong>Hello World</strong>.

That's all there is to Drupal's theme systemtwo functions. Let's create a project where we can build something a little more interesting with the theme system.

Project: menus and blocks

We will do a few short projects in this chapter. In this first project, we will create a couple of new themes. These themes will mimic two standard Drupal layout components: blocks and menus.

The focus of this project will be to see how PHP Templates and PHP functions can be converted into JavaScript-based theme functions.

Our theming project is going to add a new menu inside of new block to a page using JavaScript. Although we will be adding these elements from JavaScript, we want them to look and perform indistinguishably from the server-generated blocks and menus.

We will put the code inside of a new file called themes.js. It should be included with our frobnitz.info file, so make sure to add a scripts[] entry and refresh the cache.

Adding a block with a menu in it

The new menu and block will be added as soon as the document is loaded. This means we can start with the now familiar jQuery ready event handler:

$(document).ready(function () {
var links = [
{name: 'Drupal.org', link:'http://drupal.org'},
{name: 'jQuery', link:'http://jquery.com'},
{name: 'No Link'}
];
var text = Drupal.theme('shallow_menu', links);
var blockInfo = {
title: 'JavaScript Menu',
content: text
};
var block = Drupal.theme('block', blockInfo);
$('.block:first').before(block);
});

This call creates a new array of links, themes them, adds them to a block named JavaScript Menu, and then themes the entire block. Finally, the new content is added directly above the first block on the page.

The array of links is actually an array of objects, each with a name property. The first two also have URLs in the link property. The third does not:

var links = [
{name: 'Drupal.org', link:'http://drupal.org'},
{name: 'jQuery', link:'http://jquery.com'},
{name: 'No Link'}
];

This list gets passed to the Drupal.theme() function:

var text = Drupal.theme('shallow_menu', links);

This will attempt to theme the links object using a function called shallow_menu(). We will create that function soon.

The input we give the shallow_menu() function is the links arrayan array of objects. We will get back a single string object that contains our links marked up in HTML.

This string is stored in text, which is then used as part of a new object:

var blockInfo = {
title: 'JavaScript Menu',
content: text
};

This new object defines the two pieces of content we are already used to seeing in blocks: a title and some content.

To get from our object to a string of HTML that represents a block, we have to take another step. We need to theme the block:

var block = Drupal.theme('block', blockInfo);

This will look for a block() function somewhere in the Drupal.theme namespace and use that to theme our new block.

The resulting block, as an HTML string, will be stored in block. This block is inserted into the DOM using jQuery:

$('.block:first').before(block);

This query will find the first block that appears on the page, and then insert our newly minted block just before it.

One thing to note here is the order of theming. If you were to scan through the PHP source code of Drupal's server-side, you would notice that theming happens incrementally. First, one piece of information is prepared for display, and then it is combined with other data and themed into a larger chunk. This continues until one large document has been constructed. The document is then sent to the client.

Note

On the server side, Drupal uses both theming functions and PHP Template files to apply themes to data. But both are applied in this incremental fashion.

We are following the same pattern here. Once we have the link objects all built and stored in an array, we theme them. After all, we know they are ready for display.

That piece of information is then combined with more information to create a block object, which can then be themed. Now we have a larger chunk of HTML.

We can stop there since we don't have to rebuild the entire document. Instead, we insert the fully composed fragment into the existing document.

My choice of theming methodstheme the menu first, then add it to a blockisn't a trumped-up structure that I invented to make a point. In fact, I took the structure straight from the PHP system. There are existing PHP theming tools for menus and blocks, and I simply re-implemented them in JavaScript.

In the previous example, we called Drupal.theme() twice. First, we called it for'shallow_menu', and the second time we called it for'block'. These two calls have corresponding JavaScript functions that we need to create.

In the next two subsections, we will look at each of the two themes, beginning with Drupal.theme.prototype.block(), and see how they compare to their server-side counterparts.

Theming a block

When we looked at the jQuery ready handler in the previous section, we saw how the menu was themed first, and then the resulting string was used as one of the components for building a block.

We are going to look at the theming functions in reverse. The reason for this is simplicity. Block theming in both PHP and JavaScript is simple. Theming a menu is considerably more complex. So we will build up from easy to harder.

In the Bluemarine theme, which is the base for our Frobnitz theme, blocks are themed using a template. You can find this template under Drupal's installation directory at /themes/bluemarine/block.tpl.php. Here's what the PHP Template looks like:

<?php
// $Id: block.tpl.php,v 1.3 2007/08/07 08:39:36 goba Exp $
?>
<div class="block block-<?php print $block->module; ?>"
id="block-<?php print $block->module; ?>-<?php
print $block->delta; ?>">
<h2 class="title"><?php print $block->subject; ?></h2>
<div class="content"><?php print $block->content; ?></div>
</div>

That's the entire thing. It is a short snippet of HTML with a few embedded PHP statements (<?php ... ?>).

Note

We looked at another template, page.tpl.php, in Chapter 2. This one is even simpler than the one we saw there.

When this template is rendered, it will generate a piece of HTML that looks something like this:

<div class="block block-mymodule" id="block-mymodule-1">
<h2 class="title">The Title</h2>
<div class="content">The Content</div>
</div>

How does it get from the earlier template to this output? Each of the PHP statements is executed, and the results are added into the HTML.<?php print $block->module; ?> prints the name of the module that created the block ($block->module). A print statement tells PHP that the text should be added into the document.

Each piece of PHP code is simply printing parts of the block into the HTML. So if the block's content ($block->content) is The Content, then you might wonder what happens with code like this:

<div class="content"><?php print $block->content; ?></div>

The value of $block->content is inserted between the<div class="content"> and</div> tags. So the output becomes:

<div class="content">The Content</div>

Of course, as you have no doubt noticed already, the<?php and ?> as well as everything between them is left out of the rendered document. Only the HTML remains in the document that is sent to the client.

In order to re-implement this template as a JavaScript function, we need to find out what data gets placed into the template. Here's the information that gets pulled from the $block object into the template:

  • $block->module: This is the name of the module that generated the block. We have the luxury of getting to hardcode this in our function.

  • $block->delta: This is a numeric value that augments the module name, giving us a delta number for this module's block. If a module includes only one block on the page, that block's delta will be 0. The next block generated by that module will be 1, and so on. The key is to make the module name and delta number combination unique.

  • $block->subject: This is the title of the block.

  • $block->content: This is the content of the block.

These four pieces of information are all that is used to populate the template.

Now that we can interpret the PHP Template, the next step is to transform that into JavaScript.

As we saw before, JavaScript has no template system, and the Drupal theming system does not add one. Therefore, we will be turning the template into a JavaScript function that builds a string of HTML.

While this might not be the prettiest solution, it is the de facto way of doing things using the Drupal JavaScript theme system.

Here's our function for theming a block:

/**
* Theme a block object.
* This matches the bluemarine block.tpl.php.
*
* @param block
* A block object. Like the PHP version, it is expected to
* have a title and content. It may also have an id.
* @return
* Returns a string formated as a block.
*/
Drupal.theme.prototype.block = function (block) {
if (!block.id) {
block.id = "frobnitz-" + Math.floor(Math.random() * 9999);
}
var text = '<div class="block block-frobnitz" id="block-' +
block.id +
'">' +
'<h2 class="title">' +
block.title +
'</h2><div class="content">' +
block.content +
'</div></div>';
return text;
};

The function Drupal.theme.prototype.block()will be get called when we call Drupal.theme('block', blockObject).

Earlier, when we looked at the jQuery ready handler, we saw that there are two properties to the block object that gets passed into this function: title and content. These correspond to two of the values we saw before.

The first thing the previous function does is check to see if the block object also has an id property. We want to set an ID attribute on our block. It is a requirement of HTML that ID attributes have to be unique within a document.

If the block doesn't have an ID attribute, we generate one based on a random number.

That is the only additional piece of information we need. Instead of using a module name (the code isn't generated from a module, and hence does not have a module name), we are just hardcoding in the name frobnitz. So there is no need to get the fourth piece of information that the PHP Template used.

From here, all that is left is to build up a string containing the HTML and return that string. This is done in one big string concatenation operation:

var text = '<div class="block block-frobnitz" id="block-' +
block.id +
'">' +
'<h2 class="title">' +
block.title +
'</h2><div class="content">' +
block.content +
'</div></div>';
return text;

The string that this returns should be in the same form as the PHP Template's output that we just saw. It might look something like this:

<div id="block-frobnitz-3931" class="block block-frobnitz">
<h2 class="title">Title Goes Here</h2>
<div class="content">
Content Goes Here
</div>
</div>

As you can see, with a simple PHP Template, not much work is involved in translating it to JavaScript.

Note

Since the HTML is generated by JavaScript, the newly added HTML will not show up when you view the source. However, you can use the HTML inspector in Firebug. This works because Firebug shows the HTML as it currently is, not as it was when the page was loaded.

The next one we will look at is going to be a little different.

Theming a menu

Menus are a familiar fixture in Drupal. In fact, once Drupal is installed, one of the first things we do is use the menu. It looks like this:

If you quickly scan through the templates included in Bluemarine (or Garland, or any of the default themes), you won't find the template that generates this menu. Why not? That's because it comes from a set of theming functions buried deep in Drupal's includes/menu.inc file.

The main menu function is menu_tree_output(), which is not actually a theming function, technically speaking. While we're not going to dwell on it, here's how that function looks:

function menu_tree_output($tree) {
$output = '';
$items = array();
foreach ($tree as $data) {
if (!$data['link']['hidden']) {
$items[] = $data;
}
}
$num_items = count($items);
foreach ($items as $i => $data) {
$extra_class = NULL;
if ($i == 0) {
$extra_class = 'first';
}
if ($i == $num_items - 1) {
$extra_class = 'last';
}
$link = theme('menu_item_link', $data['link']);

if ($data['below']) {
$output .= theme('menu_item', $link, $data['link']['has_children'], menu_tree_output($data['below']), $data['link']['in_active_trail'], $extra_class);
}
else {
$output .= theme('menu_item', $link, $data['link']['has_children'], '', $data['link']['in_active_trail'], $extra_class);
}
}
return $output ? theme('menu_tree', $output) : '';

}

The basic idea of the previous function is to traverse a menu tree and theme it so that it is ready for display. Each call to the theme() function (the PHP equivalent of the Drupal.theme() JavaScript function) is highlighted. There are four theme() calls to three different theming functions: menu_item_link(), menu_item(), and menu_tree().

Note

The three theming functions are much less daunting and also bear some resemblance to the block function we just created. Feel free to look at them. They are all in /includes/menu.inc.

If we were to reproduce this in JavaScript, we would need to write at least four functions (assuming none of the themes would call something else).

Now we should ask ourselves some questions: What are we trying to implement in our JavaScript theme function? Do we need the complex logic present above? We could create a JavaScript equivalent, but do we need to?

All we really want to do, in our example at hand, is create a simple menu containing links. We don't need to support an elaborate tree structure at all. We don't really need to break out the theming of menu items and menu item links, or even of the menu as a whole and each of the menu items.

The most important thing is getting our code to look like the menu generated from menu_tree_output(). The fastest way of finding out how to generate our theme will not be analyzing the code. It's going to be analyzing the generated HTML.

In fact, let's take a look at the HTML source for the menu we saw in a screenshot a few pages back. Here's what that looks like:

<ul class="menu">
<li class="leaf first">
<a href="/drupal/user/1">My account</a>
</li>
<li class="collapsed">
<a href="/drupal/node/add">Create content</a>
</li>
<li class="collapsed">
<a href="/drupal/admin">Administer</a>
</li>
<li class="leaf last">
<a href="/drupal/logout">Log out</a>
</li>
</ul>

Ah, that's much better. We can generalize a little more to see the structure of a menu in its simplest form like this:

<ul class='menu'>
<li class='leaf'>
<a href='link'>name</a>
</li>
</ul>

Also, from the example before, we can see that the<li></li> for the first node in a menu has the additional class first. Similarly, the last one has the last class.

We're now ready to code this up in JavaScript. This time, we will use the jQuery-based method for building:

/**
* Build a single (non-colapsed) menu list.
* Mimics the complex menu logic in menus.inc.
* @param items
* An array of objects that have a name and a link property.
* @returns
* String representation of a link list.
*/
Drupal.theme.prototype.shallow_menu = function (items) {
var list = $('<ul class="menu"></ul>');
for (var i = 0; i < items.length; ++i) {
var item = items[i];
// Get text for menu item
var menuText = null;
if (item.link) {
menuText = item.name.link(item.link);
}
else {
menuText = item.name;
}
// Create item
var li = $('<li class="leaf"></li>');
// figure out if this is first or last
if (i == 0) {
li.addClass('first');
}
else if (i == items.length - 1) {
li.addClass('last');
}
// Add item to list
li.html(menuText).appendTo(list);
}
return list.parent().html();
};

Don't let the size of this function distract you. It's actually not very complex.

This function takes a list of objects that represent links. We saw this list in the jQuery ready handler we created earlier:

var links = [
{name: 'Drupal.org', link:'http://drupal.org'},
{name: 'jQuery', link: 'http://jquery.org'},
{name: 'No Link'}
];

This is the object that is passed into our Drupal.theme.prototype.shallow _menu() function.

Note

Our theme is named shallow_menu() because it does not take the deep tree-structured menu data that its PHP counterpart did. Instead, this function only creates shallow menus.

We start out by creating a list object, which is our list element wrapped inside of a jQuery object. Once again, we are taking advantage of jQuery's flexible constructor to pass it an HTML fragment instead of a CSS selector.

Once our list container is ready, all we need to do is loop through each object in the list of links, formatting and adding it to the list as we go.

The loop starts out like this:

for (var i = 0; i < items.length; ++i) {
var item = items[i];
// Get text for menu item
var menuText = null;
if (item.link) {
menuText = item.name.link(item.link);
}
else {
menuText = item.name;
}
/* More here... */
};

The first thing we do in the for loop is store the current item in the item variable. We know that each item will have a name, but we don't know if it will have a link property.

If it has a link, we want to create a piece of HTML that looks like this:<a href='link'>name</a>. But if it doesn't, we want HTML that looks like this: name.

This is all done within that first if/else conditional.

Note

Easy linking

A useful JavaScript function that is used surprisingly infrequently is the string method link(). Any string can be turned into a link by calling this method and passing in a URL like this:'a string'.link('http://example.com'). This little snippet of code will generate an HTML looking like this:<a href="http://example.com">a string</a>.

The next part of the for loop looks like this:

for (i = 0; i < items.length; ++i) {
var item = items[i];
// Get text for menu item
var menuText = null;
if (item.link) {
menuText = item.name.link(item.link);
}
else {
menuText = item.name;
}
// Create item
var li = $('<li class="leaf"></li>');
// figure out if this is first or last
if (i == 0) {
li.addClass('first');
}
else if (i == items.length - 1) {
li.addClass('last');
}
// Add item to list
li.html(menuText).appendTo(list);

}

The highlighted portion is the new code.

First, we create a jQuery-wrapped list element:

var li = $('<li class="leaf"></li>');

Once we have the li object we check it to see if it is either the first or last menu item. If it's the first, we add the first class. And if it is last, we add the last class. All others will have only the leaf class.

Now that we have our list item element (li) ready, we add content and then append the entire thing to the list object:

li.html(menuText).appendTo(list);

By the time the for loop is done, a new li object will have been added to the list for each item that was in the original array of links.

Finally, after the for loop we have one last line. We bring things together with a last jQuery chain:

return list.parent().html();

A theme function needs to return a string, so we grab a string representation of the main<ul></ul> element (together with its contents) using parent().html().

When all of this is put together, we should get a menu embedded in a block. The entire thing should look something like this:

We've finished our project. We created two themesone based on a PHP Template file and one based on a very complex function. But when we put everything together, our JavaScript themes look just like their PHP-generated counterparts.

The JavaScript theming module

In the project we just created, we built a couple of simple themes that implemented existing Drupal functionality. On the JavaScript side, we recreated some features already present on the server side. The only theme function that ships with the Drupal 6 core is the placeholder function. Unfortunately, this means that doing this kind of basic re-implementation work is necessary when you want to recreate the look and feel.

Fortunately, there are Drupal modules that provide features that Drupal itself lacks. In this case, there is a module that provides some much-needed general purpose theming functions. This module is called the JavaScript theming module, and is available at http://www.drupal.org/project/js_theming.

Note

The JavaScript theming module makes use of some of the newer features of the JavaScript language. Not all of the features will work on older or less-supported browsers.

Installing the JavaScript theming module is as simple as downloading the module from the URL given above, unpacking it in the /sites/all/modules directory of your Drupal installation, and enabling it from Administer | Site building | Modules. Once the module is enabled, the JavaScript tools will be available to you on all pages.

This module provides themes for the following common user interface components:

  • Tables

  • Nested lists

  • Notification messages

  • Images

In addition to these themes, it also provides some basic utility functions that make working with Drupal easier (and for the PHP developer, more familiar). Two important functions are Drupal.l() and Drupal.url(), both of which are used to construct URLs.

Let's take a look at a few of these.

Theming tables

Tables provide a great way of visually presenting certain forms of data. Unfortunately, the HTML used to create them is verbose; a lot of tags are required to properly build a table. For that reason, tables make a perfect candidate for theming.

It should come as no surprise that this common user interface component is one of the themes that JavaScript theming provides. It is used like this:

Drupal.theme('table', headers, rows);

Here, headers is an array of column headings, and rows is an array of rows, where each row is an array. Let's take a look at a fragment of JavaScript code:

var headers = ['Library', 'Purpose'];
var rows = [
['jquery.js', 'Document manipulation'],
['drupal.js', 'Drupal interaction'],
['js_theming.js', 'Provide additional themes']
];
var table = Drupal.theme('table',headers, rows);

The headers array contains two column headers, Library and Purpose. Underneath that is the rows array, which contains three rows of data. Each row is, in turn, an array, and each item in that array represents a cell.

We have three rows of data, each with two cells (one for the Library column and one for the Purpose column).

Note

In code that was used for something more than an example. We would surround each of these strings with Drupal.t() to allow the translation subsystem to translate them when necessary.

At the end of this fragment, if we were to dump the contents of the table variable, it would look like this:

<table class="sticky-enabled">
<thead>
<tr>
<th>Library</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>jquery.js</td>
<td>Document manipulation</td>
</tr>
<tr class=>>even>> >
<td>drupal.js</td>
<td>Drupal interaction</td>
</tr>
<tr class=>>odd>> >
<td>js_theming.js</td>
<td>Provide additional themes</td>
</tr>
</tbody>
</table>

Our headers are now encapsulated in the<thead></thead> section, and each row of the row table is now a<tr></tr> element containing each of its items in<td></td> tags.

If we took a look at this in a browser window, it would look something like this:

The table uses standard Drupal CSS class names. Therefore, it is styled by the existing Drupal theme's stylesheets (in this case, the Bluemarine CSS that Frobnitz inherits).

This is a simple use of the table theme. It supports more complex table formatting by taking objects in addition to strings. For example, if we wanted to add an additional class to each<td></td> tag, we could do something like this:

var headers = ['Library', 'Purpose'];
var cell1 = {data: 'jQuery.js', 'class':'myClass'};
var cell2 = {data: 'Document manipulation', 'class':'myClass'};
var rows = [
[cell1, cell2]
];
var attrs = {'width': '100%'};
var caption = "Drupal JS Libraries";
var table = Drupal.theme(
'table', headers, rows, attrs, caption
);

In this example, I have shortened the table down to one row to make it a little clearer.

Note

The class is enclosed in single quotes because the bare literal is a JavaScript-reserved word. Using class without quotes will cause errors.

This time, the two cells in the row have been moved out to their own variables: cell1 and cell2. Each of these is an object with two properties: data (the element data) and class (the CSS class to add).

The object notation for cells works in the following manner: a property named data holds the table's data. All other properties will be assumed to be the name of an HTML attribute, and will be converted to attribute/value pairs. Here, class:'myClass' will become class='myClass'. We could likewise use id:'myID' to add an attribute like id='myID'.

We have also added an attrs object, which holds the attributes for the table tag, and a caption element that will hold the caption for the table. Both of these are passed as additional parameters to Drupal.theme() on the last few lines of the previous code.

When the results of this code are displayed, they will look something like this:

Notice this time the table is wider and there is a caption above the table. If we were to look at the underlying HTML, we would see the additional CSS classes we added to each table cell:

<table width="100%" class="sticky-enabled">
<caption>Drupal JS Libraries</caption>
<thead>
<tr>
<th>Library</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td class="myClass">jQuery.js</td>
<td class="myClass">Document manipulation</td>

</tr>
</tbody>
</table>

Looking at the highlighted lines, we can see the effect of turning our cells into objects.

The table has more features, including support for server-driven, sortable columns (which require a little bit of server-side coding). But we will continue to look at a few other features of the JavaScript theming library.

Sending notifications to the user

What happens when an error occurs and our code needs to inform the user of this? What if something succeeds and we want to let the user know? What if we just need to provide a little information for the user, and provide it in a standard way that is easy to identify?

On the server side there is a standard procedure for handling these three situations. There is a PHP function, drupal_set_message(), which can be used to inform users about errors, warnings, or additional information.

This is another useful feature that the JavaScript theming module provides.

This function is called like this:

Drupal.messages.set(text, level);

Here, text is the text of the message, and level is one of three predefined levels:'warning', 'error', or'status'. (Note that each of these is a string and must be passed into the function in quotation marks.)

Here's how we might use it to display an error message:

Drupal.messages.set("An error occurred.", "error");

When this executes, it will display a red box near the top of the user's page (just under the page's header section) with the error message:

This message will be displayed for several seconds, and will then disappear. (The time is configurable through the module's administration interface.)

The other two message levels function similarly, but warning will create a yellow box and status will create a grey box (colors, of course, are determined by the CSS).

If you are using this module, the Drupal.messages.set() function is a good tool for providing user notifications.

Adding links

There are many useful functions in the JavaScript theming library, but to save space (and cover more ground regarding theming), we will cover only one more. This function is Drupal.l(), and it is the main function used for creating links.

Those familiar with PHP programming for Drupal, will find it unsurprising that Drupal.l() provides the same functionality as the Drupal PHP l() function. It is typically called in this way:

Drupal.l(text, url);

Here, text is the text that will be linked and url is the relative URL of a Drupal resource. For example, we could create a link to the fifth node in our system like this:

Drupal.l('Node 5', 'node/5');

This would return a string of HTML that looked like this:

<a href="/drupal/node/5" class="active">Node 5</a>

Note that the link for the base path on my system has been adjusted. It has turned node/5 into /drupal/node/5.

There is an optional third parameter which can contain additional options. These options are all documented at http://api.drupal.org/api/function/l/6. For example, if we wanted to use an absolute URL, instead of a relative one, we could set the absolute property to true:

Drupal.l('Drupal', 'http://drupal.org', {'absolute':true});

This would result in a link pointing to http://drupal.org, instead of a relative link to our own server.

The JavaScript theming module provides some useful tools for extending the JavaScript capabilities of your Drupal 6 system.

Next up, we are going to work on another project. In this project, we will implement a simple template system.

Project: templates for JavaScript

So far we've seen a few ways in which a theming function can format text. One method is to build up a string by appending HTML fragments and variables. Our first string building example looked like this:

Drupal.theme.placeholder = function (str) {
return '<strong>' + Drupal.checkPlain(str) + '</strong>';
};

In the previous project, we used this method to implement Drupal.theme.prototype.block(). In this method, an HTML fragment is created by appending strings together. We also saw another method that relied upon jQuery to handle the HTML. We re-implemented the previous function using the jQuery method:

Drupal.theme.placeholder = function (str) {
return $('<strong></strong>').text(str).parent().html();
};

In this case, the HTML is composed at the DOM level, and is then converted back into a string at the end. Although it incurs more overhead, it looks more elegant, can improve error checking, and can also provide additional features.

Note

There is one possible advantage in using the string-based method. In some hard-to-debug cases, Internet Explorer does not correctly handle the manipulation of elements until after they are added to the parent document. This seems to occur when working with<object/> tags. However, these are rare cases, and most of the time the jQuery builder method works fine.

One of our original goals was to separate the logical workings of a program from the layout of the program. The Drupal.theme() function is a step in the right direction, to be sure. However, on the server side, the PHP Template system makes an even cleaner separation. Template files are easier to edit than strings embedded in functions. Also, for all its compact functionality, if what you are interested in is merely changing minor layout details, jQuery is probably even harder to edit.

It would be nice to have a template language for Drupal JavaScript. In this project, we are going to write one.

We want a simple template system where the HTML is separated from the JavaScript code as much as possible. We want to make it very easy for a themer to edit the look and feel of our JavaScript-themed code. This means getting the markup out of functions. Ideally, one should be able to change the layout without ever having to see a line of JavaScript code.

Oh, and we also want to do this without making any changes at all to the PHP code that drives Drupal.

Here are our design goals:

  • Taking a cue from the PHP Template system, let's make it possible for each template to be stored in its own style.

  • We don't want to reinvent the entire theme system. Therefore, we want to make this mesh with the existing Drupal.theme() function, and as much of the rest of the subsystem, as is possible.

  • We already have some powerful tools in the drupal.js and jQuery libraries. Let's try to minimize the amount of code we generate. (We will not introduce any new dependencies either).

  • Finally, we want to make our new template language simple enough for themers and developers to pick it up quickly.

The last point implies that we will be writing a new template language. Why not just re-use PHP Templates in JavaScript? What makes PHP Templates a winner for Drupal is how it balances performance and simplicity. It is trivially easy for a PHP developer to learn, and it executes quickly because it is only made up of PHP.

However, JavaScript is a different language than PHP. If we try to use PHP Templates in JavaScript, we will have a lot of work to make it possible for our scripts to be able to parse the PHP files. Since we don't want to implement all of PHP just to make a few templates, we are stuck in the situation of having to document exactly how our templates differed from standard PHP Templates.

However, maybe we can look for the same balancing point on the client side that Drupal server developers found in PHP Templates. If PHP is fast on the server, what is a browser good at parsing and interpreting? HTML, CSS, and JavaScript all come to mind. It just so happens that web developers tend to be well-versed in at least the first two of these technologies.

We now have a candidate for a template language. Let's now see if we can build a template language that primarily uses HTML and CSS for markup.

The node template

For this project, let's build some code that will add a new node to the list of nodes we see on the home page. In the previous project, we built code for adding blocks on the fly. This time, we'll add nodes on the fly.

Again, the place to start with is the normal node.tpl.php file that is included with Bluemarine:

<?php
// $Id: node.tpl.php,v 1.7 2007/08/07 08:39:36 goba Exp $
?>
<div class="node<?php if ($sticky) { print " sticky"; } ?>
<?php if (!$status) { print " node-unpublished"; } ?>">
<?php if ($picture) {
print $picture;
}?>
<?php if ($page == 0) { ?><h2 class="title">
<a href="<?php print $node_url?>"><?php
print $title?></a></h2><?php }; ?>
<span class="submitted"><?php print $submitted?></span>
<div class="taxonomy"><?php print $terms?></div>
<div class="content"><?php print $content?></div>
<?php if ($links) {
?><div class="links">&raquo; <?php print $links?></div>
<?php
}; ?>
</div>

As usual with the PHP Template files, this is made up of a mixture of HTML and very simple PHP code. There are a couple of if statements to conditionally add CSS classes, display pictures, and include links. There are plenty of print statements, which inject strings into the HTML. However, there is no complex logic, or unusual functions, used in this template.

Let's remove all of the PHP and see what the underlying HTML basically looks like:

<div class="node">
<h2 class="title"><a href="#"></a></h2>
<span class="submitted"></span>
<div class="taxonomy"></div>
<div class="content"></div>
<div class="links"></div>
</div>

Based on the CSS class names, we can take a glance at this fragment and get a pretty good idea about what each piece of the fragment ought to do. In fact, the class names provide enough information so we can even computationally fill it out. Where should the title go? How about inside the link in the tag with class='title'! We can find locations like that using a jQuery CSS selector: $('.title a').

We might want to flag a node as unpublished using the element with class submitted. If we have taxonomy terms, we can put them in the element with the taxonomy class. Similarly, content and links go inside of the div tags with the appropriate class names.

It looks like we have the information we need. Let's make this pure HTML fragment our template.

Following a naming convention similar to the PHP Template names, let's store the previous code inside of our theme with the name node.tpl.html. Notice that the last part of this filename is .html, not .php. We don't want this file to be executed on the server side (it would be a waste of time and would make this template look like a PHP Template).

From a template to a system: what next?

We have now devised a template language that fills some requirements. It should be easy for any experienced HTML author to create templates in our system. After all, they're just HTML fragments. Of course, we would want to make sure they used the correct CSS classes, but no new language will need to be learned.

We have also stored our template in its own file. That should make it easier to edit as well.

But wait. What do we do with these templates? How can we use them from JavaScript? After all, the templates are on the server, and unless the browser requests them, they will not be sent to the client.

Furthermore, templates on the server side are composed into a single HTML document before they are transmitted to the client. However, a client-side template is obviously not going to work that way. The JavaScript template engine won't even have started until a pre-built document has been sent.

What we need is a way to get the template from JavaScript, use that template for theming, and then inject the results into the existing document (the one built on the server). This might initially seem like a tall order, but we are going to borrow a little technology from the next chapter and create an engine that will do just what I have described.

Note

The strategy used here could be generalized with a little bit more code to provide template services to any Drupal JavaScript application. However, to keep our code concise, we will create a specific application to meet our needs.

We will continue adding code to the themes.js file that we created for the last project. From here, we are going to proceed as follows:

  1. We will create a simple template layer that sets up the environment we will need for templating.

  2. We will employ the existing Drupal theming system, using it as a frontend to our template system.

Let's look at the first step.

A template system

We now have HTML templates on the server and a theming system on the client. The first step toward linking these two is to fetch the templates from the server.

To do this, we are going to create a new namespace object named TplHtml. We don't want to put our custom code inside of the Drupal namespace as this code is not the official Drupal code.

This new namespace will house our main template function, in addition to all of the templates that we load from the server.

Here's what the code for our complete template system looks like:

var TplHtml = {template: {}};
TplHtml.loadTemplate = function (name, uri) {
var url = Drupal.settings.basePath + uri;
jQuery.get(url, function (txt) {
TplHtml.template[name] = txt;
});
};
$(document).ready(function () {
TplHtml.loadTemplate('node',
'/sites/all/themes/frobnitz/node.tpl.html');
});

The first line creates the new TplHtml namespace that adds an empty template object. TplHtml.template is where the templates we get from the server will be stored.

Next is the TplHtml.loadTemplate() function. This is the key piece of our template system. It takes a template name and a URL fragment (uri) that will be used to find the template on the server.

The name parameter should be a short name for our template. For example, our node template would probably have the name node. Later, we will use it as a keyword for referring to our template. It must be made up of alphanumeric characters only, and contain no spaces.

The uri parameter should be the relative path of the template within the theming system. In a few moments, we will see this used to point to the node.tpl.html file we just created just before.

The TplHtml.loadTemplate() function has only four lines of code. The first line takes the uri parameter and prepends it with the base path to the server:

var url = Drupal.settings.basePath + uri;

In Chapter 2, we discussed how the Drupal.settings.basePath variable will always point to absolute server path where Drupal is installed. By constructing the URL this way, we don't have to know anything about where on the server Drupal is installed. Instead, we can just build a relative path from Drupal's base to our specific template file.

The next three lines are responsible for fetching the file from the server:

jQuery.get(url, function (txt) {
TplHtml.template[name] = txt;
});

Here we are using a jQuery function that is new to us: jQuery.get(). In a nutshell, this function makes it possible to fetch a file from the server, and then manipulate it locally. It's part of jQuery's powerful AJAX (Asynchronous JavaScript and XML) library, which we will take a closer look at this function in the next chapter.

Note

Chapter 7 will cover AJAX in detail. There, we will cover jQuery's AJAX features and do projects that involve retrieving XML and JSON data from the Drupal server.

For now, we will just take a quick look at what it does for us in this context.

We pass it two arguments: the URL that we just constructed (url) and an anonymous function. jQuery will request the URL from the server. When the server responds, jQuery will execute the anonymous function, passing the server's response as the first parameter to this function. Therefore, txt will contain a string representation of the document we requested.

Since we will be requesting templates, txt will hold a complete template. We want to take that template and cache it somewhere convenient. We have already created that place: the TplHtml.template object.

Taking advantage of JavaScript's array-like object reference syntax, we can add this new template to the TplHtml.template object in this way:

TplHtml.template[name] = txt;

If we add a template with the name block, we could then access this template as a JavaScript object:

alert(TplHtml.template.block);

That's all there is to our template retrieval function. This gives us a tool for fetching as many templates as we need from the server.

Next, we will look at the simple jQuery ready handler:

$(document).ready(function () {
TplHtml.loadTemplate('node',
'/sites/all/themes/frobnitz/node.tpl.html');
});

With this snippet of code, we move from general to specific. When the document is ready, we load a very specific template file using our TplHtml.loadTemplate() function. This will fetch our template file, which is located at /sites/all/themes/frobnitz/node.tpl.html (the relative path from Drupal's base to our custom theme). Once retrieved, the template will be stored in TplHtml.template.node, since node is the name passed into TplHtml.loadTemplate().

With this chunk of code, within moments of the main document loading, we should also have the node template available to us. If we were to look at the contents of TplHtml.template.node, we would see a string that looked like this:

<div class="node">
<h2 class="title"><a href="#"></a></h2>
<span class="submitted"></span>
<div class="taxonomy"></div>
<div class="content"></div>
<div class="links"></div>
</div>

Does this look familiar? It should since it's the template we created at the beginning of this project.

The next step is to build theme functions that can make use of this template system.

Theming with templates

The last part of our template system is a theme function that can make use of the template.

Based on the jQuery ready handler we just wrote, we now have a template stored in TplHtml.template.node. Here, we need to provide a function that will take that template and then populate it with data.

To do this, we will once again use jQuery since it provides powerful tools for navigating HTML and modifying the document's structure. Here's our new function:

Drupal.theme.node = function (node) {
var out = '';
if (TplHtml.template.node) {
var tpl = $(TplHtml.template.node);
// Is it sticky?
if (node.sticky) {
tpl.parent().find('.node').addClass('sticky');
}
// Do title and title's link at the same time.
if (!node.nodeUrl) node.nodeUrl = '#';
tpl.find('.title a').text(node.title)
.attr('href', node.nodeUrl);
// These are the things we are going to place in
// the template.
// Fortunately for us, class names match 1-to-1 with
// the names of the node properties.
var values = ['content','submitted','taxonomy','links'];
for (var i = 0; i < values.length; ++i) {
var value = values[i];
if (node[value]) {
tpl.find('.' + value).html(node[value]);
}
else {
tpl.find('.' + value).hide();
}
}
// Now we dump the template to a string.
out = tpl.parent().html();
}
return out;
};

Though this function is large, it doesn't use any new functions or techniques. In fact, all it is doing is taking the node data and the TplHtml.template.node template, and merging the data into the template.

To get through this large function, let's look at it in smaller chunks:

Drupal.theme.node = function (node) {

The theme function takes one parameter: node. This parameter is expected to be an object and may have any of the following properties:

  • node.title: The title of the node. This is the only required item.

  • node.nodeUrl: The relative URL of the node.

  • node.sticky: A flag indicating whether this node should be treated as sticky at the top of the page. (The default is false.)

  • node.content: The content of the node.

  • node.submitted: Information about the date and submitter of the node.

  • node.taxonomy: A string containing hyperlinks to taxonomy terms.

  • node.links: A string containing additional links (like to comments).

As we build up the layout, we will put each bit of node information into its appropriate place in the template.

Let's take a look at the next section of the function:

Drupal.theme.node = function (node) {
var out = '';
if (TplHtml.template.node) {
var tpl = $(TplHtml.template.node);
// Is it sticky?
if (node.sticky) {
tpl.parent().find('.node').addClass('sticky');
}
// Do title and title's link at the same time.
if (!node.nodeUrl) node.nodeUrl = '#';
tpl.find('.title a').text(node.title)
.attr('href', node.nodeUrl)
// More here...
}
return out;
}

The out variable is going to hold the string that we return.

The first thing we do after declaring our out variable is check to see whether the template exists. If it does, we continue with the formatting. If not, we return an empty string.

Note

Returning an empty string like this is fine for demonstration purposes, but it isn't a great way of doing things on a production system. Since the template is being fetched from a remote server, it is possible that the template couldn't be loaded. It might be a better solution to add some default theming in cases where the template cannot be loaded.

Inside the conditional, we create a new jQuery object to wrap the node template. The tpl variable will refer to our template jQuery object. We can now conveniently manipulate the HTML DOM for our template.

Next, we begin modifying the template. We check to see if node.sticky is set and true. If it is, we then want to add a sticky CSS class to any element with the node class. This is done with a simple jQuery string: tpl.parent().find ('.node').addClass('sticky'). This starts from the top of the template DOM (tpl.parent()), and then finds all elements with the node class. Each of those nodes gets the sticky class added.

From here, we move on to the node title and URL. The HTML template we created had a title section that looked like this:

<h2 class="title"><a href="#"></a></h2>

We want to find that section and add both a title and a URL. We can do this with one long jQuery chain:

tpl.find('.title a').text(node.title)
.attr('href', node.nodeUrl)

This finds the link element inside the title section, sets its text to node.title, and then re-targets the link to node.nodeUrl.

The following chunk of code makes it easy enough for us to accomplish several content-filling tasks in a compact loop:

Drupal.theme.node = function (node) {
var out = '';
if (TplHtml.template.node) {
var tpl = $(TplHtml.template.node);
// Is it sticky?
if (node.sticky) {
tpl.parent().find('.node').addClass('sticky');
}
// Do title and title's link at the same time.
if (!node.nodeUrl) node.nodeUrl = '#';
tpl.find('.title a').text(node.title)
.attr('href', node.nodeUrl)
// These are the things we are going to place in
// the template.
// Fortunately for us, class names match 1-to-1 with
// the names of the node properties.
var values = ['content','submitted','taxonomy','links'];
for (var i = 0; i < values.length; ++i) {
var value = values[i];
if (node[value]) {
tpl.find('.' + value).html(node[value]);
}
else {
tpl.find('.' + value).hide();
}
}

// Now we dump the template to a string.
out = tpl.parent().html();
}
return out;
};

The first thing we do in this highlighted section is define a new array:

var values = ['content','submitted','taxonomy','links'];

We have four CSS classes defined in the document that we need to fill in: content, submitted, taxonomy, and links. We potentially have four attributes on our node object that need to be slotted in these spots: node.content, node.submitted, node.taxonomy, and node.links.

Fortunately for us, the names match up! We can loop through the values array and drop the contents of our node properties into the correct location in the template:

for (var i = 0; i < values.length; ++i) {
var value = values[i];
if (node[value]) {
tpl.find('.' + value).html(node[value]);
}
else {
tpl.find('.' + value).hide();
}
}

We can make use of the fact that JavaScript allows array-like access to object properties to check whether or not each of the items in the values array exists. For example, if the node.submitted exists, then node['submitted'] will be evaluated as true. So we look through the values array and check for each property in the node object. If it exists (if (node[value])), then we insert the value. If it doesn't exist, we hide the relevant part of the template so an empty container is not displayed.

The highlighted line in the previous code is responsible for adding the content into the template:

tpl.find('.' + value).html(node[value]);

This jQuery chain begins with the template and finds all elements whose class is the current value. During first iteration, it will look for is content, then submitted, and so on until all of the items in the values array have been inserted. With any match, it will simply add the appropriate node property's content.

We will see an example of this in a moment.

Finally, there's one more thing this function does before returning the themed content:

Drupal.theme.node = function (node) {
var out = '';
if (TplHtml.template.node) {
var tpl = $(TplHtml.template.node);
// Is it sticky?
if (node.sticky) {
tpl.parent().find('.node').addClass('sticky');
}
// Do title and title's link at the same time.
if (!node.nodeUrl) node.nodeUrl = '#';
tpl.find('.title a').text(node.title)
.attr('href', node.nodeUrl)
// These are the things we are going to place in
// the template.
// Fortunately for us, class names match 1-to-1 with
// the names of the node properties.
var values = ['content','submitted','taxonomy','links'];
for (var i = 0; i < values.length; ++i) {
var value = values[i];
if (node[value]) {
tpl.find('.' + value).html(node[value]);
}
else {
tpl.find('.' + value).hide();
}
}
// Now we dump the template to a string.
out = tpl.parent().html();

}
return out;
}

The template has now been populated. However, like all theme functions, this must return the results neither as a jQuery object, nor as HTML elements, but as a string. To do this, we call tpl.parent().html(). This goes to the very root of the template and retrieves the HTML as a string. At the end of this function, the string is returned.

Using the template system

We now have a basic template system. We can very quickly mock up a new function to test out our template-based theme:

function addNode() {
var node = {
title: "New Node",
content: "JavaScript created this node!",
nodeUrl: 'http://drupal.org'
};
$('.node:last').after(Drupal.theme('node', node));
Drupal.attachBehaviors();
}

The addNode() function creates a new node object with a title, content, and a nodeUrl. It then finds the node section and adds the new node to the end of the existing list of nodes. For example, on the front page, it would add it at the bottom of the page.

Notice that we call Drupal.theme('node', node) to invoke the Drupal.theme.prototype.node() function that we just defined, and pass it our new node object.

Finally, since we have just altered the DOM, we need to run the Drupal.attachBehaviors() function to make sure any new elements get evaluated by the behaviors system.

To make it possible to call our new function, we might also want to add a link somewhere in the document. One easy way of doing this, for testing purposes, is to add another jQuery ready function that adds a link to the pure JavaScript menu we created earlier in the chapter:

$(document).ready(function () {
$('.block:first')
.append('<a href="#">Add node</a>') .children('a:last').click(addNode);
}

This simply adds a new Add node link which, when clicked, runs our demo addNode() function.

What happens when we click this new link running the addNode() function? The node is themed and the results of that theming look like this:

<div class="node">
<h2 class="title"><a href="http://drupal.org">New Node</a></h2>
<span style="display: none;" class="submitted"></span>
<div style="display: none;" class="taxonomy"></div>
<div class="content">JavaScript created this node!</div>
<div style="display: none;" class="links"></div>
</div>

Note that several elements are marked display: none. They are hidden because the node object had no corresponding property, so we know they shouldn't be displayed.

Next, the now-populated HTML template is injected into the existing document. The results look something like this:

Note that under the already existing This is a node node (which was created in Chapter 1), there is another node called New Node. This is the node we just created with our JavaScript template tool.

A word of warning

The code we generated to retrieve templates from the server does come with a caveat. It takes time for the browser to get the template from the client. Sometimes, it may take a second or two, and at times even more.

How do we handle the case where we want to theme something and the template hasn't yet completely loaded?

There are two options:

  1. Implement a default local theme that can be used when the server hasn't sent a template back.

  2. Use a timer (setTimeout()), callback, or another similar mechanism to delay and give the server time to respond.

Note

In the next chapter, when we look at AJAX in more detail, we will see some additional jQuery functionality that could be used to make the loadTemplate() function more robust. Rewriting that function might eliminate the need for precautions like this. We might also consider rewriting the template feature to use synchronous, rather than asynchronous, requests for templates. However, this could become a performance bottleneck.

Both of these options can be used in conjunction. Here's the sample function that illustrates how we might use both:

var max_wait = 3000; // 3 seconds
var attempt = 0;
function addNode() {
var wait = 200;
var node = {
title: "New Node",
content: "JavaScript created this node!",
nodeUrl: 'http://drupal.org'
};
if (TplHtml.template.node) {
var txt = Drupal.theme('node', node);
$('.node:last').after(txt);
Drupal.attachBehaviors();
}
else if (attempt * wait < max_wait) {
++attempt;
setTimeout(addNode, wait);
console.log("delaying");
}
else {
var txt = Drupal.theme('defaultNode', node);
$('.node:last').after(txt);
Drupal.attachBehaviors();
}
}

This is a variation of the addNode() testing function we looked at before. There are two new variables, max_wait and attempt, which are in a broader scope than the function and will persist across function calls. The first onemax_waitsets the maximum amount of time that this script will wait for the server to load the template. The second oneattemptis a counter that will record the number of attempts made to use the template.

These will make more sense in a moment.

The important code is the big if/else-if/else conditional. Here's how it works.

It first checks to see if the TplHtml.template.node template exists. If it does, then we know that the template must have been loaded already. We can then go on and do our theming by using the function we created earlier.

If the template does not exist, we check the else if condition:

else if (attempt * wait < max_wait) {
++attempt;
setTimeout(addNode, wait);
console.log("delaying");
}

Here, we check to make sure that we still have time to check for the template. The wait variable, set early in the addNode() function, is the amount of time between checks. We can multiply attempt and wait to see how long we've been trying to use the template. Of course, the first time this is run, it will be 0. We haven't delayed at all as of now.

Note

Using this method, we can account for cases when the remote server is unavailable. If the server takes too long to respond, a default template is used. One drawback of using a callback (an alternative to this method) is that there is no way to gracefully handle server failures.

If we make it to the inside of the else if condition, we know that the template isn't loaded and we still have time to wait for it to be loaded. So here's what we do:

  • Increment the attempt counter: ++attempt.

  • Register a timeout function (setTimeout(addNode, wait)). This tells the JavaScript runtime to wait for wait milliseconds (200 in our case) and then call the addNode() method. Essentially, this function is registering itself as the callback.

  • For debugging purposes (this is a test function, after all), we are logging this to the Firebug console so we can tell that it is in a waiting state.

If this condition takes effect, the application will wait before trying to theme and display the content.

Note

If we expect long delays and need to notify the user, we may use a throbber as a visual device to indicate the waiting state. We will discuss this in Chapter 8.

However, if the server hasn't retrieved our template by max_wait, then the else block of the conditional will be executed. In this case, a different theming function will be called:

var txt = Drupal.theme('defaultNode', node);

This fictional theme (we haven't written a function for it) would hold a default theme implementation for a node theme. This would be an implementation that doesn't rely on a template retrieved from the server.

The example here illustrates one way of working around timing issues with our AJAX-based template system.

At this point we have finished our last project, and also the chapter.

Summary

The focus of this chapter was on the JavaScript theming system. We began by looking at the tools included in the drupal.js library. We then moved on and built our own themes. From there, we looked at the JavaScript theming module, examining some of the themes and user interface tools that it provides. Finally, we implemented our own template system, which was based on HTML, CSS, and JavaScript.

This chapter rounds out our look at drupal.js. In the remaining chapters of the book, we will make frequent use of the features we covered in this, and the previous two chapters. The next chapter will cover AJAX using jQuery and Drupal. There, we will see how much we can gain by adding more client-server interaction to our scripts.