A List Apart

Menu

Behavioral Separation

by Published in JavaScript57 Comments

Breaking up is hard to do. But in web design, separation can be a good thing. Content, style, and behavior all deserve their own space.

Style guide

One of the greatest advantages to designing with Cascading Style Sheets is the potential for separation of style and content.

You could style an entire website using nothing but inline style declarations:

<p bold; color: red;”>This text is important</p>

But then you might as well use <font> tags. It makes more sense to keep style information in a separate file and add semantically sensible hooks into your markup:

Markup: <p class=“important”>This text is important</p>

CSS: p.important { font-weight: bold; color: red; }

That makes everybody’s life easier. It’s easy for the designer to tweak the styles—like changing important text from red to blue. It’s easier for the writer to edit the text without wading through inline style declarations. It’s easier for the visitor who doesn’t have to download unnecessarily bloated pages.

What’s good for CSS is good for JavaScript. Unfortunately, a lot of JavaScript functionality is found embedded within markup.

Everything is superficially intertwingled

Suppose you want to add some behavior to a particular link. You could use what’s known as the [removed] pseudo-protocol:

<a href=”[removed] doSomething();”>click for fun</a>

That’s a short-sighted approach. User agents that don’t support JavaScript will choke on that href value. A more common approach is to use event handlers in combination with a meaningless href value:

<a href=”#” >click for fun</a>

That’s slightly better but it’s semantically meaningless. Why mark up a piece of text as a link unless it goes somewhere?

<a href=“arealpage.html”  return false;”>click for fun</a>

Now we’re getting somewhere. The link makes sense even if JavaScript isn’t enabled (and, for the first time, the link is spiderable). But the problem remains that the event handler is mixed in with the markup. Inline event handlers are the JavaScript equivalent of inline style declarations. Fortunately, the event handlers can be removed and placed in an external file, just as you would do with CSS. And, just as with CSS, you can add hooks to your markup to target the elements you want to play with:

<a href=“arealpage.html” class=“fun”>click for fun</a>

In an external JavaScript file, you could now write a script to find all the links that have a className of “fun” and have them execute a function—doSomething()—when they are clicked.

Separating out behavior from markup like this is called unobtrusive JavaScript.

Enough theory. I’m going to apply this technique to a real-world example.

Rogue’s gallery

Two years ago, I wrote an article for A List Apart on building a JavaScript image gallery. The JavaScript in that article works fine, but take a look at the markup (line wraps marked » —Ed.):

<ul>
  <li><a showPic(this)"
  href="images/bananas.jpg" »
  title="A bunch of bananas »
  on a table">some bananas</a>
    </li>
  <li><a showPic(this)"
  href="images/condiments.jpg" »
  title="Condiments in a »
  Chinese restaurant">two bottles</a>
    </li>
  <li><a showPic(this)" 
  href="images/shells.jpg" »
  title="Seashells on a table"> »
  some shells</a>
    </li>
</ul>

Those repetitive event handlers are intrusive. I could replace them with class attributes (line wraps marked » —Ed.):

<ul>
  <li><a class="gallerypic" »
  href="images/bananas.jpg" »
  title="A bunch of bananas on a table">some bananas</a>
    </li>
  <li><a class="gallerypic" »
  href="images/condiments.jpg" »
  title="Condiments in a Chinese restaurant">two bottles</a>
    </li>
  <li><a class="gallerypic" »
  href="images/shells.jpg" »
  title="Seashells on a table">some shells</a>
    </li>
</ul>

But that would be equally repetitive. I wouldn’t do that if I wanted to style every item in the list, so there’s no need to do it when I want to add a behavior to every item in the list. Instead, I can simply add a unique id to the list (line wraps marked » —Ed.):

<ul id="imagegallery">
  <li><a href="images/bananas.jpg" title="A bunch of »
  bananas on a table">some bananas</a>
    </li>
  <li><a href="images/condiments.jpg" title="Condiments »
  in a Chinese restaurant">two bottles</a>
    </li>
  <li><a href="images/shells.jpg" title="Seashells on a »
  table">some shells</a>
    </li>
</ul>

Now I need to write a function to add the event handlers externally.

Separation anxiety

I’ll write a function called prepareGallery. This function will use the Document Object Model to find the links in the document I want. This is possible with the methods getElementById and getElementsByTagName:

document.getElementById(“imagegallery”).getElementsByTagName(“a”);

Those methods work like selectors in CSS. It’s the equivalent of combining id and element selectors:

#imagegallery a

Here’s what I want to do:

  1. Make sure that the browser understands the DOM methods I will be using.
  2. Make sure that there is an element with the ID “imagegallery.”
  3. Get the list with the ID “imagegallery.”
  4. Get all the links in the list and loop through them all.
  5. Add the onclick event handler to each link, pointing it to the showPic function.

Here’s how that translates into [removed]

function prepareGallery(){
  if( document.getElementById &&
      document.getElementsByTagName ){
    if( document.getElementById( 'imagegallery' ) ){
      var gallery = document.getElementById( 'imagegallery' );
      var links = gallery.getElementsByTagName( 'a' );
      for( var i=0; i < links.length; i++ ){         links<i>.
          return showPic(this);
        };
      }
    }
  }
}

Now that I’ve written my function, all I have to do is run it. But there’s a problem. I can’t simply execute the function like this:

prepareGallery();

If I do that, the function—which is in an external file or in the head of my document—will run before the rest of the document has finished loading. The DOM methods won’t work because there won’t be any Document Object Model. Without a complete document, there can’t be a corresponding model.

I need to wait until the entire document has finished loading. Fortunately, the loading of the document triggers an event, namely the load event of the window object. I can use the corresponding onload event handler, window.onload, to assign the prepareGallery function to this event:

window.onload = prepareGallery;

Rather than hogging the onload event for this one function, it’s better to use something like Simon Willison’s excellent addLoadEvent function, which allows you to queue up functions that you want to trigger when the document finishes loading:

addLoadEvent( prepareGallery );

Trial separation

Unobtrusive JavaScript is a relatively new idea. Try it out for yourself.

  • Begin with your content,
  • give it structure with semantically descriptive markup,
  • apply a presentation layer using CSS, and finally,
  • add a behavior layer with DOM Scripting.

Just make sure they all keep their distance.

57 Reader Comments

Load Comments