POSTS

Making some progress with JavaScript menus

Blog

As I posted earlier, I’m undertaking to better understand the basics of the DOM, Javascript, and DHTML on my pilgrimage to being Ajax competent. I’m using Ajax in Action as my guide.

Recently at work, we had need of a basic ‘dashboard’. It’s a pretty simple design: boxes on the left with one level of drop down for menus, a big, central ‘content’ section in the middle.

I was thinking of how I wanted to implement this.

  1. It needed to be flexible (i.e. layout should be described in an XML file)
  2. It needed to be updatable without touching the JavaScript (to get a feel for the Ajax design)
  3. It needed to be somewhat visually appealing.

The goal was to use Javascript to create a 100% DOM rendered page that worked in IE and Firefox.

Here’s how I did it after the jump….

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
      <link rel=stylesheet type="text/css" href="dashboard.css">
      <script type="text/javascript" src="dashboard.js" ></script>
      <title>Dashboard</title>
    </head>
    <body onload="javascript:pageLoad()">
    <div id="page_container">
      <div id="navigation_bar">
      </div>
      <div id="content_area">
        <div id="titlebar_area">
          <div>
            <p id="title">A Basic Dashboard Example</p>
          </div>
        </div>
            <div id="main_area">
              <h1>Welcome to the dashboard example</h1>
              <p> Double-click an item to the left to
    expand/collapse.</p>
            </div>
            <div id="footer">
              <p>
                <a href="http://validator.w3.org/check?uri=referer"><img
                    src="http://www.w3.org/Icons/valid-html401"
                    alt="Valid HTML 4.01 Transitional" height="31" width="88">
                </a>
              </p>
              <p style="text-transform:small;font-variant:small-caps;
    font-size:8pt;text-align:right"><em>Version 0.1</em></p>
            </div>
          </div>
        </div>
        </body>
        </html>

You basically have two critical areas in this skeletal bit of HTML: the navigation_bar div and the main_area div. My code is focused on putting “main navigational areas” in the navigation_bar. Updates into the “main view” may need to happen ( it doesn’t really happen in this code ).

To lay out the prettyness of the page, we use CSS:




        body{
          margin-left:0px;
          background-color:#AAAAAA;
          font-family: arial, helvetica, courier, times;
        }

        div#page_container{
          background-color:#AAAAAA;
          margin-right: 10%;
        }

        div#navigation_bar{
          position:absolute;
          padding-left:0px;
          margin-left:0px;
          margin-top:10px;
          width: 200px;
          width: 300px;
          height: 100%;
          background-color: #AAAAAA;
        }

        div.navigation_block{
          position:relative;
          background-color: white;
          margin-bottom: 5px;
          margin-left: 5px;
          margin-right: 5px;
          border-width: 2px 1px 3px 3px;
          border-style: solid;
          border-color: #222222;
          padding-bottom:2px;
          width: 11em;
        }

        div.navigation_block p{
          width: 80%
        }

        div.navigation_block_submenu{
          margin-bottom: 5px;
          margin-left: 10px;
          margin-top: 0;
          margin-right: 0px;
          margin-left: 5px;
          padding: 0px 0px 0px 3px;
          margin-bottom:3px;
          width: 9em;
          background-color:#DDD;
        }

        .submenu_element{
          padding-top:0px;
          margin:0px;
          font-size: 0.95em;
        }

        div.navigation_block p{
          padding-left: 5px
        }

        div#content_area{
          position:relative;
          top:0px;
          left:200px;
          width:200px;
          width:40em;
          padding-left:0px;
          padding-top:2px;
          margin-left:0px;
          margin-top:10px;
          background-color: #AAAAAA
        }

        div#titlebar_area{
          background-color: white;
          border-width: 2px 1px 3px 3px;
          border-style: solid;
          border-color: #000000;
          margin-top: 8px;
          height: 52px;
          padding:0.5em;
        }

        p#title{
          color:black;
          text-align:center
        }

        div#main_area{
          background-color: #FFFFFF;
          border-width: 2px 1px 3px 3px;
          border-style: solid;
          border-color: #000000;
          height:370px;
          margin-top: 5px;
          padding: 2px;
          height:100%
        }

        div#main_area h1{
          border-width: 0 0 3px 2px;
          border-color: #AAAAAA;
          border-style:solid;
          color:black;
          font-size: 1em;
          padding-left: 2px;
          margin-right: 2%
        }

        div#footer{
          background-color:#AAA;
          padding-top: 10px;
          padding-right:0px;
          width: 40em
        }
        /* Stupid hack for stupid internet explorer */
         html>body div {
          width: 40em
          }

        /* Make changes that make IE happy, make FF happy */
        *
        {
             -moz-box-sizing: border-box !important;
        }

That’s the CSS. Now, let’s show the code on how we dynamically update these pages.

<code>

	/* dashboard.js
      AUTHOR:  Steven G. Harms (stharms@cisco.com)
      CREATION:  8/28/2006
      A script that is used with an XML file to use the DOM to create a Web2.0 /
      DHTML-y web site
    */

    // Global variables, used sparingly
    var xmlDoc;

    // Function Declaration Area
    function add_a_footer(entered_text){
     var dom = document.domain;
     var myText = document.createTextNode("hello, " + dom);
     document.getElementById("footer").appendChild(myText);


     var childEl=document.createElement("div");
     childEl.className='navigation_block';
     childEl.appendChild(myText);
     document.getElementById("footer").appendChild(childEl);
    }

    function addAnEvent(anObject, eventType, callback, capture){
      if (window.XMLHttpRequest){ // it's a mozilla-like thing!
        anObject.addEventListener(eventType, callback, capture);
        return anObject
      }else{
        anObject.attachEvent('on' + eventType ,callback);
        return anObject
      }
    }

    function appendTitleBlock(anObject, myTitle){
      //alert('cunning '+aTitle);
      if ( myTitle == "" ){
         aTitle="ERROR!";
      }
      var appendTitle = document.createTextNode(myTitle);
      var title = document.createElement('p');
      title.appendChild(appendTitle);
      anObject.appendChild(title);
      return anObject;
    }

    function browser_mutate(){
      // Some tricks to help IE render properly
      var browser = navigator.appName;
      if (browser.match(new RegExp("Microsoft", "i"))){
        // Line up the box model
        var navbar = document.getElementById('navigation_bar');
        navbar.style.marginTop='8px';

        //Make sure our submenus are properly whited-out
        for ( var divvie in  document.getElementsByTagName("div")){
          if (divvie.match(new RegExp("submenu","i"))){
            var node = document.getElementById(divvie);
            node.style.backgroundColor="#FFF"
          }
        }
      }else{
        var navbar = document.getElementById('navigation_bar');
        navbar.style.marginTop='10px';
      }
    }

    function buildSubmenuFor (aBlock, manipulationClosure){
      //alert(aBlock.getAttribute('name'));
      //alert(aBlock.childNodes.length);
      var childItem;
      var theDiv = document.createElement('div');
      for (var i=0; i<=aBlock.childNodes.length; i++){
        //alert(aBlock.childNodes.item(i));
        if (
            ( aBlock.childNodes.item(i) != null ) &&
        ( aBlock.childNodes.item(i).nodeName == "name")
           ){
              childItem = aBlock.childNodes.item(i);
          //alert ( aBlock.childNodes.item(i));
              //alert(childItem.getAttribute('href'));
              var theURL = childItem.getAttribute('href');
              //alert(childItem.firstChild.nodeValue)
          var theTarget= childItem.firstChild.nodeValue
          //Create a link item and put it in a paragraph
          var linkPara=document.createElement('p');
              linkPara.className='submenu_element';
          var link = document.createElement('a')
          link.setAttribute('href', theURL)
          link.setAttribute('class','submenu_element' )
              var textual_data = document.createTextNode(theTarget);
              link.appendChild(textual_data);
              linkPara.appendChild(link);
              theDiv.appendChild(linkPara);
        }
      }//end For
      if (manipulationClosure == null ){
         return theDiv;
      }else{
         return manipulationClosure(theDiv)
      }
    }

    /*
      This function receives a Javascript "Element" object
    */
    function createNavBlock(aBlock){
      // This might seem obvious, but this is an HTMLDivElement in
      // the API references
      var constructedBlock = document.createElement('div');
      constructedBlock.className="navigation_block";
      constructedBlock.id=aBlock.getAttribute('name');
      constructedBlock.setAttribute('id',aBlock.getAttribute('name'));

      if ( false ){
        alert('Constructed Block Object: ' + constructedBlock + "\n" +
          'Class Name: ' + constructedBlock.className + "\n" +
          'ID: ' + constructedBlock.id);
      }
      return constructedBlock;
    }

    /*
      This function creates the main navigational blocks to the left of the
      screen.

      It also seeds them with the callback function that is used to extract
      the fuller drop-down
    */
    function createNavMenu(){
      var array_of_blocks = xmlDoc.getElementsByTagName('block');
      var nav_section = document.getElementById('navigation_bar');
      for (var i=0; i<array_of_blocks.length; i++){
        var navBlock = createNavBlock(array_of_blocks[i]);

        if ( false ){
          alert('Constructed Block Object: ' + navBlock + "\n" +
            'Class Name: ' + navBlock.className + "\n" +
            'ID: ' + navBlock.id);
        } //end if

        navBlock = addAnEvent(navBlock, 'dblclick', eventHandler, 'false');
        navBlock = appendTitleBlock(navBlock, array_of_blocks[i].getAttribute('displaytitle'));
        nav_section.appendChild( navBlock );
      }// end for
    }



    function eventHandler(anEvent){
      var myEvent = anEvent ? anEvent : window.event;
      var thisName = this.id ? this.id : window.event.srcElement.id;


      /* Due to a bug in IE's rendering of CSS, it's possible that someone could click
         on the title of a box ( even though they're clicking outside of the title )
         and thus a 'null' value for thisName will be, quite legally, derived.  To
         catch this we send return, the user will assume they didn't dblclick properly
      */

      if (thisName == ""){
        return
      }

      if (false){
        alert ('howdy '+anEvent
            + "\n and..." + this
            + "\n and..." + thisName);
      }//end debugging truefalse


      if ( thisName == null ){
        return
      }

      var screenBlock = document.getElementById(thisName);
      if ( isExpanded(screenBlock) ){
         //alert('expanded');
         //alert(screenBlock.getElementsByTagName('div')[0])
         screenBlock.removeChild(screenBlock.getElementsByTagName('div')[0])

      }else{
        //alert('not expanded');
        var informationBlock = findBlockWithName(thisName);
        buildSubmenuFor(informationBlock);
        screenBlock.appendChild(buildSubmenuFor(informationBlock, function (aDiv)
          {
        aDiv.className="navigation_block_submenu";
        aDiv.setAttribute('id',thisName + '_submenu');
            return aDiv
          }));
      }
    }

    function findBlockWithName(aName){
      //alert(aName);
      var array_of_blocks = xmlDoc.getElementsByTagName('block');
      for (var i=0; i<array_of_blocks.length; i++){
        // alert('! ' + array_of_blocks[i].getAttribute('name'));
        if (array_of_blocks[i].getAttribute('name')==aName){
          return array_of_blocks[i];
        }
      }
    }

    function importXML(){
      if (document.implementation && document.implementation.createDocument) {
          xmlDoc = document.implementation.createDocument("", "", null);
          xmlDoc.onload = createNavMenu ;
      }else if (window.ActiveXObject){
          xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
          xmlDoc.onreadystatechange = function () {
              if (xmlDoc.readyState == 4) createNavMenu ()
          };
      }else{
          return;
      }
      xmlDoc.load("navigation.xml");
    }

    function isExpanded (blockToExamine){
      //alert (blockToExamine);
      var cNodes =blockToExamine.childNodes;
      for (var i=0; i<cNodes.length; i++){
        if (cNodes.item(i).tagName == 'DIV'){
          return true;
        }
      }
      return false;
    }

    function pageLoad(){
      browser_mutate();
      importXML();
    }


</code>

And lastly here’s the XML file:

<code>
 <blocks>
  <block name="aggregator_block" displaytitle="Aggregator Sites">
      <name href="http://www.reddit.com">Reddit</name>
      <name href="http://www.digg.com">Digg</name>
  </block>
  <block name="timewaster_block" displaytitle="Timewasters">
      <name href="http://www.socialitelife.com">Socialite's Life</name>
  </block>
 </blocks>