Creating Bulletin Boards
Creating a simple CGI message board
The HTML Templates
The Bulletin Board Script
Displaying the Message List
Displaying Messages
Adding New Messages
Adding Replies
Expiring Messages
The Complete Bulletin Board Script


[Previous] [Next]


Displaying the Message List


The Display_Message_Lists subroutine needs to create a single list of messages and replies from all the list files in the message-lists directory. To create this single list from the multiple files, you loop over all the files and read in the contents from each file. Then you take the header information you get from each line in the list files and format it with HTML tags. Because the message list only contains header information from each of the messages, the user needs a link to click on to display the entire message or reply. Also, for easy viewing, you should indent the header lines for all replies underneath the main message. You can do this by using the <UL> and <LI> HTML tags. The <UL> tag creates an unordered or bulleted list. Each list item is preceded by an <LI> tag. Finally, remember that most of the HTML code for the message list Web page is stored in the message-list.tmpl file. So, you also need to open this file, read in the contents of the template, add the message headers to the template, and return the modified contents of the template to the user's Web browser.

Start by opening the count.dat file and getting the file name of the last list file that was created. These three lines of Perl

 open(LISTS,"$list_count") || die "Content-type: text/html\n\nCannot open list
 count!";
 $num_lists = <LISTS>;
 close(LISTS);


open the count.dat file and read in the contents--the number used for the last file name. The first line opens the count.dat file for input. The path and file name of the count.dat file are stored in the $list_count variable, which is set at the beginning of the bulletin board script (shown in Listing 8 later in this article). Then a die statement terminates the program and outputs the contents of the string. The || operator between the open and die statements is the logical or operator. When you place this operator between the two statements, the Perl interpreter first tries to execute the open statement. If the open is successful, the Perl interpreter moves on to the next line of code. However, if the file cannot be opened, the Perl Interpreter executes the die statement. This is a common way to verify that a file is successfully opened and to terminate the Perl program if it is not.

The second line of Perl code reads in the contents of the first line in the count.dat file from the input stream <LISTS> and places it in the $num_lists variable. After the contents of the count.dat file have been read into the $num_lists variable, you can close the input stream <LISTS> by using the close command, as in the third line of code.

Now that the name of the last list file that was created is stored in the $num_lists variable, you can loop over all the list files. The loop is controlled by the for expression, as demonstrated here:

 for ($i=1; $i<=$num_lists; $i++) {


This statement is composed of three parts: the initialization of the loop variable, $i=1, the loop conditional, $i<=$num_lists, and the incrementation of the loop variable, $i++. For loops execute until the conditional statement is no longer true. In this example, the loop variable $i starts at one and is incremented by one each time the loop executes until it is greater than the value stored in the $num_lists variable. So, the body of this loop is executed once for every list file.

Inside the body of the for loop, you need to do many things. First, you need to open the current list file and read in all the contents of that file. You do this with an open statement similar to the open statement you saw a moment ago:

 open(LIST, "$list_dir/$i") || next;
 @list_contents = <LIST>;
 close(LIST);


The first line opens the file whose path is $list_dir/$i. The $list_dir variable is set at the beginning of your bulletin board script and contains the path to the message-lists subdirectory on your machine. (Listing 9.8 contains the code showing where this variable is set.) The $i variable is the loop variable that contains the name of the current list file. Notice that the die statement has been replaced with the next statement on the right side of the || operator. The next operator, if executed, advances the loop to the next iteration. For a for loop, this means the loop variable is incremented, the conditional is checked, and the loop body starts over. You use the next statement instead of the die operator so that the loop continues even if the list file cannot be opened. If the list file cannot be opened, you assume that it has been deleted by the Expires subroutine, which you will develop in the section "Expiring Messages" later in this article. Also notice that the second line uses an array, @list_contents on the left side of the assignment to the <LIST> stream. This reads all the lines from the list file into the @list_contents array, one line per element in the array.

If the body of the for loop continues to execute, the list file must have been successfully opened and the contents of the list file are in the @list_contents array. If the list file exists, there must be header information for the main message on the first line of the list file, which is now in the first element of the @list_contents array. So, you can split apart the header information for the main message, format the individual fields with HTML tags, and store them in a variable to be added to the HTML template. Remember that all header lines are in the format

 name::message subject::date message was posted::file name of message file


So, you can use the Perl split statement to split each element into a separate variable. The following lines split the header information stored in the first element of the @list_contents array and place each field in a variable.
 ($name, $subject, $message_date, $message_file) =  split(/::/, $list_contents[Ø]);
 chop $message_file;


The first line does the actual split by separating the header line at all double colons. Because the $message_file variable receives all the remaining contents of the header information following the last double colon, it also contains a new line character. The second line uses the Perl chop operator to remove this new line character from the end of the $message_file variable.

With the header information split into the appropriate variables, you can now add HTML codes to properly format the information within the user's Web browser.

The following lines of Perl add HTML codes to the values stored in the header information variables and store the corresponding values in the $header variable:

 unless (-e "$message_dir/$message_file") {
   $header = "<LI><B>$subject</B> $name -  $message_date";
 } else {
   $header = "<LI><A HREF=\"/cgi-bin/board.pl?message=$message_file&list=$i\">";
   $header .= "<B>$subject</B></A> $name -  $message_date";
 }


These lines contain an unless...else conditional, which is similar to an if...else conditional except that the statements under the unless section are executed when the conditional expression is false. In an if...else conditional, in contrast, the statements under the if section are executed when the conditional expression is true. In the first line of code, the conditional expression is contained within the parentheses. It contains the Perl operator -e, which is a file check operator. This conditional expression evaluates to true if the file $message_dir/ $message_file exists, and false if it does not.

Remember that the $message_file variable was just set in the split statement. So, it contains the file name of the message file for the main message of the list file. If you plan to enable the Expires subroutine for your bulletin board script, the list file may contain the header information for a main message even after the corresponding message file has been deleted. This situation occurs only when there are replies to the main message that have not yet expired. When there are existing replies, you still want to display them indented under the header for the main message. However, you do not want to create a link to the main message's message file because it has already been deleted. That is what this unless...else conditional does: It checks whether the message file has already been deleted. If so, it formats the header without using the <A> tag (the single expression right after the unless line). If the file does exist, the else portion of the conditional is executed, and the message's subject is made a link to the message file (see in the two lines under the preceding else expression).

The preceding lines of code properly format the header line for the main message. Now you need to properly format the header lines for any existing replies. First, you can easily determine whether there are any replies by checking the length of the @list_contents array. Remember that the header information for the main message is always in the first element of the @list_contents array. Therefore, if the array has more than one element there are replies to the main message. So, you use an if statement to execute a block of code only when the @list_contents array is greater than one. Here is the Perl code for this if block:

 if (@list_contents > 1) {
   $replies = "<UL>\n";
   for ($j=1; $j<@list_contents; $j++) {

     # Split each message header from the message list file.
     ($name, $subject, $message_date, $message_file) =
              split(/::/, $list_contents[$j]);
     chop $message_file;

     # Format the replies for display in the user's Web browser.
     $replies .= "<LI><A HREF=\"/cgi-
 bin/board.pl?message=$message_file&list=$i\">";
     $replies .= "$subject</A> $name - $message_date";
   }

   # Append the replies after the main message.
   $header .= "\n$replies</UL>\n";
 }


The first line contains the if statement and the conditional expression. Once inside the if block, you begin to format the replies by placing the HTML tag <UL> in the $replies variable. Following this line, another for loop loops over the remaining elements in the @list_contents array. Because Perl arrays are indexed beginning at 0, this loop begins at the second element of the @list_contents array at index 1. Next you use the split statement again to break apart the header information taken from the list file. Notice that this time you use the index $j in the $list_contents[$j] expression. Because $j is the loop variable, it also has the value of the current array element, so you can use it for the index of the array. At the end of the body of the loop, the reply's header is formatted with HTML tags and appended to the $replies variable. In this case, you do not need to check whether the reply's message file exists: If it did not, the reply's header information would not be in the list file. This will become clear when you develop the code for the Expires subroutine. Finally, after the loop has finished executing, all the replies are in the $replies variable, which is then appended to the $header variable along with the ending </UL> bulleted list tag.

Keep in mind that all the code segments up to the line with the for statement containing the $i loop variable are going to be in the body of that for loop. So, all these preceding lines are executed for every list file. You just need one more line to finish the body of this for loop:

 push(@lists, $header);


This line uses the Perl push statement to append the contents of the $header variable to the @lists array. This way, when the for loop is finished, all of the properly formatted messages and replies are in the @lists array, ready for insertion into the HTML template.

When the for loop has completed, you need to add two more HTML tags to the contents of the @lists array to finish formatting it. These are beginning and ending bulleted list tags, <UL> and </UL>. Remember that you already entered these tags for the replies. By adding them around all the messages, you create a bulleted list of main messages with bulleted lists of replies (if there are any) indented under each main message. Refer back to Figure 1 for an example of these bulleted lists. Because your bulletin board may contain no messages--either when no one has posted any message yet or when all the messages have expired--the @lists array could be empty. Therefore, you should check whether it contains any messages before adding the <UL> and </UL> tags. If it is empty, place a message in the @lists array that is displayed in the Web page informing the user that there are currently no messages in your bulletin board. The following if...else statement checks the length of the @lists array. If it is not zero (empty), the <UL> and </UL> tags are added. Otherwise, the no messages statement is placed in the first element of the array.

 # If there are any messages in the @lists array, finish formatting with HTML
 if (@lists) {
   unshift(@lists, "<UL>\n");
   push(@lists, "</UL>\n");
 } else {

   # No messages exist.
   $lists[Ø] = "<H2>Currently No Messages</H2>";
 }


Now your Display_Message_List subroutine just needs to read in the HTML template from the template file, insert the contents of the @lists array into the template, and return the modified contents of the template to the user's Web browser. All this is done in the following lines of Perl:
 open(TEMPLATE,"$list_template") || die "Content-type: text/html\n\nCannot open template!";
 @template = <TEMPLATE>;
 close(TEMPLATE);

 # Put the message headers in the template, and send to the user's Web browser.
 splice(@template, 8, Ø, @lists);

 print "Content-type: text/html\n\n";
 print @template;


You should recognize the open statement from earlier. Here it opens the message list template whose file and path name are stored in the $list_template variable. The contents of the file are then stored in the @template array. The next line uses the Perl splice statement to insert the contents of the @lists array into the @template array before index 8 in the @template array. Remember that index 8 corresponds to the ninth element in the array. Listing 1 shows that the ninth element in the @template array is the second of the two <HR> tags. This splice statement inserts the contents of the @lists array between the two <HR> tags. Finally, the last two lines of code output the required parsed header and the modified contents of the @template array to the user's Web browser.

Listing 3 contains the complete code for the Display_Message_Lists subroutine. Besides the sub Display_Message_Lists line, the only lines that have been added are the declarations of local variables at the beginning. The local statement is used to declare the arrays and variables as local to the Display_ Message_Lists subroutine. Remember, a local variable exists only within a portion of your Perl code, usually within a subroutine. If a variable with the same name existed outside the subroutine, Perl would consider it a different variable than the one that is declared local within the subroutine. Declaring your subroutine's variables as local helps to keep your subroutines from overwriting values of global variables. A global variable is one that is accessible throughout the entire Perl program, including any subroutines in the same Perl file. In Listing 3, the variables $list_count, $list_dir, $message_dir, and $list_template are global variables.

Listing 3: The Display_Message_Lists Subroutine
sub Display_Message_Lists {
   local (@template, @lists, @list_contents, $num_lists,
          $i, $j, $name, $subject, $message_date, $message_file,
          $header, $replies);

   open(LISTS,"$list_count") || die "Content-type: text/html\n\nCannot open list
 count!";
   $num_lists = <LISTS>;
   close(LISTS);

   # Loop over all the message list files and add the contents to the message list
   # which is displayed to the user.
   for ($i=1; $i<=$num_lists; $i++) {

     # Open the message list file. If it cannot be
     # opened, assume it has been deleted and go
     # to the next message list.
     # Windows users need to change the string "$list_dir/$i" to
     # "$list_dir\\$i"
     open(LIST, "$list_dir/$i") || next;
     @list_contents = <LIST>;
     close(LIST);

     # Split the message header from the message list file.
     ($name, $subject, $message_date, $message_file) =
              split(/::/, $list_contents[Ø]);
     chop $message_file;

     # Format the header depending on whether the message file
     # exists
     # Windows users need to change the string "$message_dir/$message_file"
     # to "$message_dir\\$message_file"
     unless (-e "$message_dir/$message_file") {
       $header = "<LI><B>$subject</B> $name -  $message_date";
     } else {
       $header = "<LI><A HREF=\"/cgi-
 bin/board.pl?message=$message_file&list=$i\">";
       $header .= "<B>$subject</B></A> $name -  $message_date";
     }

     # If the header file has more than one line, it contains header lines for
 replies.
     if (@list_contents > 1) {
       $replies = "<UL>\n";
       for ($j=1; $j<@list_contents; $j++) {

         # Split each message header from the message list file.
         ($name, $subject, $message_date, $message_file) =
                  split(/::/, $list_contents[$j]);
         chop $message_file;

         # Format the replies for display in the user's Web browser.
         $replies .= "<LI><A HREF=\"/cgi-
 bin/board.pl?message=$message_file&list=$i\">";
         $replies .= "$subject</A> $name - $message_date";
       }

       # Append the replies after the main message.
       $header .= "\n$replies</UL>\n";
     }

     # Put the header and replies (if any) in the @lists array.
     push(@lists, $header);

   }

   # If there are any messages in the @lists array, finish formatting with HTML
   if (@lists) {
     unshift(@lists, "<UL>\n");
     push(@lists, "</UL>\n");
   } else {

     # No messages exist.
     $lists[Ø] = "<H2>Currently No Messages</H2>";
   }

   open(TEMPLATE,"$list_template") || die "Content-type: text/html\n\nCannot open
 template!";
   @template = <TEMPLATE>;
   close(TEMPLATE);

   # Put the message headers in the template, and send to the user's Web browser.
   splice(@template, 8, Ø, @lists);

   print "Content-type: text/html\n\n";
   print @template;

 }






[Previous] [Next]