Your scripts mix the code for retrieving information from your database with the code for generating HTML. You want to decouple these activities so that they can be performed separately.
Use a template system that enables you to design the general layout of the page but plug in specific data on a request-specific basis. Then you can obtain the data separately and pass it to the template system for output generation.
In this chapter, we’ve been using an approach that retrieves data for a web page and generates the HTML for the page all in the same script. That has the advantage of simplicity, but there are disadvantages as well, and there are alternatives.
The primary disadvantage of mixing everything together is that there is little functional decoupling (separation of function) between the logic involved in obtaining the data needed for the page (the business or application logic) and the logic that formats the data for display (the presentation logic).
An alternative approach to web page generation is to use some kind of template system that enables functional decomposition of phases of the page-generation process. A template system does this by separating business logic from the presentation logic. If you’re partial to the Model-View-Controller (MVC) architecture, templates help you implement MVC logic by taking care of the View part.
There are many template packages to choose from, as a few minutes searching the Internet quickly reveals. Here, we’ll briefly explore two (PageTemplate for Ruby and Smarty for PHP), each of which uses the following approach:
The web server invokes a script in response to a client request for a web page.
The script determines what must be displayed and retrieves any data necessary to make that happen.
The script calls the template engine, passing a page template to it along with the data to insert into the template.
The template engine looks for special markers in the template that indicate where to insert data values, replaces them with the appropriate values, and produces the result as its output. The output is the web page that is sent to the client as the response to the client’s request.
Typically, template engines are used in web contexts to produce HTML, but depending on your requirements, you could produce other formats such as plain text or XML.
A template system provides the benefits of functional decoupling in the following way:
A programmer can change the implementation of how to obtain the information to be displayed without caring about what it will look like when displayed. For example, if you want to change database systems, you can do that in the application logic without requiring changes to any templates (the presentation logic).
A page designer can change the way information is displayed by changing the templates, without knowing anything about how to obtain the information. It could come from a file, a database, or whatever. The designer simply assumes that the information is available and doesn’t care where it comes from. In particular, the designer doesn’t need to know how to write programs.
That last sentence might be considered subject to debate. Although it’s true that page designers do not, for the most part, need to know a programming language, it’s also true that templates are embedded with special markers that indicate where to substitute data values. Depending on the complexity of the data to be displayed and the markup syntax of the template package, the markup can range from very simple to looking like a program itself! Nevertheless, it’s easier to understand template markers than a general-purpose programming language.
In this section, we’ll revisit some of the topics that were addressed earlier in the chapter: Generating paragraphs (Displaying Query Results as Paragraph Text), lists (Displaying Query Results as Lists), and tables (Displaying Query Results as Tables). But here we’ll consider how to create these structures in web pages by designing page templates for PageTemplate and Smarty. Before doing so, the discussion begins for each template package with a short tutorial that shows how to use markup for the following concepts:
How to indicate where to substitute values into a template. A given value can be substituted as is, or it can be subjected to HTML-encoding or URL-encoding first.
How to select or ignore part of a template based on the result of a test.
How to process part of a template repeatedly, once per element of a data structure such as an array or hash. This is useful for generating lists and tables.
Appendix A indicates where to get
PageTemplate and Smarty. I assume in the remainder of this recipe that
you have already installed them. The example templates and scripts
discussed here can be found under the apache directory of the recipes
distribution, in the pagetemplate and smarty subdirectories.
The examples use .tmpl as the extension for template files, but there is no requirement for either template package that you use that particular extension. (For example, Smarty applications often use .tpl.)
A PageTemplate web application consists of an HTML template
file for the output page, and a Ruby script that gathers the data
needed by the template and calls the template engine to process the
template. In PageTemplate, the markup indicators are
[%
and %]
. The content between the markers tells
PageTemplate what action to perform.
Value
substitution. To indicate where a value goes in a
PageTemplate template file, use a [%var
var_name
%]
directive:
[%var myvar%]
PageTemplate replaces the directive with the value of the
var_name
variable. By default, the value
is substituted as is. To perform HTML-encoding or URL-encoding of
the value, add the appropriate preprocessor name to the
directive:
[%var myvar :escapeHTML%] [%var myvar :escapeURI%]
:escapeHTML
has the same
effect as invokingCGI.escapeHTML()
to make sure that
special characters such as <
or &
are converted to the
corresponding <
and
&
entities. :escapeURI
has the same effect as CGI.escape()
.
Any value referred to by a [%var%]
directive must be a string, or
able to produce a string if the to_s
method is applied to it.
Conditional
testing. A conditional test begins with an [%if
var_name
%]
directive, ends with [%endif%]
, and optionally contains an
[%else%]
directive:
[%if myvar%] myvar is true [%else%] myvar is false [%endif%]
If myvar
evaluates to true,
PageTemplate processes the content following the [%if%]
directive. Otherwise, it processes
the content following [%else%]
.
For a simple conditional with no “else” part, omit the
[%else%]
part.
Iteration. The
[%in
var_name
%]
construct provides iteration over the members of the
list named by the given variable. The template content between the
[%in%]
and [%endin%]
directives
is processed once per member. If the list members are hashes, you
can refer to the hash elements by name. Suppose that mylist
is an array consisting of items,
each of which is a hash with this structure:
{ "first_name" =>value
, "last_name" =>value
}
The hash members are first_name
and last_name
, so an iterator can process the
list and refer to its item members as follows:
[%in mylist%] Name: [%var first_name%] [%var last_name%] [%endin%]
If the list is a simple indexed list, its elements don’t have
names. In that case, you can provide a name in the [%in%]
directive by which to refer to the
elements:
[%in mylist: myitem%] Item: [%var myitem%] [%endin%]
The following template file, pt_demo.tmpl, demonstrates the concepts just discussed:
<!-- pt_demo.tmpl --> <html> <head> <title>PageTemplate Demonstration</title> </head> <body> <p>Value substitution:</p> <p> My name is [%var character_name%], and I am [%var character_role%]. </p> <p>Value substitution with encoding:</p> <p> HTML-encoding: [%var str_to_encode :escapeHTML%]; URL-encoding: [%var str_to_encode :escapeURI%] </p> <p>Conditional testing:</p> <p> [%if id%] You requested information for item number [%var id%]. [%else%] You didn't choose any item! [%endif%] </p> <p>Iteration:</p> <p> Colors of the rainbow: [%in colors: color%] [%var color%] [%endin%] </p> </body> </html>
We also need a script to go with the template. The basic skeleton of a Ruby script that uses PageTemplate looks like this:
#!/usr/bin/ruby -w # Access required modules require "PageTemplate" require "cgi"...obtain data to be displayed in the output page...
# Create template object pt = PageTemplate.new # Load template file pt.load("template_file_name
") # Assign values to template variables pt["var_name1
"] =value1
pt["var_name2
"] =value2
... # Generate output (cgi.out adds headers) cgi = CGI.new("html4") cgi.out { pt.output }
The script begins by including the required Ruby library
files: PageTemplate
to generate
the page, and cgi
because its
out
method provides an easy way
to send the page to the client complete with any required
headers.
The next steps are to create a template object, load the template file, and associate values with the variables named in the template. The filename should be an absolute variable or a pathname relative to the script’s current working directory at the point when the template object is created. The syntax for assigning a value to a template variable is similar to hash-value assignment:
pt["var_name
"] =value
Finally, the script generates output. This is done with the
template object’s output
method.
As shown, the script prints output from the template using the
cgi
object’s out
method, which takes care of adding the
page headers.
We can adapt that skeleton to write a pt_demo.rb script that accompanies the pt_demo.tmpl template file:
#!/usr/bin/ruby -w # pt_demo.rb - PageTemplate demonstration script require "PageTemplate" require "cgi" pt = PageTemplate.new pt.load("pt_demo.tmpl") pt["character_name"] = "Peregrine Pickle" pt["character_role"] = "a young country gentleman" pt["str_to_encode"] = "encoded text: (<>&'" =;)" pt["id"] = 47846 pt["colors"] = ["red","orange","yellow","green","blue","indigo","violet"] cgi = CGI.new("html4") cgi.out { pt.output }
To try the application, copy the pt_demo.rb and pt_demo.tmpl files to your Ruby web script directory and then request the script with your web browser. For example, if you copy the files to your usual cgi-bin directory, use this URL to request the script:
http://localhost/cgi-bin/pt_demo.rb
You might prefer to install the .tmpl file somewhere other than the
cgi-bin directory because it
isn’t an executable file. If you do that, adjust the pathname in the
pt.load
call.
The preceding tutorial is sufficient background for developing templates to produce paragraphs, lists, and tables that display information retrieved from MySQL.
Paragraph generation. Our first PageTemplate-based MySQL application requires only the production of simple paragraphs. The application is patterned after the discussion in Displaying Query Results as Paragraph Text. The scripts developed there connect to the MySQL server, issue a query to retrieve some information, and write out a few paragraphs to report the query result: the current time, the server version, the MySQL username, and the current database. For an equivalent PageTemplate application, we need a template file and a Ruby script, here named pt_paragraphs.tmpl and pt_paragraphs.rb.
Begin by designing the page template. For this application,
the template is quite simple. It requires only simple value
substitution, not conditionals or iterators. Use the [%var
var_name
%]
construct to make a template for a page
containing simple paragraphs:
<!-- pt_paragraphs.tmpl --> <html> <head> <title>[%var title :escapeHTML%]</title> </head> <body bgcolor="white"> <p>Local time on the MySQL server is [%var now :escapeHTML%].</p> <p>The server version is [%var version :escapeHTML%].</p> <p>The current user is [%var user :escapeHTML%].</p> <p>The default database is [%var db :escapeHTML%].</p> </body> </html>
The pt_paragraphs.tmpl
template uses :escapeHTML
in the
[%var%]
directives based on the
assumption that we don’t necessarily have much knowledge about
whether the data values contain special characters. That completes
the page design.
Next, write the Ruby script that retrieves the data and
invokes the template engine. The directives embedded within the
template indicate that the accompanying script will need to provide
values for template variables named title
, now
, version
, user
, and db
:
#!/usr/bin/ruby -w # pt_paragraphs.rb - generate HTML paragraphs require "PageTemplate" require "cgi" require "Cookbook" title = "Query Output Display - Paragraphs" dbh = Cookbook.connect # Retrieve data required for template (now, version, user, db) = dbh.select_one("SELECT NOW(), VERSION(), USER(), DATABASE()") db = "NONE" if db.nil? dbh.disconnect pt = PageTemplate.new pt.load("pt_paragraphs.tmpl") pt["title"] = title pt["now"] = now pt["version"] = version pt["user"] = user pt["db"] = db cgi = CGI.new("html4") cgi.out { pt.output }
This script is a lot like the pt_demo.rb script discussed earlier. The
main differences are that it uses the Cookbook
module (for its connect
method) and it obtains the data
required for the page template by connecting to MySQL and issuing
queries.
List
generation. Lists contain repeating elements, so they are produced with the
PageTemplate [%in%]
directive
that iterates through a list. The following template generates an
ordered list, an unordered list, and a definition list:
<!-- pt_lists.tmpl --> <html> <head> <title>[%var title :escapeHTML%]</title> </head> <body bgcolor="white"> <p>Ordered list:</p> <ol> [%in list: item %] <li>[%var item :escapeHTML%]</li> [%endin%] </ol> <p>Unordered list:</p> <ul> [%in list: item %] <li>[%var item :escapeHTML%]</li> [%endin%] </ul> <p>Definition list:</p> <dl> [%in defn_list%] <dt>[%var note :escapeHTML%]</dt> <dd>[%var mnemonic :escapeHTML%]</dd> [%endin%] </dl> </body> </html>
The first two lists differ only in the surrounding tags
(<ol>
versus <ul>
), and they use the same data
for the list items. The values come from a simple array, so we
provide another argument to the [%in%]
directive that associates a name
(item
) with values in the list
and gives us a way to refer to them.
For the definition list, each item requires both a term and a
definition. We’ll assume that the script can provide a list of
structured values that have members named note
and mnemonic
.
The script that retrieves the list data from MySQL and processes the template looks like this:
#!/usr/bin/ruby -w # pt_lists.rb - generate HTML lists require "PageTemplate" require "cgi" require "Cookbook" title = "Query Output Display - Lists" dbh = Cookbook.connect # Fetch items for ordered, unordered lists # (create an array of "scalar" values; the list actually consists of # DBI::Row objects, but for single-column rows, applying the to_s # method to each object results in the column value) stmt = "SELECT item FROM ingredient ORDER BY id" list = dbh.select_all(stmt) # Fetch terms and definitions for a definition list # (create a list of hash values, one hash per row) defn_list = [] stmt = "SELECT note, mnemonic FROM doremi ORDER BY id" dbh.execute(stmt) do |sth| sth.fetch_hash do |row| defn_list << row end end dbh.disconnect pt = PageTemplate.new pt.load("pt_lists.tmpl") pt["title"] = title pt["list"] = list pt["defn_list"] = defn_list cgi = CGI.new("html4") cgi.out { pt.output }
Recall that for the definition list, the template expects that
the list items are structured values containing note
and mnemonic
members. The script should
satisfy this requirement by creating an array of hashes, in which
each hash is of this form:
{ "note" =>val1
, "mnemonic" =>val2
}
This is easily accomplished by selecting the note
and mnemonic
columns from the table that
contains the data, and using the fetch_hash
function to retrieve the rows
as hashes.
Table
generation. Designing a template for an HTML table is similar to designing
one for a list because they both have repeating elements and thus
use iterators. For a table, the repeating element is the row (using
the <tr>
element). Within
each row, you write the values for the cells using <td>
elements. The natural data
structure for this is an array of hashes, where each hash has
elements keyed by the column names.
The example shown here displays the contents of the cd
table, which has three columns:
year
, artist
, and title
. Assuming that we use a template
variable named rows
to hold the
table contents, the following template generates the table.
Actually, the template generates the table
twice (once with all rows the same color, and
once with alternating row colors):
<!-- pt_tables.tmpl --> <html> <head> <title>[%var title :escapeHTML%]</title> </head> <body bgcolor="white"> <p>HTML table:</p> <table border="1"> <tr> <th>Year</th> <th>Artist</th> <th>Title</th> </tr> [%in rows%] <tr> <td>[%var year :escapeHTML%]</td> <td>[%var artist :escapeHTML%]</td> <td>[%var title :escapeHTML%]</td> </tr> [%endin%] </table> <p>HTML table with rows in alternating colors:</p> <table border="1"> <tr> <th bgcolor="silver">Year</th> <th bgcolor="silver">Artist</th> <th bgcolor="silver">Title</th> </tr> [%in rows%] [%if __ODD__ %] <tr bgcolor="white"> [%else%] <tr bgcolor="silver"> [%endif%] <td>[%var year :escapeHTML%]</td> <td>[%var artist :escapeHTML%]</td> <td>[%var title :escapeHTML%]</td> </tr> [%endin%] </table> </body> </html>
The first table template generates a “plain”
table. The second template generates a table that has alternating
row colors. Switching between colors is easy to do by using a
conditional directive that tests the value of the built-in __ODD__
variable that is true for every
odd-numbered row.
To fetch the table data and process the template, use this script:
#!/usr/bin/ruby -w # pt_tables.rb - generate HTML tables require "PageTemplate" require "cgi" require "Cookbook" title = "Query Output Display - Tables" dbh = Cookbook.connect # Fetch table rows # (create a list of hash values, one hash per row) rows = [] stmt = "SELECT year, artist, title FROM cd ORDER BY artist, year" dbh.execute(stmt) do |sth| sth.fetch_hash do |row| rows << row end end dbh.disconnect pt = PageTemplate.new pt.load("pt_tables.tmpl") pt["title"] = title pt["rows"] = rows cgi = CGI.new("html4") cgi.out { pt.output }
A Smarty web
application consists of an HTML template file for the
output page, and a PHP script that gathers the data needed by the
template and calls the template engine to process the template. In
Smarty templates, the markup indicators are
{
and }
, which surround the commands that tell
Smarty what actions you want taken.
Value
substitution. To indicate where to substitute a value in a
Smarty template, use {$
var_name
}
notation. The substitution uses the
value with no preprocessing. If you want to HTML-encode or
URL-encode the value, add a modifier. The following three lines
substitute a value without modification, with HTML-encoding, and
with URL-encoding, respectively:
{$myvar} {$myvar|escape} {$myvar|escape:"url"}
Conditional
testing. A conditional test begins with {if
expr
}
,
ends with {/if}
, and optionally
contains one or more {elseif
expr
}
clauses and an {else}
clause.
Each expr
can be a variable or a more
complex expression (unlike PageTemplate, which allows only a
variable name). Here is a simple if-then-else test:
{if $myvar} myvar is true {else} myvar is false {/if}
Iteration. Smarty has
multiple iterator constructs. The {foreach}
command names the variable
containing the list of values to iterate over and indicates the name
by which the body of the loop will refer to each value. The
following construct specifies $mylist
as the name of the list and uses
myitem
for the list item
name:
{foreach from=$mylist item=myitem} {$myitem} {/foreach}
The {section}
command names
the variable containing the list of values and the name to use for
subscripting the list variable within the loop:
{section loop=$mylist name=myitem} {$mylist[myitem]} {/section}
Note that the syntax for referring to list items is different
for {section}
than for {foreach}
. The
$mylist[myitem]
syntax shown is
useful for iterating through a list of scalar values. If you need to
iterate through structured values such as associative arrays, refer
to structure members as $mylist[myitem].
member_name
.
The list and table applications discussed later illustrate this
syntax further.
The following template file, sm_demo.tmpl, demonstrates the preceding concepts:
<!-- sm_demo.tmpl --> <html> <head> <title>Smarty Demonstration</title> </head> <body> <p>Value substitution:</p> <p> My name is {$character_name}, and I am {$character_role}. </p> <p>Value substitution with encoding:</p> <p> HTML-encoding: {$str_to_encode|escape}; URL-encoding: {$str_to_encode|escape:"url"} </p> <p>Conditional testing:</p> <p> {if $id} You requested information for item number {$id}. {else} You didn't choose any item! {/if} </p> <p>Iteration:</p> <p> Colors of the rainbow (using foreach): {foreach from=$colors item=color} {$color} {/foreach} </p> <p> Colors of the rainbow (using section): {section loop=$colors name=color} {$colors[color]} {/section} </p> </body> </html>
We also need a script to accompany the template. An outline for a PHP script that uses Smarty looks like this:
<?php require_once "Smarty.class.php";...obtain data to be displayed in the output page...
# Create template object $smarty = new Smarty (); # Assign values to template variables $smarty->assign ("var_name1
",value1
); $smarty->assign ("var_name2
",value2
); ... # Process the template to produce output $smarty->display("template_file_name
"); ?>
The Smarty.class.php file gives you access to
Smarty’s capabilities. As mentioned earlier, I assume that you have
Smarty installed already. To make it easy for your PHP scripts to
access the Smarty.class.php
file without having to specify its full pathname, you should add the
directory where that file is located to the value of the include_path
configuration variable in
your php.ini file. For example,
if Smarty.class.php is
installed in /usr/local/lib/php/smarty, add that
directory to the value of include_path
.
The rest of the skeleton shows the essential steps for using
Smarty: create a new Smarty template object, specify values for
template variables with assign()
, and then pass the template
filename to the display()
object to generate the output.
The skeleton is easily adapted to produce the following script, sm_demo.php, to go along with the sm_demo.tmpl template:
<?php # sm_demo.php - Smarty demonstration script require_once "Smarty.class.php"; $smarty = new Smarty (); $smarty->assign ("character_name", "Peregrine Pickle"); $smarty->assign ("character_role", "a young country gentleman"); $smarty->assign ("str_to_encode", "encoded text: (<>&'" =;)"); $smarty->assign ("id", 47846); $colors = array ("red","orange","yellow","green","blue","indigo","violet"); $smarty->assign ("colors", $colors); $smarty->display ("sm_demo.tmpl"); ?>
We now have a simple Smarty application (the template file and the PHP script that uses it), but a bit of additional setup is required before we can deploy the application. Smarty uses a set of directories to do its work, so you’ll need to create them first. By default, Smarty assumes that these directories are located in the same directory where the PHP script is installed. The following instructions assume that this directory is mcb under your Apache document root (the same PHP directory that has been used throughout this chapter).
Change location to the mcb directory and create these four directories:
%mkdir cache
%mkdir configs
%mkdir templates
%mkdir templates_c
The directories you just created must be readable by the web server, and two of them must also be writable. To do this on Unix, set the group permissions appropriately, and then change their group to be that used by the web server. Set the group permissions with these commands:
%chmod g+rx cache configs templates templates_c
%chmod g+w cache templates_c
Next, determine the correct group name to use for the
directories. To do this, look in the Apache httpd.conf file for a Group
line, which might look like
this:
Group www
That line indicates that Apache runs using the www
group ID. Other common values are
nobody
or apache
. Use the group name in the
following command, which should be executed as root
:
#chgrp www cache configs templates templates_c
That completes the Smarty directory setup. You can deploy the Smarty demonstration application by copying sm_demo.php to the mcb directory and sm_demo.tmpl to the mcb/templates directory. Then request the script with your web browser:
http://localhost/mcb/sm_demo.php
For each of the following applications, follow the same principle that the PHP script goes in the mcb directory and the Smarty template goes in mcb/templates.
If you want to keep your application subdirectories outside of your document tree, create them somewhere other than in the mcb directory. In this case, you need to let your PHP scripts know where they are by setting several members of your Smarty object after you create it. For example, if you create the Smarty directories under /usr/local/lib/mcb/smarty, you modify your scripts to tell Smarty about their location as follows:
$smarty = new Smarty (); $smarty->cache_dir = "/usr/local/lib/mcb/smarty/cache"; $smarty->config_dir = "/usr/local/lib/mcb/smarty/configs"; $smarty->template_dir = "/usr/local/lib/mcb/smarty/templates"; $smarty->compile_dir = "/usr/local/lib/mcb/smarty/templates_c";
Paragraph generation. The Smarty equivalent to the PageTemplate file shown earlier for generating paragraphs is as follows:
<!-- sm_paragraphs.tmpl --> <html> <head> <title>{$title|escape}</title> </head> <body bgcolor="white"> <p>Local time on the MySQL server is {$now|escape}.</p> <p>The server version is {$version|escape}.</p> <p>The current user is {$user|escape}.</p> <p>The default database is {$db|escape}.</p> </body> </html>
The template uses nothing more than {$
var_name
}
value substitution, plus the escape
modifier to tell Smarty to perform
HTML-escaping on the values.
To retrieve the data referred to in the template and then invoke Smarty, use this script:
<?php # sm_paragraphs.php - generate HTML paragraphs require_once "Smarty.class.php"; require_once "Cookbook.php"; $title = "Query Output Display - Paragraphs"; $conn =& Cookbook::connect (); if (PEAR::isError ($conn)) die ("Cannot connect to server: " . htmlspecialchars ($conn->getMessage ())); $result =& $conn->query ("SELECT NOW(), VERSION(), USER(), DATABASE()"); if (PEAR::isError ($result)) die (htmlspecialchars ($result->getMessage ())); list ($now, $version, $user, $db) = $result->fetchRow (); $result->free (); if (!isset ($db)) $db = "NONE"; $conn->disconnect (); $smarty = new Smarty (); $smarty->assign ("title", $title); $smarty->assign ("now", $now); $smarty->assign ("version", $version); $smarty->assign ("user", $user); $smarty->assign ("db", $db); $smarty->display ("sm_paragraphs.tmpl"); ?>
List generation. The following template generates three lists. The first two are ordered and unordered lists that use the same set of scalar item values. The third list is a definition list that requires two values per iteration through a set of items:
<!-- sm_lists.tmpl --> <html> <head> <title>{$title|escape}</title> </head> <body bgcolor="white"> <p>Ordered list:</p> <ol> {foreach from=$list item=cur_item} <li>{$cur_item|escape}</li> {/foreach} </ol> <p>Unordered list:</p> <ul> {foreach from=$list item=cur_item} <li>{$cur_item|escape}</li> {/foreach} </ul> <p>Definition list:</p> <dl> {section loop=$defn_list name=cur_item} <dt>{$defn_list[cur_item].note|escape}</dt> <dd>{$defn_list[cur_item].mnemonic|escape}</dd> {/section} </dl> </body> </html>
The definition list uses the {section}
command and the $
list
[
item
].
member
notation mentioned earlier for referring to members of structured
values.
The script that processes the template needs to fetch the
values for the ordered and unordered lists as an array of scalar
values. For the definition list, the script should create an array
of associative arrays that have members named note
and mnemonic
:
<?php # sm_lists.php - generate HTML lists require_once "Smarty.class.php"; require_once "Cookbook.php"; $title = "Query Output Display - Lists"; $conn =& Cookbook::connect (); if (PEAR::isError ($conn)) die ("Cannot connect to server: " . htmlspecialchars ($conn->getMessage ())); # Fetch items for ordered, unordered lists # (create an array of scalar values) $stmt = "SELECT item FROM ingredient ORDER BY id"; $result =& $conn->query ($stmt); if (PEAR::isError ($result)) die (htmlspecialchars ($result->getMessage ())); $list = array (); while (list ($item) = $result->fetchRow ()) $list[] = $item; $result->free (); # Fetch terms and definitions for a definition list # (create an array of associative arrays) $stmt = "SELECT note, mnemonic FROM doremi ORDER BY id"; $defn_list =& $conn->getAll ($stmt, array (), DB_FETCHMODE_ASSOC); if (PEAR::isError ($defn_list)) die (htmlspecialchars ($result->getMessage ())); $conn->disconnect (); $smarty = new Smarty (); $smarty->assign ("title", $title); $smarty->assign ("list", $list); $smarty->assign ("defn_list", $defn_list); $smarty->display ("sm_lists.tmpl"); ?>
The script uses getAll()
to fetch the values for the
definition list. This is a connection object method that executes a
query and retrieves the result set in a single call. By setting the
fetch mode to DB_FETCHMODE_ASSOC
,
you get an array of associative arrays, each of which has members
named note
and mnemonic
—perfect for creating an HTML
definition list in Smarty.
Table
generation. To generate an HTML table, we need an iterator for looping through the list of
rows to produce a <tr>
element per row. As with the definition list in the previous
application, this is easily done by using a {section}
command and referring to the
values for cells within the row by using $
list
[
item
].
member
syntax. The following script uses that syntax to generate two tables
(one “plain” table, and one that has alternating row
colors):
<!-- sm_tables.tmpl --> <html> <head> <title>{$title|escape}</title> </head> <body bgcolor="white"> <p>HTML table:</p> <table border="1"> <tr> <th>Year</th> <th>Artist</th> <th>Title</th> </tr> {section loop=$rows name=row} <tr> <td>{$rows[row].year|escape}</td> <td>{$rows[row].artist|escape}</td> <td>{$rows[row].title|escape}</td> </tr> {/section} </table> <p>HTML table with rows in alternating colors:</p> <table border="1"> <tr> <th bgcolor="silver">Year</th> <th bgcolor="silver">Artist</th> <th bgcolor="silver">Title</th> </tr> {section loop=$rows name=row} <tr bgcolor="{cycle values="white,silver"}"> <td>{$rows[row].year|escape}</td> <td>{$rows[row].artist|escape}</td> <td>{$rows[row].title|escape}</td> </tr> {/section} </table> </body> </html>
To produce alternating row colors for the second table, the
template uses a {cycle}
command,
which alternately selects white
and silver
for successive
iterations through the row list. {cycle}
provides a nice convenience here
for a task that you would otherwise handle using a conditional and
the $smarty.section.
list
.iteration
value that returns the current
iteration number (beginning with 1):
{if $smarty.section.row.iteration % 2 == 1} <tr bgcolor="white"> {else} <tr bgcolor="silver"> {/if}
The following script fetches the table rows and processes the template:
<?php # smtables.php - generate HTML tables require_once "Smarty.class.php"; require_once "Cookbook.php"; $title = "Query Output Display - Tables"; $conn =& Cookbook::connect (); if (PEAR::isError ($conn)) die ("Cannot connect to server: " . htmlspecialchars ($conn->getMessage ())); # Fetch items for table $stmt = "SELECT year, artist, title FROM cd ORDER BY artist, year"; $rows =& $conn->getAll ($stmt, array (), DB_FETCHMODE_ASSOC); if (PEAR::isError ($rows)) die (htmlspecialchars ($result->getMessage ())); $conn->disconnect (); $smarty = new Smarty (); $smarty->assign ("title", $title); $smarty->assign ("rows", $rows); $smarty->display ("sm_tables.tmpl"); ?>
13.58.44.229