DHTML Lab: Hierarchical Menus Version 3 | 7 | WebReference

DHTML Lab: Hierarchical Menus Version 3 | 7


Logo

Hierarchical Menus: Version 3
item setup

WebReference

Click the link above to reveal menu. Click anywhere on the page to hide menu.

Parameters used for the menus on this page:

menuVersion = 3;
menuWidth = 120;
childOverlap = 5;
childOffset = 5;
perCentOver = null;
secondsVisible = .5;
fntCol = "black";
fntSiz = 8;
fntBold = false;
fntItal = false;
fntFam = "MS Sans Serif";
backCol = "#9FB7C0";
overCol = "#507F90";
overFnt = "white";
borWid = 2;
borCol = "#9FB7C0";
borSty = "outset";
itemPad = 2;
imgSrc = "tri.gif";
imgSiz = 8;
separator = 0;
separatorCol = "";
isFrames = false;
keepHilite = true; 
NSfontOver = false;
clickStart = true;
clickKill = true;
showVisited = "";

Background Reading:

itemSetup():
  column 14
  column 15
  column 18
  column 20

In script listings, cross-browser code is blue, Navigator-specific code is red, and Explorer code is green.

The [cc] symbol denotes code continuation. The code is part of the preceding line. It is placed on a new line for column formatting considerations only.

The itemSetup() function has considerable changes in this version. It is reproduced below, in full. We will discuss it line-by-line, immediately following.

function itemSetup(whichItem,whichArray) {
  this.onmouseover = itemOver;
  this.onmouseout = itemOut;
  this.container = (NS4) ? this.parentLayer : this.offsetParent;
  arrayPointer = (this.container.hasParent) ? (whichItem-1)*3 : ((whichItem-1)*3)+9;
  this.dispText = whichArray[arrayPointer];
  this.linkText = whichArray[arrayPointer + 1];
  this.hasMore = whichArray[arrayPointer + 2];
  if (IE4 && this.hasMore) {
    this.child = eval("elMenu" + this.id.substr(this.id.indexOf("_")-1));
    this.child.parentMenu = this.container;
    this.child.parentItem = this;
  }
  if (this.linkText) {
    if (NS4) {
      this.captureEvents(Event.MOUSEUP)
      this.onmouseup = linkIt;
    }
    else {
      this.onclick = linkIt;
      this.style.cursor = "hand";
    }
  }
  if (NS4) {
    this.document.tags.A.textDecoration = "none";
    htmStr = this.dispText;
    if (fntBold) htmStr = htmStr.bold();
    if (fntItal) htmStr = htmStr.italics();
    htmStr = "<FONT FACE=" + fntFam + " POINT-SIZE=" + fntSiz + ">" + htmStr+ "</FONT>";
    
    if (this.linkText) {
      with (this.document) {
        linkColor = this.container.menuFontColor;
        alinkColor = this.container.menuFontColor;
        vlinkColor = (showVisited) ? showVisited : this.container.menuFontColor;
      }
      htmStrOver = htmStr.fontcolor(this.container.menuFontOver).link(this.linkText);
      htmStr = htmStr.link(this.linkText);
    }
    else {
      htmStrOver = htmStr.fontcolor(this.container.menuFontOver);
      htmStr = htmStr.fontcolor(this.container.menuFontColor);
    }
    
    this.htmStr = (this.hasMore) ? imgStr + htmStr : spStr + htmStr;
    this.htmStrOver = (this.hasMore) ? imgStr + htmStrOver : spStr + htmStrOver;    
    
    this.document.write(this.htmStr);
    this.document.close();
    
    this.visibility = "inherit";
    this.bgColor = this.container.menuBGColor;
    if (whichItem == 1) {
      this.top = borWid + itemPad;
    }
    else {
      this.top = this.prevItem.top + this.prevItem.clip.height + separator;
    }
    this.left = borWid + itemPad;
    this.clip.top = this.clip.left = -itemPad;
    this.clip.bottom += itemPad;
    this.clip.right = this.container.menuWidth-(borWid*2)-itemPad;
  }
  else {
    with (this.style) {
      padding = itemPad;
      if (isRight && !this.hasMore) paddingLeft = parseInt(padding)+imgSiz;
      color = this.container.menuFontColor;
      fontSize = fntSiz + "pt";
      fontWeight = (fntBold) ? "bold" : "normal";
      fontStyle =  (fntItal) ? "italic" : "normal";
      fontFamily = fntFam;
    
      borderBottomWidth = separator + "px";
      borderBottomColor = this.container.menuSeparatorCol;
      borderBottomStyle = "solid";
      backgroundColor = this.container.menuBGColor;
    }
  }
}

First of all, the mouse event handlers are set, as before:

  this.onmouseover = itemOver;
  this.onmouseout = itemOut;

The container property is initialized to store the menu the item appears in. In version 2, this occured in the browser specific parts further down. Here we move it forward, since we need the container property in the very next statement, to identify top-level arrays, and set the arrayPointer.

  this.container = (NS4) ? this.parentLayer : this.offsetParent;
  arrayPointer = (this.container.hasParent) ? (whichItem-1)*3 : ((whichItem-1)*3)+9;

The three item array elements are assigned to the dispText, linkText and hasMore properties, as before:

  this.dispText = whichArray[arrayPointer];
  this.linkText = whichArray[arrayPointer + 1];
  this.hasMore = whichArray[arrayPointer + 2];

There is a major difference between the version 3 IE4-specific code and all other versions as far as the order-of-element-creation is concerned. In previous versions, and in the NS4-specific code in version 3, the item element is physically created before any related child menu is created. Here, we create the item after the child menu. The item's child property and the child menu's parentMenu and parentItem properties must be set here, in itemSetup(), since it is only now, after both the child menu and the item have been created, that we have objects to assign properties to. The only way to identify an associated child menu, at this late stage, is through its ID attribute:

  if (IE4 && this.hasMore) {
    this.child = eval("elMenu" + this.id.substr(this.id.indexOf("_")-1));
    this.child.parentMenu = this.container;
    this.child.parentItem = this;
  }

If the item is a link, we set the event handlers pointing to linkIt(), as before:

  if (this.linkText) {
    if (NS4) {
      this.captureEvents(Event.MOUSEUP)
      this.onmouseup = linkIt;
    }
    else {
      this.onclick = linkIt;
      this.style.cursor = "hand";
    }
  }

The remainder of itemSetup() is divided into browser-specific code. First, we'll look at the Navigator code, which includes techniques most of us didn't know were available to us, or never thought of using.

Navigator 4, as we know, does not support the CSS2 cursor property, and a "hand" cursor is only available to links. So far, we have avoided dynamically writing links (<A HREF=) to our items, as we considered it inelegant. The only way to display a link without the default underline was to declare a style rule in a STYLE tag. Two things changed my mind:

  1. the mail that insisted that NS4 get a "hand" cursor over the items
  2. a recently published article on JavaScript styling from the Netscape site

Those of us who have read all of the Netscape DHTML and stylesheet documentation, (and several times at that!), were led to believe that JavaScript StyleSheets were declared ONLY through the <STYLE TYPE="text/javascript"> tag. This is, apparently, not true. JSS can be declared within SCRIPT tags, as long as the document object precedes the statements. The DHTML documentation examples drop the document object as "redundant." It is only redundant within a STYLE tag. In a SCRIPT tag, it is a must.

Using SCRIPT-enclosed JSS, we can style our tags based on variable values, giving us new power over the look of our document. The major problem is, of course, the inability to change style once the object has been rendered. There is a major plus, however. Since JSS are properties of the document object, and NS positioned elements have their own document object, JSS can be specific to a positioned element.

In other words, Navigator 4 comes ready-build with extremely powerful dynamic style capabilities, which have been downplayed by Netscape itself. In fact, by stressing only the use of the STYLE tag, they have made authors believe that it was just a non-standard way of applying styles. The standard cross-browser CSS was a much better choice. Microsoft, on the other hand, publicized the styling-through-script abilities of its DOM and scripting engines. The Explorer syntax is remarkably close to JSS.

You have all read, or skimmed-over, or avoided the JSS documentation, so we won't delve into it here. I highly recommend you reconsider JSS in this new light. For parameter-variable-based scripts, especially external scripts, they are very useful, helping you to incorporate everything into the SCRIPT tag.

Now, about those Netscape Technology Evangelists...

Well, that's as long-winded as I've ever been in introducing a single line of script. I just thought I would share this discovery with you. So, in our NS-specific part of itemSetup(), we use JSS to ensure that any links contained in the item are not underlined:

  if (NS4) {
     this.document.tags.A.textDecoration = "none";

Next, we create the temporary variable, htmStr, to store the HTML that will be written to the item, and assign the display string from the array element (dispText) to it.

    htmStr = this.dispText;

We then, as before, make the string bold and/or italic, depending on the values of the related parameter variables, and enclose it in a <FONT> tag that uses the font family and font size variables.

  if (fntBold) htmStr = htmStr.bold();
  if (fntItal) htmStr = htmStr.italics();
  htmStr = "<FONT FACE=" + fntFam + " POINT-SIZE=" + fntSiz + ">" + htmStr+ "</FONT>";

If the item is a link, we must ensure that the text is not the page's default LINK= color, but the color we have specified in our tree-specific parameter. We also do not want an "active link" color applied (ALINK=). We may, however, want the visited links shaded, as reflected in the showVisited parameter. We can set all three options using the old JavaScript 1.0 linkColor, alinkColor, and vlinkColor properties of the document object:

    if (this.linkText) {
      with (this.document) {
        linkColor = this.container.menuFontColor;
        alinkColor = this.container.menuFontColor;
        vlinkColor = (showVisited) ? showVisited : this.container.menuFontColor;
      }

Notice that all the document properties are set before the link is created, as these properties cannot be changed after the document has been written to.

Version 3 allows font color change upon item mouseover for NS4. This is achieved by updating the contents of the item with a new HTML string that colors text differently. When the user mouses out, the original HTML string is written back. In order to achieve this effect, we need two versions of the item contents:

  1. a mouseover string: htmStrOver wraps htmStr in a <FONT COLOR= tag, which in turn is enclosed in an <A HREF= tag. The nested FONT tag overrides the linkColor property.
  2. a regular/mouseout string: htmStr is enclosed in a link tag.
      htmStrOver = htmStr.fontcolor(this.container.menuFontOver).link(this.linkText);
      htmStr = htmStr.link(this.linkText);
    }

If the item is not a link, the two strings are simply htmStr enclosed in a <FONT tag, with the COLOR= attribute set to the related tree-specific parameter.

    else {
      htmStrOver = htmStr.fontcolor(this.container.menuFontOver);
      htmStr = htmStr.fontcolor(this.container.menuFontColor);
    }

The two strings, htmStr and htmStrOver, must be made properties of the item, for easy reference. Before they are assigned, we prefix them with either the image string (imgStr) or the spacer string (spStr), depending on whether the item has a child menu.

    this.htmStr = (this.hasMore) ? imgStr + htmStr : spStr + htmStr;
    this.htmStrOver = (this.hasMore) ? imgStr + htmStrOver : spStr + htmStrOver; 

Finally, the default string is written to the item. The remainder of the statements are the same as in version 2, only with reference to tree-specific parameters when necessary.

    this.document.write(this.htmStr);
    this.document.close();
    
    this.visibility = "inherit";
    this.bgColor = this.container.menuBGColor;
    if (whichItem == 1) {
      this.top = borWid + itemPad;
    }
    else {
      this.top = this.prevItem.top + this.prevItem.clip.height + separator;
    }
    this.left = borWid + itemPad;
    this.clip.top = this.clip.left = -itemPad;
    this.clip.bottom += itemPad;
    this.clip.right = this.container.menuWidth-(borWid*2)-itemPad;    
  }

The Explorer-specific code that styles the item is exactly like the version 2 code, accounting for tree-specific parameters. The one addition is the second statement. If the menus cascade from right to left (isRight) and the item has no child menu, we must indent the text in from the left to line up with the items that have a child menu and consequently have an triangle image visible. In NS4, we did this with the spStr string, which used the <SPACER> tag. In IE4, we simply increase the left padding of the item by the width of the image.

  else {
    with (this.style) {
      padding = itemPad;
      if (isRight && !this.hasMore) paddingLeft = parseInt(padding)+imgSiz;
      color = this.container.menuFontColor;
      fontSize = fntSiz + "pt";
      fontWeight = (fntBold) ? "bold" : "normal";
      fontStyle =  (fntItal) ? "italic" : "normal";
      fontFamily = fntFam;
    
      borderBottomWidth = separator + "px";
      borderBottomColor = this.container.menuSeparatorCol;
      borderBottomStyle = "solid";
      backgroundColor = this.container.menuBGColor;
    }
  }
}

If you're still with us, you might want to see how menuSetup() fares in this version.


Produced by Peter Belesis and

All Rights Reserved. Legal Notices.
Created: Sept. 03, 1998
Revised: Sept. 03, 1998

URL: http://www.webreference.com/dhtml/column21/hier3Item.html