Refactoring a menu: Remove unnecessary IDs

If you’re new to this series, read the “Refactoring a simple menu” and “Remove inline CSS” first.

The original HTML code for the dropdown menu (see below) was full of IDs that were used as a DOM navigation tool (I’ve used getElementById instead of traversing the DOM tree). When moving from a simple wrapper JavaScript library (I was using the X library) to jQuery, it makes sense to use its powerful traversal functions to replace the ID-based navigation.

<div class="rowMenu" id="IDAHYJQB">
  <p><a href="/climbing/myClimbs/myClimbs.asp">First page</a></p>
</div>
<div class="rowMenu" id="IDAKYJQB">
  <script>menuRegister('IDAKYJQB')</script>
  <p id="IDAKYJQB_main"><a href="javascript:menuClick('IDAKYJQB')">Add ...</a></p>
  <div id="IDAKYJQB_sub">
    <p><a onclick="menuSelect('IDAKYJQB')" 
        href="/climbing/myClimbs/myClimbs_add.asp">New entry </a></p>
    <p><a onclick="menuSelect('IDAKYJQB')" 
        href="/climbing/myClimbs/myClimbs_editWall.asp?a=add">Edit</a></p>
    … more …
  </div>
</div>

The HTML code is interspersed with inline calls to three JavaScript functions:

  • menuRegister registers the ID of a drop-down menu
  • menuClick opens a drop-down menu
  • menuSelect hides the drop-down menu and

Today we’ll replace these functions with simpler jQuery equivalents while getting rid of element IDs.

The menuRegister function will not be needed once we fix the DOM traversal problem. The menuClick and menuSelect functions will be merged into a single menuClickEvent function that will identify whether it works on a main menu entry or an entry of a drop-down menu. The this variable will be passed to the menuClickEvent function to avoid any ID-based references.

The menuClickEvent function has to perform a few setup tasks:

  • Get the DOM element that was clicked, using either the function parameter or (for future use) the value of the this variable.
  • Get the parent paragraph.
  • Decide whether it’s operating on a top-row entry (the parent paragraph has a DIV sibling) or not.

Using jQuery makes the job extremely simple:

function menuClickEvent(thisParam) {
  var a = thisParam || this; 
  var p = $(a).parents('P:first');
  var d = p.siblings('DIV');
  var toprow = d.length != 0;

  … rest of the code …
}

If the menuClickEvent function works on an element of a drop-down menu, it should hide the drop-down menu and return true (indicating that the event handler should perform its default task), otherwise it should toggle the state of the down class of the current paragraph and the visibility of the sibling DIV. The jQuery-based code is much shorter, more elegant and way more readable than the original.

function menuClickEvent(thisParam) {
  var a = thisParam || this; 
  var p = $(a).parents('P:first');
  var d = p.siblings('DIV');
  var toprow = d.length != 0;

  if (toprow) {
    p.toggleClass('down');
    d.toggle();
    return false;
  } else {
    p.parents('DIV').hide().siblings('P').removeClass('down');
    return true;
  }
}

With the new jQuery-based implementation of the drop-down menu, we can get rid of all element IDs, resulting in much shorter HTML code:

<div class="rowMenu">
  <p><a href="/climbing/myClimbs/myClimbs.asp">First page</a></p>
</div>
<div class="rowMenu">
  <p><a href="#" onclick="menuClickEvent(this)">Add ...</a></p>
  <div>
    <p><a onclick="menuClickEvent(this)" 
        href="/climbing/myClimbs/myClimbs_add.asp">New entry </a></p>
    <p><a onclick="menuClickEvent(this)" 
        href="/climbing/myClimbs/myClimbs_editWall.asp?a=add">Edit</a></p>
    … more …
  </div>
</div>

Next task: remove inline JavaScript calls