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.

Peace on Earth

This Christmas is good. So much of the year was absolutely lousy. I’m glad it’s ending on the upbeat. There are more than a few painful memories in 2008.

2009 Harley-Davidson FXDL Dyna Low Rider

A new Harley goes a long way towards easing a painful memory. And my in-laws Julie and Bob have come through with the goods: raffle tickets for a 2009 Harley Dyna Low Rider.

2009 Harley-Davidson FXDL Dyna Low Rider

The kids sent me two raffle tickets last year for a 2008 Harley Fat Bob. Somehow, I didn’t win. There’s a happy Harley mom out there somewhere. Good for her.

This year, my chances of winning have increased as I have THREE tickets, not just two. That’s half again as many tickets. You don’t have to be a statistician to understand that three tickets improve your odds over just two. Just that much closer to the open road.

I really haven’t been in the holiday spirit. My spirits are now a little sunnier. Not simply because I anticipate taking delivery of my new Harley in March. But that two good people, solid people, honest people from the heartland of America remembered me on this day. I’m touched. I miss them.

My spirits are sunnier today. I join with millions around the world with the sentiment, “May there be peace on earth and goodwill towards men.”

It’s going to be a bumpy ride!

When I first read that Rails and Merb were merging, I thought it a hoax. Too good to be true.

But true it is.

I’m excited but no doubt, “It’s going to be a bumpy ride!”

grep and UTF-8

I needed to look up the various strings Apple uses to name the iTunes Library. First I tried to get name from the iTunes resource bundle

echo "this won't work..."
echo "so don't even try it"

cd /Applications/iTunes.app/Contents/Resources/English.lproj
cat Localizable.strings | grep 'PrimaryPlaylistName'

But I quickly learned that grep doesn’t work on the strings file. Why? Because Apple string files are not UTF-8. They are UTF-16. Usually. But in this case they are. I wanted to iterate over the set of resource strings and extract just the string I wanted.

First I had to convert the file from UTF-16 to another format. Really, the only format that makes sense is UTF-8. After a bit of trial and error, I finally had my script just so.

#!/bin/bash

cd /Applications/iTunes.app/Contents/Resources/

# file to look inside of
f='Localizable.strings'

# string to search for
s='PrimaryPlaylistName'

# look for directories of the form *.lproj
#
for d in `ls -1 | grep 'lproj'` ; do
  echo -n "${d}: "
  iconv -f UTF-16 -t UTF-8 ${d}/${f} | grep "${s}"
done

That’s it. That’s the script. Slap that puppy in a file (e.g., foo) and fire it off.

$ ./foo
Dutch.lproj: "kMusicLibraryPrimaryPlaylistName" = "Bibliotheek";
English.lproj: "kMusicLibraryPrimaryPlaylistName" = "Library";
French.lproj: "kMusicLibraryPrimaryPlaylistName" = "Bibliothèque";
German.lproj: "kMusicLibraryPrimaryPlaylistName" = "Mediathek";
Italian.lproj: "kMusicLibraryPrimaryPlaylistName" = "Libreria";
Japanese.lproj: "kMusicLibraryPrimaryPlaylistName" = "ライブラリ";
Spanish.lproj: "kMusicLibraryPrimaryPlaylistName" = "Biblioteca";
da.lproj: "kMusicLibraryPrimaryPlaylistName" = "Bibliotek";
fi.lproj: "kMusicLibraryPrimaryPlaylistName" = "Kirjasto";
ko.lproj: "kMusicLibraryPrimaryPlaylistName" = "보관함";
no.lproj: "kMusicLibraryPrimaryPlaylistName" = "Bibliotek";
pl.lproj: "kMusicLibraryPrimaryPlaylistName" = "Biblioteka";
pt.lproj: "kMusicLibraryPrimaryPlaylistName" = "Biblioteca";
pt_PT.lproj: "kMusicLibraryPrimaryPlaylistName" = "Biblioteca";
ru.lproj: "kMusicLibraryPrimaryPlaylistName" = "Медиатека";
sv.lproj: "kMusicLibraryPrimaryPlaylistName" = "Bibliotek";
zh_CN.lproj: "kMusicLibraryPrimaryPlaylistName" = "资料库";
zh_TW.lproj: "kMusicLibraryPrimaryPlaylistName" = "資料庫";

If I were better at command line perl, I could make a nice formatted table. That is, if I were better at perl.

Jalapeño Peppers

I was looking in my archives for a snickerdoodle recipe. Rummaging through those old sites was nostalgic and horrifying. The very first sites were hosted on Earthlink. I don’t even know the URL any more. The later sites have all been on Hurricane Electric.

I pushed the first Red Leopard website out on October 29, 2000 using Microsoft Publisher 98. Yikes! What a dog’s breakfast. Eventually, I migrated to Notepad and by 2002 had a site that I liked. A static site but I liked it. I was horrible at CSS but improved my skills in raw HTML.

When I jumped into Moveable Type in June 2003, I didn’t bring the old material over.

Too bad. It was a simpler time. I wrote about the things that delighted me. Here’s one example, republished from March 2003:

label from a can of Preciosa peppers

Ahhh. The Preciosa brand jalapeño is perhaps the tastiest jalapeño on the market. This is no ad copy. It comes from the heart. I’ve tried every brand of pepper at the mega-Albertsons just down the road. Most of the brands leave me cold but the Preciosa makes my mouth water just thinking about them.

It starts when you open the can. There is this Valdez class oil slick, a green layer of 100% jalapeño flavor covering the bounty. And I don’t just mean flavor like flavor that tickles the taste bud, but that kind of flavor that’s fat and chewy. You poke a cheese stick down in that vat and it comes out looking like a pepper popsicle.

Each pepper has been pickled just right. Not so much that it’s sloppy with no tooth. Not so little that it’s too young and lacks depth. No, these peppers are righteous and wise. Their character is summed in one word: Seasoned.

There is nothing tame about the Preciosa. It is definitely outside the bell curve. If you cannot order “spicy hot” at the local Thai joint, you may want to pass. But if the current can of jalapeños in your fridge has all the excitement of a rice cake, give these diablos a try.

Why I believe I can learn Chinese

Let me tell you. Learning Chinese is damned hard. The character set, the tones, the pinyin phonetic alphabet (which uses latin characters but the characters rarely correlate to English pronunciation), the grammar…

In comparison, learning German was a snap. And German is no snap. I can babble like an idiot in German but I’m understood. I’ve found Germans, Austrians and the Swiss quite accomodating of my linquistic struggles. Perhaps its because they already speak English and can decipher my grammatical gymnastics, translate my mangled vowels, forgive my nouns’ gender jumping. Perhaps English is a piece of common ground.

The highlight of my German-speaking adventures was at a hotel in Vienna. After speaking (strictly in German) for a few minutes, the bar tender asked if I was French. I may still have had an accent but it wasn’t an American accent. A HUGE day for me.

With Chinese, I have yet to gain purchase on terra firma. It often seems as if I were lost at sea, bobbing around like a fishing cork in the middle of the Pacific.

I have a plan.

I draw my inspiration from the essay by Konstantin Ryabitsev, How I learned French in One Year.

He is spot on about the flash cards. Flash cards are vastly underrated. Drill with them until they are grimey with fingerprints. He created his own “flip-card strategy”. I experiment with different approaches. None of them are a waste of time.

When learning the characters, you must also learn the tone that goes with the character and learn the radicals. Drill, drill, drill.

I served in the United States Infantry (1978-1982). We were issued vehicle and aircraft identification cards. Basically, they were flash cards with a silhouette of a truck, tank, helicopter… on one side and info on the other. We were tested periodically. Flash cards work really, really well.

I also use the Yellow Bridge dictionary. It accepts characters, pinyin or english for translation. You can enter the character using the keyboard or ‘draw’ the character with the mouse. I use this in conjunction with Google’s online translator.

My plan is to

(i) continue taking classes at Stanford Univeristy’s Continuing Studies. Taking a class give me a schedule to push me along. And I like school.

(ii) listen to chinese lessons on my iPod. I find it hard to run while listening to chinese but walking is fairly easy.

(iii) flash card drills

(iv) watch DVDs with both Chinese and English with subtitles (no luck so far finding them)

(v) use my account on palabea to practice my writing (like modern day penpals).

I’ve completed a couple of learning annex classes, worked with a tutor and just finished the first introductory course at Stanford. The coming year is my big push to climb out of the sea, up on the beaches and onto dry ground.

The biggest challenge to learning a language is boredom, embarassment, apathy, frustration, resignation. These are the enemies that lurk in the dark corners of the mind. These are the enemies that assault one’s resolve.

What makes me think that I can learn Chinese?

In the words of Winston Churchill, “Never give in. Never give in. Never, never, never, never–in nothing, great or small, large or petty–never give in, except to convictions of honor and good sense. Never yield to force. Never yield to the apparently overwhelming might of the enemy.”

It’s a matter of declaration and conviction. I will speak, read and write Chinese.