If you're like me and your design skills are sufficient to modify something decent to look nice but insufficient to create something from first principles, you can do a lot worse than to play with Twitter Bootstrap for your next web site.
I've used it successfully for a few projects and it's been great.
It's a lot better now that I've written my own silly little Template Toolkit plugin to reduce the need for writing lots of repetitive HTML in my templates. (It's like Haml but less ugly and more Perlish and easier to extend.)
Writing a TT2 plugin is relatively easy. Of course I do it the wrong way; when you initialize your plugin, you have the ability to manipulate TT2's stash. This is the data structure representing the variables in scope in your templates. Where a well-behaved template should use object methods to perform its operations, my code stuffs function references in the stash. Here's the relevant code:
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;
while (my ($name, $ref) = each %exports)
{
$stash->set( $name, $ref );
}
$stash->set( process => sub { $context->process( @_ ) } );
}
I'll fix this eventually, but the process of making this work was interesting.
In my first attempt (see Write
the Wrong Code First for the justification), I'd write the function I
needed, like row()
, which creates a new Bootstrap row or
maincontent()
which creates the main content area of the page.
Then I'd add that function to the %exports
hash and everything
would work.
After the sixth function, keeping that list up to date was tedious. Then I kept forgetting it. After all, any time you have to update the same data in two places, you're doing something wrong.
Now the code looks more like:
sub row :Export
{
return <<END_HTML;
<div class="row">
@_
</div>
END_HTML
}
... with a single code attribute marking those functions which I want to stuff into the template stash. I've used Attribute::Handlers before, but I always end up reading the manual and playing with things to get them to work correctly. (Something about the way you have to write another package and inherit from it to get your attributes to work correctly always confuses me.)
My second attempt lasted no longer than ten minutes. I switched to Attribute::Lexical. This is almost as trivial to use as to explain:
use Attribute::Lexical 'CODE:Export' => \&export_code;
Whenever any function has the :Export
attribute, Perl wil lcall
my export_code()
function:
my %exports;
sub export_code
{
my $referent = shift;
my $name = Sub::Identify::sub_name( $referent );
return unless $name;
$exports{$name} = $referent;
}
The first argument to this function is a reference to the exported function. I use Sub::Identify to get the name of the function reference. (That wouldn't work for anonymous functions, but I can control that here.) Then I store the name of the function and the function reference in a hash.
It took as long to write as it does to explain.
A lot of people dislike the use of attributes. Used poorly, they create
weird couplings and plenty of action at a distance.
Attribute::Handlers
can be confusing.
I like to think that I'm using attributes well here (even if I'm abusing TT2
more than a little), and that they've simplified my code so that I can avoid
repeating myself and performing manual busywork that I'm likely to forget. Even
better, the code to use them isn't magical at all: it's all hidden behind the
pleasant interfaces of Attribute::Lexical
and
Sub::Identify
.
It'd be interesting to see how a template looks uses these functions.
I did something similar for our web application to mark which methods were publicly accessible.
I tried Attribute::Handlers, but it didn't work under mod_perl2 because CHECK and INIT aren't run, and BEGIN is too early.
In the end I just wrote my own MODIFY_CODE_ATTRIBUTES sub. It's actually pretty trivial to write your own. See perldoc attributes for details.
I'd like to see that as well. I've also been playing with abuse TT with filters and HTML:Zoom and I'm very curious.
John