Maypole View Classes

In a large application, you will almost certainly want to customize the layout and design of the output pages. This task may even be the purview of a separate team of HTML designers rather than the programmers. Since a typical programmer will try to avoid touching HTML as much as possible and a typical designer will try to avoid touching Perl code, programmers have evolved a system of templating to separate the concerns of programming and designing.

One of the core concepts in Maypole is the view class, and this is responsible for routing the data produced in the model class into the templates produced by the designers. Of course, there are a great many possible templating systems and styles, and so there can be a great many possible Maypole view classes. Each view class will take the data from the controller, locate a template to be processed, and hand the whole lot to its preferred templating module, which will then do the hard work of filling in the template and coming up with the output.

You can choose whatever Maypole view class you want, but the default view class is Maypole::View::TT, and it feeds its data and templates to a module called the Template Toolkit.

The Template Toolkit

The Template Toolkit, written by Andy Wardley, is a very powerful and generic templating system. It provides its own little formatting language which supports loops, conditionals, hash and array dereferences and method calls, macro processing and a plug-in system to connect it to external Perl modules. There are several good introductions to the Template Toolkit available: you should have one installed as Template::Tutorial::Datafile; there's one at http://www.perl.com/pub/a/2003/07/15/nocode.html, and of course there's the "Badger Book" - The Perl Template Toolkit, by Andy et al.

We'll present a brief introduction here by deconstructing some of the templates used by Maypole applications. For more deconstruction, see StandardTemplates.pod, which is an entire chapter dealing with the factory supplied templates.

Here's the template which is called for the front page of the standard beer database application, custom/frontpage.

[% INCLUDE header %]

<h2> The beer database </h2>

<TABLE BORDER="0" ALIGN="center" WIDTH="70%">
[% FOR table = config.display_tables %]
<TR>
<TD>
<A HREF="[%table%]/list">List by [%table %]</A>
</TD>
</TR>
[% END %]
</TABLE>

The first thing to note about this is that everything outside of the Template Toolkit tags ([% and %]) is output verbatim. That is, you're guaranteed to see

<h2> The beer database </h2>

<TABLE BORDER="0" ALIGN="center" WIDTH="70%">

in the output somewhere. Inside the tags, magic happens. The first piece of magic is the [% INCLUDE header %] directive. This goes away and finds a file called header - don't worry about how it finds that yet, we'll come to that later on - and processes the file's contents as though they were right there in the template. Our header file happens not to contain any [% %] tags, but if it did, they would be processed in the same way as the ones in frontpage.

The next piece of magic is this line:

[% FOR table = config.display_tables %]

We're seeing a lot of things here at once. config is where we should start looking. This is a template variable, which is what templates are all about - templating means getting data from somewhere outside and presenting it to the user in a useful way, and the config hash is a prime example of data that we want to use. It's actually the hash of configuration parameters for this Maypole application, and one of the keys in that hash is display_tables, the database tables that we're allowed to play with. In the application, we probably said something like

BeerDB->config->{display_tables} = [qw[beer brewery pub style]];

This stores the four values - beer, brewery, pub and style - in an array, which is placed in the config hash under the key display_tables. Now we're getting them back again.

The Template Toolkit's dot operator is a sort of do-the-right-thing operator; we can say array.0 to get the first element of an array, hash.key to look up the key key in a hash, and object.method to call method on an object. So, for instance, if we said config.display_tables.2, we'd look up the display_tables key in the configuration hash and get our array back, then look up the 2nd element and get pub.

The FOR loop will repeat the code four times, setting our new variable table to the appropriate array element. This code:

[% FOR table = config.display_tables %]
    Hello [% table %]!
[% END %]

will produce something like

Hello beer!
Hello brewery!
Hello pub!
Hello style!

In our case, though, we're printing out a table element linking to each database table in turn.

Here's a slightly more complicated example, adapted from factory/pager. This template is responsible for printing the little page menu at the bottom of a listing if there are more rows in the listing than we want on a single page.

[% PROCESS macros %]
<P ALIGN="center">Pages:
[%
     FOREACH num = [pager.first_page .. pager.last_page];
          IF num == pager.current_page;
            "["; num; "] ";
          ELSE;
            SET args = "?page=" _ num;
            SET label = "[" _ num _ "]";
            link(classmetadata.moniker, "list", args, label);
          END;
     END;
%]
</P>

Maypole will be providing a whole bunch of variables to this template, and we'll look at them all in a moment, but the only ones we need to care about are pager and classmetadata.

We start by loading in a bunch of macros. Macros are Template Toolkit's functions - you can provide them some parameters and they'll run a little sub-template based on them. The macros file contains some handy macros that I've found useful for constructing Maypole templates; again, these will be covered in full detail in StandardTemplates.pod.

We're going to be displaying something like this:

Pages: [1] [2] [3] [4]

with most of those numbers being a link to the appropriate page. This mean we're going to have to have a list of numbers, and the FOREACH loop provides this: (FOREACH and FOR are identical, just like in Perl.)

FOREACH num = [pager.first_page .. pager.last_page];

Here we're manually constructing an array of numbers, using the range operator (..) to fill in all the numbers from the first_page (1) to the last_page (4). The same dot operator is used to ask the pager what its first_page and last_page is. Remember when we said config.display_tables, we were looking up the display_tables key in the config hash? Well, this time we're not looking anything up in a hash. pager is an object, and first_page is a method. Thing is, you don't have to care. You can pretend it's a hash if you want. The syntax is the same, and Template Toolkit knows the right thing to do.

Now we're going to be executing this loop four times, once each for num being set to 1, 2, 3, and 4. At some point, we'll come across the page that we're actually on right now:

IF num == pager.current_page;

and in that case, we don't want to produce a link to it. We just want to output it as text, surrounded by square brackets:

"["; num; "] ";

We're using string literals to output the brackets. We don't have to do that. We could say it this way:

[% ...
  IF num == pager.current_page;
%]
    [ [% num %] ] 
[% ELSE %]
   ...
[% END %]

But you know, I quite like it my way.

Now if the number we're printing isn't the number of the current page, we want to make a link. Here's how we do it:

SET args = "?page=" _ num;
SET label = "[" _ num _ "]";
link(classmetadata.table, "list", args, label);

SET declares a new variable of our own. If there was anything called args before, there isn't now. It's going to be the result of our statement "?page=" _ num. _ is the concatenation operator, and glues ?page= onto the front of our number. So if we want to link to page 4, then the args variable will contain ?page=4. Similarly, the label variable will be [4].

Now we call a macro, link with these two variables and the value of classmetadata.table. This macro takes four arguments, table, action, args and label, and constructs a link of the form

<A HREF="[% config.base_url %]/[% table %]/[% action %][% args %]">
[% label %]
</A>

In our case, it'll be filled in like so:

<A HREF="[% config.base_url %]/[% classmetadata.table %]/list?page=4">
[ 4 ]
</A>

Where classmetadata.table will actually be the name of the current table, and config.base_url will be replaced by the appropriate URL for this application.

Locating Templates

Another feature of Maypole::View::TT which may not be present in alternate view class implementations - although they are strongly encouraged to provide it - is the way that templates are located. (Remember, I did say I'd tell you about that later.) Template Toolkit allows whatever uses it to provide a path for template files to be located in. Maypole::View::TT feeds it up to three possible directories to look things up in, and it will try to find a template in each of these in turn.

When you configure a Maypole application, you can tell it the base directory of your templates like so:

BeerDB->config->{template_root} = "/var/www/beerdb/templates";

If you don't do this, most Maypole front-ends will use the current directory, which is generally what you want anyway. Off this directory, Maypole will look for a set of subdirectories.

For instance, I said we were in the middle of processing the front page and looking up a template file called header. Maypole will first look for this file in the custom subdirectory. (say, /var/www/beerdb/templates/custom) If it doesn't find one, then it looks in the factory subdirectory. If it doesn't find one there, then it gives up and dies with an error. But that's your fault, since you've called for a template which doesn't exist. Don't do that.

This behaviour means that you can provide your own site-specific templates, but if you don't do so, then you get to use a generic one provided by Maypole. Maypole's "factory setting" templates are written in such a way as to try and do the right thing no matter what your application does. They are occasionally successful at this.

Now the front page was a pretty simple example, since Maypole only looks up two directories. In most cases, it checks an additional directory, and this directory depends entirely on what Maypole is doing.

If you're writing an e-commerce application, for example, you may well have a table which represents the product catalogue and all the products you can buy. Let's call this the product table. You'll also have a data source which is specific to the user which contains all the products that they're buying on this particular visit to the site. In time-honoured tradition, we'll call this the basket table.

Now it ought to be reasonably apparent that you don't want the basket to be displayed in exactly the same way as the product catalogue. The templates for product/list and basket/list need to be different. This is where the third directory comes in. The other directory, which Maypole checks very first of all, is specific to the table that you're viewing. So if you go to http://your.shop.com/basket/list, Maypole will look in the basket directory first for a file called list, and second in the custom directory for a site-wide list template, and then fall-back to the factory directory for a generic list template. It should be obvious that you probably want to provide all of basket/list, basket/view, product/list, product/view and any other combination of classes and actions that you can think of.

What Maypole provides to a template

Maypole::View::TT provides quite a variety of template variables to the template. As these are the building blocks of your pages, it's worth looking at precisely what variables are available.

The most important variable is called objects, and is a list of all the objects that this page is going to deal with. For instance, in the template /beer/view, objects will contain the BeerDB::Beer object for the 23rd item in the database, while /brewery/list will fill objects will all the breweries; or at least, all the breweries on the current page.

This variable is so important that to help design templates with it, Maypole::View::TT provides a helpful alias to it depending on context. For instance, if you're writing your own /brewery/list template, the data in objects is also available in a template variable called breweries. If you're working on /brewery/view, though, it's available in brewery, since there's only one brewery to be displayed.

Additionally, you can get the base URL for the application from the base template variable; this allows you to construct links, as we saw earlier:

<A HREF="[% base %]/brewery/edit/[% brewery.id %]">Edit this brewery</A>

You can also get at the rest of the configuration for the site with the config variable as we saw above, and the entire request object in request, should you really need to poke at it. (I've only found this useful when working with authentication modules which stash a current user object in request.user.)

To allow the construction of the "generic" templates which live in factory, Maypole also passes in a hash called classmetadata, which contains all sorts of useful information about the class under examination:

table

This is the name of the table that is represented by the class.

class

This is the Perl's idea of the class; you don't need this unless you're doing really tricky things.

moniker

This is a more human-readable version of the table name, that can be used for display.

plural

The same, but a correctly-formed plural. For instance, "breweries".

columns

The list of columns for display; see the section "Customizing Generic CRUD Applications" in StandardTemplates.pod.

colnames

This is a hash mapping the database's name for a column to a more human-readable name. Again, see "Customizing Generic CRUD Applications>.

cgi

This is a slightly trickier one. It is a hash mapping column names to a HTML::Element suitable for entering data into a new instance of that class. That is, for the beer table, classmetadata.cgi.style should be a HTML::Element object containing a drop-down list of beer styles. This is explained in StandardTemplates.pod.

description

This is the human-readable description provided by a class.

This is a list of accessors which can be called on an object to get lists of other things that this object "has". For instance, on a brewery, it would return beers, since calling brewery.beers would give you a list of beers produced by the brewery. Note that this only caters for accessors defining one-to-many relationships, not the ordinary one-to-one relationships, such as style.

Other view classes

Please note that these template variables, config, classmetadata, objects and its user-friendly alias, as well as the rest of them are a function of one particular view class, the default Maypole::View::TT class. Other view classes may need to present an entirely different set of template variables, since the default ones might not make sense. The templates may look wildly different in other view class implementations. But that's OK, because you couldn't necessarily use the same templates with a different templating system anyway.

For instance, in really dumb templating languages which can't handle dereferencing hashes or arrays - no wait, that's most of them - passing in a hash reference like classmetadata won't help you since you can't get at any of its elements. So you'll need to take a look at the documentation for the appropriate view class to see what template variables it provides.

So if, for some perverse reason, the Template Toolkit just isn't good enough for you, then you can set your own view class while configuring your application:

package BeerDB;
use base Maypole::Application;
...
BeerDB->setup("dbi:SQLite:t/beerdb.db");
BeerDB->config->{uri_base} = "http://localhost/beerdb/";
BeerDB->config->{rows_per_page} = 10;
BeerDB->config->{view} = "Maypole::View::Mason"; 

Where do these alternate view classes come from? Gentle reader, they come from you.

Building your own view class

You should probably skip this section for the first few readings of this manual. It's only intended for people extending Maypole.

Imagine you've found a brand new templating system that's much better than the Template Toolkit. I know I'm stretching your imagination a bit here, but try. You'd like to use it with Maypole, which means writing your own view class. How is it done?

We'll demonstrate by implementing a view class for HTML::Mason, although no value judgement is implied. HTML::Mason is a templating system which embeds pure Perl code inside its magic tags. The good side of this is that it can get into hash references and objects, and so providing classmetadata, config and the Maypole request object will work out just fine. The down side is that HTML::Mason is used to running more or less standalone, and having all the template variables it wants already at its disposal through CGI parameters and the like, so we have to fiddle a bit to get these variables into our template.

The key to building view classes is Maypole::View::Base. This is the base class that you're going to inherit from and, to be honest, it does pretty much everything you need. It provides a method called vars which returns a hash of all the template variables described above, so it would be good to feed those into HTML::Mason. It also provides a paths method which turns returns the full filesystem path of the three possible template paths as shown above. Again, it would be good to use this as our component paths if we can. It also has some methods we can override if we want to, but they're not massively important, so you can see Maypole::View::Base for more about them.

The module will do the right thing for us if we agree to provide a method called template. This is responsible for taking the Maypole request object (of which more later) and putting the appropriate output either into $r->{output} or $r->{error}, depending, of course, whether things are OK or whether we got an error.

Thankfully, HTML::Mason makes things really easy for us. We can use multiple template roots, so we can use the paths method; we can pass in a hash full of interesting data structures, so we can use the vars method too. In fact, we have to do very little to make Maypole::View::Mason work. Which is somewhat annoying, because it makes a boring example. But it means I can leave the fun ones to you!

The doing-the-templating, in Mason and in any templating system, depends on three things: the paths that we're going to use to find our templates, the template name that we've been asked to fill out, and the set of variables that are going to be fed to the template. We'll assemble these for reference:

sub template {
    my ($self, $r) = @_;
    my @paths = $self->paths($r);
    my $template = $r->template;
    my %vars = $self->args($r);

We'll also declare somewhere to temporarily store the output:

my $output;

Now comes the part where we have to actually do something templating-language specific, so we open up our copy of "Embedding Perl in HTML with Mason" and find the bit where it talks about running Mason standalone. We find that the first thing we need to do is create a HTML::Mason::Interp object which knows about the component roots. There's a slight subtlety in that the component roots have to be specified as an array of arrays, with each array being a two-element list of label and path, like so:

comproot => [
    [ class   => "/var/www/beerdb/templates/brewery" ],
    [ custom  => "/var/www/beerdb/templates/custom" ],
    [ factory => "/var/www/beerdb/templates/factory" ],
]

We also find that we can set the output method here to capture Mason's output into a scalar, and also that we can tell Mason to generate sensible error messages itself, which saves us from having to worry about catching errors. At the end of all this, we come up with a constructor for our HTML::Mason::Interp object which looks like this:

my $label = "path0";
my $mason = HTML::Mason::Interp->new(
    comproot => [ map { [ $label++ => $_ ] } @paths ],
    output_method => \$output,
    error_mode => "output" 
);

The next thing we need to do is run the template with the appropriate template variables. This turns out to be really easy:

$mason->exec($template, %vars);

Now we've got the data in $output, we can put it into the request object, and return a true value to indicate that we processed everything OK. (If there was an error, then Mason will have produced some suitable output, so we can pretend that everything's OK anyway.)

$r->{output} = $output;
return 1;

And that's all we need to do. Barely twenty lines of code for the finished product. Wasn't that easy? Don't you feel inspired to write Maypole view classes for your favourite templating language? Well, don't let me stop you! Patches are always welcome!