11.8 Mason Project


In the Embperl chapter, we did a project that displayed a list of products and filtered that list based on the manufacturer's city, name , or product type. We will now develop a Mason back end that enables the addition and modification of manufacturers and their products.

This back end will consist of three pages: one to allow the user to select an action (add a manufacturer, modify a manufacturer, add a product, or modify a product), one to allow the user to enter the data for either a manufacturer or a product, and one that thanks the user for the addition or modification and allows them to select another action.

Before we show each of the three pages, we need to discuss the components involved in creating our now familiar look and feel.

The first component generates the top HTML for our pages and is named, believe it or not, _top . Its contents can be found in the file /var/www/html/mason/productbackend/_top online at either http://localhost/mason/productbackend/top or www.opensourcewebbook.com/mason/productbackend/top. The filename in those URLs is top , not _top . We copied _top into top so that the webserver would serve it up, allowing you to view its contents as examples. Remember, Apache is configured to not serve up files in this part of the document tree whose names begin with the underscore .

Looking first at the bottom of the file (where all the Mason goodies are), you will see:

 <%args>      $title      =>       $breadcrumb =>   </%args> 

These are the title in the title bar and the bread crumb trail. These components, when called from elsewhere, might have these two variables defined. So these should be declared in the <%args> tags, with simple defaults, as in the preceding code. You should then see this:

 <%init>      # get the bread crumb string and split it on the colon      my @crumbs = split /:/, $breadcrumb;      # build the bread crumb string - grab each pair of URI/text,      # build the link      my $bread_crumb_text = ;      while (@crumbs > 1) {          my $href = shift(@crumbs);          my $text = shift(@crumbs);          $bread_crumb_text .= "<a href=\"$href\"><font color=\"#999966\">                                <b>$text</b></font></a> - ";      }      # add the last text in the bread crumb string      $bread_crumb_text .= $crumbs[0];  </%init> 

Recall that the <%init> block is executed first (after <%args> , that is). This block of code generates bread crumb links much as in the Embperl chapter (see Chapter 10). The bread crumb string is split on the colon, and each pair of href/text is looped through, appending onto $bread_crumb_ string the proper HTML for the link. This component builds all the HTML up to and including the bread crumb path and the logo.

We also will create a component for the left rail called, surprisingly, _leftrail . Its contents can be found in the file /var/www/html/mason/productbackend/_leftrail at either http://localhost/mason/productbackend/leftrail or www.opensourcewebbook.com/mason/productbackend/leftrail. Close examination of this component shows that it is simply HTML, which is fine for components, but not exciting enough to describe in detail.

We now need a component for the bottom, called, cleverly enough, _bottom . Its contents can be found in /var/www/html/mason/productbackend/_bottom at either http://localhost/mason/productbackend/bottom or www.opensourcewebbook.com/mason/productbackend/bottom. It is so simple that we can show it here without taking up too much space:

 </td>  <td width="15">&nbsp;</td>  </tr>  </table>  </body>  </html> 

Again, it's only HTML.

The code to use these three components to create a bare page with no content in the main section would look like the contents of file /var/www/html/mason/productbackend/bare.html :

 <& _top, title => Bare Example,         breadcrumb => /contents/:Contents:/contents/mason/:Mason:Bare &>  <& _leftrail &>  <& _bottom &> 

This demonstrates that a file can contain only Mason commands ”no HTML and no Perl! Here we use the _top component, assigning the $title within it to the text Bare Example and the bread crumb variable $breadcrumb to a string of two href/text pairs and the text Bare .

Try it out at http://localhost/mason/productbackend/bare.html or www.opensourcewebbook.com/mason/productbackend/bare.html. You should see something resembling Figure 11.12. Now this page has the look and feel but no content. [7]

[7] That's common enough on the Internet, but we hope the other pages on our web site don't fall into this category.

Figure 11.12. A bare page using project components

graphics/11fig12.gif

One more component is needed to build the first page ”this one will allow the selection of an action to take. This component will generate the form that presents the user with several options to add or modify a manufacturer and add or modify a product. We will create a component for this form because it will be reused later. It is called _select_form and can be found in the file /var/www/html/mason/productbackend/_select_form at www.opensourcewebbook.com/mason/productbackend/select_form or http://localhost/mason/productbackend/select_form .

As before, look at the bottom first and find:

 <%init>  use DBI;  my $dbh = DBI->connect(DBI:mysql:product_database,                         apache,LampIsCool);  my $manid;  my $prodid;  my $cursor;  </%init> 

Again, this block is executed first. It uses the DBI module and then connects to the product database. Then three variables are declared with my() , to be used within the previous Perl and HTML code.

The top part of this file contains the HTML that builds the form. The form allows the user to choose one of four actions ”four radio buttons and two select buttons are created. Let's look at the code that creates the first one:

 <select name="manid">  %$cursor = $dbh->prepare(SELECT man_id  %                         FROM manufacturers  %                         ORDER BY man_id);  % $cursor->execute();  % while (($manid) = $cursor->fetchrow()) {          <option value="<% $manid %>"><% $manid %></option>  % }  </select> 

The first character in the line, "%" , means that this is a line of Perl code to execute. [8] The first line creates an SQL query to grab all the manufacturer IDs from the manufacturers table. Notice that the query is broken into three lines for clarity. That's fine, as long as each line begins with the percent character. The query is then executed, and the result is looped through; and for each ID, an option is created in the select button. The result is a select widget with all the manufacturer IDs in the database.

[8] Don't forget ”it must be the first character!

Similar code is used to generate a button of all the product IDs.

To use this component, simply add <& _select_form &> , and there it is. We do exactly that in index.html . Its contents can be found in the /var/www/html/mason/productbackend/index.html file at http://localhost/mason/productbackend/index.html.txt or www.opensourcewebbook.com/mason/productbackend/index.html.txt. And it's short enough to show here:

 <& _top, title => Project,           breadcrumb => /contents/:Contents:/contents/mason/                         :Mason:Product Backend &>  <& _leftrail &>  <p><font color="#FF0000"><b>Product Backend</b></font><br></p>  <& _select_form &>  <& _bottom &> 

The _top component, with a title and bread crumb string, is called; then _leftrail is called to build the left rail. Next there is some HTML, followed by a call to the _select_form component, and finally the _bottom component.

Loading this page generates something resembling Figure 11.13. If you look carefully at the form built by _select_form , you can see that the action is /mason/productbackend/continue/ . This means that regardless of the selection made, the data will always post to the same page, index.html under the continue directory.

Figure 11.13. Mason project select page

graphics/11fig13.gif

There is another option: Each of the four choices could go to its own page. This is often a good idea, especially if the options are sufficiently dissimilar. In this example, it makes sense for the options to post to the same page.

In any case, the page that the data is posted to will have to look at the action chosen and execute the necessary code to build the proper form.

Look at continue/index.html . Its contents can be found in the file /var/www/html/mason/productbackend/continue/index.html , or online at http://localhost/mason/productbackend/continue/index.html.txt or www.opensourcewebbook.com/mason/productbackend/continue/index.html.txt. Its contents are as follows ( breadcrumb is wrapped for readability):

 <& ../_top, title => Project  Continue,              breadcrumb => /contents/:Contents:/contents/mason/                            :Mason:/mason/productbackend/:Product Backend                            :Continue &>  <& ../_leftrail &>  <p><font color="#FF0000"><b>Product Backend - Continue</b></font><br></p>  % if ($action eq addman or $action eq modman){  <& _man_form, action => $action,                manid  => $manid &>  % } elsif ($action eq addprod or $action eq modprod){  <& _prod_form, action => $action,                 prodid => $prodid &>  % } else {  Please go back and choose an action.  % }  <& ../ bottom &>  <%args>  $action =>   $manid  =>   $prodid =>   </%args> 

The components _top , _leftrail and _bottom are used as before. Mason needs to be told where to find the components by using the relative path, such as ../_top . At the bottom, the <%args> block shows the posted variables, any combination of $action , $manid , or $action .

This code hinges on the if statement ”here the value of $action is checked to find out what the user wants to do. If it involves either adding or modifying a manufacturer, the component named _man_form builds the form for the manufacturer, given the values of $action and $manid that are passed into this component. If it involves either adding or modifying a product, the necessary variables are passed to the component _prod_form .

Note that if there is an unexpected action, the program generates a nice message asking the user to go back and choose an action.

Next we'll examine _man_form . Its contents are in the /var/www/html/mason/productbackend/continue/_man_form file online at http://localhost/mason/productbackend/continue/man_form or www.opensourcewebbook.com/mason/productbackend/continue/man_form. At the bottom, you'll see this:

 <%args>      $action =>       $manid  =>   </%args> 

This component is used with:

 <& _man_form, action => $action,                manid  => $manid &> 

where the action and manufacturer ID are passed in.

Next is a <%once> block with a function definition:

 <%once>  # define a function to make sure our manid is well formed  sub check_man_id {      my $manid = shift;      my $action = shift;      if ($manid) {          if (length($manid) <= 4 and $manid =~ /  ^  \d+$/) {              # all is well, return an empty string              return ;          } else {              return Manufacturer id is not properly formatted                      as up to 4 digit characters.;          }      } elsif ($action eq modman){          return Please go back and enter a manufacturer id to modify.;      } else {          # all is well, return an empty string          return ;      }  }  </%once> 

Here a function is defined: check_man_id() . Its job is to take the manufacturer ID and the action and check that the ID is well formed and that there is an ID if the manufacturer is being modified. If this function finds anything wrong, it returns a string that is not empty. This nonempty string is used to find out if there is an error condition.

This function is defined within the <%once> block. This function could have been defined as a component, but using <%once> is an alternative. We chose this method to show the use of this type of block.

 <%init>  use DBI;  my $name    = ;  my $address = ;  my $city    = ;  my $state   = ;  my $zip     = ;  # first things first, if we are not modifying a manufacturer,  # then we don't need a manufacturer id  if ($action ne modman){      $manid = ;  } 

In the <%init> block, we use DBI and declare some variables. The code checks that $manid has no value if the manufacturer is not being modified, just to be sure.

 my $result = ;  unless ($result = check_man_id($manid, $action)) {      if ($manid) {          # we have a manid, so query the database          my $dbh = DBI->connect(DBI:mysql:product_database,                                apache,LampIsCool);          my $cursor = $dbh->prepare(SELECT name, address, city,                                             state, zip                                     FROM manufacturers                                     WHERE man_id = ?);          $cursor->execute($manid);          ($name,$address,$city,$state,$zip) = $cursor->fetchrow;      }  }  </%init> 

The function check_man_id() is called and the result stored in $result . If the function returned an empty string (in other words, a false value), the unless block is executed. Within it the code checks to see if there is a manufacturer ID. If one is passed, a manufacturer is being modified, and the program queries the database so that the stored values are displayed in the updated form presented to the user after the changes.

Returning to the top of the file _man_form , we see:

 % if ($result) {      <% $result %>  % } else { 

Here the value of $result is checked. Remember, it was set in the <%init> block ”if it has a nonempty string, or a true value, there was some sort of error condition. Thus, if it has a value, that error is reported to the user.

If there is not an error, the form is built in the else block. The form has several widgets that resemble the following:

 <tr>    <td>Name:</td>    <td><input type="text" name="name" value="<% $name %>" size="4"></td>  </tr> 

The value of the widget is <% $name %> , the value of the name field that was grabbed from the database, if the database was queried. If the database wasn't queried, the value of the widget is blank.

Next comes this code:

 % if ($action eq modman) {  <input type="submit" value="Modify">  % } else {  <input type="submit" value="Add">  % } 

This displays the proper text in the submit button: " Modify " when modifying, otherwise " Add ".

 <input type="hidden" name="action" value="<% $action %>"> 

Finally, we have this hidden field. The purpose of this widget is to pass along to the next page the action being taken. This allows the finished page to perform the proper action based on the selection from the previous page.

So check this one out. Go to the form page and select, from the selection menu, modification of a manufacturer (we choose manufacturer ID 1001). When Modify a Manufacturer ID is selected with the value 1001, a page like Figure 11.14 shows up. The values of the fields can be changed, and when the user clicks the Modify button, the database is updated with changes.

Figure 11.14. Mason project manufacturer page

graphics/11fig14.gif

Moving on to the product form. Its contents can be found in the file /var/www/html/mason/productbackend/continue/_prod_form online at either http://localhost/mason/productbackend/continue/prod_form or at www.opensourcewebbook.com/mason/productbackend/continue/prod_form.

We don't show this code here, because it is very similar to _man_form . A quick check of the differences shows that this code expects to receive two posted variables: $action and $prodid . In the <%init> block, it assigns the empty string to $prodid if a product is not modified. It defines, using a <%once> block, a function named check_prod_id() that makes sure the product ID is well formed and that returns error messages if it finds anything it does not expect. Then the check_prod_id() function is invoked, its result is stored in $result , and if the function returns the empty string (all is well), a query is made of the database if there is a product ID (which means that a product is being modified).

In the top part of the file, $result is examined, and if it is not the empty string, an error is printed. If it is the empty string, a form is built for the product information. As with the manufacturer form, the values are set if anything was grabbed from the database. Also, as before, the correct button text is generated, either " Modify " or " Add ", and a hidden field is generated that notifies the next Mason page of what action to take.

Let's try this one out. Go back to the selection Modify a Product and choose product ID C101. It should generate a form that resembles Figure 11.15.

Figure 11.15. Mason project product page

graphics/11fig15.gif

So now we can fill out a form to add a manufacturer, modify a manufacturer, add a product, and modify a product. What happens when these forms are submitted? The HTML in both _man_form and _prod_form shows that the form action is set to /mason/productbackend/finished/ , which posts the data to index.html within that directory. So let's examine this file. Its contents can be found in the /var/www/html/mason/productbackend/finished/index.html file online at http://localhost/mason/productbackend/finished/index.html.txt or www.opensourcewebbook.com/mason/productbackend/finished/index.html.txt.

Go immediately to where the action is, the <%args> block. There are a bunch of variables here, but remember, this file is used to handle posted data for both a manufacturer and a product, so these variables handle both forms. All the variables default to the empty string because in any case chosen, several will not be assigned a posted value.

Next is the <%init> block. First, the familiar use of DBI is seen and then the connection to the product database. The remainder of the <%init> block consists of a big if .. elsif .. else like this:

 if ($action eq addman or $action eq modman) {      ...  } elsif ($action eq addprod or $action eq modprod) {      ...  } else {      $error = Invalid action.;  } 

The decision that this code makes is based on the action that was posted to this page (through the hidden field action ). Note that an error condition results if an unexpected action is received.

The variable $error plays an important role in this file: It is assigned a string if an error or unexpected condition is found. Near the top of the file, this value is checked (more on this later). The two blocks in the if/elsif are very similar, so we will look at only one in detail: the section that handles the action of either adding or modifying a manufacturer.

First, the program does a sanity check on the data:

 unless ($name and $address and $city and $state and $zip) {      $error = Please go back and fill out all fields.;  } else {      # now, check to see if the data is ok      # is $manid properly formed?      unless (length($manid) <= 4 and $manid =~ /  ^  \d+$/) {          $error = Manufacturer ID is not properly formatted                    as up to 4 digit characters;      } else {          # check each of the other variables for valid characters          foreach my $var ($name, $address, $city, $state, $zip) {              unless ($var =~ /  ^  [\w\s\.\-]+$/) {                  $error = "One or more fields contain invalid characters:                            $var";                  last;              }          }      }  } 

It first verifies that all the fields have been filled out. If not, $error is assigned a string that is used to inform the user of their mistake. [9]

[9] Remember, $error is assigned a value if there is some unexpected condition; otherwise, it is as empty string.

If all the fields are filled out, the manufacturer ID is checked to see that it is well formed: no more than four word characters. If the manufacturer ID checks out OK, the other variables containing the posted data are checked. If any of this posted data contains an illegal character (the legal characters are word characters, space characters, periods, dashes, or single quotes ”this set may need to be expanded based on the data that you allow), $error is assigned.

If all the data checks out as OK, $error is assigned its original empty string, or false value.

 unless ($error) {      if ($action eq addman){          my $cursor = $dbh->prepare(INSERT INTO manufacturers                                      (man_id, name, address, city, state,                                       zip)                                      VALUES (?, ?, ?, ?, ?, ?));          $cursor->execute($manid, $name, $address, $city, $state, $zip);          # check for an error message          $error = $cursor->errstr(); 

If $error is false, the program goes on. There are two options: either adding or modifying a manufacturer. In the case of adding a manufacturer, an SQL query is created to insert data into the manufacturers table. The value in errstr() is assigned to $error ”either the error that just happened or the empty string if there is no error.

 } elsif ($action eq modman) {      my $cursor = $dbh->prepare(SELECT name FROM manufacturers                                  WHERE man_id = ?);      $cursor->execute($manid);      # check for an error message      unless ($error = $cursor->errstr()) {          # check to see if record is in the database          my($name_in_db) = $cursor->fetchrow();          unless ($name_in_db) {              $error = Trying to modify a Manufacturer ID that is not                        in the database.;          } else {              # we found the record, so let's modify it              my $cursor = $dbh->prepare(REPLACE INTO manufacturers                                          (man_id, name, address, city,                                          state, zip)                                          VALUES (?, ?, ?, ?, ?, ?));                  $cursor->execute($manid, $name, $address, $city, $state,                                   $zip);                  # check for an error message                  $error = $cursor->errstr();              }          }      }  } 

The modification of a manufacturer requires a bit more work. First, check to see if the manufacturer to be modified exists in the database ”this is done with the SELECT query. If the query is executed correctly ( errstr() returns false), the name queried is assigned to $name_in_db . If this variable is false, the manufacturer is not in the table, so an appropriate error message is assigned to $error . If the name is found, the REPLACE command is executed, which replaces the current record for that manufacturer ID or inserts it if it doesn't exist (but it previously determined that it indeed does exist).

Now that the bottom of the file has been explored, check out the top of the file. The components _top and _leftrail are used. Then, after a bit of HTML:

 % if ($error) {  <p>  There was a problem <font color="#ff0000"><% $error %></font>  </p>  % } else {  <p>  Thank you.  </p>  <hr>  <& ../_select_form &>  % } 

This checks to see if $error has been assigned, and if so, reports the value to the user. If not, a thank-you message is reported, and the _select_form component is executed. This builds the form, allowing the user to add or modify a manufacturer, or add or modify a product.

To show the result, try changing one of the manufacturers and clicking the Modify button. You should see a page resembling Figure 11.16.

Figure 11.16. Mason project finished page

graphics/11fig16.gif



Open Source Development with Lamp
Open Source Development with LAMP: Using Linux, Apache, MySQL, Perl, and PHP
ISBN: 020177061X
EAN: 2147483647
Year: 2002
Pages: 136

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net