reptile7's JavaScript blog
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 = "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.


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.

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".


Comments: Post a Comment

<< Home

Powered by Blogger

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