Show Contents Previous Page Next Page Chapter 4 - Content Handlers / Content Handlers as File Processors Many large web sites use a navigation bar to help users find their way around the main subdivisions of the site. Simple navigation bars are composed entirely of link text, while fancier ones use inline images to create the illusion of a series of buttons. Some sites use client-side Java, JavaScript, or frames to achieve special effects like button "rollover," in which the button image changes when the mouse passes over it. Regardless of the technology used to display the navigation bar, they can be troublesome to maintain. Every time you add a new page to the site, you have to remember to insert the correct HTML into the page to display the correct version of the navigation bar. If the structure of the site changes, you might have to manually update dozens or hundreds of HTML files. Apache content handlers to the rescue. In this section, we develop a navigation bar module called Apache::NavBar. When activated, this module automatically adds a navigation bar to the tops and bottoms of all HTML pages on the site. Each major content area of the site is displayed as a hypertext link. When an area is "active" (the user is viewing one of the pages contained within it), its link is replaced with highlighted text (see Figure 4-3). Figure 4-3. The navigation bar at the top of this page was generated dynamically by Apache::NavBar. In this design, the navigation bar is built dynamically from a configuration file. Here's the one that Lincoln uses at his laboratory's site at http://stein.cshl.org: # Configuration file for the navigation bar /index.html Home /jade/ Jade /AcePerl/ AcePerl /software/boulder/ BoulderIO /software/WWW/ WWW /linux/ Linux
The right column of this configuration file defines six areas named "Home," "Jade," "AcePerl," "BoulderIO," "WWW," and "Linux" (the odd names correspond to various software packages). The left column defines the URI that each link corresponds to. For example, selecting the "Home" link takes the user to /index.html. These URIs are also used by the navigation bar generation code to decide when to display an area as active. In the example above, any page that starts with /linux/ is considered to be part of the "Linux" area and its label will be appropriately highlighted. In contrast, since Example 4-6 gives the complete code for Apache::NavBar. At the end of the example is a sample entry for perl.conf (or httpd.conf if you prefer) which activates the navigation bar for the entire site. package Apache::NavBar; use strict; use Apache::Constants qw(:common); use Apache::File (); my %BARS = (); my $TABLEATTS = 'WIDTH="100%" BORDER=1'; my $TABLECOLOR = '#C8FFFF'; my $ACTIVECOLOR = '#FF0000'; The preamble brings in the usual modules and defines some constants that will be used later in the code. Among the constants are ones that control the color and size of the navigation bar. sub handler { my $r = shift; my $bar = read_configuration($r) || return DECLINED;
The handler() function starts by calling an internal function named read_configuration(), which, as its name implies, parses the navigation bar configuration file. If successful, the function returns a custom-designed
If, for some reason, read_configuration() returns an undefined value, we decline the transaction by returning $r->content_type eq 'text/html' || return DECLINED;
As in the server-side include example, we check the MIME type of the requested file. If it isn't of type text/html, then we can't add a navigation bar to it and we return my $navbar = make_bar($r, $bar);
Having successfully processed the configuration file and opened the requested file, we call an internal subroutine named make_bar() to create the HTML text for the navigation bar. We'll look at this subroutine momentarily. This fragment of HTML is stored in a variable named $r->send_http_header; return OK if $r->header_only; local $/ = ""; while (<$fh>) { s:(</BODY>):$navbar$1:i; s:(<BODY.*?>):$1$navbar:si; } continue { $r->print($_); return OK; }
The remaining code should look familiar. We send the HTTP header and loop through the text in paragraph-style chunks looking for all instances of the <BODY> and </BODY> tags. When we find either tag we insert the navigation bar just below or above it. We use paragraph mode (by setting sub make_bar { my($r, $bar) = @_; # create the navigation bar my $current_url = $r->uri; my @cells; The make_bar() function is responsible for generating the navigation bar HTML code. First, it recovers the current document's URI by calling the Apache request object's uri() method. Next, it calls $bar->urls() to fetch the list of partial URIs for the site's major areas and iterates over the areas in a for() loop: for my $url ($bar->urls) { my $label = $bar->label($url); my $is_current = $current_url =~ /^$url/; my $cell = $is_current ? qq(<FONT COLOR="$ACTIVECOLOR">$label</FONT>) : qq(<A HREF="$url">$label</A>); push @cells, qq(<TD CLASS="navbar" ALIGN=CENTER BGCOLOR="$TABLECOLOR">$cell</TD>\n); For each URI, the code fetches its human-readable label by calling $bar->label() and determines whether the current document is part of the area using a pattern match. What happens next depends on whether the current document is part of the area or not. In the former case, the code generates a label enclosed within a <FONT> tag with the COLOR attribute set to red. In the latter case, the code generates a hypertext link. The label or link is then pushed onto a growing array of HTML table cells. return qq(<TABLE $TABLEATTS><TR>@cells</TR></TABLE>\n); } At the end of the loop, the code incorporates the table cells into a one-row table and returns the HTML to the caller. We next look at the read_configuration() function: sub read_configuration { my $r = shift; my $conf_file; return unless $conf_file = $r->dir_config('NavConf'); return unless -e ($conf_file = $r->server_root_relative($conf_file)); Potentially there can be several configuration files, each one for a different part of the site. The path to the configuration file is specified by a per-directory Perl configuration variable named NavConf. We retrieve the path to the configuration file with dir_config(), convert it into an absolute path name with server_root_relative(), and test that the file exists with the -e operator. my $mod_time = (stat _)[9]; return $BARS{$conf_file} if $BARS{$conf_file}
Because we don't want to reparse the configuration each time we need it, we cache the NavBar object in much the same way we did with the server-side include example. Each NavBar object has a modified() method that returns the time that its configuration file was modified. The NavBar objects are held in a global cache named You'll notice that we use a different technique for finding the modification
date here than we did in Toward the bottom of the example is the definition for the NavBar class. It defines three methods named new(), urls(), and label(): package NavBar; # create a new NavBar object sub new { my ($class,$conf_file) = @_; my (@c,%c); my $fh = Apache::File->new($conf_file) || return; while (<$fh>) { chomp; s/^\s+//; s/\s+$//; # fold leading and trailing whitespace next if /^#/ || /^$/; # skip comments and empty lines next unless my($url, $label) = /^(\S+)\s+(.+)/; push @c, $url; # keep the url in an ordered array $c{$url} = $label; # keep its label in a hash } return bless {'urls' => \@c, 'labels' => \%c, 'modified' => (stat $conf_file)[9]}, $class; } The new() method is called to parse a configuration file and return a new NavBar object. It opens up the indicated configuration file, splits each row into the URI and label parts, and stores the two parts into a hash. Since the order in which the various areas appear in the navigation bar is significant, this method also saves the URIs to an ordered array. # return ordered list of all the URIs in the navigation bar sub urls { return @{shift->{'urls'}}; } # return the label for a particular URI in the navigation bar sub label { return $_[0]->{'labels'}->{$_[1]} || $_[1]; } # return the modification date of the configuration file sub modified { return $_[0]->{'modified'}; } 1; The urls() method returns the ordered list of areas, and the label() method uses the NavBar object's hash to return the human-readable label for the given URI. If none is defined, it just returns the URL. modified() returns the modification time of the configuration file. Example 4-6. A Dynamic Navigation Bar package Apache::NavBar; # file Apache/NavBar.pm use strict; use Apache::Constants qw(:common); use Apache::File (); my %BARS = (); my $TABLEATTS = 'WIDTH="100%" BORDER=1'; my $TABLECOLOR = '#C8FFFF'; my $ACTIVECOLOR = '#FF0000'; sub handler { my $r = shift; my $bar = read_configuration($r) || return DECLINED; $r->content_type eq 'text/html' || return DECLINED; my $fh = Apache::File->new($r->filename) || return DECLINED; my $navbar = make_bar($r, $bar); $r->send_http_header; return OK if $r->header_only; local $/ = ""; while (<$fh>) { s:(</BODY>):$navbar$1:oi; s:(<BODY.*?>):$1$navbar:osi; } continue { $r->print($_); } return OK; } sub make_bar { my($r, $bar) = @_; # create the navigation bar my $current_url = $r->uri; my @cells; for my $url ($bar->urls) { my $label = $bar->label($url); my $is_current = $current_url =~ /^$url/; my $cell = $is_current ? qq(<FONT COLOR="$ACTIVECOLOR">$label</FONT>) : qq(<A HREF="$url">$label</A>); push @cells, qq(<TD CLASS="navbar" ALIGN=CENTER BGCOLOR="$TABLECOLOR">$cell</TD>\n); # read the navigation bar configuration file and return it as a hash. sub read_configuration { my $r = shift; my $conf_file; return unless $conf_file = $r->dir_config('NavConf'); return unless -e ($conf_file = $r->server_root_relative($conf_file)); my $mod_time = (stat _)[9]; return $BARS{$conf_file} if $BARS{$conf_file} && $BARS{$conf_file}->modified >= $mod_time; return $BARS{$conf_file} = NavBar->new($conf_file); } package NavBar; # create a new NavBar object sub new { my ($class,$conf_file) = @_; my (@c,%c); my $fh = Apache::File->new($conf_file) || return; while (<$fh>) { chomp; s/^\s+//; s/\s+$//; # fold leading and trailing whitespace next if /^#/ || /^$/; # skip comments and empty lines next unless my($url, $label) = /^(\S+)\s+(.+)/; push @c, $url; # keep the url in an ordered array $c{$url} = $label; # keep its label in a hash } return bless {'urls' => \@c, 'labels' => \%c, 'modified' => (stat $conf_file)[9]}, $class; } # return ordered list of all the URIs in the navigation bar sub urls { return @{shift->{'urls'}}; } # return the label for a particular URI in the navigation bar sub label { return $_[0]->{'labels'}->{$_[1]} || $_[1]; } # return the modification date of the configuration file sub modified { return $_[0]->{'modified'}; } 1; __END__ A configuration file section to go with Apache::NavBar might read: <Location /> SetHandler perl-script PerlHandler Apache::NavBar PerlSetVar NavConf conf/navigation.conf </Location> Because so much of what Apache::NavBar and Apache:ESSI do is similar, you might want to merge the navigation bar and server-side include examples. This is just a matter of cutting and pasting the navigation bar code into the server-side function definitions file and then writing a small stub function named NAVBAR(). This stub function will call the subroutines that read the configuration file and generate the navigation bar table. You can then incorporate the appropriate navigation bar into your pages anywhere you like with an include like this one: <!--#NAVBAR-->Show Contents Previous Page Next Page Copyright © 1999 by O'Reilly & Associates, Inc. |
HIVE: All information for read only. Please respect copyright! |