reptile7's JavaScript blog
Wednesday, May 27, 2009
 
At the Top of the Slide
Blog Entry #146

Back in Blog Entry #44, we discussed HTML Goodies' JavaScript Primers #27, which presents a script that codes a cyclic display of a series of images through which the user moves via clicking a link - a simple slide show in effect. Primer #27's Assignment, which we did not discuss in Blog Entry #44, addresses the corresponding code for running the Primer #27 slide show in reverse. Joe reprised the Primer #27/Assignment scripts for a subsequent "So, You Want A Slide Show, Huh?" tutorial, our focus today.

For the "So, You Want A Slide Show, Huh?" tutorial, Joe combines his original slide show code with a new set of four images - information.gif, interference.gif, message.gif, and nervous.gif - and also places under the slide show img placeholder a text field for holding information about the displayed image. The tutorial's new-and-improved slide show code is reproduced in the div below:

<script type="text/javascript">

var num = 1;
img1 = new Image( );
img1.src = "information.gif";
img2 = new Image( );
img2.src = "interference.gif";
img3 = new Image( );
img3.src = "message.gif";
img4 = new Image( );
img4.src = "nervous.gif"; 

text1 = "Text for Picture One";
text2 = "Text for Picture Two";
text3 = "Text for Picture Three";
text4 = "Text for Picture Four";

function slideshowUp( )
{
num = num + 1;
if (num == 5)
{ num = 1; }
document.mypic.src = eval("img" + num + ".src");
document.joe.burns.value = eval("text" + num);
}

function slideshowBack( )
{
num = num - 1;
if (num == 0)
{ num = 4; }
document.mypic.src = eval("img" + num + ".src");
document.joe.burns.value = eval("text" + num);
}

</script>

<!-- The Image and Form Codes are Below --> 

<center>
<img src="information.gif" name="mypic" border="0" height="100" width="100" alt="" />
<p>

<form name="joe" action="">
<input type="text" width="100" name="burns" value="Text For Picture One" />
</form>

<a href="JavaScript:slideshowBack( );"> Back</a>

<a href="JavaScript:slideshowUp( );"> Next</a>

Blog Entry #44's analysis of the Primer #27 script also applies to the "So, You Want A Slide Show, Huh?" script, so there's no need to do a detailed deconstruction of the latter in this post. But in his general remarks at the beginning of Primer #27, Joe exhorts us to try to figure out how you can make the script a little different, a little better - that's the least we can do for the "So, You Want A Slide Show, Huh?" code, wouldn't you say?

Automating the preloading of images

The tutorial script's script element first declares a num variable that will serve as an index number for both the slide show images and the captions under those images. Next, the script preloads the slide show images so that the user isn't kept waiting for those images to download when moving through the slide show:

var num = 1;
img1 = new Image( );
img1.src = "information.gif";
img2 = new Image( );
img2.src = "interference.gif"; // etc.
/* In the tutorial's "How It Works" section, Joe completely glosses over the preloading code; [t]he four images are listed follow[ing] a traditional format is all he says about it. */


The slide show images have ordinal object references: img1, img2, img3, and img4; if they also had ordinal file names - say, pic1.gif, pic2.gif, pic3.gif, and pic4.gif, à la the Primer #27 slide show - then we could use a for loop to automate the preloading process, as follows:

var img = new Array( );
for (i = 1; i < 5; i++) {
img[i] = new Image( );
img[i].src = "pic" + i + ".gif"; }


It's not a big deal to respectively write out the preloading code for each image for a slide show with four slides, but you wouldn't want to do that (or at least I wouldn't want to do that) for a slide show with fifty slides, in which case a loop is the way to go. The downside to automated preloading is that the images cannot have descriptive (non-ordinal) file names, but IMO that's a small price to pay to keep the volume of preloading code from getting out of hand.

Ditching eval( )

The tutorial script uses separate slideshowUp( ) and slideshowBack( ) functions to move through the slide show forwards and backwards, respectively. Both of these functions use the following commands to load a new image and caption into their respective placeholders:

document.mypic.src = eval("img" + num + ".src");
document.joe.burns.value = eval("text" + num);


The top-level eval( ) function is one means via which we can leverage num as an index number and thus use the same assignment statements for all four slides. Joe's explanation of eval( )'s role here - The eval( ) helps to turn the text in the parentheses into a variable name rather than just text - could definitely use some help. Admittedly, eval( ) is a somewhat abstract function: eval( ) acts on a stringified expression and returns the value of that expression in its unstringified form. For example, if num is 1, then with respect to the original script, eval("img" + num + ".src") returns information.gif, the value of img1.src. So returning to Joe's reading of eval( ), we might alternatively and more accurately say that eval( ) turns the expression in the parentheses into a relevant value for assignment to document.mypic.src or document.joe.burns.value.

Interestingly, Mozilla criticizes the use of eval( ), stating that eval( ) constitutes a security risk and is slow and should be avoided whenever possible. Although I'm not sure that these considerations apply to the "So, You Want A Slide Show, Huh?" script, it is nonetheless worth asking: How might we move through the slide show in an eval( )-less manner? Gratifyingly, our good friend Mr. Array can provide a "safe alternative" in this case.

You may have noticed that the for loop in the preceding section not only preloaded the slide show images but also arrayed the images' object references. After the preloading code, the script itemizes and variabilizes the four slide show image captions; let's say we array them too:

var text = new Array( );
text[1] = "Text for Picture One";
text[2] = "Text for Picture Two";
text[3] = "Text for Picture Three";
text[4] = "Text for Picture Four";


We can now change slides much more straightforwardly via the following assignments:

document.mypic.src = img[num].src;
document.joe.burns.value = text[num];


Ciao, eval( ) - maybe we'll see you in the afterlife. ;-)

N.B. As img and text are both HTML name tokens, perhaps they are not the best choices for variable names - I chose these identifiers to be consistent with Joe's code - but neither of them is a JavaScript reserved word, so they're not invalid, either.

Triggering slideshowUp( )/slideshowBack( )/slideshow( )

The user goes through the slide show forwards and backwards by respectively clicking Next and Back hyperlinks, whose underlying anchor elements are equipped with JavaScript URLs for triggering the slideshowUp( ) and slideshowBack( ) functions:

<a href="JavaScript:slideshowBack( );"> Back</a>
<a href="JavaScript:slideshowUp( );"> Next</a>


My response: "Yuck." I have previously expressed my disdain for the use of anchor elements as user interface elements, which, despite it being a very widespread practice, I view as a clear-cut violation of the W3C's "Use markup and style sheets and do so properly" accessibility guideline (see also the "A Shift Towards Semantic Mark-up" section at the beginning of HTML Goodies' "CSS Layouts Without Tables" article). A hyperlink should be used to link to another resource (either another page or another section of the same page); for calling a JavaScript function via a click event, a push button is a much better choice:

<button type="button" onclick="slideshow('previous');">Back</button>
<button type="button" onclick="slideshow('next');">Next</button>


Note that my buttons are not associated with the slideshowUp( )/slideshowBack( ) functions but instead with a common slideshow( ) function, bringing us to...

Merging slideshowUp( )/slideshowBack( )

Because the slideshowUp( ) and slideshowBack( ) functions change slides with the same commands (vide supra), why not merge them? This is easily done with a bit of conditional code:

function slideshow(direction) {

if (direction == "next") { num++; if (num == 5) num = 1; }
if (direction == "previous") { num--; if (num == 0) num = 4; }

document.mypic.src = img[num].src;
document.joe.burns.value = text[num]; }


Per the push buttons above, slideshow( ) is passed either next or previous, and then one or the other if statement increments, decrements, or resets the num index, as appropriate.

One more thing...

The script's display is centered by a deprecated center element and the mypic img placeholder is equipped with a deprecated border="0" attribute - I trust you are up to the task of converting these guys into corresponding style rules. But something else in the script's HTML caught my eye: the burns text box has been given a width="100" attribute

<input type="text" width="100" name="burns" value="Text For Picture One" />

which has never been W3C-valid for the input element. The MSDN Library lists a width attribute for the <input type="text"> element, but on my computer MSIE 5.2.3 for Mac OS X does not support it.

Width is a presentational feature, which raises the question: Can we set the CSS width property for a text box? At first glance it wouldn't seem so, because the CSS width property applies to all elements but non-replaced inline elements, table rows, and row groups, and the input element is to my understanding a "non-replaced inline element". However, Section 3.2 of the CSS 2.1 Specification states:
CSS 2.1 does not define which properties apply to form controls and frames, or how CSS can be used to style them. User agents may apply CSS properties to these elements. Authors are recommended to treat such support as experimental. A future level of CSS may specify this further.
In the event, all of my OS X browsers - Firefox, MSIE, Opera, and Safari - smoothly write the width property for the burns field; in practice, I find that an

input { width: 120px; }

rule gives a satisfactorily sized box.

But maybe you would rather just trade in the width="100" attribute for a size="25" attribute - that would also work.

In the next post, we'll roll out a demo based on the above code, plus we'll respond to some of the comments at the bottom of the tutorial.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

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