Web Page Refactoring with jQuery
- HTML Refactoring
- Menu Support Functions with the jQuery Library
- Summary
My article "Introduction to jQuery" described the benefits of using the popular jQuery library and demonstrated the enormous difference between JavaScript code using a first-generation browser-independent library and the same solution using the jQuery library. In this article, we'll focus on refactoring a five-year-old solution: a nasty implementation of drop-down menus that I took straight from one of my websites.
I'll spare you the initial cleanup details. (Do you really want to see how embarrassing the original code was? It's included in the "Refactoring a simple menu" post on my programming-related blog.) Even after removing unnecessary background images and inline CSS, the HTML code still looks terrible, with numerous element IDs and inline JavaScript calls. A simple menu with two top-row items, one of them opening a drop-down menu box, would look like this:
<div class="rowMenu" id="IDAHYJQB"> <p><a href="/">First page</a></p> </div> <div class="rowMenu" id="IDAKYJQB"> <script>menuRegister('IDAKYJQB')</script> <p id="IDAKYJQB_main"><a href="javascript:menuClick('IDAKYJQB')">Go to ...</a></p> <div id="IDAKYJQB_sub"> <p><a onclick="menuSelect('IDAKYJQB')" href="http://www.informit.com">InformIT home page</a></p> <p><a onclick="menuSelect('IDAKYJQB')" href="http://www.google.com">Google search</a></p> </div> </div>
The JavaScript code supporting this monster is even worse:
var topMenuItems = [] ; function addClass(id,sfx) { var se = getElement(id) ; if (se.className.indexOf(sfx) < 0) se.className = se.className + " " + sfx ; } function removeClass(id,sfx) { var se = getElement(id) ; var i = se.className.indexOf(sfx) ; if (i >= 0) se.className = se.className.substr(0,i) ; } function menuShow(id) { var se = getElement(id) ; se.menuActive = true ; showElement(id + "_sub") ; addClass(id+"_main","down"); } function menuHide(id) { var se = getElement(id) ; se.menuActive = false ; hideElement(id + "_sub") ; removeClass(id+"_main","down"); } function menuSelect(id) { menuHide(id) ; } function menuClick(id) { var i,se ; se = getElement(id) ; if (se.menuActive) { menuHide(id) ; return ; } for (i = 0 ; i < topMenuItems.length ; i++) { if (topMenuItems[i] != id) menuHide(topMenuItems[i]); } menuShow(id) ; } function menuRegister(id) { topMenuItems[topMenuItems.length] = id ; }
The JavaScript code uses no DOM navigation (at the time when the menu was designed, DOM navigation was not yet very popular) and relies heavily on the object IDs to find the relevant objects in the menu. It's obviously time to do significant refactoring on the menu markup and the supporting JavaScript code.
HTML Refactoring
The HTML markup used to implement the drop-down menu has a severe case of "semantically meaningful tags"; a much better choice would be to use divitis to help screen readers and other non-browser users of the data (for example, search engines) to understand the structure of the page.
Menus are usually implemented as unnumbered lists (UL/LI elements), which would make the absolute positioning of the drop-down part of our menu somewhat complex. The top-row menu entry in an unnumbered list is usually an LI element which is a sibling of the drop-down part (a nested UL element) and thus cannot provide the positioning container. I could implement three levels of nesting (with the top-row entry being a UL element having a child UL element describing the drop-down box), but decided to use a slightly richer definition lists markup.
The whole top-row menu will be enclosed in a DIV tag (using a single DIV tag enclosing a section of the page will not upset even the strictest semantic-markup pundits), with each menu entry being a definition list (DL element). Each menu entry will have a top-row button (DT element) and drop-down box (DD element), which might have numerous paragraphs (individual menu entries). Thus, the new markup of the same menu would look like this:
<div id="topRowMenu"> <dl> <dt><a href="/">First page</a></dt> </dl> <dl> <dt><a href="#">Go to ...</a></dt> <dd> <p><a href="http://www.informit.com">InformIT home page</a></p> <p><a href="http://www.google.com">Google search</a></p> </dd> </dl> </div>
This markup is clean, well structured, readable, and easy to understand. I've also decided to give the enclosing DIV tag an ID (topRowMenu), rather than a CSS class, to support browsers with disabled JavaScript. These visitors will see the full menu structure; those with JavaScript will see just the top row as soon as the JavaScript code applies the menu-specific class to the top DIV element.