Using the new Transformer DOM Methods

Ever since Kevin Shay released the brilliantly named BigPAPI plugin, plugins have been able to change Movable Type's interface extremely easily. With Movable Type 3.3, this functionality was built into the core and this opened the floodgates for "Transformer Plugins" - as they've come to be known! All (or at least the majority) of Transformer plugins work by first looking for a marker (a few lines) in an app template page and then either adding to it or replacing it with something of their own. Now besides being extremely crude (if there was the slightest change to the marker, the transformer plugin would collapse complete), this presented the problem of multiple transformer plugins using the same marker. For example, if Plugin A was to modify a marker used by Plugin B, well Plugin B would no longer work! And this proved to be quite a nightmare for support.

In Movable Type 4, along with the new interface, the entire backend app templates were rebuilt using Movable Type's own templating system. Besides being far more powerful than it's predecessor HTML::Template, it allowed for a whole new set of API to be built. This new API implements the DOM methods native to JavaScript. The DOM can be quite complicated to wrap your head around (in fact, it took me several months to truly feel confident) so here are a few tutorials to familiarize yourself with it before tackling Movable Type's DOM:

With Movable Type 4, a lot of these DOM methods have been built into the system and can (and should) be used instead of the regexes. They should be called on MT::Template objects (and thus can be found in lib/MT/Template.pm) and work by modifying nodes/elements (template tags) in the template. Lets look at how to build a Transformer plugin with these new methods.

To be able to use these DOM methods, you need to have access to an MT::Template object. Hence, in a Transformer plugin, you will need to hook into the template_param callback for a specific application (the last element you are passed in a template_param callback is an MT::Template object):

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


sub edit_entry {
    my $plugin = shift;
    my ($cb, $app, $param, $tmpl) = @_;

    # $tmpl is our MT::Template object! Mess with the DOM below
}

to run a transformer on the entry editing screen of mt.cgi

Getting the Marker

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

Tweaking a 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 was:

    [{
            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
    

    And that's just a quick example. There are lot more methods available!

Creating a 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"$>" />
...

Conclusion

This article has attempted to give a brief overview of the new powerful DOM methods available for Transformer plugins with Movable Type 4.0. For some real live examples, check out the PostVox or CrossPoster plugins which both make use of these new methods.

As you've no doubt realized, the beauty of these methods is in their "forward compatibility," as plugin authors will rely less frequently on hardcoded markup and more on the Movable Type DOM, we should hopefully see an era where plugins not only seamlessly integrate into Movable Type but one where a majority of plugins don't require upgrades or fixes when a new version of Movable Type is released!

6 Comments

Bud Gibson said:
on Aug 24, 2007 12:07 PM | Reply

Great article. The one major improvement would be if you provided the full code for the working example, so people could just download it and try it out. I think that would just involve taking what you've got and putting it in a plug-in.

BTW, I don't seem to be able to log in as budGibson.

Arvind Satyanarayan replied to Bud Gibson's comment:
on Aug 24, 2007 12:39 PM | Reply

Hi Bud, I'd forgotten to add those links. I've just updated the article with links to the PostVox and CrossPoster transformer plugins which both use the new DOM methods.

As for why your log in is failing, I'm not entirely sure. There are plenty of other users who have signed up with my MT install.

Mark Carey said:
on Aug 24, 2007 2:36 PM | Reply

Great article, Arvind. Even before you published it, I learned a great deal about using teh DOM from peaking at one of your recent plugin releases. I have already used it in a soon-to-be-released plugin. Your examples were very helpful, thanks!

Tim said:
on Aug 25, 2007 4:29 PM | Reply

Sorry for the lack of relevancy here but I'm looking for a developer to work on a project for my site which is powered by Movable Type. If you're interested please email me at tim [at] blogto [dotcom]. Thanks.

Jay Levitt said:
on Sep 7, 2007 11:04 AM | Reply

This is great! Does that mean that alt-tmpl should be a thing of the past? Upgrading from MT3.2 to MT4, I got bit by old-style templates that Media Manager had put in alt-tmpl to add its buttons; they were only minor changes to the MT3 defaults, but of course they failed miserably in MT4.

Cary said:
on Nov 19, 2007 5:32 PM | Reply

I can't seem to get this method working. For some reason, transformer plugins don't seem to change anything on my MT4.01. (even though they show up under System Plugin Settings) As a test, I tried installing PostVox, but it doesn't add anything to the edit entry screen. Any ideas?