reptile7's JavaScript blog
Tuesday, October 25, 2011
 
The Banner Loop
Blog Entry #230

In today's post we will take up HTML Goodies' "Accessible JavaScript 101: Rotating Banners" tutorial, which is credited to an HTML Goodies "Forum Member Web Developer" (hereafter "FMWD"). The "Rotating Banners" tutorial offers code that, when pieced together correctly and paired with a suitable set of images, gives the following display:

Affiliate Links

  • Search Web with Google
  • Search Web with Yahoo!
  • Search Web with MSN
 
Three image-link banners - one for Google, one for Yahoo!, one for MSN - display one at a time in an endless loop. Clicking on a banner will take you to the Web site of the company named on the banner. The tutorial code's JavaScript employs a custom JavaScript object and a variety of Core DOM properties to create the rotating banner display, and is thus quite a bit different from the image-cycle animation script presented in Blog Entry #147. HTML/CSS The tutorial code's HTML structures the banners to be rotated as an unordered list preceded by an (optional) Affiliate Links header and wrapped in an id="banners" div container: <div id="banners"> <h4>Affiliate Links</h4> <ul> <li><a href="www.google.com"><img alt="Search Web with Google" src="googlelogo.png"></a></li> <li><a href="www.yahoo.com"><img alt="Search Web with Yahoo" src="yahoologo.png"></a></li> <li><a href="www.msn.com"><img alt="Search Web with MSN" src="msnlogo.png"></a></li> </ul> </div> • The ul/li scaffolding is not really necessary but makes semantic sense if we want all of the banners to be visible for users without JavaScript support. • The anchor href values are missing their http:// parts - don't forget to pencil them in if you make use of this code. • FMWD doesn't provide a demo or any images to work with; for my demo above, the Google logo was picked out at this page, the Yahoo! logo was picked out at this page, and the MSN logo was picked out at this page. The tutorial code's CSS styles the banners div and its descendants in various ways; most importantly, it places the banners div in the page's upper-right-hand corner via: #banners { position: absolute; top: 10px; right: 10px; } Supposing that each banner image is 200px by 80px, FMWD gives the unordered list a #banners ul { display: block; height: 80px; overflow: hidden; } style rule set that effectively hides the Yahoo! and MSN banners when the page loads. However, we will later zero out those banners with liObject.style.display = "none"; commands and thus the height:80px; and overflow:hidden; declarations are redundant; moreover, ul elements are block-level elements (unless you set them otherwise), so we don't need the display:block; declaration either. FMWD notes that alternatively setting overflow to auto will allow users without JavaScript support to access the Yahoo! and MSN banners via a vertical scrollbar (overflow:scroll; will also do this); my own preference would be to throw out the overflow declaration and display all three banners simultaneously. FMWD also applies display:block; to the list's li children - li elements are also block-level elements - and attempts to get rid of the list's item markers via a list-item-style:none; declaration: #banners li { display: block; list-item-style: none; } In fact, CSS doesn't have a list-item-style property: list-style-type:none; or list-style:none; is what we want. Interestingly, the preceding rule set does remove the li markers, but it's the display:block; declaration that does the removing; presumably this is because the li elements must have an effective display value of list-item for the markers to be present. Rounding out the CSS are rules that subtract the Affiliate Links header, any link-induced img borders, and margin and padding areas within the unordered list: #banners h4 { display: none; } #banners img { border: none; } /* Firefox users will see blue img borders without this rule. */ #banners ul, #banners li, #banners a, #banners img { margin: 0; padding: 0; } If your images are not actually 200px by 80px but merely have a ≅200/80 width/height ratio, as is the case for my demo images, then you will want to add width:200px; and height:80px; declarations to the #banners img rule set. JavaScript Here's the display so far if we give the unordered list a visible overflow (its default setting):

Affiliate Links

  • Search Web with Google
  • Search Web with Yahoo!
  • Search Web with MSN
 
The list's li children do not have ids, so let's call them listItems[0], listItems[1], and listItems[2]. (You'd think that the W3C would have equipped the DOM HTMLUListElement and HTMLOListElement interfaces with a listItems attribute, but it didn't.) The tutorial code's JavaScript displays one list item and zeroes out the other two in each frame of the rotating banner animation: (1) listItems[0] is switched on and listItems[1] and listItems[2] are switched off in the first frame; (2) listItems[0] is switched off, listItems[1] is switched on, and listItems[2] is switched off in the second frame; (3) listItems[0] and listItems[1] are switched off and listItems[2] is switched on in the third frame; and so on for subsequent frames. The script does not change the source order of the li elements; the Yahoo! and MSN banners appear in the listItems[0] position because the li elements that precede them are removed from the formatting structure entirely (quoting the W3C) via a none display setting. At the beginning of the tutorial's Advanced Functionality Layer - JavaScript section, FMWD exhorts the reader to encapsulate the script operations within a class and then lays out the following template therefor: function lIterator(listElement, delay) {     /* Constructor */     /* Dynamic Methods */ } /* Static Properties */ /* Static Methods */ Some people come to JavaScript from a simpler direction, namely, they begin their coding education by learning HTML, they pick up some CSS, and then they move on to JavaScript. Some people come to JavaScript from a more advanced direction, that is, from a programming language such as C or Java. I'm in the former group; FMWD would certainly seem to be in the latter group. JavaScript does not use the class data structure and it does not have dynamic methods (or at least it doesn't call them that). The terms static property and static method do crop up in the Mozilla JavaScript Reference (e.g., parse( ) is a static method of the Date object) but do not apply in the present context. But at least the constructor part is right: the lIterator( ) function will be used to construct a custom JavaScript object that carries out the banner display action. I don't like the lIterator name because a lowercase l and an uppercase I look the same in a sans-serif font - Mozilla wouldn't like it either because it begins with a lowercase letter - and I will call the constructor function Rotator( ) going forward. The custom object is created ("instantiated") via an onload event handler: <body onload="new Rotator((document.getElementById('banners')).getElementsByTagName('ul')[0], 1000);"> <!-- The outer parentheses surrounding (document.getElementById('banners')) are unnecessary. --> When the document loads, Rotator( ) is called and passed its call arguments à la a normal function. The first Rotator( ) call argument, document.getElementById('banners').getElementsByTagName('ul')[0], references the unordered list (assuming that the list is the first-in-source-order ul element in the document) and is given a listElement identifier. The second Rotator( ) call argument, 1000, will be used to set the animation frame rate and is given a delay identifier. We're ready to fill in the Rotator( ) function body. FMWD's Rotator( ) code deals with the unordered list and its contents on a node-by-node basis (vis-à-vis, say, an element-by-element basis), accordingly beginning with a for loop that strips the list of any child nodes that are not li elements: function Rotator(listElement, delay) {     for (var i = listElement.childNodes.length - 1; i >= 0; i--)         if (!/li/i.test(listElement.childNodes[i].nodeName))             listElement.removeChild(listElement.childNodes[i]); As written out in the previous section, the unordered list has seven child nodes: (2,4,6) The three li elements are the second, fourth, and sixth child nodes of the list; the nodeName of these Element nodes is LI. (1,3,5,7) The end-of-line characters surrounding the li elements are the first, third, fifth, and seventh child nodes of the list; the nodeName of these Text nodes is #text. In each loop iteration, an if statement test( )s for a match between (a) the nodeName of a given listElement child node and (b) a /li/i* regular expression literal; if the match is unsuccessful (note the ! operator preceding the literal), as is the case for the end-of-line Text nodes, then the node is removed via a removeChild( ) command. *There's actually no need to case-insensitize the regexp pattern. We can and should use a LI pattern and ditch the i flag: the W3C stipulates that for an Element node in an HTML document, nodeName (Element.tagName) must give an all-uppercase return. FMWD correctly points out that a comment node <ul> <!-- The banners are coded below. --> <li>... whose nodeName would be #comment, would also be removed by the loop. The childNodes attribute, the nodeName attribute, and the removeChild( ) method are all part of the DOM Node interface. Also check out Mozilla's Node interface portal and its links to pages that flesh out the various Node attributes and methods. The preceding loop is followed by another loop that reduces the listItems[1] and listItems[2] li elements (the ones holding the Yahoo! and MSN banners) to nothingness: for (var i = 1; i < listElement.childNodes.length; i++)     listElement.childNodes[i].style.display = "none"; (In an earlier version of the tutorial the < character in the for condition was not escaped and for (var i = 1; i was all that displayed - this is what djt is talking about in the tutorial comment thread - the code is OK now.) FYI: listElement.childNodes[i].style would throw an error with an end-of-line Text node or a comment node; as far as I am aware, styles cannot be applied to non-Element nodes. Neither of the above loops directly relates to the state or behavior of the custom Rotator( ) object; their operations could be executed outside of the Rotator( ) function if desired. However, the remainder of FMWD's Rotator( ) code does pertain to the Rotator( ) object and we'll go through it in detail in the next post. reptile7

Friday, October 14, 2011
 
Three in the Afternoon on Shrove Tuesday
Blog Entry #229

Over the past few entries we've been discussing HTML Goodies' "Build Your Own Image Scrollbar" tutorial and its "Build Your Own Image Viewer with Scrollbar" successor tutorial. The former tutorial codes an isolated image scrollbar, whereas the latter tutorial connects that scrollbar to external nearby elements that are used to expand on the scrollbar. We cleaned up the image scrollbar code in Blog Entry #227's The image scroller code, take 2 section; in this post we'll retool the scrollbar-to-other-elements connection code. As always, we ask: How can we make the code work for as many users as possible? What can be coded more semantically? What can we consolidate or throw out altogether?

Objectize me

At the beginning of the Storing the Data: Creating a Two-Dimensional Array section on the second page of the "Image Viewer" tutorial the author says:
Since we are extending our image scrollbar we need a way to store and retrieve more data than just the paths to our images. We also need the image title and description. The best way to accomplish this is with a two-dimensional array.
Best? I must respectfully disagree. The imageData 2-D array is a good way to organize the image data and it's certainly an interesting way to organize the image data, but IMO there is a better way that is more in tune with JavaScript's object-oriented nature.

For an earlier* version of the Blog Entry #227 demo I presented the following code for arraying and preloading the scrollbar images:

imageArray = new Array(10);
for (j = 0; j < 10; j++) {
    imageArray[j] = new Image( );
    imageArray[j].src = "image" + (j + 1) + ".jpg"; }


(*In the current demo the imageArray[j].srcs are set discretely and not automatedly, but this is irrelevant to - hmmm, now that I think about it, it's actually in sync with - the discussion below, in which we will discretely set other imageArray[j] properties.)

At the end of the for loop we have ten imageArray[j] image objects sitting in RAM. Why don't we associate the image captions with the DOM title properties of those objects?

// Image caption data
imageArray[0].title = "Grasslands";
imageArray[1].title = "Tree Canopy";
imageArray[2].title = "In the Clouds";
imageArray[3].title = "Sunflower Bud";
...


Similarly, why don't we associate the image descriptions with the DOM alt properties of those objects?

// Image description data
imageArray[0].alt = "This is the description for the first image...";
imageArray[1].alt = "This is the description for the second image...";
imageArray[2].alt = "This is the description for the third image...";
imageArray[3].alt = "This is the description for the fourth image...";
...


Classical JavaScript defined an imageObject.src property but not an imageObject.alt property nor an elementObject.title property; Microsoft implemented all three properties in IE 4 and the W3C subsequently brought them, as interface attributes, into the DOM, more specifically:
src and alt are part of the HTMLImageElement interface (and other interfaces to which these attributes are relevant).
title is part of the HTMLElement interface.
Even if these properties didn't exist, however, we could still define them on the fly à la the above statements.

We can now call on our imageArray objects and their properties in writing the first three rows of the scrollbar display table, for example:

function changeCellText(cellId, myCellData) {
    document.getElementById(cellId).innerHTML = myCellData; }
...
changeCellText("imageTitleCell", imageArray[imageIndex].title);
changeCellText("imageDescriptionCell", imageArray[imageIndex].alt);


The setAttribute( ) blues

Three "Image Viewer" tutorial commenters - at least two of whom are IE 8 users - complain that after doing some scrolling, mousing over the thumbnail images has no effect on the rest of the display table, for example:
mark said on March 12, 2010 at 11:26 am
Once you scroll the thumbnails at the bottom, hovering over the thumbnails no longer displays a new large image at the top. Has anyone found a fix for this?
This mirrors my own experience at commenter George's worked-up image viewer when using IE 5.2.3, a browser for which the preloadThumbnails( ) function does not cause any scrollbar image-loading problems.

The present problem lies with the changeImageOnMouseOver( ) function:

function changeImageOnMouseOver(ImageToChange, imageIndex) {
    document.getElementById(ImageToChange).setAttribute("onmouseover", "handleThumbOnMouseOver(" + imageIndex + ");"); }


Commenter Rene reports that when using IE 8 the DOM setAttribute( ) method cannot be used to register the handleThumbOnMouseOver( ) event listener with the ImageToChange img object (OK, Rene doesn't put it quite that way), although the Remarks section of Microsoft's setAttribute( ) page avers that, yes, the setAttribute( ) method does support event handlers. Moreover, Microsoft provides a "setAttribute( ) Example" page that, inter alia, uses setAttribute( ) to coassociate a table cell object, mouseout events, and a this.bgColor=\'green\' command; this association is successful - upon clicking Cell 6 and mousing out from it, the cell's background color turns green - for all of the OS X GUI browsers on my computer except IE 5.2.3.

Microsoft unhelpfully suggests, To set an event handler in Internet Explorer, use attachEvent( ) rather than setAttribute( ). We most recently discussed the attachEvent( ) method in Blog Entry #220; as far as I am aware, on the Mac platform attachEvent( ) is only supported by Opera.

Rene recommends that we replace the changeImageOnMouseOver( ) function's setAttribute( ) command with:

document.getElementById(ImageToChange).onmouseover = new Function("handleThumbOnMouseOver(" imageIndex ");");

The syntax of the above statement is not quite right; the imageIndex parameter must be concatenated with the expression tokens flanking it:

document.getElementById(ImageToChange).onmouseover = new Function("handleThumbOnMouseOver(" + imageIndex + ");");

The creation of Function objects via the new operator, and the assignment of those objects to object.onevent expressions, are briefly discussed in the Function Object section of the Mozilla JavaScript Guide.

My own preference here is to use an anonymous function expression:

document.getElementById(ImageToChange).onmouseover = function ( ) { handleThumbOnMouseOver(imageIndex); }

Either of the two preceding statements gives a functioning changeImageOnMouseOver( ) function when using IE 5.1.6 in the SheepShaver environment, so I would certainly think that they would work with more recent versions of IE. FYI, Mozilla frowns on the use of the Function constructor to create Function objects:
Note: Using the Function constructor to create functions is not recommended since it needs the function body as a string which may prevent some JS engine optimizations and can also cause other problems.
Rene also offers the following if...else template for IE users having trouble with the style-setting setAttribute( ) commands in the scrollImages( ) function:

if (navigator.appName == "Microsoft Internet Explorer") {
    document.getElementById("scrollNextCell").style.setAttribute("cssText", "color:silver;"); }
else {
    document.getElementById("scrollNextCell").setAttribute("style", "color:silver;"); }


Introduced by Microsoft, cssText is a now-standard DOM attribute/property that crops up in several CSS-related interfaces - those would be the CSSRule, CSSStyleDeclaration, and CSSValue interfaces, in case you were wondering. The cssText command in the above if clause is invalid for two reasons:

(1) setAttribute( ) is a method of the Core Element interface, whereas document.getElementById("scrollNextCell").style represents an object implementing the CSSStyleDeclaration interface, which does feature a setProperty( ) method but not a setAttribute( ) method.

(2) The setAttribute( ) method is for setting element attributes and not DOM attributes; in HTML, cssText is not an attribute of the style element or any other element.

As cssText is a property of CSSStyleDeclaration-implementing objects, the correct syntax would be:

document.getElementById("scrollNextCell").style.cssText = "color:silver;";

Microsoft documents the cssText property here and provides a cssText demo page here (if anyone in the MSDN is reading this, the single quotes in the test paragraph's color:'green'; style declaration should be removed - it is in fact illegal to quote CSS keywords). For its part Mozilla maintains a CSSStyleDeclaration.cssText page here. The above style.cssText assignment is supported by all of the OS X GUI browsers on my computer, without exception, so there shouldn't be any need to wrap it in a browser-sniffing conditional. That said, is this statement an improvement in any way over a more basic tableCellObject.style.color = "colorValue"; statement? No, it isn't.

Five into one

The complete "Image Viewer" code is larded with functions that only contain one command. In my view, placing individual commands in functions creates unnecessary clutter and is thus bad form. We got rid of some of the single-command functions (scrollAgain( ), scrollStop( ), and changeImage( )) in Blog Entry #227; why don't we get rid of the rest of them?

In particular, all of the functions called by the handleThumbOnMouseOver( ) function - changeImage( ), changeCellText( ), changeImageAlt( ), and changeImageTitle( ) - hold one command. Rolling the handleThumbOnMouseOver( ) action into a single function is as easy as one, two, three:

function handleThumbOnMouseOver(imageIndex) {
    document.getElementById("imageLarge").src = imageArray[imageIndex].src;
    document.getElementById("imageTitleCell").innerHTML = imageArray[imageIndex].title;
    document.getElementById("imageDescriptionCell").innerHTML = imageArray[imageIndex].alt;
    document.getElementById("imageLarge").alt = imageArray[imageIndex].title + " - " + imageArray[imageIndex].alt;
    document.getElementById("imageLarge").title = imageArray[imageIndex].title + " - " + imageArray[imageIndex].alt; }


The changeImageOnMouseOver( ) function, detailed earlier, also contains just one command; this function can be thrown out too if we replace its calls in the scrollImages( ) function with a series of

window.setTimeout("document.getElementById('scrollThumb" + i + "').onmouseover = function ( ) { handleThumbOnMouseOver(" + currentIndex + "); }", delay);

commands that directly register the handleThumbOnMouseOver( ) function with the thumbnail placeholders.

Table to divs?

I gave some thought to converting the scrollbar display table to a corresponding div element structure - it would be easy to do so given the large amount of style information specified for the table - but I'm not sure it's worth it to do this. Any display problems inherent in the original table, with its specific table and img widths, should be applicable to the replacement div elements as well. Moreover, most of the table's cell content (excluding only the Next >>/<< Previous buttons) does count as data and therefore a table element is a semantically appropriate element to organize that content. So perhaps we should let the table be.

Demo

Ready for a demo? I knew you were. In the display below, the bottom-row scrollbar works exactly as it did in my Blog Entry #227 demo:
• Mousing over the Next >> button will activate the << Previous button and start forward scrolling, which will continue until the last scrollbar image loads into the last thumbnail placeholder.
• Mousing out from the Next >> button will prematurely stop forward scrolling.
• Backward scrolling is likewise controlled by mousing over and out from the << Previous button.
At any time, mousing over a thumbnail image will load a caption for the image, the image itself, and a description for the image into the top part of the display.

Grasslands
default
This is the description for the first image. Here will be where we give details on the image that is currently being viewed.
<< Previous[ ][ ][ ][ ] Next >>
 
For those of you who are sticklers for separating structure and behavior: for (var i = 1; i < 5; i++)     document.getElementById("scrollThumb" + i).onmouseover = function ( ) { handleThumbOnMouseOver(i - 1); } cannot be used to initially register the handleThumbOnMouseOver( ) function with the thumbnail placeholders. Regarding the i - 1 handleThumbOnMouseOver( ) parameter, I find that i is assigned by reference and not by value; as a result, the final value of i, 5, is determinative of the actual handleThumbOnMouseOver( ) argument for all four registrations and handleThumbOnMouseOver(4) is registered with each placeholder. However, for a reason that is not clear to me, a 'stringified' version of the registration statement eval("document.getElementById('scrollThumb" + i + "').onmouseover = function ( ) { handleThumbOnMouseOver(" + (i - 1) + "); }"); works just fine. Alternatively, the registrations can be written out 'in longhand', which is what I ended up doing. document.getElementById("scrollThumb1").onmouseover = function ( ) { handleThumbOnMouseOver(0); } document.getElementById("scrollThumb2").onmouseover = function ( ) { handleThumbOnMouseOver(1); } document.getElementById("scrollThumb3").onmouseover = function ( ) { handleThumbOnMouseOver(2); } document.getElementById("scrollThumb4").onmouseover = function ( ) { handleThumbOnMouseOver(3); } One last point: If you've checked out commenter George's image viewer, you may have noticed, if you're a sharp-eyed observer, that mousing over a thumbnail image causes some of the thumbnail positions to shift horizontally very slightly - this might not bother you, but if it does, then you can put a stop to it by giving the bottom-row cells a specific width:115px;.
In the following entry we'll move on to the next Beyond HTML : JavaScript tutorial, "Accessible JavaScript 101: Rotating Banners".

reptile7

Tuesday, October 04, 2011
 
The Image Parade, Part 3
Blog Entry #228

In today's post we will take up HTML Goodies' "JavaScript Tutorial: Build Your Own Image Viewer with Scrollbar", which builds on the "Web Developer Tutorial: Build Your Own Image Scrollbar" we've been discussing in the last two entries. As demonstrated in the previous post, the latter tutorial codes an image scrollbar positioned under a large img placeholder whose image sits unchanged while a series of images is loaded into the scrollbar's thumbnail img placeholders. "Build Your Own Image Viewer with Scrollbar" augments the "Build Your Own Image Scrollbar" code with a set of functions and array data that turns the scrollbar into a 'remote control' vis-à-vis the large img placeholder: upon mousing over a given scrollbar image, the new code
(1) loads that image into the large img placeholder,
(2-3) displays a caption and description for the new large image, and
(4-5) assigns new alt and title attribute values for the large img placeholder.

No demo is provided for the "Image Viewer" tutorial. However, tutorial commenter George has posted here a preliminary demo combining the tutorial code with his own set of images and has even put the image viewer into practice here.

A bigger table

The "Image Viewer" tutorial adds two new rows to the scrollbar display table.
(1) The row holding the large image is preceded by a row that will hold a caption for the large image.

#imageTitleCell { text-align: center; font-weight: bold; font-size: 18pt; color: silver; background-color: maroon; }
...
<tr><td colspan="6" id="imageTitleCell">Grasslands</td></tr>


(2) The row holding the large image is followed by a row that will hold a description for the large image.

#imageDescriptionCell { text-align: left; padding-right: 100px; padding-left: 100px; color: white; background-color: maroon; }
...
<tr><td colspan="6" id="imageDescriptionCell">This is the description for the first image. Here will be where we give details on the image that is currently being viewed.</td></tr>


For referencing purposes, the first-row caption cell, the third-row description cell, and the now-second-row large img placeholder have been given ids set to imageTitleCell, imageDescriptionCell, and imageLarge, respectively.

Here's a screen shot of what we've got so far:

[The pre-scroll image viewer table]

A 2-D array

The "Image Scrollbar" tutorial orders the scrollbar image file names via a conventional array; interestingly, the "Image Viewer" tutorial uses a two-dimensional array to organize the image file names, captions for the large images, and descriptions for the large images. The following code sets up a parent imageData array having ten elements, each of which is itself an array having three elements:

var imageData = new Array(10);
createTwoDimensionalArray(3);

function createTwoDimensionalArray(arraySize) {
    for (i = 0; i < imageData.length; ++i)
        imageData[i] = new Array(arraySize); }


The child arrays are populated with image data via normal assignment statements:

// Image file name data
imageData[0][0] = "image1.png";
imageData[1][0] = "image2.png";
imageData[2][0] = "image3.png";
imageData[3][0] = "image4.png";
...
// Image caption data
imageData[0][1] = "Grasslands";
imageData[1][1] = "Tree Canopy";
imageData[2][1] = "In the Clouds";
imageData[3][1] = "Sunflower Bud";
...
// Image description data
imageData[0][2] = "This is the description for the first image. Here will be where we give details on the image that is currently being viewed.";
imageData[1][2] = "This is the description for the second image...";
imageData[2][2] = "This is the description for the third image...";
imageData[3][2] = "This is the description for the fourth image...";
...


Each child array thus contains the file name/caption/description data for a given image; we could alternatively write:

imageData[0] = ["image1.png", "Grasslands", "This is the description for the first image..."];
imageData[1] = ["image2.png", "Tree Canopy", "This is the description for the second image..."];
imageData[2] = ["image3.png", "In the Clouds", "This is the description for the third image..."];
imageData[3] = ["image4.png", "Sunflower Bud", "This is the description for the fourth image..."];
...


The Storing the Data: Creating a Two-Dimensional Array section at the top of the tutorial's second page makes a respectable stab at explaining two-dimensional arrays, likening them to spreadsheets (the author doesn't use this term, but that's the gist of the discussion). The Multi-dimensional arrays section in the Mozilla JavaScript Guide is quite skeletal.

Little image to big image

A new handleThumbOnMouseOver( ) function connects the various parts of the image viewer code. The handleThumbOnMouseOver( ) function is called by mousing over a thumbnail placeholder in the scrollbar; it accepts a single imageIndex argument that is used to access a particular imageData[imageIndex] child array. handleThumbOnMouseOver( ) has no commands of its own: rather, it comprises a set of calls to other functions that manipulate the first three rows of the display table.

The handleThumbOnMouseOver( ) function is initially registered with the thumbnail placeholders via the element attribute approach (see the Register me section of Blog Entry #220), e.g.:

#scrollThumb2 { border: 1px solid; height: 100px; width: 100px; }
...
<img id="scrollThumb2" src="image2.png" onmouseover="handleThumbOnMouseOver(1);" />


So, without doing any scrolling at all, mousing over the scrollThumb2 placeholder triggers handleThumbOnMouseOver( ) and passes thereto 1, which is assigned to imageIndex.

// For a thumbnail mouseover event
function handleThumbOnMouseOver(imageIndex) { ... }


handleThumbOnMouseOver( ) first calls the changeImage( ) function

changeImage("imageLarge", imageData[imageIndex][0]);

- that would be the same changeImage( ) we saw in the "Image Scrollbar" tutorial

function changeImage(ImageToChange, MyImagePath) {
    document.getElementById(ImageToChange).setAttribute("src", MyImagePath); }


- and passes thereto imageLarge and imageData[1][0] in order to load the image2.png image into the large img placeholder.

Subsequently handleThumbOnMouseOver( ) calls twice a new changeCellText( ) function.

changeCellText("imageTitleCell", imageData[imageIndex][1]);
changeCellText("imageDescriptionCell", imageData[imageIndex][2]);


The changeCellText( ) function assigns myCellData to the innerHTML of an id=cellId element:

function changeCellText(cellId, myCellData) {
    document.getElementById(cellId).innerHTML = myCellData; }


The first call to changeCellText( ) thus writes Tree Canopy (imageData[1][1]) to the first-row imageTitleCell cell and then the second call writes This is the description for the second image... (imageData[1][2]) to the third-row imageDescriptionCell cell.

Lastly, handleThumbOnMouseOver( ) places calls to new changeImageAlt( ) and changeImageTitle( ) functions.

changeImageAlt("imageLarge", imageData[imageIndex][1] + " - " + imageData[imageIndex][2]);
changeImageTitle("imageLarge", imageData[imageIndex][1] + " - " + imageData[imageIndex][2]);


imageLarge and Tree Canopy - This is the description for the second image... are passed to both functions; the latter argument is respectively assigned by changeImageAlt( ) and changeImageTitle( ) to the alt and title attributes of the large img placeholder:

function changeImageAlt(ImageToChange, imageData) {
    document.getElementById(ImageToChange).setAttribute("alt", imageData); }
function changeImageTitle(ImageToChange, imageData) {
    document.getElementById(ImageToChange).setAttribute("title", imageData); }


And when scrolling is under way? Every time a new image is loaded into a thumbnail placeholder, the placeholder's mouseover behavior must be updated. The author deploys yet another new function, changeImageOnMouseOver( ), to register the handleThumbOnMouseOver( ) function with the thumbnail placeholders on the fly:

function changeImageOnMouseOver(ImageToChange, imageIndex) {
    document.getElementById(ImageToChange).setAttribute("onmouseover", "handleThumbOnMouseOver(" + imageIndex + ");"); }


In the scrollImages( ) function, each changeImage( ) call is accordingly paired with a corresponding changeImageOnMouseOver( ) call:

currentIndex = imageIndexLast;
changeImage("scrollThumb4", imageData[currentIndex][0]); /* Changes the scrollThumb4 image */
changeImageOnMouseOver("scrollThumb4", currentIndex); /* Updates the scrollThumb4 mouseover behavior */
currentIndex = imageIndexLast - 1;
window.setTimeout("changeImage('scrollThumb3', imageData[" + currentIndex + "][0]);", 25);
window.setTimeout("changeImageOnMouseOver('scrollThumb3', " + currentIndex + ");", 25);
...


Preloading again

This time around the "Image Viewer" tutorial's The JavaScript Scrollbar Code section includes the image-preloading function (modified slightly as per the 2-D array) that appears in the source of the current "Image Scrollbar" tutorial demo page:

<body onload="javascript:preloadThumbnails( );">
...
function preloadThumbnails( ) {
    imageObject = new Image( );
    for (i = 0; i < imageData.length; ++i)
        imageObject.src = imageData[i][0]; }


As noted in the previous entry, the effect of this code is browser-dependent: with some browsers all ten imageData[i][0] images will be cached whereas other browsers will only retain the final imageData[9][0] image.

Commenter George's first-generation demo and worked-up image viewer, cited at the outset of the post, both deploy the preloadThumbnails( ) code as-is. George declares that his image viewer looks and works great! It could well be that the preloading thing is a non-issue if you're a broadband user, as George is likely to be, I don't know for certain. But what I can state with certainty is that if you're a slowpoke dial-up user such as myself, and if you're using an only-imageData[9][0]-is-cached browser such as Firefox or Opera, then the image viewer's scrolling will be problematic - specifically, the imageData[4][0]-imageData[8][0] images will load sporadically if at all - until preloadThumbnails( ) is rewritten so that all of the images are concurrently cached.

I'll offer an alternate coding for the image viewer and a demo of my own in the following post.

reptile7


Powered by Blogger

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