Sign In/My Account | View Cart  
advertisement


Listen Print

Photo Galleries with Mason and Imager
by Casey West | Pages: 1, 2, 3, 4

Displaying Gallery Pages

Moving on to the meat of our application, the top-level dhandler. This file has the bulk of our code, roughly 150 lines. The code is neatly organized into subcomponents, so we'll start by discussing the high-level code. And from that we'll work in order of execution.

Each page in our photo gallery has just one optional argument, a page number. By default we always start on page one (1).

  <%args>
    $page => 1
  </%args>

Next, the <%shared> block is executed. It does a lot, so we'll look at it in great detail. We're using a <%shared> block instead of an <%init> block because some of the variables defined here need to be used within multiple subcomponents. As the name suggests, <%shared> blocks allow just that.

  <%shared>
    use List::Group qw[group];
    use HTML::Table;
    use File::Spec::Functions qw[:ALL];

The first step is to load the Perl modules this component will be using. List::Group turns a flat list into a List-of-Lists (LoL) based on specific grouping options, HTML::Table turns such an LoL into an HTML table structure, and File::Spec::Functions provides a number of portable file and directory operations.

    my $GALLERY_ROOT = $r->document_root . "/gallery/pictures";

Next, we define the first shared variable. $GALLERY_ROOT is the absolute path to the location of the gallery pictures on the file system.

    (my $path_from_uri = $m->dhandler_arg) =~ s!(?:(?:/index)?\.html|/)$!!;

It's time to determine the relative path to the resource being requested. Because we're inside a dhandler, Mason provides the dhandler_arg() method, which is similar in purpose to Apache's uri() method. It returns the portion of a URI that is relative to the directory containing the dhandler. If we request /gallery/Family/IMG_0001.JPG.html, $m->dhandler_arg() will return /Family/IMG_0001.JPG.html.

Because we're looking for the path to an actual photo or gallery directory, there is some information to be removed from the end of our relative path. So our regex removes useless information such as index files, HTML extensions, and extra backslashes.

    my $file = catdir $GALLERY_ROOT, $path_from_uri;
    $m->clear_buffer and $m->abort(404) unless -e $file;

From these two variables we can construct the absolute path to the file we're interested in using catdir(), from File::Spec::Functions. If this file doesn't exist, we don't want to go any further, so Mason's output buffer is cleared and the request is aborted immediately with a 404 HTTP status code, meaning Not Found.

If a gallery is being requested, not a specific photo, we must get the contents of that gallery. If a photo is being requested, we must get the contents of the gallery that photo belongs to.

    my $dir = -d $file ? $file : (splitpath $file)[1];
    opendir DIR, $dir or die $!;
    my $dir_list = [ map "$dir/$_", grep { ! /^\./ } readdir DIR ];
    closedir DIR;

Using a file test operator, we can determine if the current request is for a file or a directory. If a directory we simply assign $file to $dir. If a file, we use splitpath() from File::Spec::Functions. splitpath() returns three elements, the volume, directory tree, and filename. We're after the directory tree, or second element.

The $dir_list array reference is populated with a list of absolute paths to each file in $dir, excluding files that begin with a dot (.).

Now it's time to move on to building the breadcrumbs for navigation. This method of navigating "up" the photo gallery is important because we can have infinite levels of sub-galleries.

    my @bread_crumb = ('Gallery', splitdir $path_from_uri);

First we define our plain-text list of crumbs in @bread_crumb. The first element is the name of our photo gallery, which I imaginatively named Gallery. The rest of our breadcrumbs come from $path_from_uri by calling splitpath() to get the list of elements.

Our @bread_crumb list is great for the title of the page, but it doesn't contain any links for use inside the page for navigation. A new list of breadcrumbs will be created with correct linking.

    my @bread_crumb_href;
    push @bread_crumb_href, sprintf '<a href="/gallery/%s">%s</a>',
      join('/',@bread_crumb[1..$_]), $bread_crumb[$_]
        for 0 .. $#bread_crumb - 1;
    push @bread_crumb_href, $bread_crumb[-1];

For each breadcrumb except the very last, we create an HTML link. The reference location for each link, from left to right, needs to cumulatively add directories from the links before it. That's what join('/',@bread_crumb[1..$_]) does. Finally we tack on the last element of the breadcrumb, unlinked, because it is the currently requested resource.

To illustrate, if a request is made to /gallery/Backgrounds/Nature%20Backgrounds/ICmiddleFalls1280x1024.jpg.html, the following list is in @bread_crumb_href.

  (
   '<a href="/gallery/">Gallery</a>',
   '<a href="/gallery/Backgrounds">Backgrounds</a>',
   '<a href="/gallery/Backgrounds/Nature Backgrounds">Nature Backgrounds</a>',
   'ICmiddleFalls1280x1024.jpg'
  )

Finally, we construct two scalars to hold the contents of our breadcrumbs.

    my $bread_crumb      = join ' &middot; ', @bread_crumb;
    my $bread_crumb_href = join ' &middot; ', @bread_crumb_href;
  </%shared>

At this point we can define the .title subcomponent, using the $bread_crumb shared variable.

  <%method .title><& PARENT:.title &> &middot; <% $bread_crumb %></%method>

Notice that there is a subcomponent call to PARENT:.title. This is another illustration of Mason's inheritance model. Because dhandler inherits from autohandler, the .title subcomponent in dhandler is overriding the .title method in autohandler. That is to say, dhandler is subclassing autohandler. For this reason, if we don't want to clobber the .title subcomponent declared in autohandler we must be sure to call our parent. This is very similar to invoking a SUPER:: method in Perl.

Now we can move on to the actual gallery display.

  <h1>Photo Gallery</h1>
  <h2><% $bread_crumb_href %></h2>

Using another shared variable, $bread_crumb_href, we construct our backward navigation.

  <table>
    <tr>
      <td valign="top" width="15%">
        <& SELF:.sub_gal_list, dir_list => $dir_list &>
      </td>
      <td valign="top" width="35%">
        <& SELF:.photo_list, dir_list => $dir_list, page => $page &>
      </td>
      <td valign="top" width="50%">
  % if ( -f $file ) {
        <& SELF:.photo_view, file => $file &>
  % }
      </td>
    </tr>
  </table>

We have three columns of information to display at any one time -- an HTML table is a good way to do that. (Some standards purists and XHTML masochists will disagree with me on this point. I'm interested in keeping the examples simple, not pure.) Each of the table cells calls a subcomponent with the appropriate arguments. Those subcomponents are discussed in detail later in this article. Notice that before we call SELF:.photo_view we check to see if the request is currently for a file. This can save us from calling that subcomponent if we currently don't want to look at a photo.

The first subcomponent called is SELF:.sub_gal_list. As the name suggests, it will list sub-galleries.

  <%method .sub_gal_list>
    <%args>
      @dir_list
      $wrap => 1
    </%args>

    <h3>Sub <% @dir_list == 1 ? "Gallery" : "Galleries" %></h3>
    <% $table %>
  
    <%init>
      @dir_list = grep { -d $_ } @dir_list;
      return unless @dir_list;
      $_ = $m->scomp('SELF:.sub_gal_view',dir => $_) for @dir_list;
      my $table = HTML::Table->new(-data => [ group \@dir_list, cols => $wrap ]);
    </%init>
  </%method>

.sub_gal_list accepts a directory listing argument. It also optionally accepts an argument detailing after how many entries in the list should be in each row.

Pages: 1, 2, 3, 4

Next Pagearrow