reptile7's JavaScript blog
Saturday, July 08, 2006
 
An Image Loop: Andante
Blog Entry #44

HTML Goodies' JavaScript Primers #27, "Putting it all together: A S[l]ide Show," comprises a reasonably straightforward application of the image-flip methodology of Primer #15, Primer #16, and the Primer #23 Assignment to the creation of a simple slide show. The Primer #27 Script enables a looplike, sequential display of the pic1.gif, pic2.gif, and pic3.gif images that we randomized in the Primer #23 Assignment, and is given below:

<html><head>
<script type="text/javascript">
var num=1;
img1 = new Image( );
img1.src = "pic1.gif";
img2 = new Image( );
img2.src = "pic2.gif";
img3 = new Image( );
img3.src = "pic3.gif";
function slideshow( ) {
num=num+1;
if (num==4) {num=1;}
document.mypic.src=eval("img"+num+".src"); }
</script></head>
<body><center>
<img src="pic1.gif" name="mypic" border="0" alt=""><p>
<a href="JavaScript:slideshow( );">Click for the next picture</a>
</center></body></html>

Image preloading

Prior to the start of the slide show, the script in the document head preloads the pic1.gif, pic2.gif, and pic3.gif images into the user's browser cache. Let's look at the code that preloads the pic1.gif image:

img1 = new Image( );
img1.src = "pic1.gif";

Recalling the right-to-left character of assignment statements:

(1) The img1 = new Image( ); command line creates with a constructor statement an image object and assigns it to the identifier img1.

You won't find anything on new Image( ) constructor statements in present-day JavaScript; rather, you have to go back to the image object section of Netscape's "obsolete" JavaScript 1.3 Client-Side Reference for this information. Indeed, JavaScript no longer has an image object, which as a host object has been spun off to the DOM; in this regard, the Image( ) constructor is, to my understanding, a 'legacy feature' belonging to "Level 0" of the DOM.

As Joe notes in the fourth list item (<li>) of his script deconstruction, Image( ) can have two parameters specifying, in order, the width and height in pixels of the image, in this case:

img1 = new Image(150,150); // all three pic#.gif images are 150×150

(2) The img1.src = "pic1.gif"; command line writes the src property of the img1 image by assigning the value/file "pic1.gif" to img1.src.

Together, these two lines of code do not display the img1/pic1.gif image on the page*, but they do cache the image on the user's hard disk, making it easier to access than if it had to be 'downloaded on the spot,' as Joe explains in his old-school "So, You Want To Pre-Load, Huh?" tutorial.
(*The slide images are displayed by the <img name="mypic"> element in the document body.)

A question arises: "Is this image preloading business really necessary?"

I can't give you a one-size-fits-all answer here. In this particular case, perhaps not, given that the pic#.gif images are small, 32K gif files (BTW, they're easily shrinkable to 4K each using Adobe Photoshop's "Save for Web" feature). For larger images, preloading won't hurt and is probably a good idea, even for users with speed-optimized systems.

A nitpicking comment on something Joe says in the fifth deconstruction <li>: "By placing all these image objects outside a function, the program preloads the pictures." Actually, if we wanted to, we could put the first seven script statements in, say, a preload( ) {statements} function and call that function with an onLoad="preload( );" event handler in the <body> tag, and this would still preload the pic#.gif images, but then the subsequent slideshow( ) function would not recognize the now-nonglobal preload( ) function code.

Moving through the slides/JavaScript URLs

We move through the pic#.gif images that compose the slide show by repeatedly clicking the following link:

<a href="JavaScript:slideshow( );">Click for the next picture</a>

"That's a little new, huh?" Joe correctly points out in the last <li> of the script deconstruction. This is indeed our first encounter in the HTML Goodies JavaScript Primers series with a JavaScript URL; here is another legacy feature that you won't find in JavaScript 1.5. A JavaScript URL has the following syntax:

javascript:executable_code;

and is assigned as a string to the href attribute of an <a> tag in place of a 'normal' Web page URL. The href="javascript:executable_code;" attribute/value gives the same effect as, but is not replaceable by, an onClick="executable_code;" event handler.
(If you'd rather use an onClick event handler than a JavaScript URL, then you can replace the link with a button:
<input type="button" value="Click for the next picture" onclick="slideshow( );">.)

A few notes on JavaScript URLs:
• The "javascript" part is not case-sensitive, but the "executable_code" part is.
• The "executable_code" can be one or more commands and/or functions.
WebReference.com notes that the void special operator should be applied to JavaScript URL commands that return values, e.g.:

href="javascript: slideshow( ); void(document.bgColor='blue'); void(window.defaultStatus='Hello');"

On my computer, I find that the 'voiding' of values is necessary when using Netscape but not when using MSIE.

Before we go any further, we need to address the rest of that last deconstruction <li>:

"See how the call is for the JavaScript rather than the function? That allows all parts to be used rather than just the function. If you use just the function with this format, you'll have no images because they will have been bypassed."

Joe is alleging here that if you leave out the "javascript" part of the JavaScript URL:

<a href="slideshow( );">Click for the next picture</a>

then clicking the link will call the slideshow( ) function 'in isolation,' i.e., slideshow( ) will not be able to make use of the global statements preceding it. This isn't what happens, folks; without the "javascript", the browser (a) sees href="slideshow( );" as a regular URL, (b) goes looking for a "http://hostname/pathname/slideshow( );" page, and - assuming that the calling document's directory doesn't contain a "slideshow( );" file - (c) pops up a "The file cannot be found" message box.

In sum, the "javascript" keyword in a JavaScript URL does not affect a function's recognition of global code but is merely a necessary part of the JavaScript URL syntax; also, the href="slideshow( );" attribute/value is not a legitimate function call and thus the slideshow( ) function is not executed.

The slideshow( ) function

The slideshow( ) function in the document head resembles a count-controlled loop; it has:
(i) a preceding initial-expression, var num=1;
(ii) a condition, if (num==4) num=1; and
(iii) an increment-expression, num=num+1.

The variable num is our counter variable and also serves as an index number for the pic#.gif image files; num is initialized with a value of 1 on the first line of the document head script. Strangely, the script deconstruction's second <li> avers that the external-to-a-function declaration of num is "something new." Our use of unfunctionized variables began as far back as Primer #3; moreover, we discussed the difference between global variables and local variables at the end of Blog Entry #33. It is in fact important, however, that num is declared globally; if the var num=1 statement were placed inside the slideshow( ) function, then num would be reset to 1 each time slideshow( ) is called and the slide show would not proceed. (For a count-controlled for or while loop, recall that the placement of the initial-expression inside the loop body sets up an infinite loop.)

We're ready now to run through a sequence of events for the slide show:

(1) When the document loads, the <img src="pic1.gif" name="mypic" border="0" alt=""> element displays the pic1.gif image as the first slide.

(2) Upon clicking the "Click for the next picture" link, the href="JavaScript:slideshow( );" code triggers the slideshow( ) function.

(3) The num=num+1 statement increments num to 2.

(4) The condition of the if (num==4) {num=1;} statement is false (num is not equal to 4), so the browser skips over the {num=1;} command (num is not reset to 1).

(5) Next, an image flip: with num=2, the right-hand side of the document.mypic.src=eval("img"+num+".src"); statement evaluates the "img"+2+".src" expression to give the string "pic2.gif", whose assignment to document.mypic.src displays the pic2.gif image as the second slide.

(6) Reclicking the "Click for the next picture" link recalls the slideshow( ) function, num increments to 3, the if condition is still false, and assignment of "pic3.gif" to document.mypic.src displays the pic3.gif image as the third slide.

(7) Reclicking the "Click for the next picture" link recalls the slideshow( ) function and num increments to 4; now, the if condition is true and num is reset to 1. Assignment of "pic1.gif" to document.mypic.src redisplays the pic1.gif image as the fourth slide.

Steps (2)-(7) repeat ad infinitum for subsequent link clicks, giving a cyclical slide show of the pic#.gif images.

"What about for a real slide show, with, like, 40 slides?"

No problem - name all of the slide images in order using the pic#.gif format and preload them as above, and then change slideshow( )'s if statement to if (num==41) {num=1;}, and you're good to go.

A bit more on eval( ), and other code possibilities

Joe offers the following description of the role of the eval( ) function in the image-flip statement:

"Notice that eval( ) causes img1.src to be the contents of img1.src, not the literal string. If it was seen as a literal string it would not be seen as a command but rather as just a run of letters."

Joe is on the right track here, but a bit of clarification is in order. When Joe says "literal string," he is referring to the string "img#.src" resulting from "img"+num+".src", the eval( ) argument. If we remove the eval( ) function from the image-flip statement, then we are left with:

document.mypic.src="img"+num+".src"; // or
document.mypic.src="img#.src";

which is still a command, to be sure, but not a meaningful one for the browser (unless the calling document's directory contains an "img#.src" image file). So what happens? You'll still see the first slide (step (1) above), but clicking the "Click for the next picture" link will not load subsequent slides because the "img#.src" "run[s] of letters" (unlike "pic#.gif") are not image file names.

You may recall that this same discussion, more or less, cropped up in Blog Entry #39 when we examined the following command in the "Or if You are REALLY Clever" answer script for the Primer #23 Assignment:

document.write("<img src=" + quot + eval("var"+num) + quot +">"); // quot="'";

A point worth reemphasizing: both eval("img"+num+".src") and eval("var"+num) return their respective values as strings; we can verify this via the typeof special operator, for example:

typeof(eval("img"+num+".src")) // returns string

At least for the Primer #27 image-flip statement, this is important because the use of an unstringified pic#.gif in the command:

document.mypic.src=pic#.gif;

would throw a "pic# is not defined" error.

If you'd rather not use the eval( ) function, then I can think of a couple of alternatives:

(1) Per the Blog Entry #39 discussion cited above, we can replace eval("img"+num+".src") with "pic"+num+".gif":

document.mypic.src="pic" + num + ".gif";

(2) With the JavaScript Array( ) object now under our belts, we can create an array of images and retool the document head script as follows:

<script type="text/javascript">
var num=0;
imgx = new Array( );
imgx[0] = new Image(150,150); imgx[0].src = "pic1.gif";
imgx[1] = new Image(150,150); imgx[1].src = "pic2.gif";
imgx[2] = new Image(150,150); imgx[2].src = "pic3.gif";
function slideshow( ) {
num=num+1;
if (num==3) num=0;
document.mypic.src=imgx[num].src; }
</script>

(The coordination of the loop counter num with the imgx index numbers via the imgx[num].src expression was inspired by the Primer #26 Script.)

We'll convert the Primer #27 slide show into an animation of the pic#.gif images in Primer #28, which we'll take on in the next entry.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

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