reptile7's JavaScript blog
Sunday, June 16, 2013
 
Order on the Page
Blog Entry #292

Welcome back to our ongoing discussion of the "So, You Want A Shopping Cart, Huh?" shopping cart. In today's post we will detail how the cart tallies up the user's running order.

Suppose we go to the pagetwo.html page and add one p2i2/$3.33 item to the shopping cart. Subsequently we go to the pagethree.html page and add one p3i3/$7.77 item to the cart. Here is our order string so far:

itemlist[1] = {code: "p2i2", price: "3.33", desc: "Page_2_Item_2", quan: 1, url: "pagetwo.html"}
itemlist[2] = {code: "p3i3", price: "7.77", desc: "Page_3_Item_3", quan: 1, url: "pagethree.html"}


Total it

We click the Review updated Order Form link at the bottom of the pagethree.html page and go to the order.html page. Writing the tables[2] table's total text field

document.write("<input type='text' name='total' size='6' value='" + format(parent.all_order_totals( ), 2) + "'>");

to the order.html page initially calls the shopcartindex.html all_order_totals( ) function.

var order_total = 0;
...

function all_order_totals( ) {
    order_total = 0;
    if (item_num > 0) {
        for (i = 1; i < item_num; i++) {
            order_total = order_total + item_tot_price(i); } }
    return order_total; }


An order_total variable is initialized to 0 in the top-level part of the shopcartindex.html script. The all_order_totals( ) function begins by resetting order_total to 0, an operation that is redundant for a first all_order_totals( ) run but is definitely needed if all_order_totals( ) is re-called during the shopping cart session: all_order_totals( ) builds the order total from scratch and thus it is necessary to clear order_total for a subsequent all_order_totals( ) run.

The item_num index for our order string is 3: item_num was incremented from 1 to 3 by both the shopcartindex.html additem( ) function and the shopcartindex.html remove_nil_items( ) function. And because item_num is 3, the all_order_totals( ) function's if statement condition returns true - the if (item_num > 0) { ... } gate is actually excess baggage as item_num never falls below 1 - and its for loop runs for two iterations. The for loop calls on an item_tot_price( ) function to calculate the total price for each item in the cart.

var total_item_price = 0;
...

function item_tot_price(i) {
    total_item_price = eval((itemlist[i].price * itemlist[i].quan));
    return total_item_price; }


The item_tot_price( ) function's eval( ) operation is unnecessary: although itemlist[i].price has a string type, itemlist[i].quan has a number type, and multiplying itemlist[i].price by itemlist[i].quan gives the number product that you would expect. The item_tot_price( ) multiplication operation could just as easily be carried out inside the all_order_totals( ) function, but let's go with what we've got for the time being.

(1) For the all_order_totals( ) loop's first iteration:
The item_tot_price( ) function multiplies itemlist[1].price by itemlist[1].quan to give 3.33, which via a total_item_price variable is returned to the all_order_totals( ) function. The item_tot_price( ) return is added to order_total and the resulting sum, 3.33, is assigned to order_total.

(2) For the all_order_totals( ) loop's second iteration:
The item_tot_price( ) function multiplies itemlist[2].price by itemlist[2].quan to give 7.77, which is returned to the all_order_totals( ) function and added to order_total to give 11.1, which is assigned to order_total.

Finally, order_total itself is returned to the all_order_totals( ) call on the order.html page.

Format it

Now, if you're following along at home, you'll notice that the order.html total field displays 11.10 and not 11.1: 11.1 is converted to 11.10 via the order.html format( ) function.

The total field's document.write( ) command (vide supra) calls the format( ) function and passes it two arguments:
(1) the all_order_totals( ) return, 11.1, and
(2) 2, the desired number of post-decimal point digits.
Here's the format( ) function:

function format(val, post) {
    var decpoint, begin, end, valstr, temp_char;
    valstr = "" + val;
    decpoint = valstr.indexOf(".");
    if (decpoint != -1) {
        begin = valstr.substring(0, decpoint);
        end = valstr.substring(decpoint + 1, valstr.length); }
    else {
        begin = valstr;
        end = ""; }
    if (end.length < post) {
        while (end.length < post) end += "0"; }
    end = end.substring(0, post);
    return (begin + "." + end); }


In brief, here's what happens:
• The all_order_totals( ) return, which has a number type, is assigned to a val variable, which is stringified by concatenating it with an empty string to give a valstr string.
• The pre- and post-decimal point parts of valstr are extracted and respectively assigned to begin and end variables; if valstr doesn't contain a decimal point, then end is set to an empty string.
• If the length of end is less than the function's post = 2 argument, then 0's are iteratively appended to end until end.length is 2.
• Finally, begin, a decimal point, and end are concatenated and the resulting string is returned to the format( ) call and then assigned to the value of the total field.

This is how we had to create a \d+\.\d{2} total value in 1997; thankfully, today we can reach for the toFixed( ) method of the Number object so as to replace the format( ) functionality with a single command:

document.write("<input type='text' name='total' size='6' value='" + parent.all_order_totals( ).toFixed(2) + "'>");

Item detail

If the user accesses the order.html page without adding any items to the cart, then the order.html page prints out a You have not ordered any items so far message below the tables[2] table.

if (parent.items_ordered == 0)
    document.write("<font color='#000080'><b>You have not ordered any items so far</b></font>");


However, if the user adds an item to the cart, subtracts the item from the cart, and then goes to the order.html page, this message does not appear because the shopcartindex.html subitem( ) function does not decrement the items_ordered value, i.e., items_ordered is still 1 after subtracting the item.

If there are any items in the cart, then the order.html page prints out a table of itemlist data for those items below the tables[2] table.

var index = 0;
...

if (parent.item_num > 0) {
    for (i = 1; i < parent.item_num; i++) {
        if (parent.itemlist[i].quan > 0) {
            index = index + 1;
            document.write("<a href='" + parent.itemlist[i].url + "'><i><b> review : </b></i></a>");
            document.write("<input size='10' type='text' name='" + parent.itemlist[i].code + "' value='" + parent.itemlist[i].code + "'>");
            document.write("<input size='6' type='text' name='" + parent.itemlist[i].code + "' value='" + parent.itemlist[i].price + "'>");
            document.write("<input size='20' type='text' name='" + parent.itemlist[i].code + "' value='" + parent.itemlist[i].desc + "'>");
            document.write("<input size='2' type='text' name='" + parent.itemlist[i].desc + "' value='" + parent.itemlist[i].quan + "'><br>"); } } }

/* In the original order.html source, the five document.write( ) commands are written as a single command, which I have broken up for the sake of clarity. */

The for loop writes out a row of data for each item in the cart; each row contains
(1) a review : link to the page at which the item was ordered,
(2) a text field displaying the item's identifier,
(3) a text field displaying the item's price (sans a beginning $ character),
(4) a text field displaying the item's description, and
(5) a text field displaying the item quantity.

Oddly, the names of the identifier, price, and description fields are set to the identifier value (parent.itemlist[i].code) whereas the name of the quantity field is set to the description value (parent.itemlist[i].desc); it would make more sense to use the otherwise-not-used index variable to create a code1, price1, desc1, quan1, code2, ... series of names as follows:

document.write("<input size='10' type='text' name='code" + index + "' value='" + parent.itemlist[i].code + "'>");
document.write("<input size='6' type='text' name='price" + index + "' value='" + parent.itemlist[i].price + "'>"); // Etc.


Here's the loop output for our p2i2 item + p3i3 item order:

The itemlist detail for a p2i2 item + p3i3 item order

If no items have been added to the cart prior to arriving at the order.html page, then the item_num index, which was initialized to 1, is still 1; in this case, the outer if statement's parent.item_num > 0 condition returns true but the for loop doesn't run for any iterations because the loop's i < parent.item_num condition returns false from the get-go.

If one or more items have been added to and then subtracted from the cart so that there are no items in the cart, then item_num will be greater than 1 - like items_ordered, item_num is not decremented by the shopcartindex.html subitem( ) function - in this case, the loop runs for item_num-1 iterations but nothing is printed out because the inner if statement's parent.itemlist[i].quan > 0 condition returns false (parent.itemlist[i].quan is 0) for each of those iterations.

Other code possibilities

Although it is necessary to obtain the total field's value scriptically, there is no reason whatsoever to write the tables[2] table's structure (and presentation) via a script.

#totalRow { background-color: #ff9999; }
#totalLabel { font-size: 24px; text-align: right; }
#messageTd { font-weight: bold; text-align: center; }
#messageSpan { font-style: italic; }
...
window.onload = function( ) { document.order.total.value = parent.all_order_totals( ).toFixed(2); }
...
<table id="orderTable">
<tr id="totalRow">
<td id="totalLabel"><label for="totalValue">Running Total : $</label></td>
<td><input type="text" id="totalValue" name="total" size="6"></td>
</tr><tr>
<td id="messageTd" colspan="2">This is your Order Total so far<br><span id="messageSpan">(Including Shipping Worldwide)</span>.</td>
</tr></table>


The tables[2] content can alternatively be given a div-based structure.

<div id="orderDiv">
<div id="totalDiv"><label>Running Total : $ <input type="text" name="total" size="6"></label></div>
<div id="messageDiv">This is your Order Total so far<br><span id="messageSpan">(Including Shipping Worldwide)</span>.</div>
... </div>


If the shopcartindex.html subitem( ) function decremented the items_ordered value so that items_ordered at all times represented the number of items in the cart, then the you-haven't-ordered-anything vs. here-are-your-items situations discussed in the previous section could be cleanly separated as follows:

<script type="text/javascript">
if (!parent.items_ordered) // If the cart is empty
    document.write("<span style='color:navy;font-weight:bold;'>You have not ordered any items so far</span>");
else { // If there are items in the cart
    for (i = 1; i <= parent.items_ordered; i++) {
        index++;
        ... document.write( ) command(s) ... } }
</script>


The above script element can be appended to the messageDiv div in the orderDiv div.

The here-are-your-items document.write( ) commands look a bit ugly, and I gave some thought to replacing them with corresponding Core DOM commands (createElement( ), setAttribute( ), insertBefore( )) but ultimately decided that it would be too much trouble to do so.

In the following entry, we'll briefly run through the remaining order.html tables and then begin work on the shopping cart's form submission code.

Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)