reptile7's JavaScript blog
Sunday, January 01, 2012
 
Lightbox II
Blog Entry #237

Today we will begin in earnest a deconstruction of the lightbox.js script of the lightbox image viewer spotlighted by HTML Goodies' "How To Use the JavaScript Lightbox Image Viewer" tutorial. You may peruse the lightbox.js code in its entirety here.

When the lightbox.html page for the user has finished loading, the lightbox.js script's initLightbox( ) function is called per its assignment to window.onload by the script's addLoadEvent( ) function as discussed in the Pre-load section of the previous post. The initLightbox( ) function does two things:
(1) It registers the script's showLightbox( ) function with the rel="lightbox" anchor elements in the lightbox.html document.
(2) It codes, but does not display, most of the lightbox image viewer.

showLightbox( ) registration

The lightbox.js showLightbox( ) function, which does display the lightbox image viewer and which we'll get to later, is registered with the rel="lightbox" links by the following code:

function initLightbox( ) {
    if (!document.getElementsByTagName) { return; }
    var anchors = document.getElementsByTagName("a");
    for (var i = 0; i < anchors.length; i++) {
        var anchor = anchors[i];
        if (anchor.getAttribute("href") && (anchor.getAttribute("rel") == "lightbox")) {
            anchor.onclick = function ( ) { showLightbox(this); return false; } } } ...


We first check the browser's document.getElementsByTagName( ) support; browsers without that support are sent packing, i.e., they exit the initLightbox( ) function via a return statement. The getElementsByTagName( ) method goes back to Level 1 of the Core DOM, so unless you're using a really old browser (as in IE 4.x/Netscape 4.x or earlier) you should be good to go.

We next scoop up all of the lightbox.html anchor elements, whether or not they are associated with the lightbox image viewer; these elements are organized as an anchors array.

Finally, we use a for loop
(1) to identify the anchors members
(a) that have a href attribute not set to an empty string AND
(b) whose rel attribute is set to lightbox, and
(2) to assign a function ( ) { showLightbox(this); return false; } function expression to the onclick attribute of those anchors.
The return false; business cancels the HTML behavior of the anchors (going to the href URL when clicked) for users with JavaScript support.

We wouldn't need to screen for the href attribute if we were to instead work with the document.links[ ] collection. Moreover, the lightbox links are more appropriately grouped via the class attribute than via the rel attribute. If we give each lightbox link a class="lightboxLink" marker, then the above showLightbox( ) registration code can be rewritten as:

for (var i = 0; i < document.links.length; i++)
    if (document.links[i].className == "lightboxLink")
        document.links[i].onclick = function ( ) { showLightbox(this); return false; }


Before moving on...
Tobe in the tutorial comment thread asks:
I really do appreciate this wonderful image viewer script but then I would like my user to just hover over the image to produce a lightbox effect showing a larger image rather than clicking on it first. How can this be done?
Just replace onclick with onmouseover and you've got it, Tobe.

Viewer structure

The remainder of the initLightbox( ) function codes almost all of the lightbox image viewer's structure, most of its behavior, and some of its functional (layout-related) styles. Assuming that the loading.gif and close.gif images are available, here's the structure that we'll be creating:

<div id="overlay">
<a href="#">
<img src="loading.gif" id="loadingImage" />
</a>
</div>
<div id="lightbox">
<a href="#" title="Click to close">
<img src="close.gif" id="closeButton" />
<img id="lightboxImage" />
</a>
<div id="lightboxDetails">
<div id="lightboxCaption"></div>
<div id="keyboardMsg">press <kbd>x</kbd> to close</div>
</div>
</div>


There are ten elements in all; each element is created by a document.createElement( ) command:

var objOverlay = document.createElement("div"); ...
var objLoadingImageLink = document.createElement("a"); ...


The id, href, and title attributes are set via Element.setAttribute( ) commands whereas the img src attributes are set directly as properties of the calling Image objects:

objOverlay.setAttribute("id", "overlay"); ...
objLoadingImageLink.setAttribute("href", "#"); ...
objLoadingImage.src = loadingImage; ...
objLink.setAttribute("title", "Click to close"); ...


The setAttribute( ) method can sometimes cause problems for IE users - I don't know if this is true in the present case - it might be safer to set all of the attributes à la the src attribute:

objOverlay.id = "overlay"; ...
objLoadingImageLink.href = "#"; ...
objLink.title = "Click to close"; ...


The overlay div will be the viewer overlay (once we kit it out) and holds the loading.gif image; it is deployed as the first child of the body element by the following commands:

var objBody = document.getElementsByTagName("body").item(0); ...
objBody.insertBefore(objOverlay, objBody.firstChild);


When I first saw this, I said, "Huh?" Elements?? The HTML html element has a HEAD, BODY content model: a valid document cannot contain more than one body element. Alternatively, we can and should reference the body element with document.body, which was standardized in Level 1 of the HTML DOM.

The lightbox div holds the close.gif image, an img placeholder for the main image (whose src will be set later), a div container for an image caption (currently empty), and a div containing a press <kbd>x</kbd> to close 'keyboard message'; variabilized as objLightbox by its createElement( ) command, it is added to the body element as a sibling of the overlay div by:

objBody.insertBefore(objLightbox, objOverlay.nextSibling);

The remaining elements are put in place via Node.appendChild( ) commands:

objOverlay.appendChild(objLoadingImageLink); ...
objLoadingImageLink.appendChild(objLoadingImage); ...


Before moving on...
The W3C doesn't have much to say about the kbd element: Indicates text to be entered by the user - that's all that's there, folks.

Viewer behavior

The lightbox.js hideLightbox( ) function, which zeroes out the lightbox image viewer and which we'll get to later, is registered with the overlay div (objOverlay), the overlay div's anchor element child (objLoadingImageLink), and the lightbox div's anchor element child (variabilized as objLink by its createElement( ) command), more specifically, a function ( ) { hideLightbox( ); return false; } function expression is assigned to the onclick attribute of these elements:

objOverlay.onclick = function ( ) { hideLightbox( ); return false; } ...
objLoadingImageLink.onclick = function ( ) { hideLightbox( ); return false; } ...
objLink.onclick = function ( ) { hideLightbox( ); return false; }


The objLoadingImageLink assignment is redundant: even if we were to click directly on the loading.gif image, that click event would bubble up to the overlay div. And because the only purpose of the objLoadingImageLink link is to serve as a carrier for a hideLightbox( ) trigger, it follows that the link itself is redundant and can be thrown out. For that matter, the objLink link is also unnecessary as its hideLightbox( ) trigger and title attribute can be transferred to the lightbox div.

If the hideLightbox( ) registrations are vested in the overlay and lightbox divs, then they can be simplified to:

objOverlay.onclick = hideLightbox;
objLightbox.onclick = hideLightbox;


The div element doesn't have a default behavior and consequently doesn't need a function expression equipped with a return false; statement.

The press <kbd>x</kbd> to close behavior is set up by the showLightbox( ) function and we'll get to it when we discuss that function.

Viewer layout, part 1

The initLightbox( ) function effectively creates the following style sheet for the lightbox image viewer:

/* I refer you to the lightbox.js source for the actual statements; I'm writing them out as a style sheet as that'll take up less space. */
#overlay { display: none; position: absolute; top: 0px; left: 0px; z-index: 90; width: 100%; }
#loadingImage { position: absolute; z-index: 150; }
#lightbox { display: none; position: absolute; z-index: 100; }
#closeButton { position: absolute; z-index: 200; }
#lightboxCaption { display: none; }


The origin (upper-left-hand corner) of the overlay div is aligned with that of the document content area by the position: absolute; top: 0px; left: 0px; declaration set.

I initially suspected the overlay div's width: 100%; style to be unnecessary as the div element, like most block-level elements, ordinarily has an effective width of 100%: I was wrong. As an absolutely positioned, non-replaced element with a non-auto left value and an auto right value, the overlay div actually has a "shrink-to-fit" width*; in practice, that width will shrink to 0 when the loading.gif image is later removed from the div's "box" by virtue of its own absolute positioning that centers it in the viewport, or at least that's what happens on my computer, and so yes, we do need to give the overlay div a specific width.

*Regarding the
shrink-to-fit width = min(max(preferred minimum width, available width), preferred width)
formula, the "available width" is the width of the div's containing block (i.e., the width of the viewport, see below) whereas the "preferred minimum width" and the "preferred width" are both evidently 0 if the div doesn't contain any renderable content, and thus it's the "preferred width" that establishes the width of the div.

The overlay div's containing block is the "initial containing block", which has the dimensions of the viewport. If the width of the document exceeds that of the viewport, then the overlay div will not cover the part of the document that lies beyond the right edge of the viewport. This is an easily remediable problem, however, and we'll get it sorted out when we display the overlay div in the showLightbox( ) function.

(Relatedly, the initLightbox( ) function contains unnecessary calls to the lightbox.js getPageSize( ) and getPageScroll( ) functions:

var arrayPageSize = getPageSize( );
var arrayPageScroll = getPageScroll( );


These statements also appear in the showLightbox( ) function, which uses the arrayPageSize/arrayPageScroll returns to further lay out the overlay div, the loading.gif image, and the lightbox div; however, initLightbox( ) does nothing with the arrayPageSize/arrayPageScroll returns.)

There's no need to zero out the lightboxCaption div as its parent lightbox div is zeroed out.

The z-index stuff deserves its own section and we'll go through it next time.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

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