Templating with Widgets, Not Primitives

| 3 Comments

After a disastrous attempt to write my own templating language as about the third program I ever wrote in Perl (it was the dot-com boom of the '90s, not that that's any excuse), I moved to Template::Toolkit and have been relatively happy with it ever since.

My first real paid programming job was a little GUI app for a customer service group at HP. Customer service agents who needed to escalate to second line support would click on the button for the printer line about which they had to ask a question, and the program recorded the vote, then printed a nice report at the end of the day. It solved a problem. As far as I know, it was still running when I left HP a couple of years later.

A couple of years later, I took a job where we refactored, maintained, and extended a GUI point of sale system.

Since then, I've avoided most graphical programming. Sure, I put together websites for clients once in a while, but most of my work has been emitting the most basic semantically-useful HTML possible such that a Real Designer can manipulate things with CSS (CSS being, of course, one of those so-horrible-it's-almost-good things in that it's the only way to get things done, but you always want to take a shower after you use it, lest you think you start to appreciate it for anything other than its efficacy. See also JavaScript and PHP.).

Most of this meant dropping a big blob of content in a Template Toolkit wrapper in the middle of some HTML, or maybe templatizing some repeated HTML element while iterating over a collection in the toolkit.

Then I decided to redesign a site.

Twitter has its problems (I hope never to understand how or why someone would take a perfectly functional website then try to make it work like a buggy phone app in the same way that I never understood why the first Harry Potter movie hewed so closely to the book it was as boring as a Merchant and Ivory movie and it had wizards in it), but Twitter's Bootstrap CSS framework actually made sense to me, and it let me put together a couple of pages that looked good—far better than the Frankenstein's monster I cobbled together from the "Hey, everything's a blog now, right?" OSWD designs I liked.

(Bootstrap has its problems too, but the worst one is that the Less CSS abstraction layer of CSS is intricately tied to the Lovecraftian bonepile of crazy that is Node.js, because if there's anything I want to do in JavaScript less than write a templating system to perform text substitutions in a cooperative multitasking system, I don't know what it is. It probably involves sharks, skydiving, live volcanoes, and dental work. Yet even only being able to extract repeated colors into named variables and build a static CSS file is a huge improvement, so I put on protective eyeware.)

The experience turned out relatively enjoyable. I had a nice looking wrapper and a decent framework for displaying and managing content.

Then I wanted to change the way I displayed certain elements.

Here's the thing about web programming, or at least the way I'm doing this project. I don't think in terms of pages. I think in terms of components of pages. I have a templates/components/ directory full of reusable Template Toolkit components processed with INCLUDE and PROCESS. The big blurbs of marketing text and instructions and explanations on various pages all live in individual components. Sure, it's a little bit of work to figure out the layout, but this separation of concerns makes editing the site much easier.

It also makes revising the layout more difficult—if changing the layout requires modifying lots of templates with the wrong <div> names and classes and such.

It's possible to write more TT components to abstract away these changes, but the point of diminishing returns appears quickly: TT's syntax and semantics just aren't strong enough to define functions and manage parameters.

Good thing Perl is.

Fewer than 20 minutes after I realized I needed a custom plugin, I had it written:

package MyProject::Template::Plugin::Bootstrap;
# ABSTRACT: basic Bootstrap helpers for the Template system

use Modern::Perl;

use parent 'Template::Plugin';

sub new
{
    my ($class, $context, @params) = @_;

    $class->add_functions( $context );

    return $class->SUPER::new( $context, @params );
}

sub add_functions
{
    my ($class, $context) = @_;
    my $stash             = $context->stash;

    for my $function (qw( row sidebar sideblock maincontent fullcontent span ))
    {
        $stash->set( $function, $class->can( $function ) );
    }

    $stash->set( process => sub { $context->process( @_ ) } );
}

sub row
{
    return <<END_HTML;
<div class="row">
    @_
</div>
END_HTML
}

sub sidebar
{
    return <<END_HTML;
<div class="span4">
    @_
</div>
END_HTML
}

sub sideblock
{
    return <<END_HTML;
<div class="well">
    @_
</div>
END_HTML
}

sub maincontent
{
    return <<END_HTML
<div class="span8 maincontent">
    <div class="hero-unit">
        @_
    </div>
</div>
END_HTML
}

sub fullcontent
{
    return <<END_HTML
<div class="maincontent">
    <div class="hero-unit">
        @_
    </div>
</div>
END_HTML
}

sub span
{
    my $cols = shift;
    return <<END_HTML;
<div class="span$cols">
    @_
</div>
END_HTML
}

1;

This turned my templates into:


[% USE Bootstrap %]

[% row(
    maincontent( process( 'components/content/home_text.tt' ) ),
    sidebar(
        sideblock( process( 'components/forms/login_box.tt' )),
        sideblock( process( 'components/boxes/top_recommendation.tt' ) ),
        sideblock( process( 'components/content/newsletter_text.tt' ) ),
    ),
) %]

This reminds me a bit of Generating HTML from Smalltalk's Seaside or Ruby HAML, except it's less "Wow, lots of tags here!" than Seaside and "So cute people hate you for saying how ugly it is!" than HAML.

I haven't convinced myself I have the right abstractions yet—it's not quite to the point of the semantics I like, it produces its own repetitions, and the idea of returning strings of HTML from a template plugin feels a little wrong to me, but the big advantage is that it's taken a lot of repetitive niggly code and turned it into less code that's much more declarative. The repetition is in the structure of the semantics of the site and not the mechanics of how to produce those semantics.

In other words, this is a step toward thinking in widgets rather than UI primitives.

3 Comments

Template::Declare seems a good fit for that style, something like:


template newsletter => sub {
row {
maincontent {
show( 'components/content/home_text' );
};
sideblock { show( 'components/forms/login_box' ); };
sideblock { show( 'components/boxes/top_recommendation' ); };
sideblock { show( 'components/content/newsletter_text' ); };
};
};

Template::Declare->show('newsletter');

Each of login_box, top_recommendation and newsletter_text being templates defined similarly, using TD's declarative template syntax.

There is, incidentally, also a Perl port of LESS: http://search.cpan.org/~drinchev/CSS-LESSp-0.86/ Its implementation has a few... scary edges, but at least they are Perl-scary, as opposed to Node.js-terrifying.

And on a totally sympathetic unrelated note, a segment of my latest book does involve skydiving, surfing on the lava of an erupting volcano, and dealing with gigantic tentacled land sharks... So I guess, whatever you do, don't visit Morvak 13. You won't like it. :-)

I tried CSS::LESSp on a project and it didn't handle all of the Less I needed. I didn't spend much time debugging it. (Did I mention I hate writing, maintaining, and patching parsers?)

Modern Perl: The Book

cover image for Modern Perl: the book

The best Perl Programmers read Modern Perl: The Book.

sponsored by the How to Make a Smoothie guide

Categories

Pages

About this Entry

This page contains a single entry by chromatic published on February 9, 2012 10:52 AM.

Null Objects, Error Handling, and Robustness was the previous entry in this blog.

Easy and Attractive Graphs with Chart::Clicker is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.


Powered by the Perl programming language

what is programming?