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.

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.

I’m pleased with the results. On to the next project.

Your email will never published nor shared. Required fields are marked *...

*

*

Type your comment out: