I will first discuss writing the ShoppingCart class as it is used by the storefront files. Then I will discuss the two front-end files, shop.php and cart.php. Finally, I will cover the administrative files, which you will use to populate the inventory database with entries.
The first component I will present is the ShoppingCart class. The class will be responsible for maintaining the list of items shoppers have selected until they are ready to check out. Essentially this list is an array, but various methods and properties will be made available to make working with the list easier. Table 8-1 shows the properties ShoppingCart will expose and Table 8-2 shows the methods.
Property | Description |
---|---|
contents | Returns the entire contents of the cart as an array |
isEmpty | Returns Boolean whether or not the cart is empty |
totalItems | Returns the total number of distinct items in the cart |
totalQty | Returns the total quantity of items in the cart |
The class will need one private property to store the list of products which will be initialized to a blank array in the constructor. The array will later store the quantity of each item keyed by the product id when items are added to the cart.
class ShoppingCart { private $items; public function __construct() { $this->items = array(); } ... }
The public properties offered by ShoppingCart should be read-only and offer a view of the current state of the cart. Thus, I have decided to expose them through __get(). They don't exist as variables within the class but rather they are calculated whenever their values are needed. A switch is used to determine which property was called and return the correct information.
public function __get($value) { switch ($value) { case 'contents':
return $this->items; brake; case 'isEmpty': return (count($this->items) == 0); break; case 'totalItems': return count($this->items); break; case 'totalQty': return array_sum($this->items); break; } }
To add a product to the shopping cart, addItem() accepts the item's id and then assigns the item to the internal items property. By default, the quantity will be 1 unless an optional value is also passed.
public function addItem($item, $qty = 1) { $this->items[$item] = $qty; }
The quantity of a product in the cart can be verified with the qtyItem(). It accepts the item id and checks the internal list. If the requested item isn't found then qtyItem() returns 0.
public function qtyItem($item) { if (!isset($this->items[$item])) { return 0; } else { return $this->items[$item]; } }
Removing items from the cart may be done with the removeItem() and removeAll() methods. removeItem() removes a particular product indicated by the item's id while removeAll() will purge all items from the cart by reassigning an empty array to the internal list.
public function removeItem($item) { unset($this->items[$item]); } public function removeAll() { $this->items = array(); }
Here is the entire code listing for lib/ShoppingCart.php:
<?php class ShoppingCart { // collection of items placed in the shopping cart private $items; // initialize a ShoppingCart object public function __construct() { $this->items = array(); } // expose read-only convenience properties public function __get($value) { switch ($value) { // contents - returns the entire contents of the cart case 'contents': return $this->items; brake; // isEmpty - returns whether or not the cart is empty case 'isEmpty': return (count($this->items) == 0); break; // totalItems - returns the total number of distinct items // in the cart case 'totalItems': return count($this->items); break; // totalQty - returns the total quantity of items in the cart case 'totalQty': return array_sum($this->total); break; } } // add an item to the shopping cart public function addItem($item, $qty = 1) { if (!$qty) { $this->removeItem($item); }
else { $this->items[$item] = $qty; } } // returns an item's quantity in the cart public function qtyItem($item) { if (!isset($this->items[$item])) { return 0; } else { return $this->items[$item]; } } // empty the contents of the shopping cart public function removeAll() { $this->items = array(); } // remove an item from the shopping cart public function removeItem($item) { unset($this->items[$item]); } } ?>
Most of the work done with the shopping cart will be in cart.php. Calls from shop.php to add and remove items from the cart will be made to cart.php, which will take the appropriate action and redirect to the storefront. For example, cart.php?add&item=xxx will add the indicated item to the cart and cart.php?remove&item=xxx will remove it. A call to cart.php?empty will completely empty the cart. Therefore the first part of cart.php will need to create or resume the session and obtain an instance of the ShoppingCart class. Then any incoming parameters will be analyzed, validated and acted upon.
Before the script redirects the visitor to another page, the shopping cart must be serialized and stored back to the $_SESSION array. The serialize() function converts data structures such as arrays and objects into a representation that can safely be stored. The representation is passed to unserialize() which recreates the data structure and its state.
include '../lib/ShoppingCart.php'; session_start(); if (isset($_SESSION['cart'])) { $cart = unserialize($_SESSION['cart']); } else { $cart = new ShoppingCart(); } if (isset($_GET['empty'])) { $cart->removeAll(); $_SESSION['cart'] = serialize($cart); header('Location: shop.php'), end(); }
In the case of objects, in order to successfully unserialize the data structure the class definition must be available. Just the object's properties and state are stored — not the method definitions. This is important to remember because lib/ShoppingCart.php must be included in both shop.php and cart.php as the ShoppingCart object will be passed between the two in the user's session. You cannot simply serialize an object in one script and unserialize it in another without the class's definition available or PHP will generate an error such as the following:
Notice: main() [function.main]: The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "ShoppingCart" of the object you are trying to operate on was loaded before unserialize() gets called or provide an __autoload() function to load the class definition.
Both calls to add and remove an item from the cart must include an item parameter so this commonality should be checked first and validated. If it is valid, then the appropriate action is performed on the cart depending on whether add or remove was detected.
if (isset($_GET['item'])) { $query = sprintf('SELECT ITEM_ID FROM %sSHOP_INVENTORY WHERE ' . 'ITEM_ID = %d', DB_TBL_PREFIX, $_GET['item']); $result = mysql_query($query, $GLOBALS['DB']); if (mysql_num_rows($result)) { $row = mysql_fetch_assoc($result); $item = $row['ITEM_ID']; // add item to cart if (isset($_GET['add']))
{ $cart->addItem($item); } // remove item from cart else if (isset($_GET['remove'])) { $cart->removeItem($item); } } mysql_free_result($result); $_SESSION['cart'] = serialize($cart); header('Location: ' . htmlspecialchars($_SERVER['HTTP_REFERER'])); exit(); }
A for loop can be used to iterate through the contents of the cart to present an order summary. Only the item's id and quantity are stored in the cart so a query is made to retrieve the item's description and pricing information.
if ($cart->isEmpty) { echo '<p><b>Your cart is empty.</b></p>'; } else { $total = 0; echo '<table>'; echo '<tr><th>Item</th><th>Qty</th><th>Price</th><th>Total</th></tr>'; foreach ($cart->contents as $id => $qty) { $query = sprintf('SELECT ITEM_NAME, PRICE FROM %sSHOP_INVENTORY ' . 'WHERE ITEM_ID = %d', DB_TBL_PREFIX, $id); $result = mysql_query($query, $GLOBALS['DB']); $row = mysql_fetch_assoc($result); echo '<tr>'; echo '<td>' . $row['ITEM_NAME'] . '</td>'; echo '<td>' . $qty . '</td>'; echo '<td>$' . number_format($row['PRICE'], 2) . '</td>'; echo '<td>$' . number_format($row['PRICE'] * $qty, 2) . '</td></tr>'; $total += $row['PRICE'] * $qty; mysql_free_result($result); } echo '</table>'; echo '<p>Total Items: ' . $cart->totalItems . '<br/>'; echo 'Total Quantity: ' . $cart->totalQty . '</p>'; echo '<p><b>Total Price: $' . number_format($total, 2) . '</b></p>'; }
This summary doesn't allow the user to adjust the quantity of an item in his cart though which presents a minor problem. This can be easily resolved by augmenting the display with HTML form tags and replacing the quantity value with select elements. The select lists will be preset with the item's current quantity but the shopper will be able to change them to adjust the number of an item in the cart as desired. (A select list limits the number of items a customer may purchase. Depending on your checkout procedure, you may provide them more flexibility with their quantities by providing a text input field.)
echo '<form method="post" action="cart.php?update">'; ... echo '<select name="qty[' . $id . ']">'; for ($i=0; $i < 11; $i++) { echo '<option '; echo ($i == $qty) ? 'selected="selected" ' : ''; echo 'value="' . $i . '">' . $i . '</option>'; } echo '</select>'; ... echo '<input type="submit" value="Update"/>';
The form should submit itself back to cart.php and append an update parameter to the URL. Prior to displaying the summary table the parameter can be identified and the contents of the cart updated accordingly.
if (isset($_GET['update'])) { foreach ($_POST['qty'] as $item => $qty) { $cart->addItem($item, $qty); } }
Links to navigate back and forth within the shopping system should also be made available to the shopper. For example, a link to view the category lists should appear at the top of a page. If the shopper came from viewing the list of items in a particular category, then a link to return there should be made available as well. A category parameter can be passed to cart.php so the script knows which category to link back to. For now note that this script will be named shop.php; I discuss it in the next section.
echo '<p><a href="shop.php">Back to all categories</a>'; if (isset($_GET['category'])) { $query = sprintf('SELECT CATEGORY_ID, CATEGORY_NAME FROM ' . '%sSHOP_CATEGORY WHERE CATEGORY_ID = %d', DB_TBL_PREFIX, $_GET['category']); $result = mysql_query($query, $GLOBALS['DB']); if (mysql_num_rows($result))
{ $row = mysql_fetch_assoc($result); echo ' / <a href="shop.php?category=' . $row['CATEGORY_ID'] . '">Back to ' . $row['CATEGORY_NAME'] . '</a>'; } mysql_free_result($result); } echo '</p>';
The address given in the form element's action property must be modified to include the category value if it exists so that shoppers won't lose the link to the previous category when they update the quantities in their cart.
echo '<form method="post" action="cart.php?update'; if (isset($row['CATEGORY_ID'])) { echo '&category=' . $row['CATEGORY_ID']; } echo '">';
The item name in the listing can also be made into a link to lead the shopper back to the product's description page.
echo '<a href="shop.php?item=' . $id . '">' . $row['ITEM_NAME'] . '</a>';
Figure 8-1 shows cart.php in action by displaying the contents of a user's shopping cart.
Here is the complete code listing for public_files/cart.php:
<?php include '../lib/common.php'; include '../lib/db.php'; include '../lib/ShoppingCart.php'; // create or resume session and retrieve shopping cart session_start(); if (isset($_SESSION['cart'])) { $cart = unserialize($_SESSION['cart']); } else { $cart = new ShoppingCart(); } // empty the shopping cart and redirect user to list of categories if (isset($_GET['empty'])) { $cart->removeAll(); $_SESSION['cart'] = serialize($cart); header('Location: shop.php'), end(); } // item parameter indicates an attempt to add or remove items if (isset($_GET['item'])) { // verify item is valid $query = sprintf('SELECT ITEM_ID FROM %sSHOP_INVENTORY WHERE ' . 'ITEM_ID = %d', DB_TBL_PREFIX, $_GET['item']); $result = mysql_query($query, $GLOBALS['DB']); if (mysql_num_rows($result)) { $row = mysql_fetch_assoc($result); $item = $row['ITEM_ID']; // add item to cart if (isset($_GET['add'])) { $cart->addItem($item); } // remove item from cart else if (isset($_GET['remove'])) { $cart->removeItem($item); } }
mysql_free_result($result); // save cart to session and redirect to the previously viewed page $_SESSION['cart'] = serialize($cart); header('Location: ' . htmlspecialchars($_SERVER['HTTP_REFERER'])); exit(); } // view shopping cart's contents else { // update item quantities in shopping cart if (isset($_GET['update'])) { foreach ($_POST['qty'] as $item => $qty) { $cart->addItem($item, $qty); } } ob_start(); echo '<h1>Your Cart</h1>'; echo '<p><a href="shop.php">Back to all categories</a>'; // verify category parameter and construct suitable back link if passed if (isset($_GET['category'])) { $query = sprintf('SELECT CATEGORY_ID, CATEGORY_NAME FROM ' . '%sSHOP_CATEGORY WHERE CATEGORY_ID = %d', DB_TBL_PREFIX, $_GET['category']); $result = mysql_query($query, $GLOBALS['DB']); if (mysql_num_rows($result)) { $row = mysql_fetch_assoc($result); echo ' / <a href="shop.php?category=' . $row['CATEGORY_ID'] . '">Back to ' . $row['CATEGORY_NAME'] . '</a>'; } mysql_free_result($result); } echo '</p>'; if ($cart->isEmpty) { echo '<p><b>Your cart is empty.</b></p>'; } else
{ // display empty cart link echo '<p><a href="cart.php?empty">'; echo '<img src="img/cartempty.gif" alt="Empty Cart"/></a></p>'; // encapsulate list in form so quantities may be changed echo '<form method="post" action="cart.php?update'; // if a category was passed and was validated successfully earlier // then append it to the action url so the back link remains available if (isset($row['CATEGORY_ID'])) { echo '&category=' . $row['CATEGORY_ID']; } echo '">'; // list each item in the cart, keeping track of total price $total = 0; echo '<table>'; echo '<tr><th>Item</th><th>Qty</th><th>Price</th><th>Total</th></tr>'; foreach ($cart->contents as $id => $qty) { $query = sprintf('SELECT ITEM_NAME, PRICE FROM %sSHOP_INVENTORY ' . 'WHERE ITEM_ID = %d', DB_TBL_PREFIX, $id); $result = mysql_query($query, $GLOBALS['DB']); $row = mysql_fetch_assoc($result); echo '<tr>'; echo '<td><a href="shop.php?item=' . $id . '">' . $row['ITEM_NAME'] . '</a></td>'; echo '<td><select name="qty[' . $id . ']">'; for ($i=0; $i < 11; $i++) { echo '<option '; if ($i == $qty) { echo 'selected="selected" '; } echo 'value="' . $i . '">' . $i . '</option>'; } echo '</td>'; echo '<td>$' . number_format($row['PRICE'], 2) . '</td>'; echo '<td>$' . number_format($row['PRICE'] * $qty, 2) . '</td>'; echo '</tr>'; $total += $row['PRICE'] * $qty; mysql_free_result($result); } echo '</table>'; echo '<input type="submit" value="Update"/>';
echo '<p>Total Items: ' . $cart->totalItems . '<br/>'; echo 'Total Quantity: ' . $cart->totalQty . '</p>'; echo '<p><b>Total Price: $' . number_format($total, 2) . '</b></p>'; // display link to checkout echo '<p><a href="checkout.php">'; echo '<img src="img/checkout.gif" alt="Proceed to Checkout"/></a></p>'; } // save cart to session and display the page $_SESSION['cart'] = serialize($cart); $GLOBALS['TEMPLATE']['content'] = ob_get_clean(); include '../templates/ template-page.php'; } ?>
The next file to be presented is shop.php. You've seen it referenced in cart.php and it is used to display categories, product lists, and individual product descriptions.
Like cart.php, shop.php begins with creating or resuming an existing session and obtaining a reference to a ShoppingCart object. It doesn't modify the contents of the cart but still needs to have the cart available when showing the individual product page to determine whether to show an Add to cart or Remove from cart link.
include '../lib/ShoppingCart.php'; session_start(); if (isset($_SESSION['cart'])) { $cart = unserialize($_SESSION['cart']); } else { $cart = new ShoppingCart(); }
Depending on which parameters are passed to the script, its display behavior will be different. For example, if item=xxx is passed in the URL, then the script will display a product page. category=xxx will cause shop.php to generate a listing of all of the products within the requested category. No parameters will default to a list of all available categories. Invalid parameters will also redirect the shopper to the list of product categories.
if (isset($_GET['item'])) { // display sales page for a particular item ... } else if (isset($_GET['category']))
{ // display list of items in category ... } else { // display main list of categories ... }
You will need to verify the parameter's value is valid by querying the database. If no records are returned from the query then it is safe to presume the value is invalid and you should redirect the user to shop.php (providing no parameters) so they can see the list of categories. Here is a sample showing the item parameter's validation; the process is the same to validate category albeit with a different query.
$query = sprintf(' SELECT ITEM_ID, ITEM_NAME, ITEM_DESCRIPTION, PRICE, ITEM_IMAGE, C.CATEGORY_ID, CATEGORY_NAME FROM %sSHOP_INVENTORY I JOIN %sSHOP_CATEGORY C ON I.CATEGORY_ID = C.CATEGORY_ID WHERE ITEM_ID = %d', DB_TBL_PREFIX, DB_TBL_PREFIX, $_GET['item']); $result = mysql_query($query, $GLOBALS['DB']); if (!mysql_num_rows($result)) { mysql_free_result($result); header('Location: shop.php'), exit(); } $row = mysql_fetch_assoc($result);
Here is a query to validate category:
$query = sprintf('SELECT CATEGORY_ID, CATEGORY_NAME FROM ' . '%sSHOP_CATEGORY WHERE CATEGORY_ID = %d', DB_TBL_PREFIX, $_GET['category']);
Allow me to present the code responsible for generating the list of categories first. I realize that the logic appears in the last block of the shop.php page's main if structure. However, I would like to present the screens in the order in which users will see them.
A query is sent to the database to retrieve the list of product categories. By joining the WROX_SHOP_CATEGORY table against WROX_SHOP_INVENTORY and grouping the results by the category id, you are able to retrieve the number of items under each category at the same time. Use JOIN (instead of LEFT JOIN), so the categories without any items will not be returned in the result set.
You can display the results anyway you want, but I've chosen here to display them as a simple unordered list. This is shown in Figure 8-2.
$query = sprintf(' SELECT C.CATEGORY_ID, CATEGORY_NAME, COUNT(ITEM_ID) AS ITEM_COUNT FROM %sSHOP_CATEGORY C JOIN %sSHOP_INVENTORY I ON C.CATEGORY_ID = I.CATEGORY_ID GROUP BY C.CATEGORY_ID ORDER BY CATEGORY_NAME ASC', DB_TBL_PREFIX, DB_TBL_PREFIX); $result = mysql_query($query, $GLOBALS['DB']); echo '<ul>'; while ($row = mysql_fetch_assoc($result)) { printf('<li><a href="shop.php?category=%d">%s</a> (%d %s)</li>', $row['CATEGORY_ID'],
$row['CATEGORY_NAME'], $row['ITEM_COUNT'], (($row['ITEM_COUNT'] == 1) ? 'product' : 'products')); } mysql_free_result($result); echo '</ul>';
Moving forward in what the customer should see but backwards in the script's organization, you now address generating the list of items within the selected category. When the script detects the category parameter it retrieves the items' ids, names, and image URLs from the database and displays the information with a while loop. Figure 8-3 shows the category's contents displayed.
// retrieve items $query = sprintf('SELECT ITEM_ID, ITEM_NAME, ITEM_IMAGE ' . 'FROM %sSHOP_INVENTORY WHERE CATEGORY_ID = %d ORDER BY ITEM_NAME ASC', DB_TBL_PREFIX, $id); $result = mysql_query($query, $GLOBALS['DB']); while ($row = mysql_fetch_assoc($result)) { echo '<table>'; echo '<tr><td><img src="' . $row['ITEM_IMAGE'] . '" style="width:50px;height:50px;"/></td>'; echo '<td><a href="shop.php?item=' . $row['ITEM_ID'] . '">' . $row['ITEM_NAME'] . '</a>' . '</td></tr>'; echo '</table>'; }
The final view is generated when the item parameter is provided and displays a single product's information. The record is retrieved from the database and displayed. Also, this is where the ShoppingCart object is referenced, as there should be a link to add or remove the item from the shopping cart. If the product is not found in the cart, then the link should direct the user to add it by pointing to cart.php?add. Otherwise, the link would point to cart.php?remove so the shopper may remove it.
echo '<table>'; echo '<tr><td rowspan="3">'; echo '<img src="' . $row['ITEM_IMAGE'] . '"/></td>'; echo '<td><b>' . $row['ITEM_NAME'] . '</b></td></tr>'; echo '<tr><td>' . nl2br($row['ITEM_DESCRIPTION']) . '</td></tr>'; echo '<tr><td>$' . number_format($row['PRICE'], 2) . '<br/>'; if (!$cart->qtyItem($row['ITEM_ID'])) { echo '<a href="cart.php?add&item=' . $row['ITEM_ID'] . '">'; echo '<img src="img/cartadd.gif" alt="Add to Cart"/></a>'; } else { echo '<a href="cart.php?remove&item=' . $row['ITEM_ID'] . '">'; echo '<img src="img/cartremove.gif" alt="Remove from Cart"/></a>'; } echo '</td></tr>'; echo '</table>';
Figure 8-4 shows an item's individual product page.
You may have noticed in each figure links to view the contents of the shopping cart and to return to either the full category listing or, if suitable, a specific category's products. The code to generate the links for the category's product listing page must provide a category parameter to cart.php so it can reciprocate an appropriate back link. The link to view all the categories simply points to shop.php with no parameters so it will display its default view.
echo '<p><a href="cart.php?category=' . $id . '">'; echo '<img src="img/cartview.gif" alt="View Cart"/></a></p>'; echo '<p><a href="shop.php">Back to all categories</a></p>';
The links for the individual product's view should also include a category parameter that is obtained in the query to validate the item parameter it received. Here is the code to generate the links to the shopping cart, all categories view, and the category listing under which the item is organized.
echo '<p><a href="cart.php?category=' . $row['CATEGORY_ID'] . '">'; echo '<img src="img/cartview.gif" alt="View Cart"/></a></p>'; echo '<p><a href="shop.php">Back to all categories</a> / '; echo '<a href="shop.php?category=' . $row['CATEGORY_ID']. '">Back to ' . $row['CATEGORY_NAME'] . '</a></p>';
Here is the complete code listing for public_files/shop.php. Because I didn't discuss the file's logic linearly, please take extra time to read through it so you have an understanding of how the pieces I've discussed in this section come together.
<?php include '../lib/common.php'; include '../lib/db.php'; include '../lib/ShoppingCart.php'; // create or resume session and retrieve shopping cart session_start(); if (isset($_SESSION['cart'])) { $cart = unserialize($_SESSION['cart']); } else { $cart = new ShoppingCart(); } // display sales page for a particular item if (isset($_GET['item'])) { // verify item exists $query = sprintf(' SELECT ITEM_ID, ITEM_NAME, ITEM_DESCRIPTION, PRICE, ITEM_IMAGE, C.CATEGORY_ID, CATEGORY_NAME FROM %sSHOP_INVENTORY I JOIN %sSHOP_CATEGORY C ON I.CATEGORY_ID = C.CATEGORY_ID WHERE
ITEM_ID = %d', DB_TBL_PREFIX, DB_TBL_PREFIX, $_GET['item']); $result = mysql_query($query, $GLOBALS['DB']); // item does not exist so redirect to main categories list if (!mysql_num_rows($result)) { mysql_free_result($result); header('Location: shop.php'), exit(); } $row = mysql_fetch_assoc($result); ob_start(); echo '<p><a href="cart.php?category=' . $row['CATEGORY_ID'] . '">'; echo '<img src="img/cartview.gif" alt="View Cart"/></a></p>'; echo '<h1>' . $row['ITEM_NAME'] . '</h1>'; echo '<p><a href="shop.php">Back to all categories</a> / '; echo '<a href="shop.php?category=' . $row['CATEGORY_ID']. '">Back to ' . $row['CATEGORY_NAME'] . '</a></p>'; echo '<table>'; echo '<tr><td rowspan="3">'; echo '<img src="' . $row['ITEM_IMAGE'] . '"/></td>'; echo '<td><b>' . $row['ITEM_NAME'] . '</b></td></tr>'; echo '<tr><td>' . nl2br($row['ITEM_DESCRIPTION']) . '</td></tr>'; echo '<tr><td>$' . number_format($row['PRICE'], 2) . '<br/>'; // show link to either add or remove item from cart if (!$cart->qtyItem($row['ITEM_ID'])) { echo '<a href="cart.php?add&item=' . $row['ITEM_ID'] . '">'; echo '<img src="img/cartadd.gif" alt="Add to Cart"/></a>'; } else { echo '<a href="cart.php?remove&item=' . $row['ITEM_ID'] . '">'; echo '<img src="img/cartremove.gif" alt="Remove from Cart"/></a>'; } echo '</td></tr>'; echo '</table>'; $GLOBALS['TEMPLATE']['content'] = ob_get_clean(); } // display list of items in category else if (isset($_GET['category']))
{ // verify if category parameter is valid $query = sprintf('SELECT CATEGORY_ID, CATEGORY_NAME FROM ' . '%sSHOP_CATEGORY WHERE CATEGORY_ID = %d', DB_TBL_PREFIX, $_GET['category']); $result = mysql_query($query, $GLOBALS['DB']); // category does not exist so redirect to main categories list if (!mysql_num_rows($result)) { mysql_free_result($result); header('Location: shop.php'), exit(); } $row = mysql_fetch_assoc($result); $id = $row['CATEGORY_ID']; $name = $row['CATEGORY_NAME']; mysql_free_result($result); ob_start(); echo '<p><a href="cart.php?category=' . $id . '">'; echo '<img src="img/cartview.gif" alt="View Cart"/></a></p>'; echo '<h1>Products in ' . $name . '</h1>'; echo '<p><a href="shop.php">Back to all categories</a></p>'; // retrieve items $query = sprintf('SELECT ITEM_ID, ITEM_NAME, ITEM_IMAGE ' . 'FROM %sSHOP_INVENTORY WHERE CATEGORY_ID = %d ORDER BY ITEM_NAME ASC', DB_TBL_PREFIX, $id); $result = mysql_query($query, $GLOBALS['DB']); while ($row = mysql_fetch_assoc($result)) { echo '<table>'; echo '<tr><td><img src="' . $row['ITEM_IMAGE'] . '" style="width:50px;height:50px;"/></td>'; echo '<td><a href="shop.php?item=' . $row['ITEM_ID'] . '">' . $row['ITEM_NAME'] . '</a>' . '</td></tr>'; echo '</table>'; } $GLOBALS['TEMPLATE']['content'] = ob_get_clean(); } // display main list of categories and the number of products within each else
{ ob_start(); echo '<p><a href="cart.php">' . '<img src="img/cartview.gif" alt="View Cart"/></a></p>'; echo '<h1>All Categories</h1>'; // Note: LEFT JOIN not specified so any categories without products will // not be included in the results $query = sprintf(' SELECT C.CATEGORY_ID, CATEGORY_NAME, COUNT(ITEM_ID) AS ITEM_COUNT FROM %sSHOP_CATEGORY C JOIN %sSHOP_INVENTORY I ON C.CATEGORY_ID = I.CATEGORY_ID GROUP BY C.CATEGORY_ID ORDER BY CATEGORY_NAME ASC', DB_TBL_PREFIX, DB_TBL_PREFIX); $result = mysql_query($query, $GLOBALS['DB']); echo '<ul>'; while ($row = mysql_fetch_assoc($result)) { printf('<li><a href="shop.php?category=%d">%s</a> (%d %s)</li>', $row['CATEGORY_ID'], $row['CATEGORY_NAME'], $row['ITEM_COUNT'], (($row['ITEM_COUNT'] == 1) ? 'product' : 'products')); } mysql_free_result($result); echo '</ul>'; $GLOBALS['TEMPLATE']['content'] = ob_get_clean(); } // display the page include '../templates/template-page.php'; ?>
The administrative portion of the shopping cart allows you to manage the store's inventory. You are able to add, edit, and delete product categories and add, edit, and delete products within the categories. By applying the Ajax paradigm with JavaScript, the interface becomes easier and more intuitive to use.
The form to allow category management will consist of a select list populated with all the possible categories and an option to create a new category. When users select an option from the list they are presented a text field in which they can enter the category's name. In the case of editing an existing entry, a check box to delete the category is also presented.
Another select list will also be displayed to create or select an existing product. A text field is needed to accept the product's name, price and an image URL as well as a textarea element for the item's description.
The form elements can all be coded on to the same page and hidden or shown at the appropriate times with JavaScript. Initially, however, the section of the category form other than its select list and both the select and item sections of the product form should be hidden using style="display:none;". Here is the HTML code that creates the forms and which I have saved as inventory.html:
<html> <head> <title>Inventory</title> <style type="text/css"><!-- td { vertical-align: top; } --></style> <script type="text/javascript" src="js/ajax.js"></script> <script type="text/javascript" src="js/shop_inventory.js"></script> </head> <body> <h1>Store Inventory</h1> <form> <table id="cat_select_tbl"> <tr> <td><label for="cat_select">Category</label></td> <td id="cat_select_cell"></td> </tr> </table> <table id="cat_form_tbl" style="display:none;"> <tr> <td><label for="cat_name">Category</label></td> <td><input type="text" name="cat_name" id="cat_name"/></td> </tr> <tr id="cat_delete_row"> <td></td> <td><input type="checkbox" name="cat_delete" id="cat_delete"/> <label for="cat_delete">Delete</label> </td> </tr><tr> <td></td> <td><input type="button" id="cat_submit" value="Save"/> <input type="button" id="cat_cancel" value="Cancel"/></td> </tr> </table> <table id="item_select_tbl" style="display:none;"> <tr> <td><label for="item_select">Item</label></td> <td id="item_select_cell"></td> </tr> </table> <table id="item_form_tbl" style="display:none;"> <tr> <td><label for="item_name">Item</label></td> <td><input type="text" name="item_name" id="item_name"/></td> </tr><tr> <td><label for="item_description">Description</label></td> <td><textarea name="item_description" id="item_description"
cols="50" rows="5"></textarea></td> </tr><tr> <td><label for="item_price">Price</label></td> <td><input type="text" name="item_price" id="item_price"/></td> </tr><tr> <td><label for="item_image">Image</label></td> <td><input type="text" name="item_image" id="item_image"/></td> </tr> <tr id="item_delete_row"> <td></td> <td><input type="checkbox" name="item_delete" id="item_delete"/> <label for="item_delete">Delete</label> </td> </tr><tr> <td></td> <td><input type="button" id="item_submit" value="Save"/> <input type="button" id="item_cancel" value="Cancel"/></td> </tr> </table> </form> </body> </html>
Figure 8-5 shows all the elements (without applying the display:none styles). Note that the two select fields are missing; in the HTML they are represented as div elements. The actual select elements will be retrieved via Ajax and inserted into their respective placeholders when necessary.
The PHP code responsible for processing the Ajax requests must be written to handle various actions; this is denoted with a parameter passed in the calling URL:
retrieve_category_select: Query the database to retrieve the list of product categories and return them in the form of an HTML select list.
retrieve_category: Fetch the requested category's record from the database and return it as a JSON encoded string.
save_category: Accept a post of category information and create, update or delete the database record
retrieve_item_select: Query the database to retrieve the list of products within the categories and return them in the form of an HTML select list.
retrieve_item: Fetch the requested product's record from the database and return it as a JSON encoded string.
save_item: Accept a post of product information and create, update or delete the item's database record
There's no sense in discussing each action individually as the code is simple data retrieval or update processing. I would, however, like to touch on the SQL used to retrieve the list of categories. Like the statement used to pull the categories in shop.php, the names and the number of items within each category can be retrieved at the same time. At this point, however, it is advisable to use a LEFT JOIN instead of JOIN so that categories without products will still be included in the list.
Here is the complete code for public_files/inventory_process.php:
<?php include '../lib/common.php'; include '../lib/db.php'; // return HTML for category select list if (isset($_GET['retrieve_category_select'])) { echo '<select id="cat_select" name="cat_select">'; echo '<option>Select</option>'; echo '<option value="new">Create New Category</option>'; $query = sprintf(' SELECT C.CATEGORY_ID, CATEGORY_NAME, COUNT(ITEM_ID) AS ITEM_COUNT FROM %sSHOP_CATEGORY C LEFT JOIN %sSHOP_INVENTORY I ON C.CATEGORY_ID = I.CATEGORY_ID GROUP BY C.CATEGORY_ID ORDER BY CATEGORY_NAME ASC', DB_TBL_PREFIX, DB_TBL_PREFIX);
$result = mysql_query($query, $GLOBALS['DB']); while ($row = mysql_fetch_assoc($result)) { printf('<option value="%d">%s (%s)</option>', $row['CATEGORY_ID'], $row['CATEGORY_NAME'], $row['ITEM_COUNT']); } mysql_free_result($result); echo '</select>'; } // return JSON-encoded string with category information else if (isset($_GET['retrieve_category'])) { $query = sprintf('SELECT CATEGORY_NAME FROM %sSHOP_CATEGORY WHERE ' . 'CATEGORY_ID = %d', DB_TBL_PREFIX, $_GET['id']); $result = mysql_query($query, $GLOBALS['DB']); $row = mysql_fetch_assoc($result); echo json_encode(array('cat_name' => $row['CATEGORY_NAME'])); mysql_free_result($result); } // process save request for category information else if (isset($_GET['save_category'])) { // create a new record if ($_POST['id'] == 'new') { $query = sprintf('INSERT INTO %sSHOP_CATEGORY (CATEGORY_NAME) ' . 'VALUES ("%s")', DB_TBL_PREFIX, mysql_real_escape_string($_POST['name'], $GLOBALS['DB'])); } else { // delete an existing record if (isset($_POST['delete'])) { $query = sprintf('DELETE FROM %sSHOP_CATEGORY WHERE ' . 'CATEGORY_ID = %d', DB_TBL_PREFIX, $_POST['id']); } // update an existing record else
{ $query = sprintf('UPDATE %sSHOP_CATEGORY SET ' . 'CATEGORY_NAME = "%s" WHERE CATEGORY_ID = %d', DB_TBL_PREFIX, mysql_real_escape_string($_POST['name'], $GLOBALS['DB']), $_POST['id']); } } mysql_query($query, $GLOBALS['DB']); } // return HTML for item select list else if (isset($_GET['retrieve_item_select'])) { echo '<select id="item_select" name="item_select">'; echo '<option>Select</option>'; echo '<option value="new">Create New Item</option>'; $query = sprintf('SELECT ITEM_ID, ITEM_NAME FROM %sSHOP_INVENTORY ' . 'WHERE CATEGORY_ID = %d ORDER BY ITEM_NAME ASC', DB_TBL_PREFIX, $_GET['id']); $result = mysql_query($query, $GLOBALS['DB']); while ($row = mysql_fetch_assoc($result)) { echo '<option value="' . $row['ITEM_ID'] . '">' . $row['ITEM_NAME'] . '</option>'; } mysql_free_result($result); echo '</select>'; } // return JSON-encoded string with item information else if (isset($_GET['retrieve_item'])) { $query = sprintf('SELECT ITEM_NAME, ITEM_DESCRIPTION, PRICE, ' . 'ITEM_IMAGE FROM %sSHOP_INVENTORY WHERE ITEM_ID = %d', DB_TBL_PREFIX, $_GET['id']); $result = mysql_query($query, $GLOBALS['DB']); $row = mysql_fetch_assoc($result); echo json_encode(array( 'item_name' => $row['ITEM_NAME'], 'item_description' => $row['ITEM_DESCRIPTION'], 'item_price' => $row['PRICE'], 'item_image' => $row['ITEM_IMAGE'])); mysql_free_result($result); }
// process save request for item information else if (isset($_GET['save_item'])) { // create a new record if ($_POST['id'] == 'new') { $query = sprintf('INSERT INTO %sSHOP_INVENTORY (ITEM_NAME, ' . 'ITEM_DESCRIPTION, PRICE, ITEM_IMAGE, CATEGORY_ID) VALUES ' . '("%s", "%s", %02f, %d)', DB_TBL_PREFIX, mysql_real_escape_string($_POST['name'], $GLOBALS['DB']), mysql_real_escape_string($_POST['description'], $GLOBALS['DB']), $_POST['price'], mysql_real_escape_string($_POST['image'], $GLOBALS['DB']), $_POST['cat_id']); } else { // delete an existing record if (isset($_POST['delete'])) { $query = sprintf('DELETE FROM %sSHOP_INVENTORY WHERE ' . 'ITEM_ID = %d', DB_TBL_PREFIX, $_POST['id']); } // update an existing record else { $query = sprintf('UPDATE %sSHOP_INVENTORY SET ' . 'ITEM_NAME = "%s", ITEM_DESCRIPTION = "%s", ' . 'PRICE = %02d, ITEM_IMAGE = "%s", CATEGORY_ID = %d ' . 'WHERE ITEM_ID = %d', DB_TBL_PREFIX, mysql_real_escape_string($_POST['name'], $GLOBALS['DB']), mysql_real_escape_string($_POST['description'], $GLOBALS['DB']), $_POST['price'], mysql_real_escape_string($_POST['image'], $GLOBALS['DB']), $_POST['cat_id'], $_POST['id']); } } mysql_query($query, $GLOBALS['DB']); } ?>
JavaScript is used to bind the interface forms in inventory.html to the back-end processing of inventory_process.php.
The first event triggered when the page has finished loading in the user's browser is the window object's onload event and as such is typically the best place to assign event handlers. Wiring events like this instead of mixing on events in the HTML elements allows you to keep everything separate and easier to maintain.
window.onload = function() { document.getElementById('cat_delete').onclick = warnCategoryDelete; document.getElementById('cat_cancel').onclick = resetCategoryForm; document.getElementById('cat_submit').onclick = submitCategoryForm; document.getElementById('item_delete').onclick = warnItemDelete; document.getElementById('item_cancel').onclick = resetItemForm; document.getElementById('item_submit').onclick = submitItemForm; resetCategoryForm(); }
When the product information form is shown the controls in the category form should be disabled. This gives the user a visual cue that even after drilling down to the product level in the category the focus is on the category form. It also prevents any accidental submissions of the category form. Consequently, when the category form is reset, it is important to make sure the elements have been enabled again. The resetCategoryForm() resets the category form by making sure the appropriate elements are enabled and hides the sub-forms. It also calls retrieveCategorySelect(), which issues the Ajax call to pull in the category select list.
function resetCategoryForm() { document.getElementById('cat_name').disabled = false; document.getElementById('cat_delete').disabled = false; document.getElementById('cat_submit').disabled = false; document.getElementById('cat_cancel').disabled = false; document.getElementById('cat_form_tbl').style.display = 'none'; document.getElementById('item_select_tbl').style.display = 'none'; document.getElementById('item_form_tbl').style.display = 'none'; document.getElementById('cat_delete').checked = false; document.getElementById('cat_submit').style.backgroundColor = ''; retrieveCategorySelect(); document.getElementById('cat_select_tbl').style.display = ''; }
The retrieveCategorySelect() function uses an XMLHttpObject object to communicate with inventory_process.php. It attaches the retrieve_category_select parameter to the URL as well as a nocache timestamp to avoid caching by the browser or any intermediate proxies. The response is set in the cat_select_celltd to be displayed to the user. The code must also wire the onchange event handler to the select list once it is in place so that is functional for the user.
function retrieveCategorySelect() { var url = 'inventory_process.php?retrieve_category_select&nocache=' + (new Date()).getTime(); window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function()
{ if (window.httpObj.readyState == 4) { document.getElementById('cat_select_cell').innerHTML = window.httpObj.responseText; // assign select list's event handler document.getElementById('cat_select').onchange = showCategoryForms; } } window.httpObj.open('GET', url, false); window.httpObj.send(null); }
The showCategoryForms() function displays the category form which was initially set hidden. A call is made to retrieveCategoryValues() to populate the field with the category's name and retrieveItemSelect() to sync the product list to the selected category.
function showCategoryForms() { document.getElementById('cat_select_tbl').style.display = 'none'; var select = document.getElementById('cat_select'), retrieveCategoryValues(select.options[select.selectedIndex].value); if (select.options[select.selectedIndex].value != 'new') { retrieveItemSelect(select.options[select.selectedIndex].value); document.getElementById('item_select_tbl').style.display = ''; } document.getElementById('cat_form_tbl').style.display = ''; }
retrieveCategoryValues() is responsible for populating the cat_name field. If the Create New option is selected from the list, then the field is shown empty and ready to accept user input. Otherwise the function issues an Ajax call and pre-fills the field.
function retrieveCategoryValues(value) { if (value == 'new') { // clear fields if creating a new record document.getElementById('cat_name').value = ''; document.getElementById('cat_delete_row').style.display = 'none'; } else { var url = 'inventory_process.php?retrieve_category&id=' + value + '&nocache=' + (new Date()).getTime(); window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function()
{ if (window.httpObj.readyState == 4) { var r = eval('(' + window.httpObj.responseText + ')'), document.getElementById('cat_name').value = r.cat_name; document.getElementById('cat_delete_row').style.display = ''; } } window.httpObj.open('GET', url, false); window.httpObj.send(null); } }
When a user is editing an existing category, an option to delete the record is presented in the form of a check box. Because it is irreversible, it is nice to provide extra notification to the severity of the action. The warnCategoryDelete() function highlights the submit button red when the check box is selected.
function warnCategoryDelete() { var btn = document.getElementById('cat_submit'), if (document.getElementById('cat_delete').checked) { btn.style.backgroundColor = '#FF0000'; } else { btn.style.backgroundColor = ''; } }
The submit button for the item form should be highlighted red just like the category form's when the record is marked for deletion. warnItemDelete() handles this and is essentially the same as warnCategoryDelete() with the exception of the button being affected, so I will not show the code here.
submitCategoryForm() is responsible for sending the form data to inventory_process.php. It first verifies whether the delete check box has been selected, and if so it displays a confirmation message as an extra precaution. The data is sent via Ajax using the POST method and the form is cleared.
Only a relatively small amount of information can be passed in a URL parameter with the GET method, but larger amounts can be passed using POST. Because of this, POST is more suitable for sending the product information since the descriptive text could be quite lengthy. Although the category information can easily be sent via GET, I've decided to send it as POST just for the sake of consistency in my code.
function submitCategoryForm() { if (document.getElementById('cat_delete').checked) { if (!confirm('Deleting a category will delete the inventory items ' + 'it contains as well. Are you sure you wish to proceed?'))
{ return; } } // prepare the url and data var url = 'inventory_process.php?save_category&nocache=' + (new Date()).getTime(); var select = document.getElementById('cat_select'), var data = 'id=' + select.options[select.selectedIndex].value + '&name=' + escape(document.getElementById('cat_name').value); if (document.getElementById('cat_delete').checked) { data += '&delete=true'; } window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function() { if (window.httpObj.readyState == 4) { // reset the form when submission is complete resetCategoryForm(); } } window.httpObj.open('POST', url, false); window.httpObj.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'), window.httpObj.setRequestHeader('Content-length', data.length); window.httpObj.setRequestHeader('Connection', 'close'), window.httpObj.send(data); }
The resetItemForm() resets the product form by making sure the controls in the category form are disabled for safety reasons, hides the product's information fields and makes a call to retrieveItemSelect() to sync the list to the viewed category.
function resetItemForm() { document.getElementById('cat_name').disabled = true; document.getElementById('cat_delete').disabled = true; document.getElementById('cat_submit').disabled = true; document.getElementById('cat_cancel').disabled = true; document.getElementById('item_form_tbl').style.display = 'none'; document.getElementById('item_delete').checked = false; document.getElementById('item_submit').style.backgroundColor = '';
var select = document.getElementById('cat_select'), retrieveItemSelect(select.options[select.selectedIndex].value); document.getElementById('item_select_tbl').style.display = ''; }
retrieveItemSelect(), like its category counterpart, is responsible for retrieving a select list from inventory_process.php, but the category's id is passed as a URL parameter so that the products included are only those associated with the currently viewed category. It also assigns the onchange event handler to the list once it is in place so that is functional for the user.
function retrieveItemSelect(id) { var url = 'inventory_process.php?retrieve_item_select&id=' + id + '&nocache=' + (new Date()).getTime(); window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function() { if (window.httpObj.readyState == 4) { document.getElementById('item_select_cell').innerHTML = window.httpObj.responseText; // assign select list's event handler document.getElementById('item_select').onchange = showItemForm; } } window.httpObj.open('GET', url, false); window.httpObj.send(null); }
The showItemForm() function displays the hidden form fields for the product item form. It also makes a call to retrieveItemValues() to populate the fields with product information.
function showItemForm() { var select = document.getElementById('item_select'), retrieveItemValues(select.options[select.selectedIndex].value); document.getElementById('item_select_tbl').style.display = 'none'; document.getElementById('item_form_tbl').style.display = ''; document.getElementById('item_submit').style.backgroundColor = ''; }
retrieveItemValues() is responsible for populating the product fields. If the user selected the Create New option, then all the fields will be empty. However, if an existing product is chosen, then an Ajax call is issued to inventory_process.php. The resulting JSON-encoded string is parsed and used to fill in the field values.
function retrieveItemValues(value) { if (value == 'new') { document.getElementById('item_name').value = ''; document.getElementById('item_description').value = ''; document.getElementById('item_price').value = ''; document.getElementById('item_image').value = ''; document.getElementById('item_delete_row').style.display = 'none'; } else { var url = 'inventory_process.php?retrieve_item&id=' + value + '&nocache=' + (new Date()).getTime(); window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function() { if (window.httpObj.readyState == 4) { var r = eval('(' + window.httpObj.responseText + ')'), document.getElementById('item_name').value = r.item_name; document.getElementById('item_description').value = r.item_description; document.getElementById('item_price').value = r.item_price; document.getElementById('item_image').value = r.item_image; document.getElementById('item_delete_row').style.display = ''; } } window.httpObj.open('GET', url, false); window.httpObj.send(null); } }
The final function, submitCategoryForm(), is responsible for sending the form data to inventory_process.php after it verifies whether the delete check box has been selected. The data is sent via Ajax using the POST method.
function submitItemForm() { if (document.getElementById('item_delete').checked) { if (!confirm('You are about to delete an inventory item. ' + 'Are you sure you wish to proceed?')) { return; } } var url = 'inventory_process.php?save_item&nocache=' + (new Date()).getTime();
var i_select = document.getElementById('item_select'), var c_select = document.getElementById('cat_select'), var data = 'id=' + i_select.options[i_select.selectedIndex].value + '&name=' + escape(document.getElementById('item_name').value) + '&description=' + escape(document.getElementById('item_description').value) + '&price=' + document.getElementById('item_price').value + '&image=' + escape(document.getElementById('item_image').value) + '&cat_id=' + c_select.options[c_select.selectedIndex].value; if (document.getElementById('item_delete').checked) { data += '&delete=true'; } window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function() { if (window.httpObj.readyState == 4) { // reset the form when submission is complete resetItemForm(); } } window.httpObj.open('POST', url, false); window.httpObj.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'), window.httpObj.setRequestHeader('Content-length', data.length); window.httpObj.setRequestHeader('Connection', 'close'), window.httpObj.send(data); }
Whew! That is a lot of JavaScript code, isn't it? Here is the full code listing for the client-side functionality which I have saved as public_files/js/inventory.js:
// register event handlers and set initial view window.onload = function() { document.getElementById('cat_delete').onclick = warnCategoryDelete; document.getElementById('cat_cancel').onclick = resetCategoryForm; document.getElementById('cat_submit').onclick = submitCategoryForm; document.getElementById('item_delete').onclick = warnItemDelete; document.getElementById('item_cancel').onclick = resetItemForm; document.getElementById('item_submit').onclick = submitItemForm; resetCategoryForm(); } // reset the category form function resetCategoryForm()
{ // make sure all controls are enabled document.getElementById('cat_name').disabled = false; document.getElementById('cat_delete').disabled = false; document.getElementById('cat_submit').disabled = false; document.getElementById('cat_cancel').disabled = false; // hide sub forms document.getElementById('cat_form_tbl').style.display = 'none'; document.getElementById('item_select_tbl').style.display = 'none'; document.getElementById('item_form_tbl').style.display = 'none'; // reset the submit button's background color and the delete option document.getElementById('cat_delete').checked = false; document.getElementById('cat_submit').style.backgroundColor = ''; // populate the category select list and make visible retrieveCategorySelect(); document.getElementById('cat_select_tbl').style.display = ''; } // populate the category select list via AJAX function retrieveCategorySelect() { var url = 'inventory_process.php?retrieve_category_select&nocache=' + (new Date()).getTime(); window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function() { if (window.httpObj.readyState == 4) { document.getElementById('cat_select_cell').innerHTML = window.httpObj.responseText; // assign select list's event handler document.getElementById('cat_select').onchange = showCategoryForms; } } window.httpObj.open('GET', url, false); window.httpObj.send(null); } // display the category's form and possibly a synced item list function showCategoryForms() { // hide the category select list document.getElementById('cat_select_tbl').style.display = 'none'; var select = document.getElementById('cat_select'), retrieveCategoryValues(select.options[select.selectedIndex].value); if (select.options[select.selectedIndex].value != 'new')
{ // populate the item list for this category and make visible retrieveItemSelect(select.options[select.selectedIndex].value); document.getElementById('item_select_tbl').style.display = ''; } document.getElementById('cat_form_tbl').style.display = ''; } // populate the category form via AJAX function retrieveCategoryValues(value) { if (value == 'new') { // clear fields if creating a new record document.getElementById('cat_name').value = ''; document.getElementById('cat_delete_row').style.display = 'none'; } else { var url = 'inventory_process.php?retrieve_category&id=' + value + '&nocache=' + (new Date()).getTime(); window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function() { if (window.httpObj.readyState == 4) { var r = eval('(' + window.httpObj.responseText + ')'), document.getElementById('cat_name').value = r.cat_name; document.getElementById('cat_delete_row').style.display = ''; } } window.httpObj.open('GET', url, false); window.httpObj.send(null); } } // highlight the submit button if it will cause records to be deleted function warnCategoryDelete() { var btn = document.getElementById('cat_submit'), if (document.getElementById('cat_delete').checked) { btn.style.backgroundColor = '#FF0000'; } else { btn.style.backgroundColor = ''; } }
// submit the category form via AJAX function submitCategoryForm() { // warn if the submit will cause records to be deleted if (document.getElementById('cat_delete').checked) { if (!confirm('Deleting a category will delete the inventory items ' + 'it contains as well. Are you sure you wish to proceed?')) { return; } } // prepare the url and data var url = 'inventory_process.php?save_category&nocache=' + (new Date()).getTime(); var select = document.getElementById('cat_select'), var data = 'id=' + select.options[select.selectedIndex].value + '&name=' + escape(document.getElementById('cat_name').value); if (document.getElementById('cat_delete').checked) { data += '&delete=true'; } window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function() { if (window.httpObj.readyState == 4) { // reset the form when submission is complete resetCategoryForm(); } } // set headers and send content window.httpObj.open('POST', url, false); window.httpObj.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'), window.httpObj.setRequestHeader('Content-length', data.length); window.httpObj.setRequestHeader('Connection', 'close'), window.httpObj.send(data); } // reset the item form function resetItemForm() { // make sure all category controls are disable document.getElementById('cat_name').disabled = true; document.getElementById('cat_delete').disabled = true;
document.getElementById('cat_submit').disabled = true; document.getElementById('cat_cancel').disabled = true; // hide sub form document.getElementById('item_form_tbl').style.display = 'none'; // reset the submit button's background color and the delete option document.getElementById('item_delete').checked = false; document.getElementById('item_submit').style.backgroundColor = ''; // populate the item list and make it visible var select = document.getElementById('cat_select'), retrieveItemSelect(select.options[select.selectedIndex].value); document.getElementById('item_select_tbl').style.display = ''; } // populate the item select list for the selected category via AJAX function retrieveItemSelect(id) { var url = 'inventory_process.php?retrieve_item_select&id=' + id + '&nocache=' + (new Date()).getTime(); window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function() { if (window.httpObj.readyState == 4) { document.getElementById('item_select_cell').innerHTML = window.httpObj.responseText; // assign select list's event handler document.getElementById('item_select').onchange = showItemForm; } } window.httpObj.open('GET', url, false); window.httpObj.send(null); } // display the item's form function showItemForm() { var select = document.getElementById('item_select'), // populate the item list for this category and make visible retrieveItemValues(select.options[select.selectedIndex].value); // hide item select list and make item form visible document.getElementById('item_select_tbl').style.display = 'none'; document.getElementById('item_form_tbl').style.display = ''; document.getElementById('item_submit').style.backgroundColor = ''; }
// populate the item form via AJAX function retrieveItemValues(value) { if (value == 'new') { // clear fields if creating a new record document.getElementById('item_name').value = ''; document.getElementById('item_description').value = ''; document.getElementById('item_price').value = ''; document.getElementById('item_image').value = ''; document.getElementById('item_delete_row').style.display = 'none'; } else { var url = 'inventory_process.php?retrieve_item&id=' + value + '&nocache=' + (new Date()).getTime(); window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function() { if (window.httpObj.readyState == 4) { var r = eval('(' + window.httpObj.responseText + ')'), document.getElementById('item_name').value = r.item_name; document.getElementById('item_description').value = r.item_description; document.getElementById('item_price').value = r.item_price; document.getElementById('item_image').value = r.item_image; document.getElementById('item_delete_row').style.display = ''; } } window.httpObj.open('GET', url, false); window.httpObj.send(null); } } // highlight the submit button if it will cause records to be deleted function warnItemDelete() { var btn = document.getElementById('item_submit'), if (document.getElementById('item_delete').checked) { btn.style.backgroundColor = '#FF0000'; } else { btn.style.backgroundColor = ''; } } // submit the item form via AJAX function submitItemForm()
{ // warn if the submit will cause records to be deleted if (document.getElementById('item_delete').checked) { if (!confirm('You are about to delete an inventory item. ' + 'Are you sure you wish to proceed?')) { return; } } // prepare the url and data var url = 'inventory_process.php?save_item&nocache=' + (new Date()).getTime(); var i_select = document.getElementById('item_select'), var c_select = document.getElementById('cat_select'), var data = 'id=' + i_select.options[i_select.selectedIndex].value + '&name=' + escape(document.getElementById('item_name').value) + '&description=' + escape(document.getElementById('item_description').value) + '&price=' + document.getElementById('item_price').value + '&image=' + escape(document.getElementById('item_image').value) + '&cat_id=' + c_select.options[c_select.selectedIndex].value; if (document.getElementById('item_delete').checked) { data += '&delete=true'; } window.httpObj = createXMLHTTPObject(); window.httpObj.onreadystatechange = function() { if (window.httpObj.readyState == 4) { // reset the form when submission is complete resetItemForm(); } } // set headers and send content window.httpObj.open('POST', url, false); window.httpObj.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'), window.httpObj.setRequestHeader('Content-length', data.length); window.httpObj.setRequestHeader('Connection', 'close'), window.httpObj.send(data); }
Now you should be able to populate the store's inventory and offer products for sale. Figure 8-6 shows adding a new category through the interface and Figure 8-7 shows adding a new product.
3.15.144.56