Landscape

Javascript Theme Functions in Drupal 7

Like it or not, sometimes you have to output HTML in javascript.

Recently, I ran across a line of code something like this while reviewing a pull-request for a client:

var inputMarkup = '<span><label data-val="' + inputText + '"
for="checkbox-' + index + '" data-tid="' + tid + '">' +
  inputText + '</label><input type="checkbox" id="checkbox-' + index + '"
  data-tid="' + tid + '" data-val="' + inputText + '"
  /></span>';

Aside from the fact that this code was hard to read (and therefore would be more difficult to maintain), the same code was used with no significant modification in three separate locations in the pull-request.

In PHP, most developers familiar with Drupal would immediately reach for one of the well-known parts of Drupal's theme system, render arrays, theme(), or a *.tpl.php file. In javascript, however, I seldom see much use of Drupal 7's extensive javascript API (also made available in a nicely browseable--though not quite up-to-date--form by nod_).

In this case, the relatively difficult-to-read code, combined with the fact that it was repeated several times across more than one file were clear signs that it should be placed into a theme function.

The Drupal.theme() function in the javascript API works much like theme() in PHP. When using theming functions in PHP, we never call them directly, instead using the theme() function.

In javascript, it's similar; when output is required from a given theme function, we call Drupal.theme() with the name of the theme function required, and any variable(s) it requires.

For example, drupal.org shows the following usage:

Drupal.theme('myThemeFunction', 50, 100, 500);

The example uses Drupal.theme() to call the theme function, myThemeFunction(), and pass it the arguments it requires (50, 100, and 500 in this instance). A theme function can accept whatever number of arguments is necessary, but if your theme function requires more than one parameter, it's good practice to define the function to take a single javascript object containing the parameters required by the function.

So in the case of my code-review, I suggested we use a theme function like this:

/**
 * Provides a checkbox and label wrapped in a span.
 *
 * @param {object} settings
 *   Configuration object for function.
 * @param {int} settings.index
 *   A numeric index, used for creating an `id` attribute and corresponding
 *   `for` attribute.
 * @param {string} settings.inputText
 *   The text to display as the label text and in various attributes.
 * @param {int} settings.tid
 *   A Drupal term id.
 *
 * @return {string}
 *   A string of HTML with a checkbox and label enclosed by a span.
 */
Drupal.theme.checkboxMarkup = function(settings) {
  "use strict";

  var checkboxId = 'checkbox-' + settings.index;
  var inputText = Drupal.checkPlain(settings.inputText);
  var checkboxMarkup = '';

  // Assemble the markup--string manipulation is fast, but if this needs
  // to become more complex, we can switch to creating dom elements.
  checkboxMarkup += '<span>';
  checkboxMarkup += '<label data-val="' + inputText + '" for="' + checkboxId + '" data-tid="' + settings.tid + '">';
  checkboxMarkup += inputText;
  checkboxMarkup += '</label>';
  checkboxMarkup += '<input type="checkbox" value="' + inputText + '" id="' + checkboxId + '" data-tid="' + settings.tid + '" data-val="' + inputText + '">';
  checkboxMarkup += '</span>';

  return checkboxMarkup;
};

This allowed the calling code to be much simpler:

// Creates themed checkbox.
checkboxMarkup = Drupal.theme('checkboxMarkup', {
  index: i,
  inputText: $('.inputText').val(),
  tid: $('.tid')
});

$container.append(checkboxMarkup);

The HTML generation is now also more loosely coupled, and more portable, meaning that we can easily use Drupal.theme.checkboxMarkup() elsewhere in this project--or in any other Drupal project.

Related Articles