reptile7's JavaScript blog
Monday, April 22, 2013
 
Living in the Material World
Blog Entry #286

In today's post we will begin an analysis of the e-commerce shopping cart code of HTML Goodies' "So, You Want A Shopping Cart, Huh?" tutorial. Authored by Joe Burns, the "Shopping Cart" tutorial dates to early 1999 and was one of the first tutorials to be offered by HTML Goodies' Beyond HTML : JavaScript sector. The "Shopping Cart" code itself was crafted by Gordon Smith, who deployed it at a now-gone Scottish Gifts site. Gordon's www.mearns.org.uk site is still live (sort of) but it doesn't sell anything anymore.

Cart overview

The tutorial shopping cart consists of seven pages: shopcartindex.html, navigate.html, welcome.html, pageone.html, pagetwo.html, pagethree.html, and order.html.

The shopcartindex.html page hosts a two-frame frameset

<frameset cols="25%,*" resize="yes" onload="parent.get_that_cookie( );">
<frame src="navigate.html" name="navigate">
<frame src="welcome.html" name="main">
<noframes>This page requires frames.</noframes>
</frameset>


for displaying the other six pages.

Initially the frameset's right frame holds the welcome.html page, which welcomes the user to the shopping cart.

Throughout the shopping cart process the frameset's left frame holds the navigate.html page, which features a set of links for respectively loading the pageone.html, pagetwo.html, pagethree.html, and order.html pages into the right frame.

The pageone.html, pagetwo.html, and pagethree.html pages detail items for sale and provide controls for adding/subtracting those items to/from the shopping cart. The order.html page tallies up the user's order and provides a form for submitting the order and the user's contact/shipping information.

Frameset/frame notes

In the course of checking whether the frameset element has a resize attribute I discovered that HTML5 is obsoleting the frameset and frame elements. We will exchange the cart's frameset for an alternative structure in due course.

As it happens, the frameset element does not have a resize attribute, but it doesn't really need one - well, at least not one set to yes - in that frames are resizable by default. A frame can be resized by dragging its interframe boundaries or by dragging the viewport's resizing grippy; the frame element does have a boolean noresize attribute that will shut down the former but not the latter. To lock the cols="25%,*" frameset layout, the correct HTML syntax would be:

<frame src="navigate.html" name="navigate" noresize>
<frame src="welcome.html" name="main" noresize>
/* In practice I find it is not necessary to noresize both frames: locking one frame effectively locks the other. */


Cart download

At the tutorial's The Pages section the shopping cart's pages can be downloaded either individually or as a single shopcart/ .zip package. If you're trying this out at home, I strongly encourage you to grab the .zip package because the one-at-a-time pages all contain HTML Goodies site stuff - five meta elements, two script elements, and a noscript element - that is completely unrelated to the shopping cart and shouldn't be there. Moreover, the .zip package contains a formmail.pl PERL script that, unlike the order.html page's mailto: form, will directly email the user's order to you.

The shopping cart demo, then and now

In the tutorial's The Shopping Cart Program section Joe says:
As long as you do not alter the JavaScript programming, the cart should work straightaway.
The .zip package has one little flaw, specifically, it's missing the thisback.gif image that the pageone.html and order.html pages call on for a background, but apart from that its code runs smoothly; ditto for Joe's original tutorial demo.

Here's thisback.gif, which wallpapered most of the pages at the Scottish Gifts site:

The thisback.gif image, whatever it is

At some point in 2011 the aforementioned HTML Goodies site stuff was inserted into each shopping cart demo page as the result of a site overhaul. The meta elements were placed in the document head for all seven pages - so far, so normal - but for some strange reason the meta element addition also brought the document type declaration into the document head for the welcome.html, pageone.html, pagetwo.html, and pagethree.html pages (the other pages don't have a declaration), for example:

<head>
<meta name="WT.qs_dlk" content="UWHpGgrIZ2cAABwmNwIAAAAY"/>
...other four meta elements...
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<title></title>
...


Misplacement of the declaration invalidates the document - without getting into the details, it will throw two errors upon running the document through the W3C's markup validator - but it doesn't prevent the browser from rendering the document body normally.

For their part, the script elements and the noscript element were harmlessly* placed at the end of the document body - i.e., just before the </body> tag - for the six frame pages but were prepended to the </body>-holding value of a global en_astr variable at the shopcartindex.html frameset page:

var en_astr = '<script language="JavaScript" type="text/javascript">
    var gDomain = "www.qsstats.com";
    ...
    </script>
    <script type="text/javascript" src="/imageserver/common/webtrends.js"></script>
    <noscript> ... </noscript>
    </body>';


As I trust y'all know, JavaScript string literals cannot contain (unescaped) line breaks, and the line break that separates the first and second lines of the above en_astr declaration promptly throws an 'unterminated string literal' syntax error; this error has a propagative effect in that it prevents
(a) the retrieval of any cookies associated with the shopping cart,
(b) the addition/subtraction of items to/from the cart, and
(c) the loading of the order.html page into the right frame.
Commenting out the en_astr declaration (en_astr plays a small role in a not-called display_pic( ) function) solves these problems.

*At the HTML Goodies site the script elements import wtid.js and webtrends.js scripts; on my desktop the unavailability of these scripts throws 'failed to load resource' errors with some browsers but does not affect the operation of the cart.

You can go through the one-at-a-time pages and clean them up if you want, but again, you won't have to do this if you download the .zip package.

State of the order

In the tutorial's How Does This Shopping Cart Work? section Joe claims that cookies are used to store the information somewhere as the viewer moves from page to page. In actual fact, the user's running order is stored in an itemlist collection of custom JavaScript objects; it is moved from page to page via the frames and parent properties of the window object. The cart's cookie machinery can be used to remember the user's contact information upon leaving the order.html page but it has no connection with the item part of the cart.

We'll start deconstructing the shopping cart code in earnest in the following entry.

Saturday, April 13, 2013
 
LEIA Intermission
Blog Entry #285

As of this writing we have worked through the scripts offered by Sections 1, 6, 7, and 8 of the JavaScript subsector of Lissa Explains It All. There are ten sections in the JavaScript subsector. What's in those other sections?

(2) Mousing over the Section 2 link of the subsector's navigation menu indicates that Section 2 should hold four "hover" (mouseover-triggered) code snippets: a visit to Section 2 reveals it to be under construction.

(3) The first three subsections of Section 3 contain no JavaScript but instead provide HTML and .class file components for assembling several Java applets; at no point does the word "Java" appear in the subsection text. Section 3 concludes with a basic image-flipping script that is similar to the code we saw in HTML Goodies' "So, You Want A Dual Image Flip, Huh?" tutorial.

(4) Section 4's first subsection briefly addresses the generation of secondary windows via the open( ) method of the window object. Section 4's second subsection details two old-school* ways to 'free' a frame src page from its frameset ancestor(s).

*At a frameset page, the context menus of most modern browsers (Google Chrome being a conspicuous exception) sport Open Frame in New Window and Open Frame in New Tab commands for displaying an isolated frame page.
A context menu with a highlighted Open Frame in New Window command
(5) Section 5 covers alert( ), prompt( ), and confirm( ) dialog boxes. Lissa does not specify that alert( ), prompt( ), and confirm( ) are methods of the window object, not that that's an original sin on her part, of course.

(9) Section 9's first subsection offers a script for creating a "chromeless" (read customizable) secondary window; the script's complexity - its openIT( ) and chromeless( ) functions respectively expect 10 and 28 arguments, for example - is wildly out of proportion to the simple window that is opened by the subsection's demo with a variety of browsers on my iMac. Section 9's second subsection provides a snippet for putting a frame around a Web page; the snippet was formerly IE only but will today work with other browsers if we recast it as document.body.style.border = "15px outset black";.

(10) We strike gold in Section 10. Mousing over the Section 10 link of the subsector's navigation menu indicates that Section 10 should contain a single subsection with a snippet that will add auto favorites, that is, prompt the user to let the current page bookmark itself, a coding situation we explored in the Your new fave section of Blog Entry #246. Section 10 does in fact feature a window.external.AddFavorite( ) snippet, but it also offers another three Peter Gehrig scripts - a splash tracker script, a letter magnet script, and a preload splash script - that summon us to put them under the microscope.

The Section 10 material will have to wait, however. Recently I've been gearing up for a go at some unfinished business, namely, HTML Goodies' "So, You Want A Shopping Cart, Huh?" tutorial, which we did not cover in our long tour of the HTML Goodies Beyond HTML : JavaScript sector and which I am eager to take on for at least three reasons:
(1) The "Shopping Cart" tutorial dates to the Joe Burns era, but its code is much more complicated than that in any of Joe's other tutorials and will give us a good workout.
(2) The present-day "Shopping Cart" demo doesn't work/throws errors, and we just can't let that stand, now can we?
(3) This Shopping Cart Uses Cookies, Joe proclaims in bold text. Oh yeah? The "Shopping Cart" code does include setCookieArray( ) and setCookie( ) functions for setting cookies and getCookieArray( ) and getCookie( ) functions for extracting the values of those cookies, but at no point in the code are any cookies actually set, so we need to get the cookie thing sorted out too.
So that's what we'll be doing for a while.

Saturday, April 06, 2013
 
No Sulfur Suppression
Blog Entry #284

We continue today our discussion of Matt Gabbert's mouse fireworks script. We are currently working through the script's mouseDown( ) function: at this point we have
(1) clicked the mouse fireworks page and gotten the click's page coordinates,
(2) visibilized the © divs - absolutely positioned but not given left and top offsets, the divs are transiently rendered at a common normal-flow position, wherever that happens to be - and
(3) switched on the sparksAflyin flag, signifying that a fireworks burst is in progress.

The mouseDown( ) function concludes by iteratively calling the script's moveTo( ) function in order to send the © projectiles on their way.

for (i = 0; i <= 9; i++)
    eval('moveTo(' + i + ', 0, ' + mousex + ', ' + mousey + ')');


As in the script's SHOW( ) function, the above use of eval( ) is completely unnecessary:

for (i = 0; i <= 9; i++) moveTo(i, 0, mousex, mousey);

Moreover, the mousex and mousey values do not change during a fireworks burst and there would be no need to feed them to the moveTo( ) function had we declared mousex and mousey globally and not in the mouseDown( ) function, but let's keep going for the time being.

Projectile motion

Before the mouse fireworks page loads, the script's JavaScript defines twenty arrays - anim_0_x, anim_0_y, ... anim_9_x, anim_9_y - whose values are used by the moveTo( ) function to calculate and set left and top offsets for the © divs during a fireworks burst. Per their names, the arrays respectively adjust the x-axis and y-axis positions of the © divs, for example, the sDiv0 div's x-axis position is adjusted by the anim_0_x array's values and its y-axis position is adjusted by the anim_0_y array's values.

The mouseDown( ) function calls the moveTo( ) function ten times, once per © div. For each div the moveTo( ) function is recursively executed a variable number of (12-23) times depending on how many values are in the div's arrays, e.g., the anim_0_x and anim_0_y arrays each contain 12 values

anim_0_x = new Array(20, 20, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0);
anim_0_y = new Array(-20, -40, -60, -80, -60, -40, -20, 0, 20, 40, 60, 80);


and therefore moveTo( ) runs 12 times for the sDiv0 div. The number of array values is tracked by a j counter variable that is initialized to 0 and runs to the length of each array.

function moveTo(i, j, mousex, mousey) {
    if (j < eval('anim_' + i + '_x.length')) {
        ...
        // timeout: 50 = fireworks speed, larger number = slower speed
        window.setTimeout("moveTo(" + i + ", " + j + ", " + mousex + ", " + mousey + ");", 50); } ... }


Regarding the if condition, anim_#_x (unlike document.all and document.layers) is not a recognized collection expression and thus the preceding use of eval( ) might seem necessary; however, it occurred to me that formulating the div arrays as two-dimensional arrays would allow us to throw out the eval( ) operation even in this case*:

if (j < animx[i].length) {
...
animx[0] = [20, 20, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0];
animy[0] = [-20, -40, -60, -80, -60, -40, -20, 0, 20, 40, 60, 80];
...


*This insight was inspired by the two-dimensional array code appearing in HTML Goodies' "Build Your Own Image Viewer with Scrollbar" tutorial.

The eval( ) function crops up a few more times in the script; I will henceforth replace eval( ) operations with non-eval( ) equivalents.

After verifying that j is in the 0 to divArray.length-1 range, the moveTo( ) function calculates new tempx and tempy offsets for each © div by respectively adding an x/y pair of array values to the mousex/mousey click coordinates.

var tempx = animx[i][j] + mousex;
var tempy = animy[i][j] + mousey;


On the Netscape side, tempx and tempy are respectively assigned to the left and top properties of the © divs, which are layer objects by virtue of being absolutely positioned.

if (n) {
    document.layers["sDiv" + i].left = tempx;
    document.layers["sDiv" + i].top = tempy; }


On the IE side the offset-setting code is a bit more involved.
(1) If the tempx value will place the left edge of a © div inside a 30px-wide buffer abutting the right edge of the page, then tempx is decreased (pushed leftward) by 30.
(2) If the tempy value will place the top edge of a © div inside a 30px-high buffer abutting the bottom edge of the page, then tempy is decreased (pushed upward) by 30.
(3) Adjusted or not, tempx and tempy are then respectively assigned to the style.left and style.top properties of the © divs.

if (ie) {
    if (tempx + 30 > (document.body.offsetWidth + document.body.scrollLeft))
        tempx = document.body.offsetWidth + document.body.scrollLeft - 30;
    if (tempy + 30 > (document.body.offsetHeight + document.body.scrollTop))
        tempy = document.body.offsetHeight + document.body.scrollTop - 30;
    document.all("sDiv" + i).style.left = tempx + "px";
    document.all("sDiv" + i).style.top = tempy + "px"; }


When using IE 4.5 and IE 5.1.6, I find that document.body.offsetWidth and document.body.offsetHeight, which are meant to return the viewport dimensions in the above code,
(a) return the viewport dimensions if the document content area does not exceed the viewport, in which case there is no need to correct for user scrolling, and
(b) return the page dimensions if the document content area exceeds the viewport, in which case we do not want to correct for user scrolling.
Alternatively, document.body.clientWidth and document.body.clientHeight in quirks mode reliably return the viewport dimensions regardless of whether the document content area is larger or smaller than the viewport.

Although Netscape 4.x supports neither offsetWidth/offsetHeight nor scrollLeft/scrollTop nor document.body for that matter, the tempx and tempy offsets could have been similarly pre-adjusted in the corresponding Netscape code via:

if (tempx + 30 > window.innerWidth + window.pageXOffset)
    tempx = window.innerWidth + window.pageXOffset - 30;
if (tempy + 30 > window.innerHeight + window.pageYOffset)
    tempy = window.innerHeight + window.pageYOffset - 30;


You might not care about pre-adjusting tempx and tempy - after all, the user shouldn't expect to see a proper fireworks burst upon clicking next to the viewport's right or bottom edge - that said, the aforementioned clientWidth/clientHeight approach will keep the projectiles from moving off-viewport for the greatest number of users.

Subsequently the moveTo( ) function increments j (j++;) and then calls itself after a 50-millisecond delay (vide supra). In the following moveTo( ) run mousex/mousey are adjusted by the next x/y pair of array values, per the incremented value of j; in the moveTo( ) run after that we adjust mousex/mousey by the next x/y pair of array values; and so on. Suppose we click the page at (x,y) = (200,200). In its first moveTo( ) iteration the sDiv0 div is placed at (tempx,tempy) = (220,180), in its second moveTo( ) iteration the div moves to (tempx,tempy) = (220,160), in its third moveTo( ) iteration it moves to (tempx,tempy) = (210,140), etc.

Ending the show

When j maxes out at the divArray.length(s), an else clause (a) calls on the script's HIDE( ) function to hide each © div and (b) counts up the divs as they disappear via the totalSparks variable.

else {
    HIDE("sDiv" + i);
    totalSparks++; }
...
function HIDE(divName) {
    if (document.all)
        document.all(divName).style.visibility = "hidden";
    else if (document.layers)
        document.layers[divName].visibility = "hide"; }


The moveTo( ) function concludes by resetting sparksAflyin and totalSparks to 0 when totalSparks hits 10.

if (totalSparks == 10) {
    sparksAflyin = 0;
    totalSparks = 0; }


Demos

I don't know where Matt got the animx/animy array values - maybe he has a doctorate in pyrotechnics or something. The values move most of the © divs on roughly parabolic paths; to see the individual paths for the sDiv0, sDiv6, and sDiv8 divs, click the corresponding links in the div below.

Click here to see the sDiv0 div path. Click here to see the sDiv6 div path. Click here to see the sDiv8 div path.

Let me wrap up this post with one more demo. As cool as the previous post's ♥ demo was, it wasn't really very fireworks-y, was it? Click in the div below to see a display that more resembles an actual fireworks burst.

Baby, you're a firework...

This demo uses asterisks for its projectiles, gives the projectiles new colors that are suitable for a navy background, further increases the projectile size, and slows things down by doubling the setTimeout( ) delay to 100 milliseconds.
We may or may not be done with the Lissa Explains It All JavaScript subsector - we'll take stock of the situation in the following entry.


Powered by Blogger

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