Just a Theory

By David E. Wheeler

Posts about Template Toolkit

Create a Template::Declare Wrapper

Next in my ongoing series of posts on using Catalyst with Template::Declare and DBIx::Connector, we pick up again in chapter 3 to create a wrapper for the view. I added the wrapper support to Template::Declare over a year ago, and while the idea is sound, the interface makes it feel like it’s bolted on. See if you agree with me.

Returning to the MyApp project, open lib/MyApp/Templates/HTML.pm and implement a wrapper like so:

use Sub::Exporter -setup => { exports => [qw(wrapper) ] };

create_wrapper wrapper => sub {
    my ($code, $c, $args) = @_;
    xml_decl { 'xml', version => '1.0' };
    outs_raw '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" '
            . '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
    html {
        head {
            title { $args->{title} || 'My Catalyst App!' };
            link {
                rel is 'stylesheet';
                href is $c->uri_for('/static/css/main.css' );
            };

        };

        body {
            div {
                id is 'header';
                # Your logo can go here.
                img {
                    src is $c->uri_for('/static/images/btn_88x31_powered.png');
                };
                # Page title.
                h1 { $args->{title} || $c->config->{name} };
            }; # end header.

            div {
                id is 'bodyblock';
                div {
                    id is 'menu';
                    h3 { 'Navigation' };
                    ul {
                        li {
                            a {
                                href is $c->uri_for('/books/list');
                                'Home';
                            };
                        };
                        li {
                            a {
                                href is $c->uri_for('/');
                                title is 'Catalyst Welcome Page';
                                'Welcome';
                            };
                        };
                    };
                }; # end menu

                div {
                    id is 'content';
                    # Status and error messages.
                    if (my $msg = $args->{status_msg}) {
                        span { class is 'message'; $msg };
                    }
                    if (my $err = $args->{error_msg}) {
                        span { class is 'error'; $err };
                    }

                    # Output the template contents.
                    $code->($args);
                }; # end content

            }; # end bodyblock
        };
    };
};

This looks like more work than it is because of the copious use of whitespace I’ve used. Personally, I find the pure Perl syntax easier to read than the mix of HTML and TT syntax in the Template Toolkit wrapper. Anyway, this is a nearly direct port of the Template Toolkit wrapper from the tutorial. Template::Declare wrappers expect at least one argument: a code reference that will output the content of the main template. You can see it used here near the end of the code, with the line $code->($args).

Unfortunately, Template::Declare doesn’t make such wrappers easily available to templates. So we have to add the Sub::Exporter line at the top to export the wrapper function it creates, named wrapper.

Next, open up lib/MyApp/Templates/HTML/Books.pm and edit the list template to take advantage of the wrapper. The new code looks like this:

use MyApp::Templates::HTML 'wrapper';

template list => sub {
    my ($self, $args) = @_;
    wrapper {
        table {
            row {
                th { 'Title'  };
                th { 'Rating' };
                th { 'Author' };
            };
            my $sth = $args->{books};
            while (my $book = $sth->fetchrow_hashref) {
                row {
                    cell { $book->{title}  };
                    cell { $book->{rating} };
                    cell { $book->{author} };
                };
            };
        };
    } $self->c, $args;
};

First we import the wrapper function from MyApp::Templates::HTML, and then we simply use it to wrap the contents of our template. Note that the context object and template arguments must be passed on to the wrapper; they’re not provided to the wrapper by Template::Declare. That’s something else I’d like to adjust.

In the meantime, contrary to the tutorial, I don’t think the template should set the title of the page. It seems to me that’s more the responsibility of the controller. So while this template could easily add a title key to the $args hash before passing it on to the wrapper, I recommend editing the list action in MyApp::Controller::Books instead:

sub list : Local {
    my ($self, $c) = @_;
    my $stash = $c->stash;
    $stash->{title} = 'Book List';
    $stash->{books} = $c->conn->run(fixup => sub {
        my $sth = $_->prepare('SELECT isbn, title, rating FROM books');
        $sth->execute;
        $sth;
    });
}

So, with the wrapper in place, let’s create the stylesheet the wrapper uses:

$ mkdir root/static/css

Then open root/static/css/main.css and add the following content:

#header {
    text-align: center;
}
#header h1 {
    margin: 0;
}
#header img {
    float: right;
}
#footer {
    text-align: center;
    font-style: italic;
    padding-top: 20px;
}
#menu {
    font-weight: bold;
    background-color: #ddd;
    float: left;
    padding: 0 0 50% 5px;
}
#menu ul {
    margin: 0;
    padding: 0;
    list-style: none;
    font-weight: normal;
    background-color: #ddd;
    width: 100px;
}
#content {
    margin-left: 120px;
}
.message {
    color: #390;
}
.error {
    color: #f00;
}

Now restart the app as usual and reload the books list at http://localhost:3000/books/list. You should now see a nicely formatted page with the navigation and header stuff, as well as the book list. You can change the CSS and the wrapper to modify the overall look of your site, and then use the wrapper in all of your page request templates to get the same look and feel across your site.

While this works, I’m not satisfied with the overall interface for Template::Declare wrappers. The need to explicitly export them and pass arguments is annoying. Maybe the Jifty guys have some other approach that works better. But if not, I’ll likely go back to the drawing board on wrappers and see how they can be made better.

Next up: More database fun!

Looking for the comments? Try the old layout.