8.3. Code and Code Explanation

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.

8.3.1. The ShoppingCart Class

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.

Table 8-1. ShoppingCart Properties
PropertyDescription
contentsReturns the entire contents of the cart as an array
isEmptyReturns Boolean whether or not the cart is empty
totalItemsReturns the total number of distinct items in the cart
totalQtyReturns the total quantity of items in the cart

Table 8-2. ShoppingCart Methods
MethodDescription
construct()Initializes a new ShoppingCart object
addItem(item[, qty])Adds an item to the shopping cart
qtyItem(item)Returns the quantity of an item in the cart
removeItem(item)Removes an item from the shopping cart
removeAll()Empties the contents of the shopping 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]);
    }
}
?>

8.3.2. Working with the Shopping Cart

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.

Figure 8-1. Figure 8-1

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';
}
?>

8.3.3. Building the Storefront

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.

Figure 8-2. 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.

Figure 8-3. Figure 8-3

// 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.

Figure 8-4. Figure 8-4

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';
?>

8.3.4. Adding Inventory

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.

8.3.4.1. HTML Structure of the Forms

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.

Figure 8-5. Figure 8-5

8.3.4.2. Server-Side Processing

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 &nbsp;  (%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']);
}
?>

8.3.4.3. Client-Side Support

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.

Figure 8-6. Figure 8-6

Figure 8-7. Figure 8-7

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.144.56