Photo Galleries with Mason and Imager
by Casey West
|
Pages: 1, 2, 3, 4
Jumping to the <%init> block (remember the order of execution?), we filter the directory listing to exclude any entries that are not directories themselves. If that produces an empty list, there's no need to continue processing this subcomponent, so just return. Next, each of the entries are reformatted by passing them to the SELF:.sub_gal_view method. This is where it gets fun.
When a subcomponent is called, it's really just syntactic sugar to call $m->comp().
<& SELF:.sub_gal_view, dir => $_ &>
The previous statement is exactly equivalent to the following:
% $m->comp( 'SELF:.sub_gal_view', dir => $_ );
Mason also defines the scomp() method, which compiles a subcomponent but returns its output as a string, just like Perl's sprintf.
After reformatting the entries, we group the flat list into a List-of-Lists containing just one column. That list is used as the value of the -data parameter to HTML::Table-new()>, which returns a table object.
Now it's time to process the template portion. First a heading is created. It's only plural if we have more than one sub-gallery. After the heading the sub-gallery table is displayed. Because an HTML::Table object overloads stringify, there's no need to call a method on it to get the HTML output.
Let's quickly look at the .sub_gal_view subcomponent used to reformat each directory listing.
<%method .sub_gal_view>
<%args>
$dir
</%args>
<a href="/gallery/<% $rel_dir %>"><% $label %></a>
<%init>
my $rel_dir = abs2rel $dir, $GALLERY_ROOT;
my $label = (splitpath $rel_dir)[-1];
</%init>
</%method>
This subcomponent is extremely straightforward. It accepts a directory. Inside <%init>, $rel_dir is set to the relative directory path in relation to the $GALLERY_ROOT, which will give us a proper URL for the link. Finding the label for the link is simple, it is the real directory name, which is the last element of the list returned by splitpath(), from File::Spec::Functions.
This subcomponent finally generates the proper link for navigating to sub-galleries.
The next subcomponent called by our top-level component is .photo_list, which generates the thumbnail view of our images.
<%method .photo_list>
<%args>
@dir_list
$wrap => 5
$rows => 7
$page => 1
</%args>
<h3><% @dir_list == 1 ? "Photo" : "Photos" %>
<& SELF:.photo_pager, page => $page, pages => $pages &></h3>
<% $table %>
<%init>
@dir_list = grep { -f $_ } @dir_list;
return unless @dir_list;
$_ = $m->scomp('SELF:.thumb_view',file => $_, page => $page)
for @dir_list;
my @files = group \@dir_list, cols => $wrap;
my $pages = int( @files / $rows );
$pages += 1 if $pages < ( @files / $rows );
@files = splice @files, $rows * ($page - 1), $rows;
my $table = HTML::Table->new(-data => \@files);
</%init>
</%method>
Just like .sub_gal_list, the only required argument to this component is the directory listing. The other optional arguments correspond to how many images should be in each row ($wrap), how many rows to show on a page ($rows), and what page we're currently on ($page).
Once again we jump to the <%init> block where the directory listing is filtered to only include files. If there are no files, there's no reason to go any further, so just return from this subcomponent. Just as we did with sub-gallery listings, we reformat the remaining list of files by calling a subcomponent and storing its output. Next, we group the list of files into a List-of-Lists (LoL), each row containing $wrap entries.
Photo galleries may contain any number of photos, so it's essential to support paging for thumbnails. First we need to determine how many pages this gallery will have in total. To do that we divide the total number of rows by the number of rows we want on each page. That could return a fractional number that will be cut off to the nearest decimal by int. If that's the case then we want to increment the number of pages by one (1). Next we can extract the rows for our current page from all the rows currently in @files using a splice.
Finally, a new HTML::Table object is created, and populated with @files.
In the template portion a header is output, again only using the plural if we have more than one photo. Our header also contains paging information, provided by the .photo_pager subcomponent. Lastly, the HTML table full of thumbnails is displayed.
Speaking of thumbnails, it's time to look at the code in the .thumb_view subcomponent.
<%method .thumb_view>
<%args>
$file
$page
</%args>
<a href="/gallery/<% $rel_img %>.html?page=<% $page %>">
<img src="/gallery/images/<% $rel_img %>?xsize=50;ysize=40" border="0" />
</a>
<%init>
my $rel_img = abs2rel $file, $GALLERY_ROOT;
</%init>
</%method>
This component takes two arguments. $file is the image to be turned into a thumbnail, and $page is the current page of this gallery. The <%init> block just finds the relative path of this image from the $GALLERY_ROOT. In the template the thumbnail is linked to the HTML file that this image would be displayed on, and includes the current page information as a means of saving that state.
The source of the image points to a file under /gallery/images, and includes query parameters for maximum width (xsize) and height (ysize). This is interesting because the pictures don't live there at all. If you recall, the only thing inside the images directory was a dhandler. More on that later.
The other subcomponent that .photo_list called was .photo_pager.
<%method .photo_pager>
<%args>
$page
$pages
</%args>
(
% for ( 1 .. $pages ) {
% if ( $_ == $page ) {
<strong><% $page %></strong>
% } else {
<a href="?page=<% $_ %>"><% $_ %></a>
% }
<% $_ != $pages ? "·" : "" %>
% }
)
<%init>
return if $pages == 1;
</%init>
</%method>
This subcomponent takes two arguments, the current page and the total number of pages. Before anything is output, the <%init> block checks to make sure we have more than one page. If not, no sense in going on. Looping through all the page numbers, we link all the numbers except our current page. After every number except the last one, we output a stylish separator. This subcomponent is very simple, but big enough that it's worth abstracting from the .photo_list subcomponent.
The final subcomponent in the top-level dhandler is .photo_view.
<%method .photo_view>
<%args>
$file
</%args>
<h3>Photo</h3>
<img src="/gallery/images/<% $rel_image %>?xsize=400x;ysize=300" />
<%init>
my $rel_image = abs2rel $file, $GALLERY_ROOT;
</%init>
</%method>
This component does things that we've already seen done in .thumb_view, so there's no need to expound upon it here.

