HTML::Seamstress::Quickstart - A gentle introduction to HTML::Seamstress
This guide is designed to get you started with dynamically generating and
modifying ("templating") HTML with HTML::Seamstress.
We will work through several examples, with each one increasing your ability to
work with Seamstress effectively.
All the files for the samples are in the directory
lib/HTML/Seamstress/Quickstart
Welcome to the first example. This is our bare-bones example. Let's say we want
to dynamically modify the following HTML:
<html>
<head>
<title>Greetings</title>
</head>
<body>
<h1>Greetings</h1>
Hello there <span id=name>person</span>, your lucky number is
<span id=lucky_number>666</span>
</body>
</html>
Let's not use Seamstress at all in this case. Remember Seamstress just makes
using HTML::Tree more convenient when writing software - it is completely
optional and totally non-magical. So here's the (admittedly verbose) pure
TreeBuilder solution:
use strict;
use warnings;
use HTML::TreeBuilder;
my $name = 'Redd Foxx';
my $number = 887;
my $tree = HTML::TreeBuilder->new_from_file('html/greeting.html');
my $name_elem = $tree->look_down(id => 'name');
$name_elem->delete_content;
$name_elem->push_content($name);
my $number_elem = $tree->look_down(id => 'lucky_number');
$number_elem->delete_content;
$number_elem->push_content($number);
print $tree->as_HTML(undef, ' ');
There's a convenience function in HTML::Element::Library which makes it easy to
replace all the content of an element. This will make our script shorter. If
we simply use Seamstress, its "new_from_file()" method will bless
the HTML tree into a class which inherits from HTML::Element::Library, making
it easy for us to shorten our program. So let's rework the example using
bare-bones Seamstress.
Since we used HTML::Seamstress instead of HTML::TreeBuilder, our $tree was
blessed as an instance of "HTML::Seamstress". Since
"HTML::Seamstress" inherits from "HTML::TreeBuilder" and
"HTML::Element::Library", we have a $tree which can use the methods
of both.
We will take advantage of the "replace_content" method in
HTML::Element::Library to shorten our program:
use strict;
use warnings;
use HTML::Seamstress;
my $name = 'Redd Foxx';
my $number = 887;
my $tree = HTML::Seamstress->new_from_file('html/greeting.html');
my $elem = $tree->look_down(id => 'name');
$elem->replace_content($name);
$elem = $tree->look_down(id => 'lucky_number');
$elem->replace_content($number);
print $tree->as_HTML(undef, ' ');
Now of course, this program is just itching to not repeat itself, so we will
clean it up just a tad:
use strict;
use warnings;
use HTML::Seamstress;
my $name = 'Redd Foxx';
my $number = 887;
my $tree = HTML::Seamstress->new_from_file('html/greeting.html');
my %replace = (
name => $name,
lucky_number => $number
);
$tree->look_down(id => $_)->replace_content($replace{$_})
for (keys %replace) ;
print $tree->as_HTML(undef, ' ');
Ok sweet, we have a nice tight program. But is this really application-level
code? As the user of ultra-scaffolded frameworks such as Class::DBI and
Catalyst, I can say no. Our inline code must be much tighter. It must do no
more than "use", "new", and "operation()"
whatever "operation()" may be.
The key abstraction technique of the uber-modules is a package. Normally a
package collects together a set of methods. In our case, it is collecting
together an HTML file and the object-oriented operations on it.
alert,
alert: acronym, alert from this point forward, a Perl class abstracting a
file and tree operations on the file will be called a
LOOM - (L)ibrary
(O)f (O)bject-oriented (M)ethods for HTML files.
On the whole, there are two ways to build a "LOOM". The quick and
dirty way is to stick a
.pm file in the same directory as the html
file. This is fine for most purposes and is what I like to use.
However in some cases it is not desirable or possible for the HTML and Perl to
be in the same directory. This is the slow and clean approach, which does have
some additional advantages which will be discussed.
We have an "html::Greeting" module like this:
package html::Greeting;
use strict;
use warnings;
use base qw(HTML::Seamstress);
sub new {
my $tree = __PACKAGE__->new_from_file('html/greeting.html');
$tree;
}
sub process {
my $tree = shift;
my %replace = (
name => 'Jim Rays',
lucky_number => 222
);
$tree->look_down(id => $_)->replace_content($replace{$_})
for (keys %replace) ;
}
1;
which we make nice tight application-level use of like this:
use html::Greeting;
my $tree = html::Greeting->new;
$tree->process;
print $tree->as_HTML(undef, ' ');
Cleaning up our Perl class
We are flowing smoothly now with nice tight code in our application. But should
we be happy with this module? I see a few drawbacks which require improvement:
- •
- our file name is given as a relative path name
Relative paths are fine as long as we are certain to start in the same
directory, but we cannot be sure of that when building applications, so we
need an absolute path.
- •
- we had to manually create this package
Let's fix the first problem first.
Make path to HTML file absolute
Again, Seamstress just happens to have a subroutine which guesses the name of
the HTML file associated with a Seamstress-style Perl module. It is called
"html()" and here we see it in use to give us the path to our file
in absolute fashion:
package html::GreetingAbs;
use strict;
use warnings;
use base qw(HTML::Seamstress);
use Data::Dumper;
print Dumper \%INC;
our $html = __PACKAGE__->html(__FILE__ , 'html');
{
last;
# The stuff in these braces is not for the first reading of this!
# $html is
# /ernest/dev/seamstress/lib/HTML/Seamstress/Quickstart/html/GreetingAbs.html
# but the real HTML file is greeting.html not GreetingAbs.html
$html =~ s!Abs!!;
# change Greeting to greeting since file is greeting.html not Greeting.html
$html =~ s!Greeting!greeting!;
}
sub new {
my $tree = __PACKAGE__->new_from_file($html);
$tree;
}
sub process {
my $tree = shift;
my %replace = (
name => 'Jim Rays',
lucky_number => 222
);
$tree->look_down(id => $_)->replace_content($replace{$_})
for (keys %replace) ;
}
1;
and main code body is still the same:
use html::GreetingAbs;
my $tree = html::GreetingAbs->new;
$tree->process;
print $tree->as_HTML(undef, ' ');
Here we need to slip a class in between "HTML::Seamstress" and our
LOOM:
package Local::Seamstress::Base;
use base qw(HTML::Seamstress);
sub comp_root {
'/ernest/dev/seamstress/lib/HTML/Seamstress/Quickstart/html';
}
1;
This class will make it easy to track down the directory of our HTML files.
The LOOM class inherits from it and makes use of it in its constructor:
package html::Greeting;
use strict;
use warnings;
use base qw(Local::Seamstress::Base); # not HTML::Seamstress!
# we need an intermediate base class
# with the comp_root() method so that
# we can get an absolute path to the
# HTML file... remember this is an
# example where the LOOM is in a
# different directory to the the HTML
# file it operates on.
sub new {
my $comp_root = __PACKAGE__->comp_root();
my $html_file = "$comp_root/greeting.html";
warn "html_file: $html_file";
my $tree = __PACKAGE__->new_from_file($html_file);
$tree;
}
sub process {
my $tree = shift;
my %replace = (
name => 'Slow Clean Greeting Machine',
lucky_number => 737
);
$tree->look_down(id => $_)->replace_content($replace{$_})
for (keys %replace) ;
}
1;
And out main body code is just as tight. We have a few statements to make sure
to use the right version of "html::Greeting", but other than that,
no changes:
use lib 'lib';
use html::Greeting;
use Data::Dumper;
print Dumper \%INC; # shows we are using the hhtml::Greeting which
# obtains its file via HTML::Seamstress::Base::comp_root()
# instead of via __PACKAGE__->html( )
my $tree = html::Greeting->new;
$tree->process;
print $tree->as_HTML(undef, ' ');
Slow and clean has extra programming advantages
Slow and clean is not just a different way to do the same thing. By creating a
local base class, you have full control over how to connect an HTML file to
your Perl code.
Your HTML designer can have his files mounted elsewhere. And you dont
- •
- infect his directory with your ".pm" files =item * infect his
HTML with all sorts of dynamic HTML logic
And being infection-free is the way I like it.. in Perl and uhhh... other
endeavors.
Not recommended slow clean way: Inline the HTML
There is no Seamstress API or utility support for this. But it is an idea and I
just wanted to mention it for completeness. You can create a
.pm with
the HTML along the lines of this.
package html::hello_world;
sub new {...}
sub process {...}
__HTML__
<html>
<head>
...
</head>
<body>
...
</body>
</html>
And then you could use File::Slurp to read it in and submit an appropriate
"HTML::Seamstress::new_from_content()" method to do the proper
blessings.
But I don't like this approach. It makes it hard to stay synchronized with the
designer as he continually makes updates.
Instead of manually creating or copying and pasting packages to create
Seamstress-style packages, the
spkg.pl script in the Seamstress was off
use. It is designed to help build the slow-clean LOOMs not the quick-and-dirty
ones.... it really should be updated to support either usage mode.
The final phase in Seamstress best practices is to break each tree manipulation
down into a separate subroutine whose parameters are specified by
Params::Validate.
If you do this, then you can control the dynamic HTML generation by
parameterizing your subs properly. This advice will make more sense as you do
more complex things with Seamstress
Now you're ready for the big time! Have fun!
If your dummy HTML has a section which will be used as a sample and cloned
repeatedly (either by you manually or by an HTML::Element::Library method),
then locate it by a "class" tag and do not assign it an
"id" tag. This is because HTML becomes invalid when an id tag
appears more than once in the same document.
For example here is some Template code:
[% FOR column = item.columns('view') %]
<b class="title">[% column %]</b><br/>
[% item.$column %]<br/><br/>
[% END %]
Here is how I converted it to a Seamstress document:
<span class="columns_view">
<b class="title">column_name</b><br/>
<span class="item_column">column_value</span><br/><br/>
</span>
Copyright 2002-2006 by Terrence Brannon.
This library is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.