Not a developer? Go to MovableType.com

Documentation

Transformation Callbacks

This article has been inspired by and largely derived from Arvind Satyanarayan’s blog post on the same topic.

Movable Type offers a number of different ways to customize the user interface, including:

This document is a guide to the third of these mechanism. It demonstrates through a number of different examples how developers can add elements to the Movable Type user interface in order to customize the user experience.

Overview of Transformation Callbacks

There are two primary types of Transformation callbacks, representing the two different APIs a developer can use to modify the UI. They are:

  • Regular Expression Callbacks
  • DOM Callbacks

Registering Your Transformation Callback

To be able to use these DOM methods, you need to have access to an MT::Template object. Thus, in a Transformer plugin, you will need to hook into the template_param callback for a specific application as with template_param, the last element you are passed is an MT::Template object. The following example shows how to modify the “Edit Entry” screen within Movable Type. The first thing you need to do is add your transformation callback to the registry like so:

sub init_registry {
    my $plugin = shift;
    $plugin->registry({
        callbacks => {
            'MT::App::CMS::template_param.edit_entry' => sub { $plugin->edit_entry(@_); }
        }
    });
}

The format for registering a transformation callback is simple. First you reference the CMS package name, and then append the name of the template (as it exists on the file system) that you would like to modify. So, in the example above, the CMS object is MT::App::CMS::template_param and the template name for the edit entry screen is edit_entry.

Transformation Callback Code Sample

The actual callback that the above code sample refers to would then look like this:

sub edit_entry {
    my $plugin = shift;
    my ($cb, $app, $param, $tmpl) = @_;
    # $tmpl is our MT::Template object! Mess with the DOM below
}

The callback is passed four parameters as input:

  • callback object - the actual MT::Callback object
  • app context - the current state of the application, this gives you access to configuration variables and more
  • parameter hash - This is the form parameters submitted or passed to the current screen being rendered or modified
  • template object - the actual MT::Template object representing the current page

DOM Transformation Callbacks

“DOM,” which stands for Document Object Model is a method for navigating, searching and modifying the elements of a page based upon the elements name, or its class, or its ID. Here are a few resources to help introduce you to this concept.

Accessing a DOM Node

So as I mentioned before, the first step was to get a marker from the template. Several methods exist which allow you to do this:

  • $tmpl->getElementByID('id') - this method returns the node (template tag) that matches the given ID. So, on the entry editing screen, if the basename app:setting tag was my marker, I could simply do

    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).

    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).

Modifying a DOM Node

In all the above cases, you would be end up with either one or a series of template tags (which are technically known as template nodes) and a number of methods are available to use (and are also in Template.pm under the MT::Template::Node definition). Here are a couple to get you started:

Getting/Changing a node’s attributes

The aptly named getAttribute and setAttribute methods allow you to manipulate a node’s attributes. For example, the new hidden class allows us to easily hide elements (rather than setting their style to none), so if I wished to hide an element using hidden, I could do this:

    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);

Traversing the DOM

The DOM is essentially a hierarchy of different nodes. Thus when you have a node in the DOM, you can easily move up, through or down the tree. Say this was our template:

<mt:loop name="object_loop">
    <mtapp:setting
         id="$id"
         label="Name">
      <input type="text" name="name" value="<mt:var name="name" escape="html">" id="name" />
    </mtapp:setting>            
    <mt:setvarblock name="status_msg_id">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>

Where $object_loop is:

[
    {
        id => 1,
        name => 'melody',
        created_on => 20070502
    }
]

So here’s what I could have in my Transformer plugin:

# First, lets getting app:setting#$id
my $setting = $tmpl->getElementById('1');

# This would give me the loop
my $loop = $setting->parentNode;

# This would give me an array of all the tags within the loop
my $children = $loop->childNodes;

# This would give me the setvarblock
my $setvarblock = $setting->nextSibling

The above is just a quick example, more DOM API methods exist in accordance with the conventions of Javascript DOM APIs.

Creating a DOM Node

As I’d mentioned at the start of this article, the typical process of a transformer is to get a marker, tweak the marker and finally add their own elements. So lets look at the final part.

Two extremely convenient methods make creating your node a piece of cake. The first is the excellently named createElement. You need to call createElement by passing it the name of the tag you wish to create along with a list of its attributes. So, if I wished to create this element

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

My transformer plugin would contain

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

Next, to set the contents of our node (only applicable to container/block tags of course), we can call the element’s innerHTML method, like so:

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 in essence creating 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>

The final step is inserting our newly created element into the DOM. For this, we have two great methods insertBefore and insertAfter both of which must be passed our new element and the marker (from which our element is inserted before or after). For example, if we wanted to add $setting before the basename field on the entry editing screen, this is what our Transformer plugin would look like:

# 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 give us 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"$>" />
...

Regular Expression Callbacks

Regular Expression callbacks function in much the same way, however text on the page is substituted using basic string substitutions and manipulations. For example, to customize a template using a regular expression your transformation callback would look like this:

sub my_cb {
    my ($cb, $app, $tmpl) = @_;
    my $slug = <<END_TMPL;
A whole bunch of HTML here
END_TMPL
    $$tmpl =~ s/(<li><mt:__trans phrase=\"Utilities\">\n<ul class=\"sub\">)/$1$slug/;
}
Back

5 Comments

Mark Carey

Mark Carey on August 31, 2007, 11:34 a.m. Reply

A key point missing here on the regular expression side:

With the DOM method you use a templateparam callback — but with the regular expression method, your would use a templatesource callback. As mentioned previously, complete examples (including callback registration, template code, and callback function) are ideal here.

Also, you could add a section on template_output callbacks. While these won’t be used as often, it would be good to document and maybe list use cases for them.

You may also want to note when “in the filesystem” the tmpl files can be found.

Mark Carey

Mark Carey on November 1, 2007, 11:44 a.m. Reply

Note that as of 4.01, if you want to do a templateparam callback for template in a subdirectory of /tmpl/cms/ you need to include the subdirectory in callback registration. But this is not true for templatesource callbacks. If you want to do both a template_param and template source on the same tmpl that reside in a subdirectory, you need to register like this:

‘callbacks’ => { ‘MT::App::CMS::templateparam.dialog/assetupload’ => \&paramsub, ‘MT::App::CMS::templatesource.assetupload’ => \&sourcesub, },

Mark Carey

Mark Carey on November 1, 2007, 11:47 a.m. Reply

Strange that in my comment above, the most of the underscores were scrubbed. The should be underscores between ‘template’ and ‘param’, between ‘template’ and ‘source’ and between ‘asset’ and ‘upload’….

MikeT

MikeT on August 15, 2008, 12:08 p.m. Reply

I don’t know if this is intuitive or not for others, but it wasn’t for me. In the regular expression function shown above where it has my ($cb, $app, $tmpl) = @_; the following is valid:

my ($cb, $app, $tmpl, $param) = @_;

The parameter hash is available in there in case someone needs it.

https://me.yahoo.co.jp/a/IKZmiBpBSYSZbyY3M1sxuXh8xRD6GHOPLw--#254b7

https://me.yahoo.co.jp/a/IKZmiBpBSYSZbyY3M1sxuXh8xRD6GHOPLw--#254b7 on August 21, 2008, 3:14 a.m. Reply

This article is very useful for me. Thanks.

But I can’t understand “MT::App::CMS::template_param”. What this mean? Is this a name of method in MT::App::CMS class?

To:Mark Carey
This comment system adopt Markdown style. So “underscore” is interpreted as a kind of modifier. You can use “Â¥” literal to escape the interpretation.

_