Open Source Web Development with LAMP Using Linux, Apache, MySQL, Perl, and PHP [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Open Source Web Development with LAMP Using Linux, Apache, MySQL, Perl, and PHP [Electronic resources] - نسخه متنی

James Lee, Brent Ware

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید










10.8 Embperl Project


Now you know enough to have some real fun, so we'll do a real Embperl projecta product filtering system.


10.8.1 The Problem


We will create two web pages: The first enables the user to filter a list of products, narrowing the selected items based on three criteria: the manufacturer's city, the manufacturer's name, or the product category (the user can select any combination of these three categories); the second page provides details for a selected product.

For a sneak preview, see http://localhost/embperlproject/productfilter/ or www.opensourcewebbook.com/embperlproject/productfilter/. These Embperl pages are built from a MySQL database, so the first step is to create the tables and populate them with data.

We will have two tables: a table of manufacturers and a table of products. Each manufacturer has a unique manufacturer ID, and each product relates to a manufacturer through this ID.

The manufacturers table (named manufacturers, strangely enough) has the following fields:


man_id
the manufacturer ID, a 10-character string (the key to the table)


name
a 60-character string


address
a 60-character string


city
a 60-character string


state
a 2-character string


zip
a 10-character string



The products table (named products, strangely enough) has the following fields:


prod_id
the product ID, a 10-character string (the key to the table)


man_id
the ID of the manufacturer of the product


name
a 60-character field that is the product name


category
a 30-character field that is the category for the product


description
a BLOB describing what the product is and what it does


price
a double



Now we need to log in to MySQL to create the tables and insert some data. One way to accomplish this is for us to show you the commands and have you type them in. If we were mean, we would do this. However, there is another approach: Create the tables and insert the data using a text file that contains the table definition and the MySQL insert commands, and use a single, simple command to build the tables.

We're not mean, so we have created this file for you. You can find it at /var/www/misc/product_database.sql or online at http://localhost/embperl/product_database.sql or www.opensourcewebbook.com/embperl/product_database.sql.

To magically build this, first log in to MySQL and create the database:


$ mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or g.
Your MySQL connection id is 3 to server version: 3.23.36
Type ´help;´ or ´h´ for help. Type ´c´ to clear the buffer
mysql> CREATE DATABASE product_database;
Query OK, 1 row affected (0.00 sec)
mysql> USE mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> GRANT SELECT,INSERT,UPDATE,DELETE
-> ON product_database.*
-> TO apache@localhost;
Query OK, 0 rows affected (0.00 sec)

Execute this shell command from directory that contains this text file:


$ mysql -uapache -p product_database < product_database.sql
Enter password:

Like magic, the database is created and populated. You're welcome!


10.8.2 Creating a Consistent Look and Feel


Before we get into the nitty-gritty of the project, let's talk in general about some options we can use to create a web site with a consistent look and feel (such as www.opensourcewebbook.com/).

There are several ways to create such a web site with a consistent look and feel to all the web pages.


    Hard-code all the HTML pagesbad idea; it's static and hard to maintain.


    Use WML (see Chapter 6) to create a static look and feelbetter, but not dynamic.


    Use CGI (see Chapter 7)a little better; easy to do, certainly.


    Use mod_perl (see Chapter 8)better still, but a bit too complicated, perhaps.


    Use Embperl and write functions to return top and bottommuch better.


    Use Embperl and EmbperlObjectbest!



Method 1: Hard-Code HTML Files


All the pages can be hard-coded with the same HTML. Although this seems like the simplest thing to do, it isn't a great ideaif any content changes in the common parts, such as adding a link or changing the placement of a logo, every page has to be modified. Yuck. Good programmers are lazy, so a little work up front can save a lot of work later.

Method 2: Use WML to Create HTML Files


Using WML, as in Chapter 6, is much better than hard-coding HTML files. Creating a template for a look and feel is possible, so when (not if) changes to the look and feel occur, the changes need only be made once and will apply to all the HTML files. This is fine for static pages.

WML can be used with Embperl because WML has the <protect> tag, which makes WML ignore all the content within. Thefore, the WML files can be used to generate HTML, and that HTML can also contain Embperl code. We don't discuss this here because, in practice, wrapping <protect> ... </protect> around all the Embperl code can cause us to have dozens of protected blocks, and that is not pleasant. We don't go into this except to mention it here, but keep in mind that you can use WML and Embperl to create a consistent look and feel. TMTOWTDI in all things LAMP.

Method 3: Use CGI


We can certainly use CGI to create dynamic web sites; we did so in Chapter 7. Just take the HTML created by the web designers, add a bunch of print() functions, add our code to do the dynamic stuff (such as reading from a database), and we can solve the problem. But this requires us to transform the HTML files into Perl programs. Sometimes this is more work than we wantremember, good programmers are lazy.

Method 4: Use mod_perl


mod_perl, discussed in Chapter 8, is great for many uses, especially if we want to access Apache directly or if we want to speed up the CGIs. But, as in CGI, creating mod_perl pages means we convert the HTML into a program. And as we mentioned earlier, most of the features of mod_perl are available to our Embperl programs (such as speed of execution because of caching and access to Apache internals), so why convert?

Method 5: Use Embedded Programs to Build HTML


We can create a couple of functions to generate the HTML for the top and the bottom of the HTML. Then, in the HTML files, we can have Embperl code that calls these functions to generate the HTML for the look and feel. As you might have guessed, because this is the Embperl chapter, this is the method we talk about here.

To illustrate, we'll create a file to hold functions for the top and bottom of the HTML. We'll put the code in a file not within the document treeput it in /var/www/lib/embperl_template.pl. Hey, did you notice this is outside the web document tree /var/www/html? That is good security because the program is not viewable by someone visiting the web site, and therefore we don't show them the internals of how we are building the web pages. The less information we give crackers, the better.

First create this directory, make it accessible by user apache, and then create the file within it that is readable by apache:


# mkdir /var/www/lib
# chmod a+rx /var/www/lib
# cd /var/www/lib
# vi embperl_template.pl
# chmod a+r embperl_template.pl

These are the contents of embperl_template.pl:


# file: /var/www/lib/embperl_template.pl
##
## the subroutine to return the HTML for the top
## of the page
##
sub top {
return <<EOHTML;
<html>
<head>
<title>Embperl Project Take 1</title>
</head>
<body bgcolor="#ffffff">
<h1>A First Stab at Our Embperl Project</h1>
EOHTML
}
##
## the subroutine to return the HTML for the
## bottom of the page
##
sub bottom {
return <<EOHTML;
</body>
</html>
EOHTML
}
1; # don't forget this line!

This file simply defines two functions: top() and bottom(). The top() function returns the HTML for the top of the HTML, and the bottom() function returns the bottom of the HTML.

Two things to note: First, there is no shebang (#!/usr/bin/perl) in this file because it is not an executable file but a file that defines a couple of functions that are used by other programs; second, the file must end with 1;. This is the same syntactical Perl requirement seen in Chapter 8; the file must evaluate to true in the end, or the compilation fails, and the easiest way to guarantee a true value is by ending the program with 1;.

Now create a file that will source these two functions. Go to the directory with all of the Embperl code, /var/www/html/embperl, and create the file take1l:


[-
require ´/var/www/lib/embperl_template.pl´;
$escmode = 0;
-]
[+ top() +]
This is a test of the Embperl Project Take 1
[+ bottom() +]

Load one of these URLs: http://localhost/embperl/take1l or www.opensourcewebbook.com/embperl/take1l. You should see Figure 10.7.


Figure 10.7. Embperl project take 1


At the top of take1l is a [- ... -] command; the Perl code within will be executed. The first line of this Perl code is a require. This statement will go out, find the file in question (the embperl_template.pl file containing the top() and bottom() function definitions), source that file, and compile the functions. Then, we see $escmode = 0;.

This is an important statement and deserves some discussion. When Embperl generates output, as with the top() function, Embperl automatically HTML- and URL-escapes it. In other words, if the Embperl code generates <hr>, it is escaped to be &lt;hr&gt;, which is displayed in the browser as the characters <hr>, not as the horizontal rule we wanted. There are several solutions to this problem, including escaping the characters we don't want to be HTML-escaped or URL-escaped with the backslash, as in <hr>. You can imagine that this could become a real drag if a whole mess of HTML is generated.

The better solution is to assign $escmode the value 0, which turns off this HTML- and URL-escape behavior. If this is turned off, when <hr> is generated, it indeed becomes a horizontal rule.

But there is a drawback to using require in this manner. Before we describe the problem click the Reload button on the browser a couple of times to reload this page (so that some of the many httpd processes will read in and compile the top() and bottom() functions).

Modify the top() function to add more HTML. Change the definition of top() to:


##
## the subroutine to return the HTML for the top
## of the page
##
sub top {
return <<EOHTML;
<html>
<head>
<title>Embperl Project Take 1</title>
</head>
<body bgcolor="#ffffff">
<h1>A First Stab at Our Embperl Project</h1>
Here is some text followed by a horizontal rule.
<hr>
EOHTML
}

We simply added some text followed by a <hr> tag. Reload take1l, then reload several more times. Chances are that sometimes you will see the new HTML, and sometimes the old. Why?

When new httpd processes are started, they compile the new definition. But the existing httpd processes do not recompile them, because Embperl has cached the compiled code. Embperl recompiles the Perl byte code if the Embperl file (take1l) has changed, but in our case it hasn't only the template file embperl_template.pl has changed. Moreover, once the Apache child process has compiled embperl_template.pl, it won't recompile it even if its contents changethe only file that is recompiled is the page that is requested (in our example, take1l), not the pages that they require. Remember, because this speeds up execution time, this is one of the advantages of Embperl!

How do we force the existing versions of httpd to recompile the changed functions? Reload Apache. This is not ideal, because one has to be root to reload, which makes it a pain during code development.

To prove this actually solves the problem, reload Apache (as root, of course):


# /etc/init.d/httpd graceful

Now, reload the page as many times as you like, and you will see the new content on all the pages. All the versions of httpd have the new definition of top().


Method 6: Use HTML::EmbperlObject

There's a more powerful way to build a web site with Embperl: HTML::EmbperlObject, aka EmbperlObject. This Perl module allows the creation of templates for the entire web site or for portions thereof, and simple modifications to the HTML in the templates take effect immediatelythere is no reason to restart the server.

It works like this: When an l file is requested, a base file (call it basel, configured in httpd.conf) is searched for in the directory that contains the file requested. This basel contains HTML for the look and feel of the web sitethe top, left rail, and bottom HTML. If basel is not found in that directory, EmbperlObject looks in directories up the tree until it finds one named basel. If EmbperlObject searches up to the directory root (/var/www/html, in these examples) and does not find a basel, the user sees a "Not Found" error.

We start with a simple example to illustrate the concepts and then move on to implement a project. First we must create a new directory because the examples and project must be told to use the EmbperlObject module. Create a new directory:


# mkdir /var/www/html/embperlproject
# chmod a+rx /var/www/html/embperlproject

Next, reconfigure Apache so it knows about the new part of the web site. As root, add the following to the bottom of Apache's configuration file:


<Directory /var/www/html/embperlproject>
PerlSetEnv EMBPERL_OBJECT_BASE basel
<Files *l>
SetHandler perl-script
PerlHandler HTML::EmbperlObject
Options ExecCGI
</Files>
</Directory>

This directive tells Apache that under the directory /var/www/html/embperlproject, the EmbperlObject base file, or template file, is named basel. Then, for all files that end in l, process them with the handler HTML::EmbperlObject. As mentioned before, this directory is configured this way because Apache should serve up .txt, etc., from this directory as normal files. See the docs (perldoc HTML::Embperl) for all the different ways to configure this directory.

When this is done, load the altered Apache configuration file:


# /etc/init.d/httpd graceful

A Simple Example


The base file is named basel. You can create one in /var/www/html/embperlproject with this content:


<html>
<head>
<title>A Simple EmbperlObject Example</title>
</head
<body bgcolor="#ffffff">
<h1>This is a simple EmbperlObject Example</h1>
This text, and the following horizontal rule, is in
basel.
<hr>
[# the following will include the contents of the #]
[# HTML file requested - `*´ means the requested file #]
[- Execute (´*´)-]
<hr>
The above horizontal rule, and this text, is in
basel.
</body
</html>

The line [- Execute (´*´)-]is important hereit means, "Include the content of the file that the user has requested (such as indexl)." So, in essence, the contents of basel are wrapped around the contents of the requested file.

The requested file is the last part of the URL. The first example of this, and the file that should be created, is (as usual) indexl, the file that is delivered if no specific l file is requested. It is in the directory /var/www/html/embperlproject and has the following content:


<i>This text, and the following message, is in
indexl.</i>
<br>
<i><b>Hello, world!</b></i>
<br>

Simple enough. The desired result would be to see the previous HTML in the middle of the template basel.

Now give it a try by loading either of these URLs: http://localhost/embperlproject/ or www.opensourcewebbook.com/embperlproject/. You should see something like Figure 10.8.


Figure 10.8. EmbperlObject simple example


The beauty of this is that if either basel or indexl is changed, the effect is seen immediately without the need to restart Apache. Give it a tryalter the content of basel, see the change, and change it back to the content shown previously.

Create another file in /var/www/html/embperlproject named cooll:


<h4>Why Open Source is Cool (in the file cooll)</h4>
<ul>
<li>It is open, so you have all the code available - reading
the code is a good way to spend a rainy day</li>
<li>The programs are free</li>
<li>The people involved in Open Source are cool, smart, and
all around "good people"</li>
<li>It is a movement bigger than any individual and more
than the sum of its parts</li>
</ul>

To see this (important) content wrapped within the template, request cooll with either http://localhost/embperlproject/cooll or www.opensourcewebbook.com/embperlproject/cooll. The content will show up within the template as in Figure 10.9.


Figure 10.9. EmbperlObject simple examplecooll


This template is applied to subdirectories under the directory that includes basel. As an example, make a new directory:


$ mkdir /var/www/html/embperlproject/sub1
$ chmod a+rx /var/www/html/embperlproject/sub1

Within this new directory, create a new file named indexl with this content:


This is <i>indexl</i> within the subdirectory
<i>sub1</i>. Requesting this file will use the
template <i>basel</i> located in the parent
directory <i>/var/www/html/embperlproject</i>,
since there is no <i>basel</i> in this
directory.

Look at this new file by asking for one of these URLs: http://localhost/embperlproject/sub1/ or www.opensourcewebbook.com/embperlproject/sub1/. The template defined in basel in the parent directory is wrapped around the content of the new indexl in subdirectory sub1. It looks like Figure 10.10.


Figure 10.10. Using basel in the parent directory


You can redefine the template for a specific subdirectoryyou don't have to use the same template for all the subdirectories. Create another subdirectory under /var/www/html/embperlproject:


$ mkdir /var/www/html/embperlproject/sub2
$ chmod a+rx /var/www/html/embperlproject/sub2

Now define a new template within the file basel. Make sure that this new file is created within the new subdirectorydon't overwrite basel in the parent directory.


<html>
<head>
<title>sub2 Template</title>
</head>
<body bgcolor="#ffffff">
<h1>sub2 Template</h1>
This is the template for all files within
<i>/embperlproject/sub2</i>.
<hr>
[- Execute (´*´)-]
<hr>
Back to the template. Hey, check it out, we are
including a footer file below! Perhaps it contains
HTML that will be the bottom of the page?
[- Execute (´bottoml´)-]

There's something interesting at the end of this example: [- Execute (´bottoml´)-]. One might surmise that this will source the file bottoml located in the same directory, and that is exactly what it does. EmbperlObject files can include other EmbperlObject files. One can have a file for the top of the page, another for the navigation for the page, another for the bottom of the page, and so on.

The file bottoml is located in the same directory:


<hr>
We are at the bottom of the page.
<br>
For comments, please email
<a href=">the
webmaster</a>.
</body>
</html>

The contents of indexl in the subdirectory sub2 is simply this:


<i>The contents of indexl.</i>

To see the result of these three files, load either one of these URLs: http://localhost/embperlproject/sub2/ or www.opensourcewebbook.com/embperlproject/sub2/. You should see Figure 10.11.


Figure 10.11. Using basel in the same directory, plus a footer


Now, one more thing is needed for this example to be really useful: the ability to pass information into the files in question so that things can change dynamically, such as the title. In the base file, we can define a method; then, in other files, the same function can be redefined. The function names must be the same in all the redefinitions. The base file uses the latest function definition.

To illustrate this, create a third subdirectory, named sub3:


$ mkdir /var/www/html/embperlproject/sub3
$ chmod a+rx /var/www/html/embperlproject/sub3

Create a very simple basel including only the barest of HTMLnote the value of the title within the <title> tags:


[!
# remember that code in these types
# of commands is executed only when
# the file is first requested or after
# the file is modified
sub title {
return ´The Default Title´;
}
!]
[-
# now, each time the page is requested,
# get the first argument passed in (it
# is the request object)
# we will then be able to call the
# title() function above using our
# request object
$req = shift;
-]
<html>
<head>
[# the code $req->title() below executes the title() #]
[# function defined in the file requested, or this #]
[# file by default #]
<title>[+$req->title()+]</title>
</head>
<body bgcolor="#ffffff">
[- Execute (´*´)-]
</body>
</html>

The content of indexl is simply this:


Hello, world! from <i>sub3</i>.

This file does not redefine the title() function, so it uses the default definition found in basel. Load this page in http://localhost/embperlproject/sub3/ or www.opensourcewebbook.com/embperlproject/sub3/. The file indexl uses the default title as shown in Figure 10.12.


Figure 10.12. Using the default title


Now create a file that redefines the title() function. Call it newtitle. html, located in sub3:


[!
# we redefine title(), so that basel
# will use this definition when this page
# is requested
sub title {
return ´The title for newtitlel´;
}
!]
This is the content of <i>newtitlel</i>.

Have a look at http://localhost/embperlproject/sub3/newtitlel or www.opensourcewebbook.com/embperlproject/sub3/newtitlel. The new title returned from title(), defined in newtitlel, is shown in Figure 10.13.


Figure 10.13. Using the title redefined in newtitlel


Now all the pieces needed to do some serious web development are in place. You now can create templates that can be given specific information for the page requested, and the server doesn't have to be restarted every time something changes.


10.8.3 Product Filter


Now you have the tools to code the product filter. Recall that we created a database of manufacturer and product information. We will now create two Embperl pages: /embperlproject/productfilter/indexl, to filter the list of products, and index. html, to display the details of a specific product.

A basel file would be helpful to define the web site look and feel, so look at the file /var/www/html/embperlproject/productfilter/basel, also found by clicking http://localhost/embperlproject/productfilter/basel.txt or www.opensourcewebbook.com/embperlproject/productfilter/basel.txt.

We don't cover all of this fileit is a bit too bigbut you can find its contents at www.opensourcewebbook.com.

First, see the [! ... !] command:


[!
# the default title
sub title {
return ´Product Filter Embperl Style´;
}
# the default bread crumb string
sub bread_crumb {
return ´productfilter´;
}
!]

This defines the functions to be used for the default title placed between the <title> ... </title> tags and the bread crumb text, which is processed in the following block of code:


[-
# get the request object
$req = shift;
# we are building a string with HTML in it, so turn off
# HTML- and URI-escaping
$escmode = 0;
# get the bread crumb string and split it on the colon
@crumbs = split /:/, $req->bread_crumb();
# build the bread crumb string - grab each pair of URI/text,
# build the link
$bread_crumb text = ´´;
while (@crumbs > 1) {
$href =
$text =
$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];
-]

First, the request object is gotten and assigned to $req. Then $escmode is set to 0 to turn off HTML- and URI-escaping because we are going to build some HTML to be included in the output. Then the bread crumb string is generated by calling the function bread_crumb() defined either in this file (the default) or a file that uses this base (which will override the default).

The elements of the string are then processed. For each URI/text pair, the href and text are shifted out of @crumbs, and an HTML link is created and concatenated to $bread_crumb_text. When all the URI/text pairs are processed, the last text is concatenated onto $bread_crumb_text.

Later we see the inclusion of the title defined in the title() function:


<title>Open Source Web Book - [+$req->title()+]</title>

Still later, we see the inclusion of the bread crumb string:


- [+$bread_crumb_text+]

Then, buried in all the HTML that builds the links on the left rail and other fun things, the statement that includes the HTML file uses this template:


[- Execute (´*´)-]

Now we come to the page that builds the filter form. It is in the file /var/www/html/embperlproject/productfilter/indexl at either http://localhost/embperlproject/productfilter/indexl.txt or at www.opensourcewebbook.com/embperlproject/productfilter/indexl.txt.

The beginning [- ... -] command works pretty hard.


use DBI;
# connect to the database
$dbh = DBI->connect(´DBI:mysql:product_database´,
´apache´,´LampIsCool´);

A connection is made to the database. Then we see the following code:


# grab the posted data
$posted_city = $fdat{city} || ´´;
$posted_manufacturer = $fdat{manufacturer} || ´´;
$posted_category = $fdat{category} || ´´;
# make sure that the posted data contains the data we want
# (no nasty characters) and the length we expect - if the
# data looks bad, set variable to empty string
unless ($posted_city =~ /^[w ´.]+$/) {
$posted_city = ´´;
}
if (length($posted_city) > 60) {
$posted city = ´´;
}

This code grabs the posted data and does some sanity checks on its contents (we show only the sanity checking of $posted_city; the other variables are similar).


# $sql_cond will contain the conditions that we will use for
# the SQL query - if we received any posted data, then we need
# to append to $sql_cond the appropriate SQL text and we will
# add the value of the variable to the array of execute()
# arguments
$sql_cond = ´´;
@execute_args = ();
if ($posted_city or $posted_manufacturer or $posted_category) {
if ($posted_city) {
$sql_cond .= ´ AND manufacturers.city = ?´;
push @execute_args, $posted_city;
}
if ($posted_manufacturer) {
$sql_cond .= ´ AND manufacturers.name = ?´;
push @execute_args, $posted_manufacturer;
}
if ($posted_category) {
$sql_cond .= ´ AND products.category = ?´;
push @execute_args, $posted_category;
}
}

The preceding code builds a string named $sql_cond, the text of which is included in the SQL query, and @execute_args, an array of values that is plugged into the "?" in the query.


<select name="city">
[# for each of the posted variables, generate the select #]
[# box HTML - this will include an SQL query to get the #]
[# data out of the database #]
<option value=">Select One</option>
<option value=">------</option>
[-
$sth = $dbh->prepare(´SELECT DISTINCT city FROM manufacturers
ORDER BY city´);
$sth->execute();
-]
[$ while (($city) = $sth->fetchrow()) $]
[$ if ($posted_city eq $city) $]
<option value="[+ $city +]" selected>[+ $city +]</option>
[$ else $]
<option>[+ $city +]</option>
[$ endif $]
[$ endwhile $]
</select>

The preceding code builds the city selection button (there is similar code to build the button to select the manufacturer and the category). Then the database is queried for all the cities, and for each city, an option is added, marking the posted city selected, if appropriate.

Notice that the Select One option and the ----- option both have the value of empty string ("). This ensures that if the user selects one of these, the value will be empty string, so that category will not be used in the filter.


[# now build the table - this query will include any conditions #]
[# that we have based on the posted data from the filter #]
[# options - we will then loop through the results, building #]
[# the rows of the table #]
[-
$sth = $dbh->prepare(´SELECT products.prod_id,products.name,
products.category,products.price
FROM manufacturers, products
WHERE manufacturers.man id = products.man_id ´
. $sql_cond .
´ ORDER BY products.category, products.name´);
$sth->execute(@execute_args);
$i=0;
-]

The preceding code queries the database for all products that match the filter criteria, including the selected city, manufacturer, and category, that were set in $sql_cond and @execute_args.


[$ while (($prod_id,$name,$category,$price) = $sth->fetchrow()) $]
[-
if ($i%2==0){
$bgcolor = "#ffffff";
} else {
$bgcolor = "#cccccc";
}
$i++;
-]
<tr bgcolor="[+ $bgcolor +]">
<td>
<a href="
?prod_id=[+ $prod_id +]"><font color="#999966">
<b>[+ $name +]</b></font></a></td>
<td>[+ $category +]</td>
<td>$[+ $price +]</td>
</tr>
[$ endwhile $]

In the preceding code, the background color alternates between white and gray on each loop, making the table more readable. A link is created for the product detail page (wrapped for readability), passing the product ID into its %fdat, and then the data is displayed.

Check page by clicking http://localhost/embperlproject/productfilter/ or www.opensourcewebbook.com/embperlproject/productfilter/. You should see a page that resembles Figure 10.14. Feel free to experiment with the filter.


Figure 10.14. Product filter with Embperl


Next, we come to the page that displays the product detail. When the product link is clicked, the page grabs the query string and prints the information to the user. It starts with:


[!
# define a title to override the default in basel
sub title {
return ´Product Detail Embperl Style´;
}
# define a bread crumb string to override the default
# in basel
sub bread_crumb {
return ´/embperlproject/productfilter/:/productfilter/
:productdetail´;
}
!]

The preceding code defines the functions that overide the default definitions in basel in the parent directorydefining the title and bread crumb string (wrapped for readability).


[-
use DBI;
# connect to the database
$dbh = DBI->connect(´DBI:mysql:product_database´,
´apache´,´LampIsCool´);
# grab the posted data
$prod_id = $fdat{prod_id} || ´´;
# make sure the product id is what we expect and the
# length is within expected limits
unless ($prod_id =~ /^[w]+$/) {
$prod_id = ´´;
}
if (length($prod_id)>4){
$prod_id = ´´;
}
-]

The preceding code connects to the database and then grabs the product ID that was sent through the query string and assigned to %fdat. Then the data is tested to be sure it contains only word characters (alphas, digits, or underscores), and the length of the product ID is checkedgood for security!


[$ if ($prod_id) $]
[# we have a good product id, so query the database and generate #]
[# the output #]
[-
$sth = $dbh->prepare(´SELECT products.name, products.category,
products.description,products.price,
manufacturers.name,
manufacturers.address,
manufacturers.city,
manufacturers.state,
manufacturers.zip,
manufacturers.man_id
FROM manufacturers, products
WHERE manufacturers.man_id = products.man_id
AND products.prod_id = ?
ORDER BY products.category, products.name´);
$sth->execute($prod_id);
($name,$category,$description,$price,$man_name,
$address,$city,$state,$zip,$man_id) = $sth->fetchrow();
-]

Next we check to make sure we have a product ID. If so, the database is queried, and we grab most of the data in it for that product ID. Then the result of the query is assigned to several variables, such as $name.


[$ if ($name) $]
[# $name has a value, so we have a record! display it #]
<font color="#ff0000"><b>Product: [+ $prod_id +]</b></font><br>
<table border="0" cellspacing="0" cellpadding="0">
<tr><th align="left">Name</th><td>&nbsp;&nbsp;</td><td>
[+ $name +]</td></tr>
<tr><th align="left">Category</th><td>&nbsp;&nbsp;</td><td>
[+ $category +]</td></tr>
<tr><th align="left">Description</th><td>&nbsp;&nbsp;</td><td>
[+ $description +]</td></tr>
<tr><th align="left">Price</th><td>&nbsp;&nbsp;</td><td>
$[+ $price +]</td></tr>
<tr><th align="left"
valign="top">Manufacturer</th><td>&nbsp;&nbsp;</td><td>
ID: [+ $man_id +]<br>
[+ $man_name +]<br>
[+ $address +]<br>
[+ $city +], [+ $state +] [+ $zip +]
</td></tr>
</table>
[$ else $]
[# oops, the record wasn't in the database, so say so #]
Product ID <b>[+ $prod_id +]</b> not found in the database.
[$ endif $]

The program then checks to see whether it received a nameif so, $name would have a value. If it has a name, the product information is displayed in the browser. If not, it prints a message saying that the product ID was not found. Finally, we have:


[$ else $]
[# oops, the user didn't give us a good product ID, so say so #]
Please enter a valid product ID.
[$ endif $]

This else occurs when there is a bad product ID. The program just prints a nice message (we did say please).

When you click one of the product links, you should see a page that resembles Figure 10.15.


Figure 10.15. Product detail with Embperl



/ 136