Not a developer? Go to MovableType.com

Documentation

Traversing and Manipulating the MT::Template DOM

When Movable Type goes through the process of rendering a template into HTML, one of the first things it does it to “compile” the template into a machine readable form, which for Movable Type is a DOM, or “Document Object Model.” In this form, the contents of the template are broken down into a discrete set of “nodes” on a tree. Each node on the tree can be identified by an ID, their class name, and/or their position within the tree.

Once a template has been compiled into its DOM-based counterpart, one can navigate and traverse the tree through a relatively simple and well established set of methods. These methods, or functions, allow you to find the node of the tree, a template tag for example, and modify its properties, and in so doing alter the output of the template.

The following section discusses these methods and techniques you can use within the context of a template_param callback to customize the appearance of a page within the Movable Type application.

Finding and Accessing a DOM Node

There are several methods available to help you obtain a pointer to someplace in the DOM tree. They are:

  • $tmpl->getElementByID('id') - this method returns the node (template tag) that matches the given ID. The ID corresponds to the same ID you would use to access the same element within javascript, or the ‘id’ attribute associated with the HTML element being modified.

    my $basename_setting = $tmpl->getElementById('basename');
    
  • $tmpl->getElementsByTagName('include') - this method searches the template for all tags that match the given tag name (you must not include the namespace part of the template tag - i.e. MT, or mt:). Note: This tag will return an array of tags that match (as there could potentially be many of the same template tag on a page). The following code sample will return a list of all the <mt:include> tags.

    my @includes = $tmpl->getElementsByTagName('include');
    
  • $tmpl->getElementsByClassName('msg') - this method returns an array of template tags whose class attribute matches the value passed, in this case class="msg"

  • $tmpl->getElementsByName('submit') - and, shockingly, this method returns an array of template tags whose name attribute matches the value passed (name="submit" in this case).

How Movable Type Parses the DOM

The DOM can roughly be described a tree. To help understand how to navigate a tree, it might be helpful to visualize in your mind something you are more intimately familiar with: your family tree. So picture in your mind your family tree.

Now if I were to point to a random person in that tree and ask you to tell me who that person’s parents were, or who their children were, or who their ancestors were, I doubt you would have a challenge doing so. Luckily the concept of a tree and a DOM in programming mirrors these concepts and the terminology used in a family tree that we take for granted almost perfectly.

Ok, so how is this concept translated for use within Movable Type? Let’s take some sample template code, and then see how Movable Type sees it in a compiled form. First, the template code:

<mt:loop id="my_loop" name="object_loop">
    <mtapp:setting
         id="$id"
         label="Name">
      <input id="my_input" type="text" name="name" 
         value="<mt:var name="name" escape="html">" id="name" />
    </mtapp:setting>            
    <mt:setvarblock name="status_msg_id" id="set_msg">status_msg_<mt:var name="id"></mt:setvarblock>
    <mtapp:statusmsg
        id="$status_msg_id"
        class="success">
      This was created on <mt:var name="created_on">
    </mt:appstatusmsg>
</mt:loop>

Now, let’s take a look at the tree that is built out from the sample code above:

  • mt:loop, attributes: id=’my_loop’, name=’object_loop’
    • <text node>, value: white space
    • mtapp:setting, attributes: id=’$id’, label=’Name’
      • <text node>, value: ‘<input id=”my_input” … />’
    • <text node>, value: white space
    • mt:setvarblock, attributes: id=’set_msg’, name=’status_msg_id’
      • <text node>, value: ‘status_msg_’
      • mt:var, attributes: name=’id’
    • <text node>, value: white space
    • mtapp:statusmsg, attributes: id=’$statusmsgid’, class=’success’
      • <text node>, value: white space
      • mt:var, attributes: name=’created_on’
      • <text node>, value: white space

There are a couple things you may notice right away that reflect upon how Movable Type interprets the text and tags it encounters within a template:

  1. The nodes of a template’s DOM consist of two types of elements: a text node and a tag node.

    • All HTML that is embedded within your template code is considered to be “text,” even if that HTML has within it properly formatted HTML ids and class names.

    • When traversing the DOM, only Movable Type template tags can be searched for my name, id, or class name. Text nodes are a type of dark matter that is completely opaque to the methods used to search and traverse the DOM.

  2. All white space found in a template (spaces, new lines, etc) is interpreted and preserved by Movable Type as text nodes.

Now that we understand how Movable Type converts template source code into a DOM representation of that code, let’s explore how you as a developer can traverse and find elements within the DOM so that you can edit or modify it.

Traversing the DOM

Whenever you wish to modify the DOM, your first task is always to obtain the node you wish to modify. Once you obtain a reference to the node you can:

  • change the node’s properties.
  • insert a node as a sibling or child to the current node.

By far the easiest and quickest way to obtain a reference to a node is to refer to it by it’s ID, like so:

my $setvar = $tmpl->getElementById('set_msg');

Furthermore, once you obtain a reference to a node, you can navigate to adjacent nodes by using any of the following methods:

# This would yield the mt:loop element
my $loop = $setvar->parentNode;

# This would yield an array of all the nodes within the loop
my $children = $loop->childNodes;

# This would give me the mt:statusmsg element
my $status = $setvar->nextSibling->nextSibling;

Of course these are just a small sampling of the various methods you can use to traverse up, down and across the DOM tree. Additional DOM API methods exist in accordance with the conventions of Javascript’s DOM APIs.

Now, once you have access to the node you want to make changes to, here are a couple of things you can do to modify them to achieve your desired results:

Getting/Changing a Node’s Attributes

The aptly named getAttribute() and setAttribute() methods allow you to manipulate a node’s attributes. For example, suppose you wanted to hide an element on the page. You can do this using the special hidden class. The following code does this by extracting the current value of the class attribute and appending to it the class “hidden.”

my $node = $tmpl->getElementById('title');

# We first get the attribute instead of setting it directly in case 
#    a value already exists so we just prepend hidden
my $class_attr = $node->getAttribute('class'); 
$class_attr = 'hidden ' . $class_attr;

$node->setAttribute('class', $class_attr);

Creating a DOM Node

Sometimes it is not sufficient to modify an existing node. Sometimes you need to create an entirely new node and insert it into the DOM. Two convenient methods make creating your node relatively simple. The first is the aptly named createElement. One can call createElement by passing it the name of the tag you wish to create along with a list of its attributes. To illustrate, suppose you wished to create this element:

<mtapp:setting id="foo_bar" required="1"></mtapp:setting>

Then your code would look like this:

my $setting = $tmpl->createElement('app:setting', { 
    id => 'foo_bar', required => 1 
});

As an alternative to building out a DOM tree attribute-by-attribute, one can simply set the element’s innerHTML via the method of the same name. However, this technique is only applicable to block tags.

my $innerHTML = '<input type="text" name="foo_bar" \
     value="<mt:var name="foo_bar" escape="html">" id="foo_bar" />';
$setting->innerHTML($innerHTML);

This perl code is therefore equivalent to the following template code:

<mtapp:setting id="foo_bar" required="1">
  <input type="text" name="foo_bar" 
      value="<mt:var name="foo_bar" escape="html">" id="foo_bar" />
</mtapp:setting>

So we have built out a node using one technique or another. Now, in our final step we need to insert it into the DOM. This is accomplished using one of two methods: insertBefore() or insertAfter(). For both of these methods one must pass two arguments: the element/node to be inserted, and the node from which to insert the new node. For example:

# Get our marker
my $basename_field = $tmpl->getElementById('basename');

# Create our element
my $setting = $tmpl->createElement('app:setting', { 
    id => 'foo_bar', required => 1 
});
my $innerHTML = '<input type="text" name="foo_bar" 
       value="<mt:var name="foo_bar" escape="html">" id="foo_bar" />';
$setting->innerHTML($innerHTML);

# Attach our element in the DOM before basename
$tmpl->insertBefore($setting, $basename_field);

This code would yield template code equivalent to the following:

<mtapp:setting id="foo_bar" required="1">
    <input type="text" name="foo_bar" 
       value="<mt:var name="foo_bar" escape="html">" id="foo_bar" />
</mtapp:setting>
<mtapp:setting
   id="basename"
   label="$basename_label"
   help_page="entries"
   help_section="basename">
       <input type="hidden" name="basename_manual" id="basename_manual" 
          value="0" />
       <input type="hidden" name="basename_old" id="basename_old" 
          value="<$mt:var name="basename_old" escape="html"$>" />
...
Back

Leave a Comment