reptile7's JavaScript blog
Wednesday, November 02, 2011
 
The Banner Loop, Part 2
Blog Entry #231

We continue today our analysis of HTML Goodies' "Accessible JavaScript 101: Rotating Banners" tutorial. We are at present discussing the JavaScript part of the tutorial code. At the conclusion of our last episode, we had triggered the Rotator( ) constructor function, removed the banner listElement's end-of-line Text nodes, and zeroed out the banner listElement's second and third li element children:

function Rotator(listElement, delay) {
    for (var i = listElement.childNodes.length - 1; i >= 0; i--)
        if (!/LI/.test(listElement.childNodes[i].nodeName))
            listElement.removeChild(listElement.childNodes[i]);
    for (var i = 1; i < listElement.childNodes.length; i++)
        listElement.childNodes[i].style.display = "none";


At this point - this point counting as the first frame of the banner animation - the Google logo is all we have for a display:

[The Google logo]

The Rotator( ) function next defines a currentLI property for representing the listElement list item currently on display.

this.currentLI = listElement.firstChild;

currentLI is bound to the custom object we are constructing via the this keyword.
currentLI is initialized to the listElement's firstChild, that is, it initially points to the li element holding the Google banner; firstChild is yet another attribute of the Core DOM Node interface.

Subsequently the Rotator( ) function defines a showNext method for manipulating the currentLI property.

this.showNext = function ( ) { ... }

The showNext method name, the showNext functionality, and this, which again acts as a proxy for the custom object, are associated à la the displayCar method example appearing in the Defining Methods subsection of the "Working with Objects" chapter in the Mozilla JavaScript Guide. Note that Mozilla formulates the displayCar functionality as a named displayCar( ) function placed outside the Car( ) constructor function, but there's no reason why you can't assign an anonymous function expression to this.methodName inside the constructor, as is done in this case.

Each frame of the banner animation maps onto a run of the showNext function. Here's what the function does in the second frame:

this.currentLI.style.display = "none";

The li element holding the Google banner is zeroed out by setting its CSS display property to none.

this.currentLI = this.currentLI.nextSibling ? this.currentLI.nextSibling : this.currentLI.parentNode.firstChild;
/* nextSibling and parentNode are also Node interface attributes. */


Read about the ?: conditional operator here. The this.currentLI.nextSibling condition returns true - the li element holding the Google banner does have a nextSibling, namely, the li element holding the Yahoo! banner - and consequently this.currentLI is switched to this.currentLI.nextSibling.

this.currentLI.style.display = "block";

The li element holding the Yahoo! banner is visibilized by setting its display property to block.

In the third animation frame, the function similarly zeroes out the li element holding the Yahoo! banner and visibilizes the li element holding the MSN banner.

In the fourth animation frame, the this.currentLI.nextSibling condition returns false - the li element holding the MSN banner doesn't have a nextSibling as it's the last list item in the listElement list - and consequently this.currentLI is switched (back) to this.currentLI.parentNode.firstChild, that is, to the li element holding the Google banner.

The mechanics of the animation are now in place: all that remains for us to do is to set the animation in motion by iteratively calling the showNext method. Had we variabilized the custom object, say, with a bannerRotator identifier

window.onload = function ( ) {
    var bannerRotator = new Rotator(document.getElementById("banners").getElementsByTagName("ul")[0]); }


then starting the animation would be a simple matter of adding a

window.setInterval("bannerRotator.showNext( );", 1000);

command to the preceding function. But for whatever reason, the tutorial author did not give the custom object a name.

Is it necessary to call the showNext method outside of the Rotator( ) constructor function? Why not put the setInterval( ) command in the constructor and reference the custom object with this? The author briefly alludes to this coding possibility in a the dynamic class method cannot be passed to setInterval( ) remark. In the event, a

window.setInterval("this.showNext( );", delay);

constructor command throws a repeating "this.showNext is not a function" error. However, the source of the error is not the showNext method but rather the this reference:
Code executed by setTimeout( ) or setInterval( ) is called from a separate execution context vis-à-vis that for the function from which setTimeout( ) or setInterval( ) was called. The usual rules for setting the this keyword for the called function apply, and if you have not set this in the call or with the bind( ) method, it will default to the global (or window) object in non–strict mode, or be undefined in strict mode. It will not be the same as the this value for the function that called setTimeout( ) or setInterval( ).
In the Questions sector of stackoverflow.com, moderator SLaks offers a solution to this problem:
You need to save a reference to this in a local variable, then pass a function to [setInterval( )] that uses the variable. For example:

var bannerRotator = this;
window.setInterval(function ( ) { bannerRotator.showNext( ); }, delay);
I've tried the above code. Works beautifully. Thanks, SLaks!

The tutorial author also uses a find-another-way-to-reference-the-custom-object approach to solving the this/setInterval( ) problem - an approach that is somewhat more circuitous than SLaks's approach. Here's how it goes:

(1) Outside of the constructor function, Rotator( ) is given an instances property that is set to an empty array.

Rotator.instances = new Array( );

instances is not a property of objects created by the Rotator( ) constructor but rather a property of the Rotator( ) Function object itself.

(2) Back inside the Rotator( ) constructor, the custom object is given an id property that is set to the length of the instances array.

this.id = Rotator.instances.length;

For a first run through the Rotator( ) function, this.id/Rotator.instances.length is 0.

(3) The custom object is loaded into the instances array via the push( ) method of the Array object.

Rotator.instances.push(this);

We are now able to reference the custom object as an element of the instances array, i.e., Rotator.instances[0].

(4) Finally, the animation is started with:

window.setInterval("Rotator.instances[" + this.id + "].showNext( );", delay);

This approach works but does seem to come out of left field if you are unaware of the this/setInterval( ) problem, which gets no mention in the aforementioned Mozilla "Working with Objects" documentation.

We'll look at two other ways to code the banner animation in the following entry.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

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