Introduction to Unobtrusive JavaScript, DOM Scripting, and the Yahoo! User Interface (YUI) Library

Filed under

If you know JavaScript but have yet to make the leap to unobtrusive JavaScript, read on. I'll demonstrate how to upgrade a traditional DHTML rollover to an unobtrusive script and then to a script that leverages the Yahoo! User Interface Library's (YUI).

What is Unobtrusive JavaScript and DOM Scripting?

Rather than hardcoding event handlers as HTML attributes, unobtrusive JavaScript assigns them using the DOM. Well-structured, valid HTML is a prerequisite to DOM scripting. Documents should declare a DOCTYPE, they should validate against the DOCTYPE, and presentational styles should be applied with CSS (read "should" as "life will be easier for everyone if you do"). Use a few targeted CSS ID and class selectors to define page layout. Don't go overboard on defining selectors, remember, descendent selectors are your friend. I'll use a simple text-based rollover for demonstration purposes. In a perfect world, with perfect browsers, this effect would be created using the CSS pseudo hover class but, as you've probably guessed, it doesn't work in Internet Explorer 6. Here's a screenshot of what we're building.

The Rollover, Obtrusively Old School Style

First, the style sheet. Nothing wrong here as long as these are stored in an external CSS file. 

#menu {
  font: 85% Arial, Helvetica, san-serif;
}
#menu .btn {
  background-color: #e7e7e7;
  border-color: #999;
  border-style: solid;
  border-width: 2px;
  color: #666;
  float: left;
  margin: 0 5px;
  width: 150px;
}
#menu .btn a {
  color: #fff;
  background-color: #999;
  display: block;
  padding: .3em .5em;
  text-decoration: none;
}
#menu p {
  padding: 0 .5em;
}

Next the markup. The old school technique involves writing the rollover script functions and triggering them from event attributes. More complex actions required more and more stuff muddying up what was a perfectly readable HTML document.

<div id="menu">
  <div id="btn1" class="btn" onmouseover="rollOver('btn1')" onmouseout="rollOut('btn1')">
    <a href="https://www.2tbsp.com/">Link 1</a>
    <p>Description for link 1</p>
  </div>
  <div id="btn2" class="btn" onmouseover="rollOver('btn2')" onmouseout="rollOut('btn2')">
    <a href="https://www.2tbsp.com/">Link 2</a>
    <p>Description for link 2</p>
  </div>
  <div id="btn3" class="btn" onmouseover="rollOver('btn3')" onmouseout="rollOut('btn3')">
    <a href="https://www.2tbsp.com/">Link 3</a>
    <p>Description for link 3</p>
  </div>
</div>

Finally, the JavaScript. A function to set styles on mouseover and another to restore the original styles on mouseout. If you change a color in the stylesheet, you'll have to change it here too.

<script type="text/javascript">
// Apply rollover styles
function rollOver(el) {
  // Get the elements
  var thisBtn = document.getElementById(el);
  var thisLink = thisBtn.getElementsByTagName('a');
  // Change their styles
  thisLink[0].style.backgroundColor = '#666';
  thisBtn.style.borderColor = '#666';
  thisBtn.style.color = '#000';
}
// Reset styles
function rollOut(el) {
  // Get the elements
  var thisBtn = document.getElementById(el);
  var thisLink = thisBtn.getElementsByTagName('a');
  // Change their styles back again
  thisLink[0].style.backgroundColor = '#999';
  thisBtn.style.borderColor = '#999';
  thisBtn.style.color = '#666';
}
</script>

Make the Script Less Obtrusive

Let's clean things up a bit. Strip those event handlers from the button containers.

<div id="menu">
  <div class="btn">
    <a href="https://www.2tbsp.com/">Link 1</a>
    <p>Description for link 1</p>
  </div>
  <div class="btn">
    <a href="https://www.2tbsp.com/">Link 2</a>
    <p>Description for link 2</p>
  </div>
  <div class="btn">
    <a href="https://www.2tbsp.com/">Link 3</a>
    <p>Description for link 3</p>
  </div>
</div>

Now, make a few CSS adjustments and additions to define the hover styles where they belong, in the style sheet.

#menu {
  font: 85% Arial, Helvetica, san-serif;
}
#menu .btn, 
#menu .btn-over {
  background-color: #e7e7e7;
  border-width: 2px;
  border-style: solid;
  color: #666;
  float: left;
  margin: 0 5px;
  width: 150px;
}
#menu .btn {
  border-color: #999;
}
#menu .btn a, 
#menu .btn-over a {
  color: #fff;
  background-color: #999;
  display: block;
  padding: .3em .5em;
  text-decoration: none;
}
#menu p {
  padding: 0 .5em;
}
/* Rollover styles */ 
#menu .btn-over {
  border-color: #666;
  color: #000;
}
#menu .btn-over a {
  background-color: #666;
}

With that done, hook it all up with the script.

<script type="text/javascript">
// <![CDATA[
// Attach event listeners to the buttons
function findButtons() {
  var menu, btns, i;
  // Get the menu element to access the btn divs
  menu = document.getElementById('menu');
  btns = menu.getElementsByTagName('div');
  for (i=0; i<btns.length; i++) {
    // Attach event listeners for rollovers to the btns
    if (/btn/.test(btns[i].className)) { 
      btns[i].onmouseover = function(){roll(this);};
      btns[i].onmouseout = function(){roll(this);};
    }
  }
}
// Attached to the buttons and ready to roll
function roll(o) {
  // btn is our off state, so turn it on
  if (o.className == 'btn') {
    o.className = 'btn-over';
  // Otherwise, turn it off
  } else {
    o.className = 'btn';
  }
}
// Initialize the rollover
window.onload = function() {
  findButtons();
}
// ]]>
</script>

Everything is now in its proper place. No more JavaScript in the HTML and styles aren't defined in the JavaScript. This will be much easier to maintain.

Unobtrusive, the YUI Style

YUI is one of the many JavaScript libraries that have gained attention in recent years. Some say it's too big and bulky and that may be true in certain cases, like creating a simple rollover. But it's rare in our Web 2.0 world that a site's only JavaScript-powered feature is a rollover. YUI is my current JavaScript framework of choice because along with being feature-rich, it's well documented, tested, and supported. So here's the rollover script powered by the YUI's Yahoo! Global Object, and DOM and Event extensions.

<script src="http://yui.yahooapis.com/2.5.1/build/yahoo-dom-event/yahoo-dom-event.js" type="text/javascript"></script>
<script type="text/javascript">
// <![CDATA[
// Create a menu object, put everything we need into it
var menu = {
  // Initialize the menu rollover
  init : function() {
    // Get the btn elements
    btns = YAHOO.util.Dom.getElementsByClassName('btn', 'div', 'menu');
    // Assign event listeners to the btns
    for (var i=0; i<btns.length; i++) {
      YAHOO.util.Event.addListener(btns[i], 'mouseover', menu.roll, i);
      YAHOO.util.Event.addListener(btns[i], 'mouseout', menu.roll);
    }
  },
  // First, turn 'em all off, then turn one on
  roll : function(e, i) {
    YAHOO.util.Dom.removeClass(btns, 'btn-over');
    YAHOO.util.Dom.addClass(btns[i], 'btn-over');
  }
};
// Initialize the menu
YAHOO.util.Event.on(window, 'load', menu.init);
// ]]>
</script>

The YUI version basically works just like the unobtrusive version. Yahoo! provides hosted versions of all the YUI extensions, just add a link to yahoo-dom-event.js. With the help of the Yahoo! Global Object and DOM and Event extensions, it's easy to grab button elements and assign event handlers that trigger the roll function. The main difference here is that everything is neatly encapsulated into the menu object. The YUI's getElementsByClassName and remove/addClass methods make DOM scripting a breeze. Take a look at the final product.

Conclusion

Hopefully these basic examples serve as a kick in the rear for those clinging to those old school ways. Now that you have the basics of event handling down, I'll show you how to improve the scripts performance through event delegation and how to make it scalable to power multiple rollover menus.

Share