/*********************************************************** {COPYRIGHT-TOP} ***
* Licensed Materials - Property of IBM
* Tivoli Presentation Services
*
* (C) Copyright IBM Corp. 2002,2003 All Rights Reserved.
*
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
************************************************************ {COPYRIGHT-END} ***
* Change Activity on 6/20/03 version 1.17:
* @00=WCL, V3R0, 04/14/2002, JCP: Initial version
* @01=D96484, V3R2, 06/14/2002, bcourt: hide select/iframe elements
* @02=D99067, V3R2, 06/25/2002, bcourt: hide listbox scrollbar
* @03=D97043, V3R3, 09/03/2002, JCP: fix launch menu item on linux NS6
* @04=D104656, V3R3, 09/16/2002, JCP: form submit instead of triggers, mozilla compatibility
* @05=D107029, V3R4, 12/03/2002, Mark Rebuck:  Added support for timed menu hiding
* @06=D110173, V3R4, 03/24/2003, JCP: selection sometimes gets stuck
* @07=D113641, V3R4, 04/29/2003, LSR: Requirement #258 Shorten CSS Names
* @08=D113626, V3R4, 06/20/2003, JCP: clicking on text doesn't launch action on linux Moz13
*******************************************************************************/

var visibleMenu_ = null;
var padding_ = 10;

var transImg_ = "transparent.gif";

var arrowNorm_ = "contextArrowDefault.gif";
var arrowSel_ = "contextArrowSelected.gif";
var arrowDis_ = "contextArrowDisabled.gif";
var launchNorm_ = "contextLauncherDefault.gif";
var launchSel_ = "contextLauncherSelected.gif";

var arrowNormRTL_ = "contextArrowDefault.gif";
var arrowSelRTL_ = "contextArrowSelected.gif";
var arrowDisRTL_ = "contextArrowDisabled.gif";
var launchNormRTL_ = "contextLauncherDefault.gif";
var launchSelRTL_ = "contextLauncherSelected.gif";

var arrowWidth_ = "12";
var arrowHeight_ = "12";

var submenuAltText_ = "+";
var noActionsText_ = "(0)";

var hideCurrentMenuTimer_ = null;

function clearMenuTimer( ) { //@05
   if (null != hideCurrentMenuTimer_) {
      clearTimeout( hideCurrentMenuTimer_ );
      hideCurrentMenuTimer_ = null;
   }
}

function setMenuTimer( ) { // @05
   clearMenuTimer( );
   hideCurrentMenuTimer_ = setTimeout( 'hideCurrentContextMenu( )', 2000);
}

function debug( str ) {
   /*
   if ( xbDEBUG != null ) {
      xbDEBUG.dump( str );
   }
   */
}

// constructor
function UilContextMenu( name, isLTR, width ) {
   // member variables
   this.name = name;
   this.items = new Array();
   this.isVisible = false;
   this.isDismissable = true;
   this.selectedItem = null;
   this.isDynamic = false;
   this.isCacheable = false;
   this.isEmpty = true;
   this.isLTR = isLTR;
   this.hiddenItems = new Array(); //@01A
   this.isHyperlinkChild = true; //  We will reset later if needed.

   // html variables
   this.launcher = null;
   this.menuTag = null;

   // external methods
   this.add = UilContextMenuAdd;
   this.addSeparator = UilContextMenuAddSeparator;
   this.show = UilContextMenuShow;
   this.hide = UilContextMenuHide;

   // internal methods
   this.create = UilContextMenuCreate;
   this.getMenuItem = UilContextMenuGetMenuItem;
   this.getSelectedItem = UilContextMenuGetSelectedItem;

   if ( this.name == null ) {
      this.name = "UilContextMenu_" + allMenus_.length;
   }
}

// adds a menu item to the context menu
function UilContextMenuAdd( item ) {
   this.items[ this.items.length ] = item;
   this.isEmpty = false;
}

function UilContextMenuAddSeparator() {
   var sep = new UilMenuItem();
   sep.isSeparator = true;
   this.add( sep );
}

// shows the context menu
// launcher- html element (anchor) that is launching the menu
// launchItem- menu item that is launching the menu
function UilContextMenuShow( launcher, launchItem ) {
   if ( this.items.length == 0 ) {
      // empty context menu
      debug( 'menu is empty!' );
      this.add( new UilMenuItem( noActionsText_, false, "javascript:void(0);" ) );
      this.isEmpty = true;
   }

   if ( this.menuTag == null ) {
      // create the context menu html
      this.create();
   }

   if ( this.menuTag != null) {
      // store the launcher for later
      this.launcher = launcher;
      if ( this.launcher.tagName == "IMG" ) {
         this.isHyperlinkChild = false;

         // we want the anchor tag
         this.launcher = this.launcher.parentNode;
      }

      // boundaries of window
      var maxX = window.pageXOffset + window.innerWidth;
      var maxY = window.pageYOffset + window.innerHeight;
      debug( 'max: ' + maxX + ', ' + maxY );
      var menuWidth = getWidth( this.menuTag );
      var menuHeight = getHeight( this.menuTag );

      // move the context menu to the right of the launcher
      var posX = 0;
      var posY = 0;
      if ( launchItem != null ) {
         // launched from submenu
         var launchTag = launchItem.itemTag;
         var launchTagWidth = getWidth( launchTag );
         var parentTag = launchItem.parentMenu.menuTag; //@04A
         var launchOffsetX = getLeft( parentTag ); //@04C
         var launchOffsetY = getTop( parentTag ); //@04C

         posX = launchOffsetX + getLeft( launchTag ) + launchTagWidth; //@04C
         posY = launchOffsetY + getTop( launchTag ); //@04C

         if ( !this.isLTR ) {
            posX -= launchTagWidth;
            posX -= menuWidth;
         }

         // try to keep it in the window
         if ( this.isLTR ) {
            if ( posX + menuWidth > maxX ) {
               // try to show it to the left of the parent menu
               var posX1 = launchOffsetX - menuWidth;
               var posX2 = maxX - menuWidth;
               if ( 0 <= posX1 ) {
                  posX = posX1;
               }
               else {
                  posX = Math.max( 0, posX2 );
               }
            }
         }
         else {
            if ( posX < 0 ) {
               // try to show it to the right of the parent menu
               var posX1 = launchOffsetX + launchTagWidth;
               if ( posX1 + menuWidth < maxX ) {
                  posX = posX1;
               }
               else {
                  posX = Math.min( 0, maxX - menuWidth );
               }
            }
         }

         if ( posY + menuHeight > maxY ) {
            var posY1 = maxY - menuHeight;
            posY = Math.max( 0, posY1 );
         }
      }
      else {
         // launched from menu link
         var launcherLeft = getLeft( this.launcher, true )
         posX = launcherLeft + this.launcher.offsetWidth;
         posY = getTop( this.launcher, true );

         if ( !this.isLTR ) {
            posX -= this.launcher.offsetWidth;
            posX -= menuWidth;
         }

         // keep it in the window
         if ( this.isLTR ) {
            if ( posX + menuWidth > maxX ) {
               // try to show it on the left side of the launcher
               var posX1 = launcherLeft - menuWidth;
               if ( posX1 > 0 ) {
                  posX = posX1;
               }
               else {
                  posX = Math.max( 0, maxX - menuWidth );
               }
            }
         }
         else {
            if ( posX < 0 ) {
               // try to show it on the right side of the launcher
               var posX1 = launcherLeft + this.launcher.offsetWidth;
               if ( posX1 + menuWidth < maxX ) {
                  posX = posX1;
               }
               else {
                  posX = Math.min( 0, maxX - menuWidth );
               }
            }
         }

         if ( posY + menuHeight > maxY ) {
            posY = Math.max( 0, maxY - menuHeight );
         }
      }

      debug( 'show ' + this.name + ': ' + posX + ', ' + posY );
      this.menuTag.style.left = posX + "px";
      this.menuTag.style.top = posY + "px";

      // make the context menu visible
      this.menuTag.style.visibility = "visible";
      this.isVisible = true;

      // set focus on the first menu item
      this.items[0].setSelected( true );
      this.items[0].anchorTag.focus();

      // @01A - Hide any items that intersect this menu
      var coll = document.getElementsByTagName("SELECT");
      if (coll!=null)
      {
         for (i=0; i<coll.length; i++)
         {
            //Hide the element
            if (intersect(this.menuTag,coll[i]) == true ) {
               if (coll[i].style.visibility != "collapse") //@02C
               {
                  coll[i].style.visibility = "collapse"; //@02C
                  this.hiddenItems.push(coll[i]);
               }
            }
         }
      }
      coll = document.getElementsByTagName("IFRAME");
      if (coll!=null)
      {
         for (i=0; i<coll.length; i++)
         {
            //Hide the element
            if (intersect(this.menuTag,coll[i]) == true ) {
               if (coll[i].style.visibility != "hidden")
               {
                  coll[i].style.visibility = "hidden";
                  this.hiddenItems.push(coll[i]);
               }
            }
         }
      }
   }
}

// Test whether two objects overlap
function intersect(obj1 , obj2) //@01A
{
   var left1 =  parseInt(document.defaultView.getComputedStyle(obj1, '').getPropertyValue("left"));
   var right1 = left1 + parseInt(document.defaultView.getComputedStyle(obj1, '').getPropertyValue("width"));
   var top1 = parseInt(document.defaultView.getComputedStyle(obj1, '').getPropertyValue("top"));
   var bottom1 = top1 + parseInt(document.defaultView.getComputedStyle(obj1, '').getPropertyValue("height"));

   var left2 =  parseInt(document.defaultView.getComputedStyle(obj2, '').getPropertyValue("left"));
   var right2 = left2 + parseInt(document.defaultView.getComputedStyle(obj2, '').getPropertyValue("width"));
   var top2 = parseInt(document.defaultView.getComputedStyle(obj2, '').getPropertyValue("top"));
   var bottom2 = top2 + parseInt(document.defaultView.getComputedStyle(obj2, '').getPropertyValue("height"));

    //alert("Comparing: " +left1 + ", " + right1+ ", " +top1 + ", " + bottom1+ " to "  +left2 + ", "  +right2 + ", " +top2 + ", " +bottom2);
   if (lineIntersect(left1,right1, left2,right2)== true &&
       lineIntersect(top1,bottom1,top2,bottom2) == true) {
      return true;
   }
   return false;
}

// Test whether the two line segments a--b and c--d intersect.
function lineIntersect(a, b, c, d) //@01A
{
   //alert (a+"--"+b + "   " + c + "--" + d);
   //Assume that a < b && c < d
   if ( (a <= c  && c <= b) ||
        (a <= d && d <= b) ||
        (c <= a && d >= b ) )
   {
      return true;
   } else {
      return false;
   }
}

// hides the context menu
function UilContextMenuHide() {
   if ( this.menuTag != null ) {
      debug( 'hide ' + this.name );

      // hide any visible submenus first
      for ( var i=0; i<this.items.length; i++ ) {
         if ( this.items[i].submenu != null &&
              this.items[i].submenu.isVisible ) {
            this.items[i].submenu.hide();
         }
      }

      // clear selection
      if ( this.selectedItem != null ) {
         this.selectedItem.setSelected( false );
      }

      // make the context menu hidden
      this.menuTag.style.visibility = "hidden";
      this.isVisible = false;
      this.isDismissable = true;

      // @01A - Show any items that were hidden by this menu
      var itemCount = this.hiddenItems.length;
      for (i=0; i<itemCount; i++)
      {
         var item = this.hiddenItems.pop();
         item.style.visibility = "visible";
      }

      // clear the launcher
      this.launcher = null;
   }
}

// creates the context menu html element
function UilContextMenuCreate( recurse ) {
   if ( this.menuTag == null ) {
      this.menuTag = document.createElement( "DIV" );
      this.menuTag.style.position = "absolute";
      this.menuTag.style.cursor = "default";
      this.menuTag.style.visibility = "hidden";
      this.menuTag.style.width = "0px"; // this causes dynamic sizing
      this.menuTag.onmouseover = contextMenuDismissDisable;
      this.menuTag.onmouseout = contextMenuDismissEnable;
      this.menuTag.oncontextmenu = contextMenuOnContextMenu;

      var numItems = this.items.length;

      // check if this context menu contains icons or submenus
      var hasIcon = false;
      var hasSubmenu = false;
      for ( var i=0; i<numItems; i++ ) {
         if ( !this.items[i].isSeparator ) {
            if ( !hasSubmenu && this.items[i].submenu != null ) {
               hasSubmenu = true;
            }
            if ( !hasIcon && this.items[i].icon != null ) {
               hasIcon = true;
            }
            if ( hasSubmenu && hasIcon ) {
               break;
            }
         }
      }

      // create the menu items
      for ( var i=0; i<numItems; i++ ) {
         this.items[i].isFirst = ( i == 0 );
         this.items[i].isLast = ( i+1 == numItems );
         this.items[i].parentMenu = this;
         this.items[i].create( hasIcon, hasSubmenu );
      }

      // create the context menu border
      var border = document.createElement( "TABLE" );
      if (!this.isLTR) border.dir = "RTL";
      //border.className = "wclPopupMenuBorder"; //@04D
      border.rules = "none";
      border.cellPadding = 0;
      border.cellSpacing = 0;
      //border.width = "100%";
      border.border = 0;
      var borderBody = document.createElement( "TBODY" );
      var borderRow = document.createElement( "TR" );
      var borderData = document.createElement( "TD" );
      var borderDiv = document.createElement( "DIV" ); //@04A
      //borderDiv.className = "pop2"; //@04A   //@06C1
      borderDiv.className = "lwpShadowBorder"; 
      borderData.appendChild( borderDiv ); //@04A
      borderRow.appendChild( borderData );
      borderBody.appendChild( borderRow );
      border.appendChild( borderBody );

      // create the context menu
      var table = document.createElement( "TABLE" );
      if (!this.isLTR) table.dir = "RTL";
      //table.className = "wclPopupMenu"; //@04D
      table.rules = "none";
      table.cellPadding = 0;
      table.cellSpacing = 0;
      table.width = "100%";
      table.border = 0;

      // add the menu items
      var tableBody = document.createElement( "TBODY" );
      table.appendChild( tableBody );

      // @04A - create another table for mozilla fix
      // (border styles not hidden correctly if set on table tag)
      var table2 = document.createElement( "TABLE" );
      if (!this.isLTR) table2.dir = "RTL";
      table2.rules = "none";
      table2.cellPadding = 0;
      table2.cellSpacing = 0;
      table2.width = "100%";
      table2.border = 0;
      var tableRow = document.createElement( "TR" );
      var tableData = document.createElement( "TD" );
      var tableDiv = document.createElement( "DIV" );
      //tableDiv.className = "pop1";     //@06C1
      tableDiv.className = "lwpBorderAll";     //@06C1
      var tableBody2 = document.createElement( "TBODY" );
      tableBody.appendChild( tableRow );
      tableRow.appendChild( tableData );
      tableData.appendChild( tableDiv );
      tableDiv.appendChild( table2 );
      table2.appendChild( tableBody2 );

      for ( var i=0; i<numItems; i++ ) {
         if ( this.items[i].isSeparator ) {
            this.items[i].createSeparator( tableBody2, hasSubmenu ); //@04C
         }
         else {
            tableBody2.appendChild( this.items[i].itemTag ); //@04C
         }
      }

      borderDiv.appendChild( table ); //@04C
      this.menuTag.appendChild( border );

      // add to document
      document.body.appendChild( this.menuTag );
   }

   if ( recurse ) {
      // this is used when cloning dynamic menus
      for ( var i=0; i<this.items.length; i++ ) {
         if ( this.items[i].submenu != null ) {
            this.items[i].submenu.create( recurse );
         }
      }
   }
}

// returns the menu item object associated with the html element
function UilContextMenuGetMenuItem( htmlElement ) {
   if ( htmlElement != null ) {
      if ( htmlElement.nodeType == 3 ) { //@06A
          // text node
          htmlElement = htmlElement.parentNode; //@06A
      }
      var tagName = htmlElement.tagName;
      var menuItemTag = null;
      if ( tagName == "IMG" ||
           tagName == "A" ) {
         menuItemTag = htmlElement.parentNode.parentNode;
      }
      else if ( tagName == "TD" ) {
         menuItemTag = htmlElement.parentNode;
      }
      for ( var i=0; i<this.items.length; i++ ) {
         if ( this.items[i].itemTag != null &&
              this.items[i].itemTag == menuItemTag ) {
            // found the item
            return this.items[i];
         }
         else if ( this.items[i].submenu != null &&
                   this.items[i].submenu.isVisible ) {
            // recurse through any visible submenus
            var item = this.items[i].submenu.getMenuItem( htmlElement );
            if ( item != null ) {
               // found the item
               return item;
            }
         }
      }
   }
   return null;
}

// returns the deepest selected menu item
function UilContextMenuGetSelectedItem() {
   var item = this.selectedItem;
   if ( item != null && item.submenu != null && item.submenu.isVisible ) {
      // recurse through the item's visible submenu
      return item.submenu.getSelectedItem();
   }
   return item;
}

// method called by an event handler (onmouseover for context menu div)
function contextMenuDismissEnable() {
   if ( visibleMenu_ != null ) {
      visibleMenu_.isDismissable = true;
      if (visibleMenu_.isHyperlinkChild) {
         setMenuTimer( ); // @05
      }
   }
}

// method called by an event handler (onmouseout for context menu div)
function contextMenuDismissDisable() {
   if ( visibleMenu_ != null ) {
      visibleMenu_.isDismissable = false;
      clearMenuTimer( ); // @05
   }
}

// method called by an event handler (oncontextmenu for context menu div)
function contextMenuOnContextMenu() {
   return false;
}

// constructor
function UilMenuItem( text, enabled, action, clientAction, submenu, icon, defItem,menuStyle, selectedMenuStyle ) {
   // member variables
   this.text = text;
   this.icon = icon;
   this.action = action;
   this.clientAction = clientAction;
   this.submenu = submenu;
   this.isSeparator = false;
   this.isSelected = false;
   this.isEnabled = ( enabled != null ) ? enabled : true;
   this.isDefault = ( defItem != null ) ? defItem : false;
   this.isFirst = false;
   this.isLast = false;
   this.parentMenu = null;
   this.menuStyle = ( menuStyle != null) ? menuStyle : "lwpMenuItem"; 
   this.selectedMenuStyle = ( selectedMenuStyle != null) ? selectedMenuStyle : "lwpSelectedMenuItem";

   // html variables
   this.itemTag = null;
   this.anchorTag = null;
   this.arrowTag = null;

   // internal methods
   this.create = UilMenuItemCreate;
   this.createSeparator = UilMenuItemCreateSeparator;
   this.setSelected = UilMenuItemSetSelected;
   this.updateStyle = UilMenuItemUpdateStyle;
   this.getNextItem = UilMenuItemGetNextItem;
   this.getPrevItem = UilMenuItemGetPrevItem;

   if ( this.submenu != null ) {
      // menu items with submenus do not have actions
      this.action = null;
   }
}

// creates the menu item html element
function UilMenuItemCreate( menuHasIcon, menuHasSubmenu ) {
   if ( !this.isSeparator ) {
      this.anchorTag = document.createElement( "A" );
      if ( this.action != null ) {
         this.anchorTag.href = "javascript:menuItemLaunchAction();";
         if ( this.clientAction != null ) {
            this.anchorTag.onclick = this.clientAction;
         }
      }
      else if ( this.submenu != null ) {
         this.anchorTag.href = "javascript:void(0);";
         this.anchorTag.onclick = menuItemShowSubmenu;
      }
      else if ( this.clientAction != null ) {
         this.anchorTag.href = "javascript:menuItemLaunchAction();";
         this.anchorTag.onclick = this.clientAction;
      }
      this.anchorTag.onfocus = menuItemFocus;
      this.anchorTag.onblur = menuItemBlur;
      this.anchorTag.onkeydown = menuItemKeyDown;
      this.anchorTag.innerHTML = this.text;
      //this.anchorTag.className = "pop5";    //@06C1
      this.anchorTag.className = this.menuStyle;    //@06C1
      if ( this.isDefault ) {
         this.anchorTag.style.fontWeight = "bold";
      }

      var td = document.createElement( "TD" );
      td.noWrap = true;
      td.style.padding = "3px";
      td.appendChild( this.anchorTag );

      // left padding or icon
      var leftPad = document.createElement( "TD" );
      leftPad.noWrap = true;
      leftPad.innerHTML = "&nbsp;";
      leftPad.style.padding = "3px";
      if ( this.icon != null ) {
         var imgTag = document.createElement( "IMG" );
         imgTag.src = this.icon;
         if ( this.text != null ) {
            imgTag.alt = this.text;
            imgTag.title = this.text;
         }
         leftPad.appendChild( imgTag );
      }
      else {
         leftPad.width = padding_;
      }

      // right padding
      var rightPad = document.createElement( "TD" );
      rightPad.noWrap = true;
      rightPad.width = padding_;
      rightPad.innerHTML = "&nbsp;";
      rightPad.style.padding = "3px";

      this.itemTag = document.createElement( "TR" );
      this.itemTag.onmousemove = menuItemMouseMove;
      this.itemTag.onmousedown = menuItemMouseDown;
      //this.itemTag.className = "pop5";  //@06C1
      this.itemTag.className = this.menuStyle;
      // put together the table row
      this.itemTag.appendChild( leftPad );
      this.itemTag.appendChild( td );
      this.itemTag.appendChild( rightPad );
      if ( menuHasSubmenu ) {
         // submenu arrow
         var submenuArrow = document.createElement( "TD" );
         submenuArrow.noWrap = true;
         submenuArrow.style.padding = "3px";
         if ( this.submenu != null ) {
            var submenuImg = document.createElement( "IMG" );
            submenuImg.alt = submenuAltText_;
            submenuImg.title = submenuAltText_;
            submenuImg.width = arrowWidth_;
            submenuImg.height = arrowHeight_;
            if (this.parentMenu.isLTR) submenuImg.src = arrowNorm_;
            else submenuImg.src = arrowNormRTL_;
            submenuArrow.appendChild( submenuImg );
            this.arrowTag = submenuImg;
         }
         else {
            submenuArrow.innerHTML = "&nbsp;";
         }
         this.itemTag.appendChild( submenuArrow );
      }

      // update the style of the menu item
      this.updateStyle( this.itemTag );
   }
}

// create the context menu separator html elements
function UilMenuItemCreateSeparator( tableBody, menuHasSubmenu ) {
   // create the context menu separator
   var numCols = ( menuHasSubmenu ) ? 4 : 3;

   for ( var i=0; i<4; i++ ) {
      var tr = document.createElement( "TR" );
      if ( i == 1 ) {
         //tr.className = "pop3";   //@06C1
	 tr.className = "portlet-separator";   //@06C1
      }
      else if ( i == 2 ) {
         //tr.className = "pop4";   //@06C1
	 tr.className = "lwpMenuBackground";   //@06C1
      }
      else {
         //tr.className = "pop5";   //@06C1
	 tr.className = "lwpMenuItem";   //@06C1
      }

      var td = document.createElement( "TD" );
      td.noWrap = true;
      td.width = "100%";
      td.height = "1px";
      td.colSpan = numCols;

      var img = document.createElement( "IMG" );
      img.src = transImg_;
      img.width = 1;
      img.height = 1;
      img.style.display = "block";
      td.appendChild( img );

      tr.appendChild( td );
      tableBody.appendChild( tr );
   }
}

// changes the selected state for menu item
function UilMenuItemSetSelected( isSelected ) {

   if ( isSelected && !this.isSelected ) {
      debug( 'selected: ' + this.text );
      // handle the previous selection first
      if ( this.parentMenu != null &&
           this.parentMenu.isVisible &&
           this.parentMenu.selectedItem != null &&
           this.parentMenu.selectedItem != this ) {
         // hide previous selection's submenu
         if ( this.parentMenu.selectedItem.submenu != null ) {
            this.parentMenu.selectedItem.submenu.hide();
         }
         // unselect previous selection from parent menu
         this.parentMenu.selectedItem.setSelected( false );
      }

      // select this menu item
      this.isSelected = true;
      if ( this.parentMenu != null && this.parentMenu.isVisible ) {
         this.parentMenu.selectedItem = this;
      }

      // update the styles
      this.updateStyle( this.itemTag );
   }
   else if ( !isSelected && this.isSelected ) {
      debug( 'deselected: ' + this.text );
      // menu item cannot be unselected if its submenu is visible
      if ( this.submenu == null || ( this.submenu != null && !this.submenu.isVisible ) ) {
         // unselect this menu item
         this.isSelected = false;
         if ( this.parentmenu != null ) {
            this.parentmenu.selectedItem = null;
         }

         // update the styles
         this.updateStyle( this.itemTag );
      }
   }
}

// recursively set the style of the menu item html element
function UilMenuItemUpdateStyle( tag, styleID ) {
   if ( tag != null ) {
      if ( styleID == null ) {
         //styleID = "pop5";  //@06C1
	 styleID = this.menuStyle;
         if ( !this.isEnabled ) {
            //styleID = "pop7"; //@06C1
	    styleID = this.menuStyle; 
         }
         else if ( this.isSelected ) {
            //styleID = "pop6";  //@06C1
	    styleID = this.selectedMenuStyle;  //@06C1
         }
         if ( this.arrowTag != null ) {
            if ( this.isEnabled && this.isSelected ) {
               if (this.parentMenu.isLTR) this.arrowTag.src = arrowSel_;
               else this.arrowTag.src = arrowSelRTL_;
            }
            else if ( !this.isEnabled ) {
               if (this.parentMenu.isLTR) this.arrowTag.src = arrowDis_;
               else this.arrowTag.src = arrowDisRTL_;
            }
            else {
               if (this.parentMenu.isLTR) this.arrowTag.src = arrowNorm_;
               else this.arrowTag.src = arrowNormRTL_;
            }
         }
      }
      tag.className = styleID;
      if ( tag.childNodes != null ) {
         for ( var i=0; i<tag.childNodes.length; i++ ) {
            this.updateStyle( tag.childNodes[i], styleID );
         }
      }
   }
}

// returns the next enabled, non-separator menu item after the given item
function UilMenuItemGetNextItem() {
   var menu = this.parentMenu;
   if ( menu != null ) {
      for ( var i=0; i<menu.items.length; i++ ) {
         if ( menu.items[i] == this ) {
            for ( var j=i+1; j<menu.items.length; j++ ) {
               if ( !menu.items[j].isSeparator && menu.items[j].isEnabled ) {
                  return menu.items[j];
               }
            }
            // no next item
            return null;
         }
      }
   }
   return null;
}

// returns the previous enabled, non-separator menu item before the given item
function UilMenuItemGetPrevItem() {
   var menu = this.parentMenu;
   if ( menu != null ) {
      for ( var i=menu.items.length-1; i>=0; i-- ) {
         if ( menu.items[i] == this ) {
            for ( var j=i-1; j>=0; j-- ) {
               if ( !menu.items[j].isSeparator && menu.items[j].isEnabled ) {
                  return menu.items[j];
               }
            }
            // no previous item
            return null;
         }
      }
   }
   return null;
}

// launches the action for a menu item
// method called by an event handler (href for anchor tag)
function menuItemLaunchAction() {
   if ( visibleMenu_ != null ) {
      //var evt = window.event;
      //var item = visibleMenu_.getMenuItem( evt.target );
      var item = visibleMenu_.getSelectedItem();
      if ( item != null && item.isEnabled ) {
         hideCurrentContextMenu( true );
         if ( item.clientAction != null ) {
            eval( item.clientAction );
         }
         if ( item.action != null ) {
            if ( item.action.indexOf( "javascript:" ) == 0 ) {
               eval( item.action );
            }
            else {
               //window.location.href = item.action; //@04D
            }
         }
      }
   }
}

// shows the submenu for a menu item
// method called by an event handler (onclick for anchor tag)
function menuItemShowSubmenu(evt) {
   if ( visibleMenu_ != null ) {
      var item = visibleMenu_.getMenuItem( evt.target );
      if ( item != null && item.isEnabled ) {
         var menu = item.submenu;
         if ( menu != null ) {
            menu.show( item.anchorTag, item );
         }
      }
   }
}

// focus handler for menu item
// method called by an event handler (onfocus for anchor tag)
function menuItemFocus(evt) {
   if ( visibleMenu_ != null ) {
      var item = visibleMenu_.getMenuItem( evt.target );
      if ( item != null ) {
         // select the focused menu item
         //item.anchorTag.hideFocus = item.isEnabled;
         item.setSelected( true );
      }
   }
}

// blur handler for menu item
// method called by an event handler (onblur for anchor tag)
function menuItemBlur(evt) {
   if ( visibleMenu_ != null ) {
      var item = visibleMenu_.getMenuItem( evt.target );
      if ( item != null ) {
         /* //jcp
         if ( item.isFirst && evt.shiftKey ) {
            debug( 'blur = ' + item.text );
            // user is shift tabbing off the beginning of the menu
            // set focus on the launcher
            item.parentMenu.launcher.focus();
            // hide the menu
            item.parentMenu.hide();
         }
         */
      }
   }
}

// key press handler for menu item
// method called by an event handler (onkeydown for anchor tag)
function menuItemKeyDown(evt) {
   var item = null;
   if ( visibleMenu_ != null ) {
      item = visibleMenu_.getMenuItem( evt.target );
   }
   if ( item != null ) {
      var next = null;
      switch ( evt.keyCode ) {
      case 38: // up key
         next = item.getPrevItem();
         if ( next != null ) {
            next.anchorTag.focus();
         }
         else if ( item.parentMenu != visibleMenu_ ) {
            item.parentMenu.launcher.focus();
            item.parentMenu.hide();
         }
         else {
            visibleMenu_.launcher.focus();
            hideCurrentContextMenu( true );
         }
         break;
      case 40: // down key
         next = item.getNextItem();
         if ( next != null ) {
            next.anchorTag.focus();
         }
         else if ( item.parentMenu != visibleMenu_ ) {
            item.parentMenu.launcher.focus();
            item.parentMenu.hide();
         }
         else {
            visibleMenu_.launcher.focus();
            hideCurrentContextMenu( true );
         }
         break;
      case 39: // right key
         if ( visibleMenu_.isLTR ) {
            if ( item.submenu != null ) {
               menuItemShowSubmenu(evt);
               item.submenu.items[0].anchorTag.focus();
            }
         }
         else {
            if ( item.parentMenu != visibleMenu_ ) {
               item.parentMenu.launcher.focus();
               item.parentMenu.hide();
            }
         }
         break;
      case 37: // left key
         if ( visibleMenu_.isLTR ) {
            if ( item.parentMenu != visibleMenu_ ) {
               item.parentMenu.launcher.focus();
               item.parentMenu.hide();
            }
         }
         else {
            if ( item.submenu != null ) {
               menuItemShowSubmenu(evt);
               item.submenu.items[0].anchorTag.focus();
            }
         }
         break;
      case 9: // tab key
         visibleMenu_.launcher.focus();
         hideCurrentContextMenu( true );
         break;
      case 27: // escape key
         visibleMenu_.launcher.focus();
         hideCurrentContextMenu( true );
         break;
      case 13: // enter key
         break;
      default:
         break;
      }
   }
}

// handle mouse move for menu item
// method called by an event handler (onmousemove for item tag)
function menuItemMouseMove(evt) {
   if ( visibleMenu_ != null ) {
      var item = visibleMenu_.getMenuItem( evt.target );
      if ( item != null ) {
         if ( !item.isSelected ) {
            // set focus on the anchor and select the menu item
            item.anchorTag.focus();
         }
         if ( item.submenu != null && !item.submenu.isVisible && item.isEnabled ) {
            // show the submenu
            item.submenu.show( item.anchorTag, item );
         }
      }
   }
}

// handle mouse down event for menu item
// method called by an event handler (onmousedown for item tag)
function menuItemMouseDown(evt) {
   /* //@08D
   if ( visibleMenu_ != null ) {
      var item = visibleMenu_.getMenuItem( evt.target );
      if ( item != null ) {
         item.setSelected( true );
      }
      else { //@03A
         item = visibleMenu_.getSelectedItem();
      }
      if ( item != null && item.anchorTag != evt.target ) {
         //item.anchorTag.click();
         menuItemLaunchAction();
      }
   }
   */
   menuItemLaunchAction(); //@08A
}

document.onmousedown = hideCurrentContextMenu;

var allMenus_ = new Array();

function createContextMenu( name, isLTR, width ) {
   var menu = new UilContextMenu( name, isLTR, width );
   allMenus_[ allMenus_.length ] = menu;
   return menu;
}

function getContextMenu( name ) {
   for ( var i=0; i<allMenus_.length; i++ ) {
      if ( allMenus_[i].name == name ) {
         return allMenus_[i];
      }
   }
   return null;
}

function showContextMenu( name, isDynamic, isCacheable ) {
   contextMenuShow( name, isDynamic, isCacheable, event.target, true );
}

function showContextMenu( name, launcher ) {
   contextMenuShow( name, false, false, launcher, true );
}

function contextMenuShow( name, isDynamic, isCacheable, launcher, doLoad ) {
   debug( "***** showContextMenu: " + name )

   //  We mnight need to find a suitable launcher...
   var oldLauncher = launcher;
   while ((null != launcher) && (null == launcher.tagName)) {
      launcher = launcher.parentNode;
   }
   if (null == launcher) { //  shouldn't happen, but...
      launcher = oldLauncher;
   }

   if ( eval( isDynamic ) ) {
      debug( 'showContextMenu: dynamic=true, load=' + eval( doLoad ) + ', cache=' + eval( isCacheable ) );
      // dynamically loaded menu
      if ( eval( doLoad ) ) {
         // load the url into hidden frame
         loadDynamicMenu( name );
      }

      // clone the dynamic menu from hidden frame
      menu = getDynamicMenu( name, eval( isCacheable ) );

      if ( menu == null && top.isContextMenuManager_ != null ) {
         // menu not done loading yet
         debug( 'showContextMenu: ' + name + ' added to queue' );
         top.contextMenuManagerRequest( name, window, launcher, isCacheable );
      }
   }
   else {
      debug( 'showContextMenu: static context menu' );
      // statically defined menu
      menu = getContextMenu( name );
      if ( menu == null ) {
         menu = createContextMenu( name, 150 );
      }
   }

   if ( menu != null ) {
      hideCurrentContextMenu( true );
      menu.show( launcher );
      visibleMenu_ = menu;
   }
   else {
      debug( 'showContextMenu: ' + name + ' unavailable' );
   }
   clearMenuTimer( ); // @05
}

// method called by an event handler (onmousedown for document)
function hideCurrentContextMenu( forceHide ) {
   if ( visibleMenu_ != null && ( forceHide == true || visibleMenu_.isDismissable ) ) {
      contextMenuDismissEnable();
      if ( visibleMenu_.isVisible ) {
         visibleMenu_.hide();
      }
      if ( visibleMenu_.isDynamic && !visibleMenu_.isCacheable ) {
         uncacheContextMenu( visibleMenu_ );
      }
      visibleMenu_ = null;
   }
}

function uncacheContextMenu( menu ) {
   debug( 'uncache menu: ' + menu.name );
   // recurse
   for ( var i=0; i<menu.items.length; i++ ) {
      if ( menu.items[i].submenu != null ) {
         uncacheContextMenu( menu.items[i].submenu );
      }
   }

   // remove from all menus array
   for ( var i=0; i<allMenus_.length; i++ ) {
      if ( allMenus_[i] == menu ) {
         var temp = new Array();
         var index = 0;
         for ( var j=0; j<allMenus_.length; j++ ) {
            if ( j != i ) {
               temp[ index ] = allMenus_[ j ];
               index++;
            }
         }
         allMenus_ = temp;
         break;
      }
   }
}

function contextMenuSetIcons( transparentImage,
                              arrowDefault, arrowSelected, arrowDisabled,
                              launcherDefault, launcherSelected,
                              arrowDefaultRTL, arrowSelectedRTL, arrowDisabledRTL,
                              launcherDefaultRTL, launcherSelectedRTL ) {
   transImg_ = transparentImage;

   arrowNorm_ = arrowDefault;
   arrowSel_ = arrowSelected;
   arrowDis_ = arrowDisabled;
   launchNorm_ = launcherDefault;
   launchSel_ = launcherSelected;

   arrowNormRTL_ = arrowDefaultRTL;
   arrowSelRTL_ = arrowSelectedRTL;
   arrowDisRTL_ = arrowDisabledRTL;
   launchNormRTL_ = launcherDefaultRTL;
   launchSelRTL_ = launcherSelectedRTL;

   contextMenuPreloadImage( transImg_ );

   contextMenuPreloadImage( arrowNorm_ );
   contextMenuPreloadImage( arrowSel_ );
   contextMenuPreloadImage( arrowDis_ );
   contextMenuPreloadImage( launchNorm_ );
   contextMenuPreloadImage( launchSel_ );

   contextMenuPreloadImage( arrowNormRTL_ );
   contextMenuPreloadImage( arrowSelRTL_ );
   contextMenuPreloadImage( arrowDisRTL_ );
   contextMenuPreloadImage( launchNormRTL_ );
   contextMenuPreloadImage( launchSelRTL_ );
}

function contextMenuSetArrowIconDimensions( width, height ) {
   arrowWidth_ = width;
   arrowHeight_ = height;
}

function contextMenuPreloadImage( imgsrc ) {
   var preload = new Image();
   preload.src = imgsrc;
}

function toggleLauncherIcon( popupID, selected, isLTR ) {
   if ( selected ) {
      if ( isLTR ) {
         document.images[ popupID ].src = launchSel_;
      }
      else {
         document.images[ popupID ].src = launchSelRTL_;
      }
   }
   else {
      if ( isLTR ) {
         document.images[ popupID ].src = launchNorm_;
      }
      else {
         document.images[ popupID ].src = launchNormRTL_;
      }
   }
   return true;
}

function contextMenuSetNoActionsText( noActionsText, submenuAltText ) {
   noActionsText_ = noActionsText;
   submenuAltText_ = submenuAltText;
}

function contextMenuGetNoActionsText() {
   return noActionsText_;
}

function getWidth( tag ) {
   return tag.offsetWidth;
}

function getHeight( tag ) {
   return tag.offsetHeight;
}

function getLeft( tag, recurse ) {
   var size = 0;
   if ( tag != null ) {
      //size = parseInt( document.defaultView.getComputedStyle(tag, null).getPropertyValue("left") ); //@04D

      //@04A
      if ( recurse && tag.offsetParent != null ) {
         size += getLeft( tag.offsetParent, recurse );
      }
      if ( tag != null ) {
         size += tag.offsetLeft;
      }

   }
   return size;
}

function getTop( tag, recurse ) {
   var size = 0;
   if ( tag != null ) {
      //size = parseInt( document.defaultView.getComputedStyle(tag, null).getPropertyValue("top") ); //@04D

      //@04A
      if ( recurse && tag.offsetParent != null ) {
         size += getTop( tag.offsetParent, recurse );
      }
      if ( tag != null ) {
         size += tag.offsetTop;
      }

   }
   return size;
}

/*****************************************************
* code for dynamically loaded menus
*****************************************************/
function loadDynamicMenu( menuURL ) {
   debug( '* loadDynamicMenu: ' + menuURL );
   var menu = getContextMenu( menuURL );
   if ( menu != null ) {
      if ( menu.isVisible ) {
         // dynamic menu requested, but it's currently showing
         menu.hide();
      }
      if ( !menu.isCacheable ) {
         // make sure it's not in the cache
         uncacheContextMenu( menu );
      }
   }
   if ( getContextMenu( menuURL ) == null ) {
      if ( top.isContextMenuManager_ != null ) {
         debug( 'loadDynamicMenu: loading' );
         top.contextMenuManagerLoadDynamicMenu( menuURL );
      }
   }
}

function getDynamicMenu( menuURL, cache ) {
   debug( '* getDynamicMenu: ' + menuURL );
   var clone = getContextMenu( menuURL );
   if ( clone == null ) {
      if ( top.isContextMenuManager_ != null ) {
         if ( top.contextMenuManagerIsDynamicMenuLoaded() ) {
            var menu = top.contextMenuManagerGetDynamicMenu();
            debug( 'getDynamicMenu: fetched menu from other frame' );
            clone = cloneMenu( menu, menuURL, cache );
            if ( clone.items.length == 0 ) {
               contextMenuSetNoActionsText( top.contextMenuManagerGetNoActionsText() );
            }
         }
         else {
            debug( 'getDynamicMenu: menu not loaded' );
         }
      }
      else {
         debug( 'getDynamicMenu: menu manager not present' );
      }
   }
   else {
      debug( 'getDynamicMenu: menu previously loaded' );
   }
   return clone;
}

function cloneMenu( menu, name, cache ) {
   var clone = getContextMenu( name );
   if ( clone == null ) {
      if ( menu != null ) {
         clone = createContextMenu( name, menu.width );
      }
      else {
         clone = createContextMenu( name, 150 );
      }
      clone.isDynamic = true;
      clone.isCacheable = cache;
      if ( menu != null ) {
         for ( var i=0; i<menu.items.length; i++ ) {
            clone.add( cloneMenuItem( menu.items[i], name + "_sub" + i, cache ) );
         }
      }
   }
   return clone;
}

function cloneMenuItem( item, submenuName, cache ) {
   var submenu = null;
   if ( item.submenu != null ) {
      submenu = cloneMenu( item.submenu, submenuName, cache );
   }
   var clone = new UilMenuItem( item.text, item.isEnabled, item.action, item.clientAction, submenu, item.icon,null,'lwpMenuItem','lwpSelectedMenuItem' );
   clone.isEnabled = item.isEnabled;
   clone.isSelected = item.isSelected;
   clone.isSeparator = item.isSeparator;
   return clone;
}

/* (C) Copyright IBM Corp. 2002, 2006  All Rights Reserved           */

/**
 * This is the JS file semantic tagging with people
 */


	if (typeof wps_loggedInFuncs == "undefined")
		wps_loggedInFuncs = new Object();
	if (typeof wps_loggedOutFuncs == "undefined")
		wps_loggedOutFuncs = new Object();
	if (typeof wps_userStatusFuncs == "undefined")
		wps_userStatusFuncs = new Object();
	if (typeof wps_userEnteredFuncs == "undefined")
		wps_userEnteredFuncs = new Object();
	if (typeof wps_userLeftFuncs == "undefined")
		wps_userLeftFuncs = new Object();

	var pa_addtostlist_window_opener = null;
	var stLinksAddToContactListFailedErrorMsg = null;
	var linkId = null;
	var defaultDynamicPersonTagURL = null;	
	var registeredInstantMessagingEvents = false;	
	var personjsSTLinksWatchNames_true = "";
	var personjsSTLinksWatchNames_false = "";
	var persontag_isLoggedIn = false;
	var persontag_awarenessversion = ""; // indicates whether we are using classic ST or SIP IM
	var persontag_resolveNames = null;   // indicates whether ST should resolve the names that we give it
	
	function setDefaultDynamicPersonTagUrl(url) {
		defaultDynamicPersonTagURL = url;
	}

	var MS_XMLHTTP_TYPES = new Array(
		"MSXML2.XMLHTTP.4.0",
		"MSXML2.XMLHTTP.3.0",
		"MSXML2.XMLHTTP",
		"Microsoft.XMLHTTP"
		);

	// Use an IFrame instead of xmlhttprequest. Bugs in IE and Firefox prevent
	// the browser cache from working w/ xmlhttprequest. Note that caching requires
	// a GET, not a POST, so if you need to POST to something 
	// (ex: peopletag portlet) you should use the xmlhttprequest version
	function getDataFromServerIFrame(url, iframeid, callback)
	{
		var iframe = document.getElementById(iframeid);
		if (iframe != null)
		{
			var myurl = document.location.protocol + "//" + document.location.host + url;
			iframe.src = myurl;

			if (isGecko())
			{
				// the iframe.onload property is only respected in
				// Firefox
				iframe.onload = function()
				{
					// don't just access the innerHTML in Moz/FF
					// it has some extra carriage returns for formatting
					var data = iframe.contentWindow.document.body.childNodes[0].nodeValue;

					// FF adds these weird extraneous carriage returns to the content
					data = data.replace(/'/g, "\'");
					data = data.replace(/"/g, '\"');
					eval(callback + "(data);");
				}
			}
			else
			{
				waitForIFrameCallback(iframeid, callback);
			}
		}
	}

	// Args have to be strings for setTimeout loop
	// This method is only called in IE
	function waitForIFrameCallback(iframeid, callback)
	{
		// document.readyState is an IE only property that gets set to
		// "complete" when the doc is loaded. 
		var iframe = document.frames[iframeid];

		if (!isGecko() && 
		    iframe.document.readyState != "complete")
		{
			setTimeout("waitForIFrameCallback('" + iframeid + "','" + callback + "');", 500);
		}
		else
		{
			// note that the replacing done here for ' and " is slightly
			// different than the replacements done for Moz
			var data = iframe.document.body.innerHTML
			data = data.replace(/'/g, "\\'");
			data = data.replace(/"/g, '\\"');
			eval(callback + "(\'" + data + "\');");
		}
	}
		
	// Returns the raw string from the server.  Caller has to extract the desired data from it.
	function getDataFromServer(url)
	{

		var maindata = "";
		var myurl = document.location.protocol + "//" + document.location.host + url;

		var xmlhttp = null;
		
		// Mozilla
		if (window.XMLHttpRequest != null) {
			xmlhttp = new XMLHttpRequest();
		}
		// IE
		else if (window.ActiveXObject != null)
		{
			var success = false;
			for (var i = 0; i < MS_XMLHTTP_TYPES.length && !success; i++) {
				try {
					xmlhttp = new ActiveXObject(MS_XMLHTTP_TYPES[i]);
					success = true;
				}
				catch (ex) {}
			}
			if (!success)
				throw "Error in dynamic person tag.  Unable to create an XMLHTTP object.";
		}
				
		var spliturl = myurl.split("?");

		xmlhttp.open("POST", spliturl[0], false);
		xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
		xmlhttp.send(spliturl[1]);
		// Check the HTTP response status code for "OK".
		if (xmlhttp.status == 200)
		{
			maindata = xmlhttp.responseText;
		}
		else if (xmlhttp.status == 304)
		{
			// should use cached copy - this doesn't seem to work, the
			// xmlhttprequest doesn't contain the content
			
			// TODO - do what here? (bug in browser...)
		}
		else
		{
			throw "HTTP request for \"" + spliturl[0] + "\" failed with status \"" +
				xmlhttp.status + " " + xmlhttp.statusText + "\"";
		}

		return maindata;
	}	
	
	function invokePersonTagMenu(counter, target, isSubMenu)
	{
		// get data for the user
		var menuDataTagObject = awarenessController.getMenuDataTagObject(counter);

		var menuData = menuDataTagObject.getMenuData();
		var displayName = menuDataTagObject.getDisplayName();

		if (menuData == null)
		{
			var userId = menuDataTagObject.getUserId();
			var userIdType = menuDataTagObject.getUserIdType();
			var contextArray = menuDataTagObject.getContextArray();		
			var theHTML = "";
			var index;

			var url = defaultDynamicPersonTagURL;
			
			// Must have a userId		
			if (typeof userId == "undefined" || userId == null || userId == "") {
				return;
			}

			// Guess the type if not given
			if (typeof userIdType == "undefined" || userIdType == null || userIdType == "") {
				index = userId.indexOf("@");
				if (index >= 0)
					userIdType = "EMAIL";
				else
					userIdType = "LDAPDN";
			}

			// Create the complete URL to the hidden portlet

			var params = "value=" + URLEncoder(userId) + "&valueType=" + userIdType +
				"&displayName=" + URLEncoder(displayName);


			// indicate we are retrieving just data
			params += "&getMenuData=TRUE";

			// Add the other params to the URL
			if (typeof contextArray != "undefined" && contextArray != null && contextArray != "") {
				var contextValue = "";
				for (var item in contextArray) {
					if (contextArray[item] != "undefined" && contextArray[item] != null && contextArray[item] != "") {
						contextValue += URLEncoder(item) + URLEncoder(",") + URLEncoder(contextArray[item]) + URLEncoder(";");
					}
				}
				params += "&contextArray=" + contextValue;
			}
		
			//strip off the last bit, since it interferes with the backend getting of params.  ie. #7_0_DO
			index = url.lastIndexOf("#");
			if (index >= 0) {
				url = url.substr(0, index);
			}

			// This means that they passed in a url created by the urlGeneration tag
			if (url.indexOf("markerstart") >= 0) {	
				url = url.replace(/markerstart=markerend/g, params);
			}
			else {
				url += "?" + params;
			}

			var maindata = getDataFromServer(url);

			// Find the section in the string where the Person Tag output lives
			index = maindata.indexOf("<dynamicpersontagdata>");
			var start = index + "<dynamicpersontagdata>".length;
			maindata = maindata.substr(start, maindata.indexOf("</dynamicpersontagdata>")-start);
			menuData = maindata;
			menuDataTagObject.setMenuData(menuData);
		}
		
		eval(menuData);
		var IMLogin = menuDataTagObject.getIMLogin();
		var businessCard = new BusinessCard();
		
		businessCard.setVariables(displayName,cardData);
		var menu = buildMenu(IMLogin,menuList,businessCard, target, isSubMenu);		
		return menu;
	}
	//	
	// javascript:invokeDynamicPersonTag("CN=wpsadmin,O=eCAT", "LDAPDN", "wpsadmin")
	//
	function invokeDynamicPersonTag (userId, userIdType, displayName, IMLogin, contextArray) {
		// Must have a userId		
		if (typeof userId == "undefined" || userId == null || userId == "") {
			return;
		}
		
		// Guess the type if not given
		if (typeof userIdType == "undefined" || userIdType == null || userIdType == "") {
			index = userId.indexOf("@");
			if (index >= 0)
				userIdType = "EMAIL";
			else
				userIdType = "LDAPDN";
		}
		
		//Defect172297_172640: New PersonTag Work: VCard Thing: parseDom is called in PeopleTree.JS
			var theHTML = "";
			var index;
			var url = defaultDynamicPersonTagURL;

			// Create the complete URL to the hidden portlet

			var params = "value=" + URLEncoder(userId) + "&valueType=" + userIdType +
				"&displayName=" + URLEncoder(displayName);


			// Add the other params to the URL
			if (typeof contextArray != "undefined" && contextArray != null && contextArray != "") {
				var contextValue = "";
				for (var item in contextArray) {
					if (contextArray[item] != "undefined" && contextArray[item] != null && contextArray[item] != "") {
						contextValue += URLEncoder(item) + URLEncoder(",") + URLEncoder(contextArray[item]) + URLEncoder(";");
					}
				}
				params += "&contextArray=" + contextValue;
			}

			//strip off the last bit, since it interferes with the backend getting of params.  ie. #7_0_DO
			index = url.lastIndexOf("#");
			if (index >= 0) {
				url = url.substr(0, index);
			}

			// This means that they passed in a url created by the urlGeneration tag
			if (url.indexOf("markerstart") >= 0) {	
				url = url.replace(/markerstart=markerend/g, params);
			}
			else {
				url += "?" + params;
			}

			var maindata = getDataFromServer(url);
			// Find the section in the string where the Person Tag output lives
			index = maindata.indexOf("<dynamicpersontagdata>");
			var start = index + "<dynamicpersontagdata>".length;
			maindata = maindata.substr(start, maindata.indexOf("</dynamicpersontagdata>")-start);	
			//alert("mainData "+maindata);
			return maindata;
		}
	

	// NEEDSWORK - better error-checking...assuming 3 identical length arrays for now
	// NEEDSWORK - put XML data island code into a common function
	function invokeArrayOfDynamicPersonTags(userIds, userIdTypes, displayNames, IMLogins, contextArray) {
	
						
		// Must have a userIds Array
		if (typeof userIds != "object" || userIds == null || userIds == "") {
			return;
		}

		var writeOutPersonCallArray = new Array();
							
		for (var x=0; x < userIds.length; x++) {					
			
				writeOutPersonCallArray[x]=invokeDynamicPersonTag(userIds[x],userIdTypes[x],displayNames[x],IMLogins[x], contextArray);
			
		}

		return(writeOutPersonCallArray);
	}		
		
		
		function addPeopleMenuMoreMenuItems(menu) {			
			if (typeof peoplemenu_more != "undefined") {
			    var items = peoplemenu_more.items;
			    var actions = peoplemenu_more.actions;
			    for (i = 0; i < items.length; i++) {
			        if (actions[i].toLowerCase().indexOf("javascript:") == -1) {
			             menu.add( new UilMenuItem(items[i], true, '', "javascript:" + actions[i], null, null, false,'lwpMenuItem', 'lwpSelectedMenuItem') );
			        } 
			        else {
			             menu.add( new UilMenuItem(items[i], true, '', actions[i], null, null, false,'lwpMenuItem', 'lwpSelectedMenuItem') );
			        }
			    }
			}
		}

	function registerInstantMessagingEvents() {
		if (persontag_awarenessversion == "classic") {
			if (typeof STLinksAddListener == "function") {
				STLinksAddListener("STLinksLoggedIn", "STLinksLoggedIn_PersonJS");
				STLinksAddListener("STLinksLoggedOut", "STLinksLoggedOut_PersonJS");
				STLinksAddListener("STLinksUserStatusChanged", "STLinksUserStatusChanged_PersonJS");
			}
			else
			{
				// Avoids collision with the Sametime Contact List and WhoIsHere portlet's STLinksLoggedIn and 
				// STLinksUserStatusChanged functions
				wps_loggedInFuncs["STLinksLoggedIn_PersonJS"] = 0;
				wps_loggedOutFuncs["STLinksLoggedOut_PersonJS"] = 0;
				wps_userStatusFuncs["STLinksUserStatusChanged_PersonJS"] = 0;
			}
		}
		else {
			if (typeof PeopleLinksAddListener == "function") {
				PeopleLinksAddListener("PeopleLinksLoggedIn", "PeopleLinksLoggedIn_PersonJS");
				PeopleLinksAddListener("PeopleLinksLoggedOut", "PeopleLinksLoggedOut_PersonJS");
				PeopleLinksAddListener("PeopleLinksUserStatusChanged", "PeopleLinksUserStatusChanged_PersonJS");
				PeopleLinksAddListener("PeopleLinksUsersStatusChanged", "PeopleLinksUsersStatusChanged_PersonJS");
			}
		}
		registeredInstantMessagingEvents = true;
	}
		
		function STLinksLoggedIn(userId,displayName)
		{
			for (var funcName in wps_loggedInFuncs) 
				eval(funcName + '("' + userId + '","' + displayName + '")');
		}
		function STLinksLoggedOut(reason)
		{
			for (var funcName in wps_loggedOutFuncs) 
				eval(funcName + '("' + reason + '")');
		}
		function STLinksUserStatusChanged(userId,displayName,status,statusMsg,groupName)
		{
			for (var funcName in wps_userStatusFuncs) {
				if (funcName == "STLinksUserStatusChanged_PersonJS")
					eval(funcName + '("' + userId + '","' + displayName + '",' + status + ',"' + statusMsg + '","' + groupName + '")');
				else
					eval(funcName + '("' + escapeForJavaScript(userId) + '","' + displayName + '",' + status + ',"' + statusMsg + '","' + groupName + '")');
			}
		}		

		function STLinksUserEnteredPlace(id,name,placeId)
		{
			for (var funcName in wps_userEnteredFuncs) 
				eval(funcName + '("' + id + '","' + name + '","' + placeId + '")');
		}		

		function STLinksUserLeftPlace(id,name,placeId)
		{
			for (var funcName in wps_userLeftFuncs) 
				eval(funcName + '("' + id + '","' + name + '","' + placeId + '")');
		}		
		
		function PeopleLinksUsersStatusChanged_PersonJS( usersInfo, groupId ) {
			if (typeof (usersInfo) == "object")
			{
				if (typeof (groupId) == "undefined" )
					groupId = "";
		
				for ( var i = 0 ; i < usersInfo.length ; i++ )
				{
					PeopleLinksUserStatusChanged_PersonJS (usersInfo[i].usermail,
								usersInfo[i].usermail,
								usersInfo[i].userstatustype, 
								usersInfo[i].userstatusdescr,
								groupId);
				}
			}
		}
		
		function PeopleLinksUserStatusChanged_PersonJS( userid, displayName,status, statusmessage, groupId ) {
			status = status + "";
			awarenessController.updatePersonStatus(userid,status,statusmessage);
		}
		
		// this is the sametime controlling object and the funtion that st calls when events occurs
		function STLinksUserStatusChanged_PersonJS(userid, displayname, status, statusmessage, groupName){
			status = status + "";	//convert to a string, in case we are passed a number rather than a string
			awarenessController.updatePersonStatus(userid,status,statusmessage);
		}
	
		function STLinksLoggedOut_PersonJS(reason)
		{
			persontag_isLoggedIn = false;
			if (typeof awarenessController != "undefined" && awarenessController != null) {
				awarenessController.loggedOut();
			}
		}

		function PeopleLinksLoggedOut_PersonJS(userEmail, reason)
		{
			persontag_isLoggedIn = false;
			if (typeof awarenessController != "undefined" && awarenessController != null) {
				awarenessController.loggedOut();
			}
		}

		// Note:  assumes that the STLinks applet is written out in the PeopleEndTag at the
		//		bottom of the HTML page!  This way we avoid race conditions.
		function STLinksLoggedIn_PersonJS(myUserId, myUserName)
		{
			// addresses performance SPR YSAI5ZAQBP - Multiple "WatchUsers" requests from the person tag
			if (persontag_isLoggedIn == true)
				return;

			persontag_isLoggedIn = true;

			// register the names again with ST, in case we get logged out then back in
			sendNamesToInstantMessaging(personjsSTLinksWatchNames_true, true, true);
			sendNamesToInstantMessaging(personjsSTLinksWatchNames_false, true, false);
		}

		function PeopleLinksLoggedIn_PersonJS(myUserEmail)
		{
			// addresses performance SPR YSAI5ZAQBP - Multiple "WatchUsers" requests from the person tag
			if (persontag_isLoggedIn == true)
				return;

			persontag_isLoggedIn = true;

			// register the names again with ST, in case we get logged out then back in
			sendNamesToInstantMessaging(personjsSTLinksWatchNames_true, true, true);
			sendNamesToInstantMessaging(personjsSTLinksWatchNames_false, true, false);
		}

		function sendNamesToInstantMessaging(names, force, resolveNames) {
			if (names == null || names.length < 1)
				return;

			// Only call watchUsers if we are logged in!
			// Used force because the ordering of event listeners may not be predictable, and could influence whether
			// ll_loggedIn or g_fIsLoggedIn has been set yet.
			if (persontag_awarenessversion == "classic") {
				if (force || (typeof ll_loggedIn != "undefined" && ll_loggedIn == true)) {
					if (typeof STLinksWatchUsers != "undefined") {
						STLinksWatchUsers(names, resolveNames);
					}
				}
			}
			else {
				if (force || (typeof g_fIsLoggedIn != "undefined" && g_fIsLoggedIn == true)) {
					if (typeof PeopleLinksWatchUsers != "undefined") {
						PeopleLinksWatchUsers(names);
					}
				}
			}
		}


function hexString (num, wid)
{
   var str = "";
   var digit = 0;
   var hexDigits = "0123456789ABCDEF";

   while (num > 0)
   {
      digit = num % 16;
      str = hexDigits.charAt(digit) + str;
      num >>= 4;
   }

   while (str.length < wid)
      str = "0" + str;

   return str;
}

function convToUtf8 (unival)
{
	var utf8 = "";

	if (unival <= 0x7f)
	{
		if( unival == 0x20 ) {
			utf8 = "+";
		}
		else if( (unival >=0x30 && unival <=0x39) || 
				 (unival >= 0x41 && unival <= 0x5a) ||
				 (unival >= 0x61 && unival <= 0x7a ) || 
				 (unival == 0x2A) || 
				 (unival == 0x2D ) ||
				 (unival == 0x2E ) ||
				 (unival == 0x5F ) ||
				 (unival == 0x3A) || 
				 (unival == 0x2F ) 				 
				 ) {
			utf8 = String.fromCharCode(unival);
		}
		else {
			utf8 = "%" + hexString( unival, 2 );
		}
	}

	else if (unival <= 0x07FF)
	{
	   utf8 = "%" + hexString(0xC0 + (unival >> 6), 2);
	   utf8 += "%" + hexString(0x80 + (unival & 0x3F), 2);
	}

	else if (unival <= 0xFFFF)
	{
	   utf8 = "%" + hexString(0xE0 + (unival >> 12), 2);
	   utf8 += "%" + hexString(0x80 + ((unival >> 6) & 0x3F), 2);
	   utf8 += "%" + hexString(0x80 + (unival & 0x3F), 2);
	}

	else if (unival <= 0x10FFFF)
	{
	   utf8 = "%" + hexString(0xF0 + (unival >> 18), 2);
	   utf8 += "%" + hexString(0x80 + ((unival >> 12) & 0x3F), 2);
	   utf8 += "%" + hexString(0x80 + ((unival >> 6) & 0x3F), 2);
	   utf8 += "%" + hexString(0x80 + (unival & 0x3F), 2);
	}

	return utf8;
}        

function URLEncoder( sstr ) {
	var dstr = "";
	var value = 0;

	for(var ix = 0; ix < sstr.length; ix++ )
	{
		value = sstr.charCodeAt(ix);
		dstr += convToUtf8( value );
	}
	return dstr;
}
	
	function handleChatPersonMenuItem(email){
		if (typeof STLinksCreateIM == "function"){
			STLinksCreateIM(escapeSlashComma(email));
		}
	}
	
	function portalShowProfile(profileURL){
		window.open(profileURL);
	}
	
	function portalFindDocs(kmapURL){
		window.open(kmapURL);
	}

	function portalShowAddToContactsUI(theUrl){
		pa_addtostlist_window_opener = window;
		window.open(theUrl, "pa_addtostlist", 
		"resizable=yes,width=400,height=500,toolbar=no,status=no,menubar=no", true);
	}
	
	function setSTLinksAddToContactListFailedErrorMsg(msgStr)
	{
		stLinksAddToContactListFailedErrorMsg = msgStr;
	}
	
	// This has to be in person.js because if it's in the Add to Sametime List portlet window (AddToSTList.jsp)
	// when the window closes, then this function won't be there to receive the event.  So we have to receive
	// the event in the opening window (usually the main portal window)
	function STLinksAddToContactListFailed(reason)
	{
		if (pa_addtostlist_window_opener != null && stLinksAddToContactListFailedErrorMsg != null)
		{
			alert(stLinksAddToContactListFailedErrorMsg + ": " + reason);
		}
	}


// we initialize a new instance of the person controller
var awarenessController = new AwarenessController();


// This class represents the business card entries used in the person tag
// stores displayName, and an array of data associated with the business
// card section. 
// also has useful helper functions to see if person has these attributes
function BusinessCard(){
		
	var displayName = '';
	var cardData = '';
	
	function setVariables(displayName, cardData) {
		this.displayName = displayName;
		this.cardData = cardData;
	}

	function getDisplayName(){
		return this.displayName;
	}
	
	function getCardData(){
		return this.cardData;
	}
	
	

	function hasCardData() {
		if (this.cardData == '') {
			return false;
		} else {
			return true;
		}
	}

	
	this.setVariables = setVariables;
	
	this.getDisplayName = getDisplayName;
	this.getCardData = getCardData;

	this.hasCardData = hasCardData;
}

// this class represents an individual menuitem used in the person tag
// it stores the menuitem's displayName, uri, and wether or not the menu item is sensitve to a 
// person's online status
function MenuItem() {
	var displayName = '';
	var uri = '';
	var awareness = false; // variable to indicate if menu item is sensitive to someone's online status

	function setVariables(displayName, uri, awareness) {
		this.displayName = displayName;
		this.uri = uri;
		this.awareness = awareness;
	}

	function getDisplayName() {
		return this.displayName;
	}

	function getURI() {
		return this.uri;
	}

	function isAwarenessSensitive() {
		return this.awareness;
	}

	this.setVariables = setVariables;
	
	this.getDisplayName = getDisplayName;
	this.getURI = getURI;
	
	this.isAwarenessSensitive = isAwarenessSensitive;		
}

// this function generates the WCL popup menu code based upon the businesscard entries, and
// menus stored in the menu list
function buildMenu(userId, menuList, businessCard, target, isSubMenu) {
	
	// retrieve the person's business card object
	//var businessCard = awarenessController.getBusinessCardForUser(userId);
	

	// check to see if user is really online (active or away)
	var status = awarenessController.getOnlineStatusForUser(userId);
	
	// generates a new menuID each time so we don't get cached results
	var menuID = userId + awarenessController.getRandomSeed(); 

	// gets BIDI info from awarenessController 
	var isLTR = awarenessController.getLTR();

	// creates the actual wcl menu	
	var menu = createContextMenu( menuID, isLTR);
	
	// used to determine if we should show a sperator between the business card entries and 
	// the menuItems
	var showEndSeperator = false;
						
	// adds the displayName entry for the menu, uses special style for first menu entry		
	menu.add( new UilMenuItem(businessCard.getDisplayName(), false, true, null, 
		null, null, true, 'lwpMenuHeader','lwpSelectedMenuItem'));

	var menuItem = new UilMenuItem('', false, null, null, null, null, false, 'lwpMenuHeader','lwpSelectedMenuItem');
	menu.add(menuItem);

	// if the user has has business card data, we iterate through the values, and
	// display it in the business card section of the menu
	if (businessCard.hasCardData()) {
		var cardData = businessCard.getCardData();
		var length = cardData.length;
		for (var i=0; i < length; i++) {
			if (cardData[i] != "") {
				showEndSeperator = true;
				menu.add( new UilMenuItem(cardData[i], false, null, null, 
				null, null, false, 'lwpMenuHeader','lwpSelectedMenuItem'));	
			}
		}
	}	
	
	// we will attempt to get their status message from sametime
	var statusMessage = awarenessController.getStatusMessageForUser(userId);
	
	// if there are actual entries in the business card section, we will add another seperator
	// to seperate it from the user status
	if (showEndSeperator && statusMessage.length > 0) {
		menu.add( new UilMenuItem('', false, null, null, 
			null, null, false, 'lwpMenuHeader','lwpSelectedMenuItem'));	
	}
	
	if (statusMessage.length > 0)
	{
		menu.add( new UilMenuItem(statusMessage, false, null, null, 
			null, null, false, 'lwpMenuHeader','lwpSelectedMenuItem')); 
	}
	
	menu.addSeparator();

	// iterate through the menu items and builds the appropriate WCL menu items for them
	for (var i=0;i < menuList.length;i++){
		if (menuList[i].isAwarenessSensitive()) {
			if (status) {
				menu.add( new UilMenuItem(menuList[i].getDisplayName(), true, '', 
					menuList[i].getURI(), null, null, false,'lwpMenuItem', 'lwpSelectedMenuItem'));
			}
		} else {					
			menu.add( new UilMenuItem(menuList[i].getDisplayName(), true, '', 
				menuList[i].getURI(), null, null, false,'lwpMenuItem', 'lwpSelectedMenuItem'));
		}
	}

	// in case users have added more menu items, we must add them to the list of official menu items
	if (typeof addPeopleMenuMoreMenuItems != "undefined") {
		addPeopleMenuMoreMenuItems(menu);
	}

	if (isSubMenu)
	{
		// show it by hand rather than using contextMenuShow because we don't
		// want to hide the current context menu necessarily (if we launch this
		// as a sub menu). The document.onmousedown handler will hide any other menus
		// if appropriate
		menu.show(target);
	}
	else
	{
		showContextMenu(menuID, target); 
	}

	return menu;
}

// function for taking an id of an <img> html tag and converting it to the down arrow gif
function swapDownImg(id) {
	var iconDirectory = awarenessController.getIconDirectory(); 
	iconDirectory += 'MenuDropdown.gif';
	var elem = document.getElementById(id);
	if (elem != null) 
		elem.src = iconDirectory;
}

// function for taking an id of an <img> html tag and converting it to the clear image gif
function swapClearImg(id) {
	var iconDirectory = awarenessController.getIconDirectory(); 
	iconDirectory += 'ClearPixel.gif';
	var elem = document.getElementById(id);
	if (elem != null) 
		elem.src = iconDirectory;
}

// This class is a collection of helper functions to manage all the PersonObjects on the page
function AwarenessController() {
	var randomSeed = 1; // seed used to help make WCL menus generate a random menuID
	var internalCounter = 1; // counter used to ensure every link has a unique id		
	var iconDirectory = '/wps/images/icons/'; // portal icon directory
	var isLTR = true; // boolean var
	var activeMsg = ''; // active message
	var awayMsg = ''; // away message
	var dndMsg = '';	// do not disturb message
	var unAvailableMsg = ''; // unavailable message
	var offlineMsg = ''; // offline message
	var clickForOptions = ''; // message for click for options
	var personArray = new Object(); // associative array which stores all the PersonObjects
	var menuDataTagObjectArray = new Object(); // associative array which stores all the menuDataTagObjects


	// This is a helper function that registers a person. The PersonObject that it creates is not
	// associated with anything, but it can be read/polled by code that needs to know the status
	// of a given person
	function registerPersonNoLinks(displayName, IMLogin)
	{
		// case where we have a new user
		if (personArray[IMLogin] == null)
		{
			//register person with STlinks
			registerSTWatch(IMLogin);

			// create a new PersonObject and populate it's variables
			var person = new PersonObject();
			person.setStatusMessage(this.unAvailableMsg);

			var businessCard = new BusinessCard();
			businessCard.setVariables(displayName, null);
			person.setBusinessCard(businessCard);

			// add PersonObject to internal PersonObjects array
			personArray[IMLogin] = person;
		}
	}
	
	// This is a helper function which creates a new PersonObject for a user
	// as well as register thems with ST links
	function registerPerson(userId,userIdType, displayName, IMLogin, counter,leftAligned,contextArray,resolveName) {
		if (!registeredInstantMessagingEvents) {
			registerInstantMessagingEvents();
		}

		// we derive the various IDs from the passed in counter
		var linkID = "menu-link-" + counter;
		var statusImgID = "status-img-" + counter;

		// in case where the user doesn't have an email, we use the displayName as
		// the IMLogin
		if (IMLogin == '') {
			IMLogin = displayName;
		}

		// store data we will later need to access the menu data tag
		var menuDataTag = new MenuDataTagObject();

		//set ther UserId, userIdType, displayName,contextArray info
		menuDataTag.setIMLogin(IMLogin);
		menuDataTag.setUserId(userId);
		menuDataTag.setUserIdType(userIdType);
		menuDataTag.setDisplayName(displayName);			
		menuDataTag.setContextArray(contextArray);
		
		menuDataTagObjectArray[counter] = menuDataTag;
		
		// case where we have a new user
		if (personArray[IMLogin] == null) {
	
			//register person with STlinks
			registerSTWatch(IMLogin, resolveName);

			// create a new PersonObject and populate it's variables
			var person = new PersonObject();
			person.setStatusMessage(this.unAvailableMsg);
			person.setLinkID(linkID);
			if (leftAligned == true) {
				person.setLeftAlignedImageID(statusImgID);
			} else {
				person.setStatusImageID(statusImgID);
			}

			
			var businessCard = new BusinessCard();
			//businessCard.setVariables(displayName, cardData);
			businessCard.setVariables(displayName, null);
			person.setBusinessCard(businessCard);

			// add PersonObject to internal PersonObjects array
			personArray[IMLogin] = person;			
		} else {

			// case where user is already in the PersonObjects array
			// we simply add the new ids associated with the user
			personArray[IMLogin].setLinkID(linkID);
			if (leftAligned == true) {
				personArray[IMLogin].setLeftAlignedImageID(statusImgID);
			} else {
				personArray[IMLogin].setStatusImageID(statusImgID);
			}

			// this will never work - the html isn't on the page yet
			// so there's nothing to update
			// personArray[IMLogin].update();
		}
	}

	// this function is called by ST links to update us on the status of the user
	function updatePersonStatus(userId, status, statusMessage) {

		//ignore case of passed-in name
		var key;
		userId = userId.toLowerCase();
		for (key in personArray) {
			if (key.toLowerCase() == userId) {
				userId = key;
			}
		}

		// we retrieve the PersonObject associated with the user
		var person = personArray[userId];

		// case where user does not exist on the system
		if (typeof person == "undefined" || person == null)
			return;	
		
		// situation in which no status message is returned to us from ST Links (occurs when
		// user has not changed their default message)
		// in this case we derive what the default status message should be for any given status
		if (statusMessage == null || statusMessage.length < 1) {
			switch (status) {
				case "0":        //offline 								
/***** Added by Anjali Sharma on 6-02-2007 for SPR #HYZG6XQLRU. %s was not getting recognized when locale were changing so it was not able to replace %s with user name & so the defect.Below code takes the next character **********/
					var len=this.offlineMsg.indexOf('%');
					var charNextToPerc=this.offlineMsg.charAt(len+1);
					var str3    = "" + "%"+ charNextToPerc;
					statusMessage=this.offlineMsg.replace(str3, this.getDisplayNameForUser(userId));
	    				//statusMessage = this.offlineMsg.replace(/%s/gi, this.getDisplayNameForUser(userId));
					break;
				case "32":        //active
    					statusMessage = this.activeMsg;
					break;
				case "64":        //not using computer
    					statusMessage = this.awayMsg;
					break;
				case "96":        //away
    					statusMessage = this.awayMsg;
					break;
				case "128":        //do not disturb
    					statusMessage = this.dndMsg;
					break;
				case "544":        //mobile active
    					statusMessage = this.activeMsg;
					break;
				case "608":        //mobile away
    					statusMessage = this.awayMsg;
					break;
				default:
/***** Added by Anjali Sharma on 6-02-2007 for SPR #HYZG6XQLRU. %s was not getting recognized when locale were changing so it was not able to replace %s with user name & so the defect.Below code takes the next character **********/
					var len=this.offlineMsg.indexOf('%');
					var charNextToPerc=this.offlineMsg.charAt(len+1);
					var str3    = "" + "%"+ charNextToPerc;
					statusMessage=this.offlineMsg.replace(str3, this.getDisplayNameForUser(userId));
					//statusMessage = this.offlineMsg.replace(/%s/gi, this.getDisplayNameForUser(userId));
				}
		}	

		// update the PersonObject with the new info
		person.setStatus(status);
		person.setStatusMessage(statusMessage);

		// tell the PersonObject to update all the person tag links on the
		// page with the appropriate information
		person.update();
	}
	
	// this function is called by us to update the person link with the right default status messages.
	function updatePersonLink(userId) {

		//ignore case of passed-in name
		var key;
		userId = userId.toLowerCase();
		for (key in personArray) {
			if (key.toLowerCase() == userId) {
				userId = key;
			}
		}

		// we retrieve the PersonObject associated with the user
		var person = personArray[userId];

		// case where user does not exist on the system
		if (typeof person == "undefined" || person == null)
			return;	
		
		// tell the PersonObject to update all the person tag links on the
		// page with the appropriate information
		person.update();
	}

	// Update each person in the UI to reflect offline status
	function loggedOut() {
		var key;
		for (key in personArray) {
				updatePersonStatus(key, "", this.unAvailableMsg);
		}
	}

	// sets the various status messages	
	function setMessages(activeMsg, awayMsg, dndMsg, unAvailableMsg, offlineMsg, clickForOptions) {
		this.activeMsg = activeMsg;
		this.awayMsg = awayMsg;
		this.dndMsg = dndMsg;
		this.unAvailableMsg = unAvailableMsg;
		this.offlineMsg = offlineMsg;
		this.clickForOptions = clickForOptions;
	}
 
	// sets the BIDI values
	function setLTR(LTR) {
		isLTR = LTR;
	}

	// sets the WPS icon directory
	function setIconDirectory(iconDirectory) {
		this.iconDirectory = iconDirectory;
	}

	// dummy function for the old dynamic person tag
	function setDynamicPTagServletName(name) {
	}

	// private method that registers user watches with STLinks
	function registerSTWatch(userName, resolveName){
		if (userName == null || userName.length < 1)
			return;
		userName = escapeSlashComma(userName);
		
		// Save the names in case we get logged out, then logged back in
		
		// We also need to keep track of which names are and are not supposed to be resolved by Sametime
	
		// If we have an explicit setting from a Portlet, ie. from the People Finder portlet, then use it.
		// Note: it is set in the portlet config mode by adding this param, CS_SERVER_SAMETIME_1.resolveNames
		// and setting the value to be true or false.
		if (typeof resolveName != "undefined" && resolveName != null)
		{
			if (resolveName == false)
			{
				personjsSTLinksWatchNames_false += (personjsSTLinksWatchNames_false.length > 0 ? ";" : "") + userName;
				sendNamesToInstantMessaging(userName, false, false);
				return;
			}
		}
		// If we have the global setting from CSEnvironment.properties, CS_SERVER_SAMETIME_1.resolveNames
		// then use it.
		else if (persontag_resolveNames != null)
		{
			if (persontag_resolveNames == false)
			{
				personjsSTLinksWatchNames_false += (personjsSTLinksWatchNames_false.length > 0 ? ";" : "") + userName;
				sendNamesToInstantMessaging(userName, false, false);
				return;
			}
		}
		
		// Otherwise use true as the default
		personjsSTLinksWatchNames_true += (personjsSTLinksWatchNames_true.length > 0 ? ";" : "") + userName;
		sendNamesToInstantMessaging(userName, false, true);

	}

	// Returns the person object associated with the userId
	function getPersonObject(userId) {
		return personArray[userId];
	}

	// Returns the menuDataTagObject associated with the counter
	function getMenuDataTagObject(counter) {
		return menuDataTagObjectArray[counter];
	}

	// retrieves the string click for options
	function getClickForOptions() {
		return this.clickForOptions;
	}

	// this function returns the counter to ensure every link has a unique id
	function getCounter() {
		return internalCounter++;
	}

	// retrieves the WPS icon directory
	function getIconDirectory() {
		return this.iconDirectory;
	}	

	// get the BIDI value
	function getLTR() {
		return(isLTR);			
	}

	// this function returns the random seed to make WCL menus generate a random menuID
	function getRandomSeed() {
		return randomSeed++;
	}

		
	// this get status message function returns a string which does not contain the words click for option
	function getStatusMessageForUser(userId){
		var statusMessage = this.unAvailableMsg;
		if (personArray[userId] != null) {
			statusMessage = personArray[userId].getStatusMessage();	
		}

		return statusMessage;			
	}

	// This function determines the online status of a user
	// will only return true if the person is active, or away
	function getOnlineStatusForUser(userId){
		switch (personArray[userId].getStatus()){
			case "32":
			case "96":
			case "544":
			case "608":
				return true;
			case "0":
			case "64":
			case "128":
				return false;
		}
		return false;					
	}

	// Returns the displayName associated with a userId
	function getDisplayNameForUser(userId) {
		var businessCard = personArray[userId].getBusinessCard();
		return businessCard.getDisplayName();
	}

	// Returns the businessCard associated with a userId
	function getBusinessCardForUser(userId) {
		var businessCard = personArray[userId].getBusinessCard();
		return businessCard;
	}

	// creates the <img src HTML that embodies the status image icon
	function createStatusImage(userId, counter,leftAligned) {
		var statusImage = awarenessController.getIconDirectory(); 
		var personObj = awarenessController.getPersonObject(userId);
		if (personObj != null)
		{
			statusImage += personObj.getStatusIcon();
		}
		else
		{
			statusImage += 'ClearPixel.gif';
		}
		var size = '16';
		if (leftAligned) {
		    size = '0';
		}

		var statusMsg = this.getStatusMessageForUser(userId);
		var output ="<img id='status-img-"+ counter + "' title=\"" + statusMsg + "\"" +
			" alt=\"" + statusMsg + "\" src='";
		output += statusImage + "' width='" + size + "' height='" + size + "' border='0' align='absmiddle'>";
		return output;
	}

	// creates the <img src HTML that embodies the down image icon
	function createDownImage(counter) {
		var clearPixel = awarenessController.getIconDirectory(); 
		clearPixel += 'ClearPixel.gif';

		var output = "<img id='down-img-" + counter + "' title=\"" + this.clickForOptions + "\"" + 
			" alt=\"" + this.clickForOptions + "\" src='";
		output += clearPixel + "' width='16' height='16' border='0' align='absmiddle'>";
		return output;
	}
	
	// creates the <a href HTML that embodies the link to enable the menu
	// !!!!!!!THIS FUNCTION DOES NOT INCLUDE THE "</a>" TO END THE HREF, PLEASE ADD THAT TO THE END OF VAR !!!!!!!!!!!!!!!!!!!!!!!
	function createMenuLink(userId, userIdType, displayName, IMLogin, counter) {

		var stStatus = this.getStatusMessageForUser(userId);
		if (stStatus.length > 0)
			stStatus += " ";
		var output = "<a id='menu-link-"+ counter +"' title='" + stStatus + this.getClickForOptions() + "'" 
		output += " href='#' class='wpsPersonName'";
			
		output += " onMouseOver=\"swapDownImg('down-img-" + counter + "');\"";
		output += " onMouseOut=\"swapClearImg('down-img-" + counter + "');\"";
		output += "onclick=\"invokePersonTagMenu('" + counter +"',event.target);  return false;\">";
		return output;		
	}

	// this function brings together the various components that make up a person tag link (<status img>displayName<down img>).
	function writeOutLink(userId,userIdType,displayName,IMLogin,leftAligned,contextArray,resolveName) {
		var counter = this.getCounter();
		this.registerPerson(userId,userIdType,displayName,IMLogin,counter,leftAligned,contextArray,resolveName);

		var statusImage = this.createStatusImage(IMLogin, counter,leftAligned);
		var downImage = "";
		var menuLink = "";
		var endTag = "";
		
		if (defaultDynamicPersonTagURL != null && defaultDynamicPersonTagURL.length > 0) {
			downImage = this.createDownImage(counter);
			menuLink = this.createMenuLink(userId,userIdType,displayName,IMLogin, counter);
			endTag = "</a>";
		}
		
		// The format of a standard person tag link is <status img>displayName<down img>, keep in mind the status 
		// img and down img is wrapped the displayName, which is just an HTML link	
		var output = menuLink + statusImage + displayName + downImage + endTag;		
		return output;
	}

	function setDynamicPTagServletBaseURL(junk) {
	}

	function setAwarenessVersion(version) {
		persontag_awarenessversion = version;
	}
	
	function setResolveNames(resolve) {
		persontag_resolveNames = resolve;
	}

	this.setIconDirectory = setIconDirectory;	
	this.setLTR = setLTR;
	this.setMessages = setMessages;
        this.setDynamicPTagServletName = setDynamicPTagServletName;

	this.getPersonObject = getPersonObject;
	this.getStatusMessageForUser = getStatusMessageForUser;
	this.getLTR = getLTR;
	this.getRandomSeed = getRandomSeed;
	this.getCounter = getCounter;
	this.getIconDirectory = getIconDirectory;
	this.getClickForOptions = getClickForOptions;
	this.getOnlineStatusForUser = getOnlineStatusForUser;
	this.getDisplayNameForUser = getDisplayNameForUser;
	this.getBusinessCardForUser = getBusinessCardForUser;
	this.getMenuDataTagObject = getMenuDataTagObject;

	this.registerPersonNoLinks = registerPersonNoLinks;
	this.registerPerson = registerPerson;
	this.updatePersonStatus = updatePersonStatus;
	this.updatePersonLink = updatePersonLink;
	this.writeOutLink = writeOutLink;
	this.createStatusImage = createStatusImage;
	this.createDownImage = createDownImage;
	this.createMenuLink = createMenuLink;
	this.setDynamicPTagServletBaseURL = setDynamicPTagServletBaseURL;
	this.loggedOut = loggedOut;
	this.setAwarenessVersion = setAwarenessVersion;
	this.setResolveNames = setResolveNames;
}

// this class represents a unique person on the page, and stores their status, status message, as
// well as the all the IDs for every link/img associated with them.  
function PersonObject(){
	var status = ''; // ST status of user
	var statusMessage = ''; // ST status message
	var statusImageIDArray = new Array(); // Array of IDs for the status image of a person
	var linkIDArray = new Array(); // Array of IDs for the link to create a menu for a person
	var businessCard; // Object holding the business card data for the user
	// Array of IDs for status images of a person that is left aligned
	var leftAlignedImageIDArray = new Array(); 
	// this function sets the business card object associated with this user
	function setBusinessCard(card) {
		this.businessCard = card;
	}
	
	// this function sets the ID for the status image so that we can later modify it's src to adjust
	// for the different imgs associated with each status
	function setStatusImageID(id){
		statusImageIDArray[statusImageIDArray.length] = id;
	}

	// this function sets the ID for the status images of left
	// aligned images so that we can later modify it's src to adjust
	// for the different imgs associated with each status
	function setLeftAlignedImageID(id){
		leftAlignedImageIDArray[leftAlignedImageIDArray.length] = id;
	}

	// this function sets the ID for the link to open the person tag so that we can later modify it's title to adjust
	// for the different status messages associated with each status
	function setLinkID(id){
		linkIDArray[linkIDArray.length] = id;
	}

	// sets the ST status of the person
	function setStatus(status){		
		this.status = status;
	}
		
	// sets the current status message for the user
	function setStatusMessage(statusMessage){
		
		// just set status message, don't add period to it
		this.statusMessage = statusMessage;
	}
	

	// returns the business card associated with this person
	function getBusinessCard() {
		return this.businessCard;
	}

	// returns the status message of the current user
	function getStatusMessage(){
		return this.statusMessage;
	}
		
	// returns the status of the current user
	function getStatus(){
		return this.status;
	}

	// this function updates the link and status image of the person with the right info
	function update() {
		// for all the links associated with the person, we update the title to have
		// the correct status message
		for (var i=0; i < linkIDArray.length;i++) {
			var elem = document.getElementById(linkIDArray[i]);
			if (elem != null) 
			{
				var stStatus = this.statusMessage;
				if (stStatus.length > 0)
					stStatus += " ";
				elem.title = stStatus + awarenessController.getClickForOptions();
			}
		}

		// for all the status images associated with the person, we update the img and alt
		// text messages to match the user's current status
		for (var i=0; i < statusImageIDArray.length;i++) {
			var iconDirectory = awarenessController.getIconDirectory(); 
			iconDirectory += this.getStatusIcon();
			var elem = document.getElementById(statusImageIDArray[i]);
			if (elem != null) {
				elem.alt = this.statusMessage;
				elem.title = this.statusMessage;
				elem.src = iconDirectory;
			}
		}

		// for all the status images associated with the person that is left alligned, 
		// we update the img and alt text messages to match the user's current status
		// like any other status image.  However, if they are offline we set the width
		// and height to 0 to hide them
		for (var i=0; i < leftAlignedImageIDArray.length;i++) {
			var iconDirectory = awarenessController.getIconDirectory(); 
			iconDirectory += this.getStatusIcon();
			var elem = document.getElementById(leftAlignedImageIDArray[i]);
			if (elem != null) {				
				if (this.status == null || this.status == "0") {
					elem.width='0';
					elem.height='0';	
				} else {
					elem.width='16';
					elem.height='16';	
				}
				elem.alt = this.statusMessage;
				elem.title = this.statusMessage;
				elem.src = iconDirectory;
			}
		}
	}
	
	// function for deriving what status icon should go with each ST status
	function getStatusIcon(){
			switch (this.status){
				case "0":        //offline 
    					return 'ClearPixel.gif';
					break;
				case "32":        //active
    					return 'StatusActive.gif';
					break;
				case "64":        //not using computer
    					return 'StatusAway.gif'; // 
					break;
				case "96":        //away
					return 'StatusAway.gif';
					break;
				case "128":        //do not disturb
					return 'StatusDoNotDisturb.gif';
					break;
				case "544":        //mobile active
					return 'StatusMobile.gif';
					break;
				case "608":        //mobile away
					return 'StatusAway.gif';  
					break;
			}
			return 'ClearPixel.gif';		
		}


	this.setBusinessCard = setBusinessCard;
	this.setStatusImageID = setStatusImageID;
	this.setLeftAlignedImageID = setLeftAlignedImageID;
	this.setLinkID = setLinkID;	
	this.setStatus = setStatus;
	this.setStatusMessage = setStatusMessage;
		

	this.getBusinessCard = getBusinessCard;
	this.getStatus = getStatus;
	this.getStatusMessage = getStatusMessage;
	this.getStatusIcon = getStatusIcon;
	this.update = update;
	
}

// this data object holds data we need to log onto the 
// the menu data tag
function MenuDataTagObject() {
	var IMLogin; // the login to get onto IM
	var userId; // the userId used to get data from WMM
	var userIdType; // the value 	
	var displayName; // the displayName used in the person tag link
	var menuData = null; // the data associated with the menu items + business card
	var contextArray = null; // stores page context info for menu items that need it

	// sets the userId to get data from WMM
	function setIMLogin(IMLogin){		
		this.IMLogin = IMLogin;
	}

	// sets the userId to get data from WMM
	function setUserId(userId){		
		this.userId = userId;
	}

	// sets the userId type to get data from WMM
	function setUserIdType(userIdType){		
		this.userIdType = userIdType;
	}

	// sets the display name used in the HTML link
	function setDisplayName(displayName){		
		this.displayName = displayName;
	}

	// sets the menu data for the menu items
	function setMenuData(menuData){		
		this.menuData = menuData;
	}

	// sets the context array
	function setContextArray(contextArray) {
		this.contextArray = contextArray;
	}

	// gets the userId to get data from WMM
	function getIMLogin(){		
		return(this.IMLogin);
	}

	// gets the userId to get data from WMM
	function getUserId(){		
		return(this.userId);
	}

	// gets the userId type to get data from WMM
	function getUserIdType(){		
		return(this.userIdType);
	}

	// gets the display name used in the HTML link
	function getDisplayName(){		
		return(this.displayName);
	}

	// gets the menu data for the menu items
	function getMenuData(){		
		return(this.menuData);
	}

	// gets the context array
	function getContextArray() {
		return(this.contextArray);
	}

	this.setIMLogin = setIMLogin;
	this.setUserId = setUserId;
	this.setUserIdType = setUserIdType;
	this.setDisplayName = setDisplayName;
	this.setMenuData = setMenuData;
	this.setContextArray = setContextArray;

	this.getIMLogin = getIMLogin;
	this.getUserId = getUserId;
	this.getUserIdType = getUserIdType;
	this.getDisplayName = getDisplayName;
	this.getMenuData = getMenuData;
	this.getContextArray = getContextArray;
}

function invokeAction(formName, actionRef){
	var array = new Array(1);
	array[0] = actionRef;
	c2a_invokeMenuAction(formName,array);	
}

function escapeForJavaScript(str)
{
	var retStr = str;
	var loc = 0;
	
	while ((loc = retStr.indexOf("\\", loc)) > 0)
	{
		retStr = retStr.substring(0, loc)+"\\"+retStr.substring(loc);
		loc += 2;
	}
	
	return retStr;
}

// converts name like this "CN=Scott, Smith,OU=CAM,O=Lotus" to "CN=Scott\, Smith,OU=CAM,O=Lotus"
// Note that it only escapes the comma if it's in the first part of the heirarchy.
function escapeSlashComma(str)
{
	var index = str.indexOf("=");
	var subStr = str.substring(index+1, str.length);
	var cnStr = subStr.substring(0, subStr.indexOf("="));
	
	var indexComma = str.indexOf(",");
	var retStr = str;
	
	if(cnStr.indexOf(",") != cnStr.lastIndexOf(","))
	{
		if(str.charAt(indexComma-1) != '\\' && str.charAt(indexComma-2) != '\\') 
		{
			retStr = str.substring(0, indexComma) + "\\"  + str.substring(indexComma);
		}
	}
	return retStr;
}
	
function STLinkClicked(person, personLinkText, status, event)
{
	linkId = person;

	if (typeof STLinksCreateIM == "function")
		STLinksCreateIM(escapeSlashComma(linkId));
}


//*************************************************************************************

// TODO
// does servlet context root need to be customizable?
// no sorting for addressbook
// clean up imlogin name stuff in addressbook

// test caching w/ Moz
// global configuration (via props file?)
// repackage servlet into ear
// add caching of entry set on page context

// CHANGES
// hideCurrentContextMenu -> remove contextMenuDismissEnable; in context_ie.js
// this.itemTag.onmousedown = menuItemLaunchAction; in context_ie.js
// buildMenu -> showContextMenu (is this a bug?)
// input and textarea positioning change?

// Array of PabEntry - this is a "static" across all instances on the page
 var InputHandler_pabData = null;

// Array of Input Handlers - this is a "static" containing all input handlers
var InputHandler_handlerArray = new Array();

// get the pab data from the server here
function InputHandler_getServerPabData()	
{
	var servletData = null;
	// if the tag wrote out an iframe to use for the server communication, use it
	if (document.getElementById("typeAheadIFrame") != null)
	{
		// typeAheadServletUrl is a js variable written out by the TypeAheadTag
		getDataFromServerIFrame(typeAheadServletUrl, "typeAheadIFrame", "InputHandler_getServerPabData_callback");
	}
	else
	{
		// just use xmlhttprequest - xmlhttprequest works synchronously, so
		// just call the callback function manually here
		servletData = getDataFromServer(typeAheadServletUrl);
		InputHandler_getServerPabData_callback(servletData);
	}
}	

function InputHandler_getServerPabData_callback(servletData)
{
	if (servletData != null)
	{
		if (InputHandler_pabData == null)
		{
			InputHandler_pabData = new Array();
		}
		while (servletData.length > 0)
		{
			var nameCount = servletData.substring(0,3) - 0;
			var pabEntryName = servletData.substring(3, nameCount+3);
			servletData = servletData.substring(nameCount+3);

			var emailCount = servletData.substring(0,3) - 0;
			var pabEntryEmail = servletData.substring(3, emailCount+3);
			servletData = servletData.substring(emailCount+3);

			var locationCount = servletData.substring(0,3) - 0;
			var pabEntryLocation = servletData.substring(3, locationCount+3);
			servletData = servletData.substring(locationCount+3);

			var imloginCount = servletData.substring(0,3) - 0;
			var pabEntryIMLogin = servletData.substring(3, imloginCount+3);
			servletData = servletData.substring(imloginCount+3);

			// before we do anything else, check if we already have 
			// this email in the list
			var exists = false;
			for (var i = 0; i < InputHandler_pabData.length; i++)
			{
				if (InputHandler_pabData[i].getEmail() == pabEntryEmail)
				{
					exists = true;
					break;
				}
			}

			if (exists)
			{
				break;
			}

			var pabEntry = new PabEntry();
			pabEntry.setName(pabEntryName);
			pabEntry.setEmail(pabEntryEmail);
			pabEntry.setLocation(pabEntryLocation);
			pabEntry.setIMLogin(pabEntryIMLogin);

			InputHandler_pabData[InputHandler_pabData.length] = pabEntry;			
		}
	}

	// now that everything is downloaded, start the polling loop
	InputHandler_callPollInput();
}

function InputHandler_callPollInput()
{
	for (var i = 0; i < InputHandler_handlerArray.length; i++)
	{
		InputHandler_handlerArray[i].pollInput();
	}

	// loop this call every second
	setTimeout("InputHandler_callPollInput();", 1000);
}

function InputHandler_registerInputHandler(inputHandler)
{
	if (InputHandler_handlerArray.length == 0)
	{
		// this is the very first InputHandler that was created.
		// append the InputHandler_getServerPabData function to
		// the original onload function
		var origOnLoad = window.onload;
		window.onload = function()
		{
			if (origOnLoad)
			{
				eval(origOnLoad);
			}
			InputHandler_getServerPabData();
		}
	}

	inputHandler.index = InputHandler_handlerArray.length;
	InputHandler_handlerArray[InputHandler_handlerArray.length] = inputHandler;
}


// Cache of menu items.
// This is based on the assumption that only one type ahead menu can be open at a
// time and that, as such, they can share menu items.
var InputHandler_menuItemArray = new Array();

function InputHandler_createTypeAheadMenuItem(data, inputHandler)
{
	var menuItem = null;

	if ((typeof InputHandler_menuItemArray[data.getEmail()] != "undefined") &&
	    (InputHandler_menuItemArray[data.getEmail()] != null))
	{
		// use old menu item
		menuItem = InputHandler_menuItemArray[data.getEmail()];
	}
	else
	{
		menuItem = new UilMenuItem("", true, "", "", null, null, false,'lwpMenuItem', 'lwpSelectedMenuItem');
		menuItem.superclass_create = menuItem.create;
		menuItem.data = data;

		// register awareness links - will no op if already registered
		if (inputHandler.showAwareness &&
		    data.getIMLogin() != null &&
		    data.getIMLogin() != "")
		{
			awarenessController.registerPersonNoLinks(data.getName(), data.getIMLogin());
		}

		InputHandler_menuItemArray[data.getEmail()] = menuItem;
	}

	// regenerate this method because the inputHandler might be different
	menuItem.create = function(menuHasIcon, menuHasSubmenu)
	{
		// always pass in false so that the super class doesn't add
		// in the "expand" arrow
		this.superclass_create(menuHasIcon, false);

		// add in the people submenu icon - note that we have to
		// manually add in the menu item because there is no submenu
		// and we don't want to create the people menu until it's
		// needed

		// all the global variables come from context_ie.js
		this.showPeopleMenu = inputHandler.showPeopleMenu &&
					data.getIMLogin() != null &&
					data.getIMLogin() != "";
		if (this.showPeopleMenu)
		{
			var submenuArrow = document.createElement( "TD" );
			submenuArrow.noWrap = true;
			var submenuImg = document.createElement( "IMG" );
			submenuImg.alt = submenuAltText_;
			submenuImg.title = submenuAltText_;
			if (this.parentMenu.isLTR) submenuImg.src = arrowNorm_;
			else submenuImg.src = arrowNormRTL_;				
			submenuArrow.appendChild( submenuImg );
			this.arrowTag = submenuImg;
			this.itemTag.appendChild( submenuArrow );
			submenuImg.width = arrowWidth_;
			submenuImg.height = arrowHeight_;
		}
		else if (inputHandler.showPeopleMenu)
		{
			// this is a group so no people tag - just put in a blank cell
			var submenuArrow = document.createElement( "TD" );
			this.itemTag.appendChild( submenuArrow );			
		}

		this.anchorTag.onkeydown = null;
		this.itemTag.onmousedown = function(event)
		{
			InputHandler_typeAheadMenuItemLaunchAction(inputHandler, event);
		}

		this.itemTag.onmousemove = function(event)
		{
			if ( visibleMenu_ != null ) {
				var item = visibleMenu_.getMenuItem( getEventTarget(event) );
				if ( item != null )
				{
					if ( !item.isSelected )
					{
						// set focus on the anchor and select the menu item
						item.anchorTag.focus();
					}
				}
			}
		}
	}


	// reset the icon - does this need to "sleep" before the update comes back
	// from the STLinks?
	if (inputHandler.showAwareness &&
	    data.getIMLogin() != null &&
	    data.getIMLogin() != "")
	{
		var icon = awarenessController.getIconDirectory() + awarenessController.getPersonObject(data.getIMLogin()).getStatusIcon();
		menuItem.icon = icon;
	}

	// reset the name (and the bold)
	var inputString = inputHandler.parseLastName().toLowerCase();


	var matchString = ("\"" + data.getName() + "\" <" + data.getEmail() + ">").toLowerCase();

	if (matchString.indexOf(inputString) == 0)
	{
		var pre = matchString.substring(0, inputString.length).replace(/&/g, "&amp;").replace(/</g, "&lt;");
		var post = matchString.substring(inputString.length).replace(/&/g, "&amp;").replace(/</g, "&lt;");

		matchString = "<b>" + pre + "</b>" + post;
	}
	else 
	{

		var matchIndex = matchString.indexOf(" " + inputString);
		if (matchIndex == -1)
		{
			matchIndex = matchString.indexOf("\"" + inputString);
			if (matchIndex == -1)
			{
				matchIndex = matchString.indexOf("<" + inputString);
			}
		}

		if (matchIndex != -1)
		{
			// add +1 to get the space at the end
			var pre = matchString.substring(0, matchIndex + 1);
			var mid = matchString.substring(matchIndex + 1, matchIndex + 1 + inputString.length);
			var post = matchString.substring(matchIndex + 1 + inputString.length);

			// escape chars
			pre = pre.replace(/&/g, "&amp;").replace(/</g, "&lt;");
			mid = mid.replace(/&/g, "&amp;").replace(/</g, "&lt;");
			post = post.replace(/&/g, "&amp;").replace(/</g, "&lt;");

			matchString = pre + "<b>" + mid + "</b>" + post;
		}
	}	

	menuItem.text = matchString;

	// reset the javascript
	menuItem.clientAction = "InputHandler_handlerArray[" + inputHandler.index + "].returnValue('"+ data.getName() + "','" + data.getEmail() +"');"

	return menuItem;
}

// This is set immediately before openModalDialog is called. It is used to remember
// which inputHelper was used to open the dialog. You can't just pass in this.returnValue
// as the callback because the callback is executed without any state (it passes in the
// function pointer)
var pickerResultsFunctionIndex = 0;
function pickerResultsFunction(data)
{
	var peopleArray = new Array();
	
	if (data)
	{
		var str = "";
		for (var i=0; i < data.length; i++)
		{
			for (var j=0; j < data[i].length; j++)
			{
				var email=data[i][j].email;
				var name=data[i][j].name;
				peopleArray[peopleArray.length] = new Object();
				peopleArray[peopleArray.length-1].name = name;
				peopleArray[peopleArray.length-1].email = email;
			}
		}

		InputHandler_handlerArray[pickerResultsFunctionIndex].returnArrayValues(peopleArray);
	}
}

function openTypeAheadPeoplePicker()
{
	var inputHandler = InputHandler_handlerArray[pickerResultsFunctionIndex];
	inputHandler.peoplePickerArgs.prepop = inputHandler.parseLastName();
	openModalDialogWithArgs(inputHandler.peoplePickerArgs.url, inputHandler.peoplePickerArgs, pickerResultsFunction);
}

// This keeps a key press from propagating to the underlying control. 
// It swaps the focus to a dummy input and then adds a handler to the dummy
// (which is immediately called) that returns the focus
// to the input. This must be called on an onkeydown event, or something else that
// happens before the key has propagated
var blockSub = null;
function blockkeypress(inputId)
{
	if (isGecko())
	{
		document.getElementById("typeaheadfocusdump").onfocus = function()
		{
			setTimeout("document.getElementById('"+inputId+"').focus()", 100);
		}
		document.getElementById("typeaheadfocusdump").focus();
	}
	else
	{
		// in IE, the onkeyup handler is triggered when an element is focused
		// during a keypress, so swap the focus back as soon as this happens
		document.getElementById("typeaheadfocusdump").onkeyup = function()
		{
			document.getElementById("typeaheadfocusdump").form.onsubmit = blockSub;
			document.getElementById(inputId).focus();
		}
		
		blockSub = document.getElementById(inputId).form.onsubmit;
		document.getElementById("typeaheadfocusdump").form.onsubmit = function()
		{
			document.getElementById(inputId).focus();
			document.getElementById("typeaheadfocusdump").form.onsubmit = blockSub;
			return false;
		}
		document.getElementById("typeaheadfocusdump").focus();
	}
}

function InputHandler(inputId, peoplePickerUrl, showAwareness, showPeopleMenu, maxRows)
{
	// the index of this inputHandler in the array of input handlers
	this.index = 0;

	// flag for whether or not you're preparing to read
	this.getInput = true;
	
	// inputId for the control we're attaching to
	this.inputId = inputId;
	
	// main wcl menu
	this.menu = null;

	// the last value we polled - used to determine if anything has changed
	this.lastPolled = "";

	// the index of the last character that was changed
	this.changedIndex = 0;

	// flag for whether or not to display awareness
	this.showAwareness = showAwareness;

	// flag for whether or not to display the people menu as a submenu
	this.showPeopleMenu = showPeopleMenu;
	
	// the "window" argument is added by default by openModalDialog in the case w/
	// no arguments, so I add it here. No idea why it's needed.
	this.peoplePickerArgs = new Object();
	this.peoplePickerArgs.url = peoplePickerUrl;

	this.peoplePickerArgs.window = window;
	
	// if we render a textinput and this number is > 1
	// this is the number of rows of the textarea that the textinput will expand to
	this.maxRows = maxRows;
	
	// handle arrow keys. It should never trigger the menu (and basically no ops
	// if the menu isn't displayed)
	this.captureKeys = function(event)
	{
		// just ignore if the menu is hidden
		if (this.menu != null && this.menu.isVisible && !this.menu.isEmpty && !event.shiftKey)
		{
			if (event.keyCode == 40)
			{
				// Down arrow
				var curMenu = this.menu;
				if (this.menu.selectedItem.submenu != null &&
					this.menu.selectedItem.submenu.isVisible)
				{
					curMenu = this.menu.selectedItem.submenu;
				}

				if (curMenu.selectedItem == null)
				{
					// nothing is selected, select the first one
					curMenu.items[0].setSelected(true);
					
				}
				else if (curMenu.selectedItem.getNextItem() != null)
				{
					curMenu.selectedItem.getNextItem().setSelected(true);
				}
				
				// keep the cursor from moving if you use arrows
				// while menu is up
				return blockkeypress(this.inputId);
			}
			else if (event.keyCode == 38)
			{
				// Up arrow
				var curMenu = this.menu;
				if (this.menu.selectedItem.submenu != null &&
					this.menu.selectedItem.submenu.isVisible)
				{
					curMenu = this.menu.selectedItem.submenu;
				}
				
				if (curMenu.selectedItem == null)
				{
					// nothing is selected, select the first one
					curMenu.items[curMenu.items.length - 1].setSelected(true);
					
				}
				else if (curMenu.selectedItem.getPrevItem() != null)
				{
					curMenu.selectedItem.getPrevItem().setSelected(true);
				}

				// keep the cursor from moving if you use arrows
				// while menu is up
				return blockkeypress(this.inputId);
			}
			else if (event.keyCode == 39)
			{
				// right
				var item = this.menu.selectedItem;
				if (item.showPeopleMenu)
				{
					// If the people menu has already been created, just reshow
					if (item.submenu != null)
					{
						if (!item.submenu.isVisible)
						{
							item.submenu.show(item.anchorTag, item);

							// temporarily set the focus for the submenu so that it
							// registers menu.selectedItem
							item.submenu.items[0].anchorTag.focus();
							return blockkeypress(this.inputId);
						}
					}
					else
					{

						var counter = awarenessController.getCounter();
						awarenessController.registerPerson(item.data.getEmail(), "EMAIL", item.data.getName(), item.data.getIMLogin(), counter, false, null, null);
						var menu = invokePersonTagMenu(counter, item.arrowTag, true);
						item.submenu = menu;

						item.submenu.items[0].anchorTag.focus();
						return blockkeypress(this.inputId);
					}
 				}
			}
			else if (event.keyCode == 37)
			{
				// left
				var item = this.menu.selectedItem;
				// If the people menu has already been created, just reshow
				if (item.submenu != null &&
				    item.submenu.isVisible)
				{
					item.submenu.hide(item.anchorTag, item);

					// keep the cursor from moving if you use arrows
					// while menu is up
					return blockkeypress(this.inputId);
				}
			}
			else if (event.keyCode == 13)
			{			
				// enter
				var curMenu = this.menu;
				if (this.menu.selectedItem.submenu != null &&
					this.menu.selectedItem.submenu.isVisible)
				{
					curMenu = this.menu.selectedItem.submenu;
				}


				// this logic is copied from context_ie.js
				// menuItemLaunchAction
				if (curMenu.selectedItem != null)
				{
					eval(curMenu.selectedItem.clientAction);
					if (curMenu.selectedItem.action != null)
					{
						if (curMenu.selectedItem.action.indexOf("javascript:") == 0)
						{
							eval(curMenu.selectedItem.action);
						}
					}
				}
				curMenu.hide();

				// keep enter from submitting the form
				// while menu is up
				return blockkeypress(this.inputId);
			}
		}

		var input = document.getElementById(this.inputId);
		// input size by default??
		var size = 40;
		if (input.size != null)
		{
			size = input.size;
		}
		
		// maxRows gets set to -1 if the swap is disabled, if it's
		// already been done, etc.
				
		// The (* .8) is a hack to take into account the fact that with
		// a non-fixed width font the size of the text input doesn't
		// correspond to anything
		if (this.maxRows > 1 &&
		    input.value.length > (size * .8))
		{
			var newnode = document.createElement("textarea");
			newnode.className = 'wpsEditField';
			newnode.rows = this.maxRows;
			if (input.size != null &&
			    input.size > 0)
			{
				newnode.cols = input.size;
			}
			newnode.value = input.value;
			newnode.onkeydown = input.onkeydown;
			newnode.id = input.id;
			newnode.name = input.name;
			newnode.autocomplete = "OFF";
			newnode.style = input.style;
			
			// reset maxRows so the swap won't happen anymore
			this.maxRows = -1;
			
			input.parentNode.replaceChild(newnode, input);
			if (isGecko())
			{
				newnode.focus();
			}
			else
			{
				// In IE, I think replaceChild is done asynchronously
				// so give it some time to complete and then refocus.
				// Do a dummy change to the content to move the cursor to the end
				setTimeout("document.getElementById('" + input.id + "').focus();" + 
				           "document.getElementById('" + input.id + "').value=document.getElementById('" + input.id + "').value", 100);
			}
		}

	}

	this.parseLastName = function()
	{
		// parse out the most recent name typed into the form input based on 
		// the changedIndex
		var inputString = document.getElementById(this.inputId).value;
		
		// the "+ 1" avoids the actual comma, and also handles if index = -1
		var startIndex = inputString.substring(0, this.changedIndex+1).lastIndexOf(",") + 1;

		// figure out how many characters past the changedIndex the next comma is
		var endIndex = inputString.substring(this.changedIndex+1).indexOf(",");
		if (endIndex == -1)		
		{
			endIndex = inputString.length;
		}
		else
		{
			endIndex = endIndex + this.changedIndex+1;
		}
		
		inputString = inputString.substring(startIndex, endIndex);

		// manually trim the string
		startIndex = 0;
		while (startIndex < inputString.length && 
			(inputString.charAt(startIndex) == ' ' ||
			 inputString.charAt(startIndex) == '\r' ||
			 inputString.charAt(startIndex) == '\n')
		      )
		{
			startIndex = startIndex + 1;
		}

		endIndex = inputString.length - 1;
		while (endIndex < inputString.length && 
			(inputString.charAt(endIndex) == ' ' ||
			 inputString.charAt(endIndex) == '\r' ||
			 inputString.charAt(endIndex) == '\n')
		      )
		{
			endIndex = endIndex - 1;
		}

		var lastName = "";
		if (startIndex < (endIndex + 1))
		{
			lastName = inputString.substring(startIndex, endIndex+1);
		}

		return lastName;
	}

	// only reads off the input every second
	this.pollInput = function()
	{
		var inputString = document.getElementById(this.inputId).value;		
		
		// check if something changed in the last second, or if the user
		// is just using arrow keys
		if (inputString != this.lastPolled)
		{
			// figure out the last character that changed so we know where to do the insert
			for (var i = 1; i <= inputString.length; i++)
			{
				if (i >= this.lastPolled.length ||
				    this.lastPolled.charAt(this.lastPolled.length - i) != inputString.charAt(inputString.length - i))
				{
					this.changedIndex = inputString.length - i;
					break;
				}
			}
		
			// process the change
			this.lastPolled = inputString;
			inputString = this.parseLastName().toLowerCase();

			if (inputString == '')
			{
				if (this.menu != null)
				{
					this.menu.hide();
				}
				return;
			}

			// search for the correct string
			var result = null;
			if(inputString != null && inputString.length>0)
			{
				// Array of PabEntrys from PabData (?)
				result = new Array();
				for(x = 0; x < InputHandler_pabData.length; x++)
				{
					// for each pab entry check if name or email is matched

					var pabEntry = InputHandler_pabData[x];
					var matchString = ("\"" + pabEntry.getName() + "\" <" + pabEntry.getEmail() + ">").toLowerCase();

					if (matchString.indexOf(inputString) == 0 ||
					    matchString.indexOf(" " + inputString) != -1 ||
					    matchString.indexOf("\"" + inputString) != -1 ||
					    matchString.indexOf("<" + inputString) != -1)
					{
						result[result.length] = pabEntry;
					}
				}
			}
			this.createMenu(result);

			// make sure the form element is still in focus
			// (hiding the menu on an "enter" press steals focus)
			document.getElementById(this.inputId).focus();
		}
	}

	// returns the menu js object
	this.createMenu = function (data)
	{
		if (data != null)
		{
			// generates a new menuID each time so we don't get cached results
			var menuID = "jave" + awarenessController.getRandomSeed();

			// gets BIDI info from awarenessController 
			var isLTR = awarenessController.getLTR();

			// creates the actual wcl menu	
			this.menu = InputHandler_createTypeAheadContextMenu(menuID, isLTR);
	
			// iterate through the menu items and builds the appropriate WCL menu items for them
			for (var i=0;i < data.length;i++)
			{
				var menuItem = InputHandler_createTypeAheadMenuItem(data[i], this);
				this.menu.add(menuItem);
			}

			// openModalDialog is a js routine from openModalDialog.js
			if ((this.peoplePickerArgs.url != null) &&
			    (typeof openModalDialog != "undefined"))
			{
				// pass in the name to search for as the argument to the openModalDialogWithArgs call
				// this is retrieved via JS in the people picker
				var fullSearchJs = "pickerResultsFunctionIndex="+this.index+";openTypeAheadPeoplePicker();";
				var fullSearchMenuItem = new UilMenuItem("Do full search on: <b>" + this.parseLastName().replace(/&/g, "&amp;").replace(/</g, "&lt;") + "</b>", true, '', fullSearchJs, null, null, false,'lwpMenuItem', 'lwpSelectedMenuItem');

				fullSearchMenuItem.superclass_create = fullSearchMenuItem.create;
				fullSearchMenuItem.create = function(menuHasIcon, menuHasSubmenu)
				{
					this.superclass_create(menuHasIcon, false);
					if (showPeopleMenu)
					{
						// this is a group so no people tag - just put in a blank cell
						var submenuArrow = document.createElement( "TD" );
						this.itemTag.appendChild( submenuArrow );			
					}
				}

				var icon = awarenessController.getIconDirectory() + "Search_Task.gif";
				fullSearchMenuItem.icon = icon;

				this.menu.add(fullSearchMenuItem);
			}

			// set the icons we will use - clean this up
			var iconDirectory = awarenessController.getIconDirectory();
			var transparentImage = iconDirectory + 'ClearPixel.gif';
			var downArrow =	iconDirectory + 'MenuDropdown.gif';
			var normArrow =	iconDirectory +'MenuDropdown.gif'
			contextMenuSetArrowIconDimensions(16,16);
			
			/*
			transparentImage,
			arrowDefault,
			arrowSelected,
			arrowDisabled,
			launcherDefault,
			launcherSelected,
			arrowDefaultRTL,
			arrowSelectedRTL,
			arrowDisabledRTL,
			launcherDefaultRTL,
			launcherSelectedRTL
			*/			
			contextMenuSetIcons( transparentImage,
                              transparentImage, downArrow, downArrow,
                              downArrow, downArrow,
                              downArrow, downArrow, downArrow,
                              downArrow, downArrow );

			// shows the fully generated popup menu
			contextMenuShow( menuID, false, false, document.getElementById(this.inputId), true );
		}
	}

	// this is called by the people picker to return a bunch of people all at once
	this.returnArrayValues = function(people)
	{
		for (var i = 0; i < people.length; i++)
		{
			this.returnValue(people[i].name, people[i].email);
		}
	}


	// This is what happens when you select someone - adds the name to the list
	// at the top
	this.returnValue = function (name, email)
	{
		var newPerson = "\"" + name + "\" <" + email + ">";
	
		// parse out the last (partially completed) name
		// part of this is basically copied from parseLastName
		var inputString = document.getElementById(this.inputId).value;
		
		// the "+ 1" avoids the actual comma, and also handles if index = -1
		var startIndex = inputString.substring(0, this.changedIndex+1).lastIndexOf(",") + 1;
		var preString = inputString.substring(0, startIndex);

		// this isn't the first name so preString ends with a comma
		if (preString.length > 0)
		{
			preString = preString + " ";
		}


		// figure out how many characters past the changedIndex the next comma is
		var endIndex = inputString.substring(this.changedIndex+1).indexOf(",");
		if (endIndex == -1)		
		{
			endIndex = inputString.length;
		}
		else
		{
			// + 1 for the actual changed characters and + 1 for the comma
			endIndex = endIndex + this.changedIndex + 2;
		}


		var postString = inputString.substring(endIndex);
		if (postString.charAt(0) == " ")
		{
			postString = postString.substring(1);
		}
		var retVal = preString + newPerson + ", " + postString;
		document.getElementById(this.inputId).value = retVal;
		this.changedIndex = retVal.length;
		this.lastPolled = retVal;
		this.menu.hide();
	}

	// register the input handler and kick off the polling loop if necessary
	InputHandler_registerInputHandler(this);
}

function PabEntry()
{
	this.setName = function (displayName) {
		this.name = displayName;
	}

	this.setEmail = function (inputEmail) {
		this.email = inputEmail;
	}

	this.setLocation = function (location) {
		this.location = location;
	}

	this.setIMLogin = function (login) {
		this.IMLogin = login;
	}

	this.getName = function () {
		return(this.name);
	}

	this.getEmail = function () {
		return(this.email);
	}

	this.getLocation = function() {
		return (this.location);
	}

	this.getIMLogin = function () {
		return(this.IMLogin);
	}
}

function InputHandler_createTypeAheadContextMenu( name, isLTR, width )
{
	var menu = new UilContextMenu( name, isLTR, width );

	menu.superclass_show = menu.show;
	menu.show = function ( launcher, launchItem )
	{
		this.superclass_show(launcher, launchItem);

		// replace the onmousedownhandler - the superclass hide method
		// replaces the original handler anyway so we don't need to
		// save the old one
		document.onmousedown = InputHandler_documentOnMouseDown;

		if (this.isEmpty)
		{
			// suppress the "this menu is empty" menu
			this.hide();
		}
		else
		{
			if ( this.launcher.tagName == "INPUT" ||
			     this.launcher.tagName == "TEXTAREA" )
			{
				var launcherLeft = getLeft( this.launcher, true );
				var menuHeight = getHeight( this.menuTag );
				var maxY = document.documentElement.scrollTop + document.documentElement.clientHeight;
				var maxY2 = document.body.scrollTop + document.body.clientHeight;
				if ( maxY2 > maxY ) maxY = maxY2;

				posX = launcherLeft;

				// bidi
				if ( !this.isLTR )
				{
					posX += getWidth( this.launcher ) - getWidth( this.menuTag );
				}

				posY = getTop( this.launcher, true );

				if ( posY + menuHeight > maxY )
				{
					// top
					posY -= menuHeight;
				}
				else
				{
					// bottom
					posY += getHeight( this.launcher );
				}

				this.menuTag.style.left = posX + "px";
				this.menuTag.style.top = posY + "px";

				// reset the items hidden by the menu (because it was
				// moved)
				var itemCount = this.hiddenItems.length;
				for (i=0; i<itemCount; i++)
				{
					var item = this.hiddenItems.pop();
					item.style.visibility = "visible";
				}

				var coll = document.getElementsByTagName("SELECT");
				if (coll!=null)
				{
					for (i=0; i<coll.length; i++)
					{
						//Hide the element
						if (intersect(this.menuTag,coll[i]) &&
						    coll[i].style.visibility != "hidden")
						{
							coll[i].style.visibility = "hidden";
							this.hiddenItems.push(coll[i]);
						}
					}
				}


			}
			
			this.launcher.focus();
		}
	}

	// turn off all of the timer stuff
	menu.superclass_create = menu.create;
	menu.create = function (recurse)
	{
		this.superclass_create (recurse);
		if (this.menuTag != null)
		{
			this.menuTag.onmouseover = null;
			this.menuTag.onmouseout = null;
		}

	}

	allMenus_[ allMenus_.length ] = menu;
	return menu;
}

// launches the action for a menu item method called by an event handler (href for anchor tag)
// "data" is the PabEntry
function InputHandler_typeAheadMenuItemLaunchAction(inputHandler, event)
{
	if (visibleMenu_ != null)
	{
		// only check the selected item from the root menu
		// don't care what's selected in the people tag
		var item = visibleMenu_.selectedItem;
		
		// If you can display the people menu and you clicked on the arrow
		if (inputHandler.showPeopleMenu && 
		    getEventTarget(event) == item.arrowTag)
		{
			// If the people menu has already been created, just reshow
			if (item.submenu != null)
			{
				if (item.submenu.isVisible)
				{
					item.submenu.hide(item.anchorTag, item);
				}
				else
				{
					item.submenu.show(item.anchorTag, item);
				}
			}
			else
			{
				var counter = awarenessController.getCounter();
				awarenessController.registerPerson(item.data.getEmail(), "EMAIL", item.data.getName(), item.data.getIMLogin(), counter, false, null, null);
				var menu = invokePersonTagMenu(counter, item.arrowTag, true);
				item.submenu = menu;
			}

			// invokePersonTagMenu calls "menu.show" which sets the
			// document.onmousedown handler.

			// replace the onmousedownhandler - the superclass hide method
			// replaces the original handler anyway so we don't need to
			// save the old one
			document.onmousedown = InputHandler_documentOnMouseDown;

		}
		else
		{
			hideCurrentContextMenu( true );
			if ( item.clientAction != null ) {
				eval( item.clientAction );
			}
			if ( item.action != null )
			{
				if ( item.action.indexOf( "javascript:" ) == 0 )
				{
					eval( item.action );
				}
			}
		}
	}
}

// document level onmousedown handler that we use while the type ahead menu is up
function InputHandler_documentOnMouseDown(event)
{
	// do the hide unless you clicked on one of the images (or anything else
	// with a src attribute)
	// if you clicked on the awareness icon the menu will hide itself anyway in
	// InputHandler_typeAheadMenuItemLaunchAction
	if (!getEventTarget(event).src)
	{
		hideCurrentContextMenu( true );
	}
}

function getEventTarget(event)
{
	if (isGecko())
	{
		return event.target;
	}
	else
	{
		return window.event.srcElement;
	}
}
