Javascript DOM Listeners
I’ve wanted to build a simple gallery viewer for some time. “But there are thousands of already built viewers,” you may be thinking. Yes there are. But I wanted a simple viewer that was at once trés snappy and minimalist.
I dislike the gallery systems that open another window or create a floating panel which must be dismissed. I wanted a simple picture portal with simple thumbnails. I wanted something that didn’t require jacking javascript or event listeners directly into the html. It had to be easy to use with wordpress.
I wanted to add a title and caption to each picture. Clicking the thumbnail would change out the picture, the title and the caption.
Since I would add galleries to a blog, the code had to handle multiple galleries per page. Therefore, the galleries could use class attributes but not id attributes in the event listeners.
For this project, I wrap each gallery in a <div />
element and give the element a class type of gallery
.
<div class="gallery"> <h3>Photo 1 Title</h3> <p><img width="592" height="330" alt="" src="/img/p1.jpg" /></p> <div class="gallery-thumbs"> <img width="50" height="50" title="Photo 1 Title" alt="Photo 1 Caption." longdesc="/img/p1.jpg" src="/img/t1.jpg" /><img width="50" height="50" title="Photo 2 Title" alt="Photo 2 Caption." longdesc="/img/p2.jpg" src="/img/t2.jpg" /> </div> <p>Photo 1 Caption</p> </div>
The main parts of the viewer are:
<h3 />
element which displays the photo title<p /> element wrapping the <img />
element which is the main photo portal<div />
element with a class of<gallery-thumbs />
which is a container for all the thumbnail images; all thumbnail<img />
elements are placed inside this container<p />
element which displays the photo caption
I chose to make my photo portal 592 by 330 pixels as this fits well with several of my templates. I chose this aspect ratio—which is close to “wide-screen”—as it gives me more room to display a lengthy caption without scrolling. However, any aspect ratio may be used. One limitation of this system is that all photos in a gallery set must have the same width and height dimensions.
The thumbnail <img />
elements use the alt
and title
attributes as they were intended. I still needed to supply the main photo URL associated with each thumbnail. Since I want my gallery code to validate, I couldn’t simply add a non-html attribute. I pragmatically chose to use the longdesc
attribute in a unorthodox manner to declare the main image URL.
I like small thumbnails. Mine are 50 by 50 pixels. Some people are very, very, very adament that thumbnail images must be an exact replica of the original image. Not me. The thumbnail ratio is square regardless of the original photo ratio. I may or may not resize the image before taking a thumbnail. My thumbnails do not even have to be taken from the photo. They could be icons, for all I care. As long as they communicate effectively and are visually appealing.
I add a bit of CSS seasoning to help with presentation. Season your gallery to taste.
div.gallery { margin: 0 0 2em 0; padding: 0; border: 0; } div.gallery img { margin: 0; padding: 0; border: 0; } div.gallery-thumbs { margin: 0; padding: 0; } div.gallery-thumbs img { margin: 4px 8px 0 0; padding: 0; border: 1px solid black; } div.gallery p { clear: both; margin: 0; padding: 0; }
Before I get into the core javascript functions, let’s look at how we register the event listeners. This is the code that must be added <head />
element of every page that contains a gallery.
<script type="text/javascript"> /*<![CDATA[*/ function init() { registerGalleries(); } window.onload = init; /*]]>*/ </script>
I initialize the event listeners after the page loads using the window.onload
event. While I could have included the init
function in an external file, I may want to extend it in the future to register events for other systems. The registerGalleries
function is defined in the external gallery javascript file.
<script type="text/javascript" src="js/gallery.js"></script>
Once the window loads, init
is called which in turn calls registerGalleries
which registers event listeners for any and all galleries on the page.
function registerGalleries() { // get every element in the document var allTags = document.getElementsByTagName("*"); // check each element to see if it's a // div element and gallery class for (var i = 0; i < allTags.length; i++) { if (allTags[i].tagName == "DIV") { if (allTags[i].className == "gallery") { // it is a gallery, so get all the subelements var galleryTags = allTags[i].childNodes; // check each subelement for the gallery-thumbs container for (var j = 0; j < galleryTags.length; j++) { if (galleryTags[j].className == "gallery-thumbs") { // it is a gallery-thumbs, so get all the subelements var thumbTags = galleryTags[j].childNodes; // and register event listeners on all thumbnails registerThumbs(thumbTags); } } } } } }
The registerThumbs
function looks for the image tags within gallery-thumbs
container.
function registerThumbs(thumbs) { for (var i = 0; i < thumbs.length; i++) { // the gallery-thumbs container has more than just <img /> // elements. The whitespace between each <img /> element // is also a node. So, we must check for the actual thumbnail // code and add the event listener to that. if (thumbs[i].tagName == "IMG") { addListener(thumbs[i], "click", updateGallery); } } }
Attaching events using javascript and DOM differs between Microsoft and the rest of the world. {{sign}} The following code has worked well for me on numerous projects.
function addListener(element, event, process) { if ( element.addEventListener ) { element.addEventListener(event, process, false); } else if ( element.attachEvent ) { element.attachEvent("on"+event, process); } }
From this point on, the gallery system gets brittle. The javascript depends on a specific node order for node traversal. If the node order changes, you must modify the javascript accordingly. Perhaps one day I will make the code node-order independent. The gist of updateGallery
is to get the thumbnail <img />
element from the event, extract the contextually relevant information from that element, traverse the nodes in the gallery
container to find the title, picture portal and caption elements and update those elements with the new information.
function updateGallery(e) { thumb = e.target ? e.target : e.srcElement; var thumbTtl = thumb.getAttribute("TITLE"); var thumbAlt = thumb.getAttribute("ALT"); var thumbLnk = thumb.getAttribute("LONGDESC"); var thumbTxt = thumb.parentNode .nextSibling .nextSibling; var thumbPix = thumb.parentNode .previousSibling .previousSibling .firstChild; var thumbCap = thumb.parentNode .previousSibling .previousSibling .previousSibling .previousSibling; thumbPix.src = thumbLnk; thumbTxt.firstChild.nodeValue = thumbAlt; thumbCap.firstChild.nodeValue = thumbTtl; }
That’s it. Below is an example that I created for this project. It uses ten Scandinavian photos from a stock image/clipart DVD I got years ago.
White Barn
A nice white barn under a blue sky surrounded by yellow flowers.
I found that when I entered the gallery code into wordpress, wordpress added <br /> elements where I didn’t want them. This would break the layout (duh!). Originally, I didn’t have a paragraph element wrapping the main image. WordPress insisted on adding the tag. Likewise, I originally had each thumbnail begin on a new line. WordPress insisted on adding a break element so I ended up butting the end of one thumbnail image element against the next (no white space).
Just for grins, I added another gallery from the same clipart collection. This time from India.
Train Station
Some people in the doorway of the train. Looks as if they are hanging out over a ledge.
I’m pleased with the results. On to the next project.