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.