Enhancing the Catalog

Basic Enhancements

Now that you have a working catalog, you can go back and add improvements and test them incrementally. This section walks you through several, and then suggests even more enhancements you can attempt to do on your own (the exercises for the readers are fantastic, aren't they :).

Price Pictures

You may have noticed that the product prices aren't formatted as prices usually are. The way to correct this is with an Interchange feature called price pictures.

There are several properties to price pictures: the currency symbol, the thousands separator, the decimal point, the number of digits to show behind the decimal, and so on. Most Unix systems have U.S. currency and the English language as the default locale, which is called en_US. The only thing you need to do on such a system is specify the currency symbol, which, in this case, is the dollar sign. To do this, add the following line to your catalog.cfg file:

Locale en_US currency_symbol $
		

Restart Interchange and view your catalog. You will notice little has changed on the welcome page or the flypages, but in the shopping cart (pages/ord/basket.html) all your prices should be formatted as U.S. dollars ("1347.3" has become "$1,347.30"). Why the currency is only displayed on the basket page is easy to understand; we use the [item-price] tag there. That tag is equivalent to [item-field price] used elsewhere, but it has that extra logic associated with it that automatically displays the currency format. To use [item-price] without the auto-format, you'd have to change the [item-price] tag to [item-price noformat].

But that's probably not what you want to do. You're probably more interested in formatting all the other prices (such as those on the Welcome page) to a proper currency-dependent display. To do that, you could obviously replace [item-field price] with [item-price], but we'll take on more general approach here. Simply use the [currency] tag for all price values. Make the following change to pages/index.html:

  [loop search="ra=yes/fi=products"]
  <tr>
    <td>[loop-code]</td>
    <td>
      <a href="[loop-code].html">
      [loop-field description]
      </a>
    </td>
-    <td align="right">[loop-field price]</td>
+    <td align="right">[currency][loop-field price][/currency]</td>
    <td>[order [loop-code]]Order Now</a></td>
  </tr>
  [/loop]
    

[Note] Note

The line that begins with '-' should be deleted. Do not type the '-'. The next line, that starts with '+', replaces it. (It's the context diff format we mentioned, remember?)

A similar change to the [item-field price] tag in the pages/flypage.html page will fix that currency display. View the page in your browser. All your prices should be formatted for U.S. currency.

If your prices are not being formatted correctly, your default system locale may be set up differently or your en_US locale settings may be wrong. There are a few other catalog.cfg directives you can use to correct the situation:

Locale en_US p_cs_precedes 1
		

Makes the currency symbol precede the currency value. A '0' setting makes the symbol come after the currency value.

Locale en_US mon_thousands_sep ,
		

Sets your thousands separator to a comma. It can be set to any value.

Locale en_US mon_decimal_point .
		

Sets your decimal separator to a comma. Many countries use a comma instead of a period to separate the integer from the decimal part.

[Note] Note

Consult the Interchange documentation and your operating system manual for more information on locale settings.

Catalog Variables

Interchange provides a very useful feature that has not been discussed yet called catalog variables. It provides a way for you to set a variable to a certain value in the catalog.cfg file and then use it anywhere in your catalog pages. The Variable directive allows an Interchange catalog variable to be created with the name coming from the first parameter and the value from the rest of the line, like this:

Variable SOMENAME whatever value you want
		

To access that variable in your pages, type the token . Notice that there are two underscore characters before the variable name and two after it, and that in place of the word SOMENAME you would put the actual name of the variable. The first thing Interchange does on a page is to replace the token with the variable's value. The variable value can also include Interchange tags to be parsed.

This was an example of a catalog variable. There are also global variables which are defined in the same way (but in the interchange.cfg file), and are accessed using the token. A convenient, syntax is also valid, and first checks the existence of a variable in the local catalog, falling back to the global value if no catalog-specific value has been set.

More Interesting Page Footer

You can put a contact email address at the bottom of each page in case your customers want to contact you. You could just add it to the footer, but by putting it into a variable you can use it in contact pages as well. This allows you to easily change the variable information and have that change reflected in all instances of that variable. The following is an example of how to set a catalog variable in catalog.cfg:

Variable CONTACT_EMAIL myname.surname@mydomain.local
		

Now make the following change to your template file bottom:

  </td>
  </tr>
  <tr><td colspan="2" align="center">
-    (bottom)
+    <a href="mailto:">Contact us</a> if you have any questions.
  </td>
  </tr>
  </table>
  </div>
</body>
</html>
    

Be sure to restart Interchange (or reconfig the catalog at least) before reloading the page in your browser, since you made a change to catalog.cfg.

Let's add another variable to your catalog. This variable demonstrates how an Interchange tag can be included in the variable. This Interchange tag returns the current date in a standard format. Add the following to catalog.cfg:

Variable DISPLAYDATE [time]%A, %B %d, %Y[/time]
		

Now add the following to the left template piece:

  <tr>
  <td align="center">
-    (left)
+    
  </td>
  <td align="center">
    

Restart Interchange and view the page.

Advanced Credit Card Expiration Date Options

To reduce the possibility of human error at checkout time, most on-line stores use a pull-down option menu to list the months and the years for the credit card expiration date, instead of having the user to type the numbers by hand. The menu also lets you avoid explaining whether the user should enter a 2- or 4-digit year.

Make the following change to your pages/checkout.html page. The section that follows explains the code. Read the explanation section below before typing the code to be sure you know where tabs should be used instead of spaces and where to watch out for `back-ticks`.

  <tr>
  <td align=right><b>Credit card expiration date:</b></td>
  <td>
- Month (number from 1-12):
- <input type=text name=mv_credit_card_exp_month value="" size=2 maxlength=2>
- <br>
- Year (last two digits only):
- <input type=text name=mv_credit_card_exp_year value="" size=2 maxlength=2>
+
+ Month:
+ <select name=mv_credit_card_exp_month>
+ [loop
+    lr=1
+    option=mv_credit_card_exp_month
+    list="
+ 1     01 - January
+ 2     02 - February
+ 3     03 - March
+ 4     04 - April
+ 5     05 - May
+ 6     06 - June
+ 7     07 - July
+ 8     08 - August
+ 9     09 - September
+ 10    10 - October
+ 11    11 - November
+ 12    12 - December"]
+ <option value="[loop-code]">[loop-pos 1]
+ [/loop]
+ </select>
+
+ Year:
+ <select name=mv_credit_card_exp_year>
+ 
+ [loop option=mv_credit_card_exp_year lr=1 list=`
+   my $year = $Tag->time( '', { format => '%Y' }, '%Y' );
+   my $out = '';
+   for ($year .. $year + 7) {
+     /\d\d(\d\d)/;
+     $last_two = $1;
+     $out .= "$last_two\t$_\n";
+   }
+   return $out;
+ `]
+   <option value="[loop-code]">[loop-pos 1]
+ [/loop]
+ </select>
+
  </td>
  </tr>

  </table>
    

In the first set of <select> </select> HTML tags a list is generated of the months to choose from. This is accomplished by using a [loop] tag. In this case we are looping over an explicit list. The list is provided in the list parameter. Use caution when typing this, as it is sensitive to formatting (which may not be reflected in this document). Make sure that the numbers are the first characters on each new line and that the single tab separates them from the rest of the line text. Since the columns in this list are not named, the first element can be accessed using [loop-code] or [loop-pos 0] with subsequent elements being accessed by [loop-pos N] where N is the number of the column you want. Notice that the elements are zero-indexed. Each time through this loop Interchange generates a select <option> with a number as the value and the name of the month as the text for the select menu.

For the next set of <select> </select> tags embedded Perl is used to generate the list which is iterated over. Perl code can be embedded in Interchange pages in order to extend the abilities of the system. Make sure you type back-ticks (grave accents) after "list=" and before the closing bracket (and not apostrophes). This code generates an entry for seven years in addition to the current year. It is not necessary at this point for you to understand this Perl code.

Sorting the Product List

The products listed on your welcome page are shown in the same order that you entered them into products/products.txt. As you add more products, you will want this list to show up in a predictable order. To do this, you need to change the search parameters in pages/index.html, which were originally:

  [loop search="
    ra=yes
    fi=products
  "]
    

You will recall that 'ra' stands for 'return all' and 'fi' stands for file. Let's add the search parameter 'tf', which specifies the sort field. You can specify the field either by name or by number (starting with 0), with names and order as given in the first line of products/products.txt). Make the following change in pages/index.html:

  [loop search="
    ra=yes
    fi=products
    tf=price
  "]
    

Refresh your browser. The default ordering is done on a character-by-character basis, but we were looking to do a numeric sort. For this you need to set 'to', the sort order, to 'n', for numeric:

  [loop search="
    ra=yes
    fi=products
    tf=price
    to=n
  "]
    

Refresh your browser. Now try reversing the sort order by adding 'r' to the 'to' setting:

  [loop search="
    ra=yes
    fi=products
    tf=2
    to=nr
  "]
    

You'll notice that it worked equally well to specify the sort field by number instead of name. You could also do a reverse alphabetical sort by description:

  [loop search="
    ra=yes
    fi=products
    tf=1
    to=r
  "]
    

Now let's try narrowing the search down a bit. Instead of returning all, we'll give 'se', the search parameter, and and use 'su', which allows substring matches. To search only for products that contain substring "test" in any of their fields, and sort the results by description, type:

  [loop search="
    se=test
    su=yes
    fi=products
    tf=description
  "]
    

Which seems like something that would be better done in a search box for your store visitors. So we'll implement a search box.

But before moving on, just change this search back to the simple list, sorted by description:

  [loop search="ra=yes/fi=products/tf=description"]
    

Adding a Search Box

Your customers might appreciate the ability to search for a test by SKU or part of the test description. To do this, you need to add a search box to the left portion of the page layout. Make the following change to the file left:

  <tr>
  <td align="center">
-    
+ <form action="[area search]" method="post">
+ [form-session-id]
+ Search:<br>
+ [set testname]
+ su=yes
+ fi=products
+ sf=sk
+ sf=description
+ [/set]
+ <input type="hidden" name="mv_profile" value="testname">
+ <input type="text" name="mv_searchspec" size="15" value="">
+ </form>
+ <hr>
+ 
  </td>
  <td align="center">
    

This is a simple HTML form with a single input box for text. The action goes to a special Interchange processor called 'search' that will perform the search and pass the results to a page called pages/results.html (that has not been created yet). The search will be case-insensitive, but will only match complete words, not substrings.

The [set testname]...[/set] tag sets an Interchange scratch variable that will be (in this case, of course) used as a predefined search profile. We specify all the search parameters except the one the user will enter, mv_searchspec (the long name for 'se'). We then tell Interchange we want to use this search profile in a hidden form variable named mv_profile.

The search box will now appear on all catalog pages, but you still need to create the search results page. To create the search results page, type the following code and save it as pages/results.html.

[include top]
[include left]

<h3>Search Results</h3>

[search-region]

  [on-match]
  <table cellpadding="5">
  <tr>
  <th>Test #</th>
  <th>Description</th>
  <th>Price</th>
  </tr>
  [/on-match]

  [search-list]
  <tr>
  <td>[item-code]</td>
  <td><a href="[item-code].html">[item-field description]</a></td>
  <td align="right">[item-field price]</td>
  <td>[order [item-code]]order now</a></td>
  </tr>
  [/search-list]

  [on-match]
  </table>
  [/on-match]
  [no-match]
  <p>Sorry, no matches were found for '[cgi mv_searchspec]'.</p>
  [/no-match]
[/search-region]

<hr/>
<p align="center">[page index]Return to welcome page</a></p>
<p align="center">[page order]View shopping cart</a></p>

[include bottom]

		

The search results will be contained in the [search-region] [/search-region] tags. The text in the [on-match] [/on-match] container will be displayed only if matches were found for the search. The text in the [no-match] [/no-match] container will be displayed only if no matches were found. The [search-list] [/search-list] container functions just like [loop] [/loop], iterating over its contents for each item in the search results list.

Default Catalog Page

As you know, a standard Interchange catalog page URL looks like http://myhost.mydomain.local/cgi-bin/ic/tutorial/index.html.

But what happens if you leave off the page name, as people often do when typing URLs in by hand? Visit http://myhost.mydomain.local/cgi-bin/ic/tutorial and you'll get a Page not found error. This is because Interchange is looking for special_pages/catalog.html which we didn't create. It would seem useful to "redirect" those requests to an actual existing page, most probably your catalog entry page - pages/index.html. Fortunately, this is easily achieved with the following catalog.cfg directives[4]:

SpecialPage catalog index
DirectoryIndex index
		

Restart Interchange and try the above URL again. If you want to make the welcome page something other than pages/index.html, modify the 'index' part of the directives appropriately.

High-traffic Changes

During this tutorial you have created catalog pages that use the [include] tag to include template pieces in the pages. This has worked well, but there are a few drawbacks. First, if you want to rename any of the template piece files or move them out of the main catalog directory and into their own subdirectory, you would have to update the [include] tag on every page. To avoid this, you can create catalog variables set to the [include] tags. Add these lines to your catalog.cfg file:

Variable TOP    [include top]
Variable LEFT   [include left]
Variable BOTTOM [include bottom]
		

Now change every instance of [include top] to , doing the same for each [include] tag. At this point, you might not want to do a search-and-replace[5] on all the .html files you just created, but keep this capability in mind for the next catalog you work on.

If you made all of the replacements and then renamed and moved your top file elsewhere, you would only have to make a single change for each region in catalog.cfg to get your pages up to date:

Variable TOP    [include templates/main-top]
		

Every time a catalog page is viewed, each file in an [include] tag must be loaded from disk. In a test situation, this takes no noticeable amount of time. But on a busy Interchange server, this can slow your system.

You can switch to a high-traffic mode that doesn't require each template piece to be read from disk every time the page is loaded. Instead, all of the pieces are read into variables once when Interchange is started and they remain in memory until you restart Interchange, or reconfigure the catalog. On very busy Interchange catalogs, this can increase your speed noticeably. The (only) drawback is that you need to restart Interchange (or reconfigure the catalog) every time you make a change to the template pieces. You can set up those high-traffic templates by changing the Variable directives in catalog.cfg as follows:

Variable TOP    <top
Variable LEFT   <left
Variable BOTTOM <bottom
    

Phase 5: Tutorial Files

We have prepared the files from this tutorial phase for download in .tar, .tar.gz, .tar.bz2, and expanded formats.



[4] We need two directives; the first one, SpecialPage, only handles the catalog entry point (the cgi-bin/ic/tutorial/ case). The other, somewhat Apache-like DirectoryIndex directive, sets the index page for catalog subdirectories (say, cgi-bin/ic/tutorial/mydir/), but unlike Apache it only takes one argument.

[5] If you're adventurous enough, try this Perl one-liner on the command line for automatic replace: cd /var/lib/interchange/catalogs/tutorial; perl -pi -e 's/\[include (\S+)\]/"__".uc($1)."__"/ge' `find . -name '*.html'`

DocBook! Interchange!