/**
 ******************************************************************************
 * Copyright (c) 2006-2007. EMC Corporation.  All Rights Reserved.
 ******************************************************************************
 *
 * Project        WDK
 * File           wdk.js
 * Description    WDK Core Javascript Library
 * Created on     20th July 2006
 * Tab width      3
 *
 ******************************************************************************
 *
 * PVCS Maintained Data
 *
 * Revision       $Revision: 20$
 * Modified on    $Date: 6/11/2008 5:03:25 AM$
 *
 * Log at EOF
 *
 ******************************************************************************
 */

if (typeof wdk == "undefined")
{ // support portal

var wdk = new Object();

/******************** DOM Module ***********************************/
wdk.dom = new function()
{
   this.ELEMENT_NODE = 1;
   this.ATTRIBUTE_NODE = 2;
   this.TEXT_NODE = 3;
   this.CDATA_SECTION_NODE = 4;
   this.ENTITY_REFERENCE_NODE = 5;
   this.ENTITY_NODE = 6;
   this.PROCESSING_INSTRUCTION_NODE = 7;
   this.COMMENT_NODE = 8;
   this.DOCUMENT_NODE = 9;
   this.DOCUMENT_TYPE_NODE = 10;
   this.DOCUMENT_FRAGMENT_NODE = 11;
   this.NOTATION_NODE = 12;

   /**
    * Retrieves DOM element(s), based on id
    * Accepts either a single ID string or an Array of string IDs
    *
    * If a string ID is passed in, returns the element matching the ID or null if not found
    *
    * If an Array of string IDs is passed in, returns an Array of the same length. Each member of the array is either
    * the DOM element retrieved for the id,  or null if not found
    *
    * As a convenience operation, if a DOM element is passed in, it will return it, hence the caller does not need
    * to check - if they have a sting ID reference or the actual DOM element before calling the method
    *
    * @param e - Either a string ID or an Array of string IDs
    * @return a DOM element, an Array of DOM elements or null if not found
    */
   this.get = function(e)
   {
      if (e)
      {
         if (typeof(e) == "string")
         {
            // lookup element
            return document.getElementById(e);
         }
         else if (e.getElementsByTagName)
         {
            // return element itself - convenience operation in case someone passes in the element
            return  e;
         }
         else if (e.push)
         {
            // return array of elements
            var elems = new Array(e.length);
            for (var i = 0;i < e.length; i++)
            {
               elems[i] = this.get(e[i]);
            }
            return elems;
         }
      }
      return null;
   }

   /**
    * Determines if the specified CSS class is set on the DOM element passed in. Accounts for elements with multiple
    * classes.
    *
    * @param elem - DOM element
    * @param className - the CSS class name to check for. Case sensitive
    * @return true if the className is set on DOM element, false if not
    */
   this.hasClass = function(elem, className)
   {
      if (!regExpCache[className])
      {
         if (regExpCount > MAX_REGEXP_COUNT) {
            regExpCount = 0;
            regExpCache = {};
         }
         regExpCache[className] = new RegExp('(^|\\s+)' + className + '(\\s+|$)');
         regExpCount++;
      }
      return regExpCache[className].test(elem['className']);
   }

   var regExpCache = {};
   var regExpCount = 0;
   var MAX_REGEXP_COUNT = 250;

   /**
    * Finds ancestors of the given DOM Element (including itself) which have the given CSS Class
    *
    * @param elem - DOM Element to start looking up the heirarchy from
    * @param className - the CSS class name to check for. Case Sensitive
    * @return The closest ancestor DOM Element which has the className specified, or null
    */
   this.findAncestorWithClassName = function(elem, className)
   {
      for (var node = elem; node != null; node = node.parentNode)
      {
         if (this.hasClass( node, className ))
         {
            return node;
         }
      }
      return null;
   }

   /**
    * Finds ancestors of the given DOM Element (including itself) which have the given Tag Name
    *
    * @param elem - DOM Element to start looking up the heirarchy from
    * @param tagName - the Tag Name (e.g. td, div, table etc..) to check for. Case insensitive
    * @return The closest ancestor DOM Element which has the className specified, or null
    */
   this.findAncestorWithTagName = function(elem, tagName)
   {
      tagName = tagName.toUpperCase();
      for (var node = elem; node != null; node = node.parentNode)
      {
         if (node.tagName && node.tagName.toUpperCase() == tagName)
         {
            return node;
         }
      }
      return null;
   }

   /**
    * Returns descendants of the given DOM Element which have the given CSS Class
    *
    * @param elem - DOM element to start looking down the heirarchy from
    * @param className - the CSS class name to check for. Case sensitive
    * @param tagName - the tag name of elements to to consider in search, like for example DIV or TH
    * @return An array of descendants which have the CSS Class specified, or an empty Array if none found
    */
   this.getElementsByClassName = function(elem, className, tagName)
   {
      var elems = new Array();
      var parentNode = elem;
      if (!parentNode)
      {
         parentNode = document;
      }

      tagName = tagName || '*';
      var all = parentNode.getElementsByTagName(tagName);
      for (var i=0;i < all.length;i++)
      {
         var _elem = all[i];
         if (this.hasClass(_elem, className))
         {
            elems[elems.length] = _elem;
         }
      }
      return elems;
   }

   /**
    * Adds the specified CSS Class to the given DOM Element
    *
    * @param elem - The DOM element to which the CSS Class is to be added
    * @param className - The CSS Class to be added
    */
   this.addClass = function(elem, className)
   {
      if (this.hasClass(elem, className))
      {
         return;
      }
      if (elem.className)
      {
         className = elem.className + " " + className;
      }
      elem.className = className;
   }

   /**
    * Removes the specified CSS Class from the given DOM Element. It will remove duplicates also.
    *
    * @param elem - The DOM element from which the CSS Class is to be removed
    * @param className - The CSS Class to be removed
    */
   this.removeClass = function(elem, className)
   {
      if (elem.className == className)
      {
         elem.className = null;
         return;
      }

      if (elem.className == null)
      {
         return;
      }
      var classes = elem.className.split(" ");
      for (var i = classes.length - 1; i >= 0; i--)
      {
         if (classes[i] == className)
         {
            classes[i] = '';
            // don't break - to account for duplicates
         }
      }
      elem.className = classes.join(" ");
   }

   var camelized_prop_cache = {};

   /**
    * Retrieves computed style property value
    *
    * @param dom element for which style info is to be retreived
    * @param style property name to be looked up. This must be in hyphenized form
    */
   this.getStyle = function(elem, styleName)
   {
      var s;
      if (elem.currentStyle)
      {
         var camelStyleName = hyphen2Camel(styleName);
         s = elem.currentStyle[camelStyleName];
      }
      else if (document.defaultView)
      {
         s = document.defaultView.getComputedStyle(elem, null).getPropertyValue(styleName);
      }
      return s;
   }

   /**
    * Converts hyphenated-strings-like-this to camelStringsLikeThis.
    */
   function hyphen2Camel(prop)
   {
      if (!camelized_prop_cache[prop])
      {
         var t = prop.indexOf('-') != -1;
         if(prop.indexOf('-') != -1)
         {
            var a = prop.split("-");
            for (var i = 1; i < a.length; i++)
            {
               a[i] = a[i].substring(0,1).toUpperCase() + a[i].substring(1,a[i].length);
            }
            camelized_prop_cache[prop] = a.join("");
         }
         else
         {
            camelized_prop_cache[prop] = prop;
         }
      }
      return camelized_prop_cache[prop];
   }

   /**
    * Retrieves the containing window of a DOM element
    * this is to facilitate firing functions residing in the same window as the DOM element.
    *
    * @param elem - DOM element whose containing window is desired
    * @return the window containing the elem.
    */
   this.getElementWindow = function (elem)
   {
      var targetWindow = null;
      if (typeof elem.parentWindow != undefined && elem.parentWindow != null)
      {
         // I am a document, give me the window
         targetWindow = elem.parentWindow;
      }
      else if (typeof elem.ownerDocument != undefined && elem.ownerDocument != null)
      {
         // I am a node, give me the window
         if (typeof elem.ownerDocument.parentWindow != undefined && elem.ownerDocument.parentWindow != null)
         {
            // IE style
            targetWindow = elem.ownerDocument.parentWindow;
         }
         else if (typeof elem.ownerDocument.defaultView != undefined && elem.ownerDocument.defaultView != null)
         {
            // Mozilla Style
            targetWindow = elem.ownerDocument.defaultView;
         }
      }
      else if (elem.window == elem)
      {
         // I am a window. give me myself.
         targetWindow = elem;
      }
      return targetWindow;
   }

   /**
    * Traverse the DOM starting from the node specified by elem and apply "visitFunction" on each of the node
    * traversed.
    *
    * @param elem - DOM element where traversal begins
    * @param visitFunction - visitor that operates on each node visited.
    */
   this.traverseDom = function (elem, visitFunction)
   {
      visitFunction(elem);
      if (elem && elem.childNodes && elem.childNodes.length != 0)
      {
         for (var i = 0; i < elem.childNodes.length; i++)
         {
            this.traverseDom(elem.childNodes[i], visitFunction);
         }
      }
   }
}

/******************** Positioning Module ***********************************/

wdk.pos = new function()
{
   var IE_STRICT = document.compatMode && document.compatMode != "BackCompat";
   var IE_QUIRKS = document.compatMode && document.compatMode == "BackCompat";

   /**
    * Rect Object Constructor to represent a rectangle based on top, left co-ordinates
    *
    * Has top, left, height, width, bottom and right public attributes.
    *
    * @param top - The top co-ordinate of the rectangle
    * @param left - The left co-ordinate of the rectangle
    * @param height - The height co-ordinate of the rectangle in pixels
    * @param width - The width co-ordinate of the rectangle in pixels
    */
   this.Rect = Rect;

   /**
    * Returns a wdk.pos.Rect object, respresenting the bounding box of the element passed in.
    *
    * By default if the bAccountForScroll parameter is not provided, element scroll offsets are accounted
    * for when calculating Rect.top, Rect.left. If scroll offsets are not required, set bAccountForScroll to false;
    *
    * @param elem - The DOM Element for which a Rect object is required
    * @param bAccountForScroll - If true(default), scroll offsets are accounted for when calculating Rect.top, Rect.left
    *                            If false, scroll offsets are ignored when calculating Rect.top, Rect.left.
    * @return rect - a wdk.pos.Rect object, with attributes set to match the elem's bounding box
    */
   this.getElementRect = function(elem, bAccountForScroll)
   {
      if (typeof bAccountForScroll == "undefined")
      {
         // Default to true if not passed in.
         bAccountForScroll = true;
      }
      
      var offsetLeft  = 0;
      var offsetTop   = 0;
      // IE case      
      if (document.documentElement.getBoundingClientRect) 
      {
         var box = elem.getBoundingClientRect();
         offsetLeft = box.left;
         offsetTop = box.top;
         if (!bAccountForScroll)
         {
            var clRect = wdk.pos.getClientRect();
            offsetLeft += clRect.left;
            offsetTop += clRect.top;
         }
      }
      else 
      {
         var offsetTrail = elem;
         var scrollTop   = 0;
         var scrollLeft  = 0;
         
         while(offsetTrail)
         {
            offsetLeft += offsetTrail.offsetLeft;
            offsetTop  += offsetTrail.offsetTop;
         
            if (bAccountForScroll && offsetTrail.tagName != "BODY" && offsetTrail.tagName != "HTML")
            {
               scrollTop += offsetTrail.scrollTop;
               scrollLeft += offsetTrail.scrollLeft;
            }
         
            offsetTrail = offsetTrail.offsetParent;
         }
         
         if (bAccountForScroll)
         {
            offsetTop -= scrollTop;
            offsetLeft -= scrollLeft;
         }
      }
      return new Rect(offsetTop, offsetLeft, elem.offsetHeight, elem.offsetWidth);
   }

   /**
    * Sets position of an element within the page. The element's position attribute should be absolute or relative.
    * If element's position is static, the function changes it to relative.
    *
    * @param elem dom element to be positioned
    * @param pageXY array of length 2 containing the desired x and y page coordinates for the element.
    *    To set position in one axis, set the other value to null.
    */
   this.setElementPosition = function(elem, pageXY)
   {
      setElementPosition(elem, pageXY, false);
   }

   /**
    * Helper function for this.setElementPosition hiding private function args.
    */
   function setElementPosition(elem, pageXY, recursed)
   {
      var posType = wdk.dom.getStyle(elem, 'position');
      // if positioned statically, we need to change position to relative,
      // to be able to move the element relative to it's current position
      if (posType == "static") {
         elem.style.position = posType ="relative";
      }

      var currentPageXY = wdk.pos.getElementRect(elem);
      // position in x axis
      setXOrY(elem, posType, currentPageXY, pageXY[0], "left", "offsetLeft");
      // now position in y axis as above
      setXOrY(elem, posType, currentPageXY, pageXY[1], "top", "offsetTop");

      // Check if position is correct, and if not retry
      // if last arg is true, we've recursed below already
      if (!recursed) {
         var newXY = wdk.pos.getElementRect(elem);
         if (newXY.left != pageXY[0] || newXY.right != pageXY[1]) {
            setElementPosition(elem, pageXY, true);
         }
      }
   }

   /**
    * Helper for setElementPosition.
    */
   function setXOrY(elem, posType, pageXY, newPos, topOrLeft, offsetTopOrLeft)
   {
      if (newPos !== null) {
         var elemTopOrLeft = parseInt(wdk.dom.getStyle(elem, topOrLeft), 10);
         // Make sure we're dealing with an int value - we may get "auto" instead of pixel value
         if (isNaN(elemTopOrLeft)) {
            // if we didn't get a number above, we're going to assume current element offset
            // or 0 otherwise
            elemTopOrLeft = (posType == "absolute") ? elem[offsetTopOrLeft] : 0;
         }
         // Position the element.
         elem.style[topOrLeft] = newPos - pageXY[topOrLeft] + elemTopOrLeft + "px";
      }
   }

   /**
    * Returns a wdk.pos.Rect object, representing the Client Viewport (the visible area of the window/frame)
    *
    * The top/left of the Rect determines how far the document is scrolled vertically/horizontally
    * The height/width of the Rect determines the size of the viewport
    *
    * @return rect - a wdk.pos.Rect object, with attributes set to match the Client Viewport bounding box
    */
   this.getClientRect = function()
   {
      var top = 0;
      var left = 0;
      var width = 0;
      var height = 0;

      // Need undefined check to account for 0
      if (typeof self.pageXOffset != 'undefined')
      {
         top = self.pageXOffset;
         left = self.pageYOffset;
         width = self.innerWidth;
         height = self.innerHeight;
      }
      else if (IE_STRICT)
      {
         var docElement = document.documentElement;
         top = docElement.scrollTop;
         left = docElement.scrollLeft;
         width = docElement.clientWidth;
         height = docElement.clientHeight;
      }
      else if(IE_QUIRKS)
      {
         var body = document.body;
         top = body.scrollTop;
         left = body.scrollLeft;
         width = body.clientWidth;
         height = body.clientHeight;
      }

      return new Rect(top, left, height, width);
   }

   /**
    * Given a Left co-ordinate relative to the Client Viewport(frame/window), make it relative to the entire
    * page/document, which adjusts it for the amount the page is scrolled.
    *
    * @param clientLeft - The left co-ordinate relative to the frame/window
    * @return the left co-ordinate relative to the entire page/document
    */
   this.clientLeftToPageLeft = function(clientLeft)
   {
      if (typeof self.pageXOffset != 'undefined')
      {
         // Most browsers
         return clientLeft + self.pageXOffset;
      }
      else if (IE_STRICT)
      {
         return clientLeft + document.documentElement.scrollLeft;
      }
      else if (IE_QUIRKS)
      {
         return clientLeft + document.body.scrollLeft;
      }

      return clientLeft;
   }

   /**
    * Given a Top coordinate relative to the Client Viewport(frame/window), make it relative to the entire
    * page/document, which adjusts it for the amount the page is scrolled.
    *
    * @param clientTop - The Top co-ordinate relative to the frame/window
    * @return the Top co-ordinate relative to the entire page/document
    */
   this.clientTopToPageTop = function(clientTop)
   {
      if (typeof self.pageYOffset != 'undefined')
      {
         // Most browsers
         return clientTop + self.pageYOffset;
      }
      else if (IE_STRICT)
      {
         return clientTop + document.documentElement.scrollTop;
      }
      else if (IE_QUIRKS)
      {
         return clientTop + document.body.scrollTop;
      }

      return clientTop;
   }

   /**
    * Returns the cursor position for the provided event, relative to the origin of the page/document
    * It adjusts to account for the amount the page is scrolled.
    *
    * The position is returned as an Object with x,y attributes set
    *
    * @param e - Event object
    * @return An Object with x, y attributes reflecting the cursor position for the event
    */
   this.getCursorPosition = function(e)
   {
      e = e || window.event;
      var cursor = {x:0, y:0};

      if (e.pageX || e.pageY)
      {
        cursor.x = e.pageX;
        cursor.y = e.pageY;
      }
      else
      {
        cursor.x = e.clientX +
            (document.documentElement.scrollLeft ||
            document.body.scrollLeft) -
            document.documentElement.clientLeft;
        cursor.y = e.clientY +
            (document.documentElement.scrollTop ||
            document.body.scrollTop) -
            document.documentElement.clientTop;
      }

      return cursor;
   }

   /**
    * Tests whether or not the 2 rectangles passed in intersect each other or not.
    * By default, it will return true if the rectangles intersect and false otherwise.
    *
    * If the bReturnIntersection param is set to true, it will return a wdk.pos.Rect object representing
    * the interesection or null, if no intersection exists
    *
    * @param rect1, rect2 - wdk.pos.Rect objects which need to be tested for intersectin
    * @param bReturnIntersection - if true, a wdk.pos.Rect object representing the intersection is returned
    * @return true, if the rectangles intersect, false otherwise OR the rectangle representing the intersection
    */
   this.intersect = function(rect1, rect2, bReturnIntersection)
   {
      var intersection = null;

      var top = Math.max(rect1.top, rect2.top);
      var bottom = Math.min(rect1.bottom, rect2.bottom);

      var left = Math.max(rect1.left, rect2.left);
      var right = Math.min(rect1.right, rect2.right);

      if (top > bottom || left > right)
      {
         intersection = (bReturnIntersection) ? null : false;
      }
      else
      {
         intersection = (bReturnIntersection) ? new Rect(top, left, bottom-top, right-left) : true;
      }
      return intersection;
   }

   /**
    * Returns the width of the scrollbar.
    */
   this.getScrollbarWidth = function()
   {
      if (typeof this.getScrollbarWidth.width == "undefined")
      {
         var outer = document.createElement("DIV");
         with (outer.style) {
            top = "-300px";
            left = "0px"
            width = height = "100px";
            overflow = "scroll";
            position = "absolute";
         }
         var inner = document.createElement("DIV");
         with (inner.style) {
            width = height = "400px";
         }
         outer.appendChild(inner);
         document.body.appendChild(outer);

         var width = outer.offsetWidth - outer.clientWidth;

         document.body.removeChild(outer);
         outer.removeChild(inner);
         outer = inner = null;

         this.getScrollbarWidth.width = width;
      }
      return this.getScrollbarWidth.width;
   }

   function Rect(top, left, height, width)
   {
      this.top = top;
      this.left = left;
      this.height = height;
      this.width = width;

      // Calculated Values
      this.right = left + width;
      this.bottom = top + height;
   }
}

/******************** Events Module ***********************************/

wdk.events = new function()
{
   var listenerRegistry = new EventListenerRegistry();

   /**
    * Returns an object that supports the W3C DOM Level 2 EventTarget interface [ addEventListener, removeEventListner,
    * dispatchEvent ]. This object can be used to add and remove event listeners for the node.
    *
    * All event listeners registered through this object are removed on page unload to avoid a memory leak issue
    * in certain browsers
    *
    * NOTE: Currently Safari 1.x, 2.x support for dblclick is broken when using W3C's addEventListener method
    * Hence addEventListener falls back to elem.ondblclick for this scenario and the capture flag is ignored
    *
    * @param elem - DOM Element for which a EventTarget interface is required
    * @return eventTarget - Object which supports W3C EventTarget interface
    */
   this.getEventTarget = function(elem)
   {
      if (elem.addEventListener)
      {
         // Node supports the interface natively, but we need to wrap for memory leak cleanup
         return new EventTargetW3C(elem);
      }
      else if (elem.attachEvent)
      {
         // Need to return our WinIE glue code.
         return new EventTargetIE(elem);
      }
   }

   this.keycodes =
   {
      TAB : 9,
      ENTER : 13,
      LEFT_ARROW : 37,
      UP_ARROW : 38,
      RIGHT_ARROW : 39,
      DOWN_ARROW : 40,
      CONTEXT_MENU : 93,
      ESC : 27,
      SPACEBAR : 20
   };

   function EventListenerRegistry()
   {
      this.m_registry = new Array();
   }

   EventListenerRegistry.prototype.add = function(evtTarget, evtName, func, bCapture)
   {
      this.m_registry.push({ evtTarget: evtTarget, evtName: evtName, func: func, bCapture : bCapture});
   }

   EventListenerRegistry.prototype.cleanup = function()
   {
      var registry = this.m_registry;
      for (var i = 0; i < registry.length; i++)
      {
         var listener = registry[i];
         var evtTarget = listener.evtTarget;
         if (evtTarget)
         {
            // Safari - needs special case handling for dblclick. Was hoping to avoid this kind of thing
            if (listener.evtName == "dblclick" && navigator.userAgent.toLowerCase().indexOf("safari") != -1)
            {
               listener.evtTarget.ondblclick = null;
            }
            else
            {
               evtTarget.removeEventListener(listener.evtName, listener.func, listener.bCapture);
            }
         }
      }
   }


   /**
    * PRIVATE: DOM Level 2 EventTarget impl for W3C Browsers
    */
   function EventTargetW3C(node)
   {
      this.m_node = node;
   }

   EventTargetW3C.prototype.addEventListener = function(evtName, func, bCapture)
   {
      listenerRegistry.add(this.m_node, evtName, func, bCapture);

      // Safari - needs special case handling for dblclick. Was hoping to avoid this kind of thing
      if (evtName == "dblclick" && navigator.userAgent.toLowerCase().indexOf("safari") != -1)
      {
         this.m_node.ondblclick = func;
      }
      else
      {
         this.m_node.addEventListener(evtName, func, bCapture);
      }
   }

   EventTargetW3C.prototype.removeEventListener = function(evtName, func, bCapture)
   {
      this.m_node.removeEventListener(evtName, func, bCapture);
   }

   EventTargetW3C.prototype.dispatchEvent = function(evt)
   {
      return this.m_node.dispatchEvent(evt);
   }

   /**
    * PRIVATE: DOM Level 2 EventTarget impl for WinIE
    */
   function EventTargetIE(node)
   {
      this.m_node = node;
   }

   EventTargetIE.prototype.addEventListener = function (evtName, func, bCapture)
   {
      listenerRegistry.add(this, evtName, func, bCapture);
      var elem = this.m_node;

      // WinIE sets the 'this' object in event handlers to 'window'. Use this proxy
      // function to make the 'this' object refer to the handler's element.
      var proxy = function()
      {
         func.call( elem, new EventIE(window.event, elem) );
      };

      function ProxyHelper(func, proxy)
      {
         this.m_func = func;
         this.m_proxy = proxy;
      };

      ProxyHelper.prototype.getProxy = function()
      {
         return this.m_proxy;
      }

      ProxyHelper.prototype.matches = function(func)
      {
         return this.m_func === func;
      }

      var key = evtName + func + bCapture;
      if (elem.attachEvent( "on" + evtName, proxy ))
      {
         if (!elem[key])
         {
            elem[key] = new Array();
         }
         var proxyHelperArray = elem[key];
         proxyHelperArray.push(new ProxyHelper(func, proxy));

         if (bCapture)
         {
            elem.setCapture(true);
         }
      }
   }

   EventTargetIE.prototype.removeEventListener = function(evtName, func, bCapture)
   {
      var elem = this.m_node;
      var key = evtName + func + bCapture;
      var proxyHelperArray = elem[key];
      var proxy = null;
      var arrayIndex;
      if (!!proxyHelperArray)
      {
         for (var arrayIterIndex = 0; arrayIterIndex < proxyHelperArray.length; arrayIterIndex++)
         {
            var currentProxyHelper = proxyHelperArray[arrayIterIndex];
            if (currentProxyHelper.matches(func))
            {
               arrayIndex = arrayIterIndex;
               proxy = currentProxyHelper.getProxy();
               break;
            }
         }
      }

      if (proxy)
      {
         elem.detachEvent( "on" + evtName, proxy );
         proxyHelperArray.splice(arrayIndex, 1);

         if (bCapture)
         {
            elem.releaseCapture();
         }
      }
   }

   /**
    * We use the fireEvent method for Internet Explorer
    */
   EventTargetIE.prototype.dispatchEvent = function(evt)
   {
      return this.m_node.fireEvent( "on" + evt.type, evt );
   }

   /**
    * PRIVATE : Implement the DOM Event, UIEvent and MouseEvent interfaces on the IE event object.
    */
   function EventIE(eventObj, node)
   {
      EventIE.prototype.CAPTURING_PHASE = 1;
      EventIE.prototype.AT_TARGET = 2;
      EventIE.prototype.BUBBLING_PHASE = 3;

      this.m_event = eventObj;

      // Event properties
      this.type = eventObj.type;
      this.target = eventObj.srcElement;
      this.currentTarget = node;
      this.eventPhase = EventIE.prototype.BUBBLING_PHASE;
      this.bubbles = true;
      this.cancelable = true;
      this.timeStamp = new Date();

      // UIEvent properties
      this.view = window;
      this.detail = 0;

      // MouseEvent properties
      this.screenX = eventObj.screenX;
      this.screenY = eventObj.screenY;
      this.clientX = eventObj.clientX;
      this.clientY = eventObj.clientY;
      this.pageX = wdk.pos.clientLeftToPageLeft( eventObj.clientX );
      this.pageY = wdk.pos.clientTopToPageTop( eventObj.clientY );
      this.ctrlKey = eventObj.ctrlKey;
      this.shiftKey = eventObj.shiftKey;
      this.altKey = eventObj.altKey;
      this.metaKey = false;
      this.keyCode = eventObj.keyCode;
      this.relatedTarget = (eventObj.fromElement) ? eventObj.fromElement : eventObj.toElement;

      switch (eventObj.button)
      {
      case 1:    // Left
         this.button = 0; break;
      case 2:    // Right
         this.button = 2; break;
      case 4:    // Middle
         this.button = 1; break;
      }

   }

   EventIE.prototype.stopPropagation = function()
   {
      this.m_event.cancelBubble = true;
   }

   EventIE.prototype.preventDefault = function()
   {
      this.m_event.returnValue = false;
   }

   EventIE.prototype.initEvent = function(eventTypeArg, canBubbleArg, cancelableArg)
   {
      this.m_event.type = eventTypeArg;
      this.m_event.bubbles = canBubbleArg;
      this.m_event.cancelable = cancelableArg;
   }

   var eTarget = this.getEventTarget(window);
   eTarget.addEventListener("unload", function(){listenerRegistry.cleanup();}, false);
}

/******************** Forms Module ************************************/

wdk.forms = new function()
{
   // Types of fields which should be excluded from form submissions
   var excludedFieldTypes = {
      "button":true, "submit":true, "reset":true,
      "image":true, "file":true, "fieldset":true};

   /**
    * Retrieve all of the parameter name / value pairs to be included in the
    * request when the specified form is submitted. An optional filter allows
    * control over which fields are actually included.
    *
    * A filter is a predicate function that is passed a DOM node and returns
    * true or false depending on whether the field should be included or not.
    *
    * @param formNode The DOM node for the form element.
    * @param formFilter The filter to use to control field inclusion.
    *
    * @return The parameter name / value pairs as a MultiMap.
    */
   this.getFormFields = function(formNode, formFilter)
   {
      var fields = new wdk.lang.MultiMap();

      for (var i = 0; i < formNode.elements.length; i++)
      {
         var node = formNode.elements[i];
         if (node != null && formFilter(node))
         {
            var name = node.name || node.id;
            if (!name)
            {
               continue;
            }

            var type = node.type.toLowerCase();
            if (type == "checkbox" || type == "radio")
            {
                if (node.checked)
                {
                   fields.add(name, encodeURIComponent(node.value));
                }
            }
            else if (type == "select-multiple")
            {
               for (var j = 0; j < node.options.length; j++)
               {
                  if (node.options[j].selected)
                  {
                     fields.add(name, encodeURIComponent(node.options[j].value));
                  }
               }
            }
            else
            {
               fields.add(name, encodeURIComponent(node.value));
            }
         }
      }

      return fields;
   }

   /**
    * Determine whether or not the specified field should be included when a
    * form is submitted.
    *
    * @param node The field to check.
    *
    * @return true if the field should be excluded; false otherwise.
    */
   this.isExcludedField = function(node)
   {
      return (node.tagName.toLowerCase() == "fieldset"
         || excludedFieldTypes[node.type.toLowerCase()] == true);
   }

   /**
    * A form filter which includes only fields which are system fields, and
    * thus no application-specific fields.
    *
    * @param node The node to check.
    *
    * @return true if the node should be included; false otherwise.
    */
   this.filterSystemFields = function(node)
   {
      var name = node.id || node.name;
      var system = !!name.match(/^__dmf/);
      var excluded = wdk.forms.isExcludedField(node);
      return (system && !excluded);
   }

   /**
    * A form filter which includes all fields, so including both system and
    * application-specific fields.
    *
    * @param node The node to check.
    *
    * @return true if the node should be included; false otherwise.
    */
   this.filterAllFields = function(node)
   {
      return !wdk.forms.isExcludedField(node);
   }

   /**
    * Check the current state of special keys (i.e. shift, control and alt)
    * and add parameters to the provided MultiMap for those which are in a
    * pressed state.
    *
    * @param mmap The MultiMap to which the parameters should be added.
    */
   this.addKeyStates = function(mmap)
   {
      var wnd = getTopLevelWnd();

      // Append key press arguments - set by setKeys()

      if (typeof(wnd.shiftKeyPressed) != "undefined" && wnd.shiftKeyPressed)
      {
         mmap.add("shiftKeyPressed", "true");
      }

      if (typeof(wnd.ctrlKeyPressed) != "undefined" && wnd.ctrlKeyPressed)
      {
         mmap.add("ctrlKeyPressed", "true");
      }

      if (typeof(wnd.altKeyPressed) != "undefined" && wnd.altKeyPressed)
      {
         mmap.add("altKeyPressed", "true");
      }
   }

   /**
    * Submit the specified form, using the specified preferences to control
    * the inline request process.
    *
    * @param formNode The form to be submitted.
    * @param prefs The InlineRequestPreferences instance to use in controlling
    *              the form submission.
    * @param filter The filter for determining which form fields should be
    *               submitted.
    */
   this.submitInline = function(formNode, prefs, filter)
   {
      // If no filter is specified, submit all fields
      if (typeof filter == "undefined")
      {
         filter = wdk.forms.filterAllFields;
      }

      // Retrieve method and URL from the form
         prefs.setMethod(formNode.method);
         prefs.setUrl(formNode.action);

      // Retrieve the form fields and add them as request parameters
      prefs.addParameters(wdk.forms.getFormFields(formNode, filter));
      InlineRequestEngine.sendRequest(prefs);
   }
}

/******************** Language extensions ***********************************/

wdk.lang = new function()
{
   /**
    * Returns the index of value in an array. -1 is returned if value is not found.
    * @param array array object
    * @param value value to be found
    * @param identity if true, comparisons are made with identity (===) comparison
    */
   this.find = function(array, value, identity)
   {
      var end = array.length;
      if (identity)
      {
         for (var i=0; i<end; i++)
         {
            if(array[i] === value)
            {
               return i;
            }
         }
      }
      else
      {
         for (var i=0; i<end; i++)
         {
            if(array[i] == value)
            {
               return i;
            }
         }
      }
      return -1;
   }

   /**
    * Returns a wrapper function which will run 'fun' in the scope of 'obj'.
    */
   this.hitch = function(obj, fun)
   {
      var newFun = (typeof(fun) == "string" ? obj[fun] : fun) || function(){};

      return function() {
         return newFun.apply(obj, arguments);
      }
   }

   /********** MultiMap ***********************************************/

   // Export the MultiMap constructor, making the class public
   this.MultiMap = MultiMap;

   /**
    * MultiMap
    *
    * A MultiMap is a (hash) map in which each key can have multiple values. For
    * each key, the values are stored as an array, even if there is only one.
    */
   function MultiMap()
   {
      this.map = {};
   }

   /**
    * Retrieve the entire map of key / value pairs.
    *
    * @return The entire map.
    */
   MultiMap.prototype.getMap = function()
   {
      return this.map;
   }

   /**
    * Set the entire map of key / value pairs, replacing any values that may have
    * previously existed. If the specified map is not a MultiMap, it is converted
    * to one automatically.
    *
    * @param newMap The new map of key / value pairs.
    */
   MultiMap.prototype.setMap = function(newMap)
   {
      this.map = {};
      this.addMap(newMap);
   }

   /**
    * Add to this map all of the key / value pairs from a specified map. The
    * specified map may be a MultiMap or a plain JavaScript object.
    *
    * @param newMap The map of key / value pairs to add.
    */
   MultiMap.prototype.addMap = function(newMap)
   {
      // If it's a MultiMap, get the actual map
      if (newMap instanceof wdk.lang.MultiMap)
      {
         newMap = newMap.getMap();
      }

      for (var key in newMap)
      {
         var oldValues = this.map[key];
         var newValues = newMap[key];
         if (typeof oldValues == "undefined")
         {
            if (!(newValues instanceof Array))
         {
               newValues = [newValues];
            }
            this.map[key] = newValues;
         }
         else
         {
            if (!(newValues instanceof Array))
            {
               oldValues.push(newValues);
            }
            else
            {
               this.map[key] = oldValues.concat(newValues);
            }
         }
      }
   }

   /**
    * Add the specified value to the array of values under the specified key.
    *
    * @param key The key under which to store the value.
    * @param value The value to store under that key
    */
   MultiMap.prototype.add = function(key, value)
   {
      var values = this.map[key];
      if (!values)
      {
         values = [];
         this.map[key] = values;
      }
      values.push(value);
   }

   /**
    * Store the specified value under the specified key, replacing any other
    * values that may have existed for that key.
    *
    * @param key The key under which to store the value.
    * @param value The value to store under that key
    */
   MultiMap.prototype.set = function(key, value)
   {
      this.map[key] = [value];
   }

   /**
    * Retrieve the array of values corresponding to the specified key. Note that
    * an array is returned even for single values.
    *
    * @param key The key for which to retrieve the values.
    *
    * @return The array of values, or null if there are none.
    */
   MultiMap.prototype.get = function(key)
   {
      return this.map[key];
   }
}

/******************** General Utils ***********************************/

wdk.common = new function()
{
   /**
    * Returns a callable function, regardless of the input parameters. If a
    * callable function is provided, it is simply returned; if a function name
    * is provided, and a function with that name exists in the specified
    * context, that is returned; otherwise a no-op function is returned.
    *
    * @param name The name of the function, used as a reference when logging errors.
    * @param value The value (function or string) of the function to locate.
    * @param context The context in which a named function should be looked up.
    *                If not defined, global context is used.
    *
    * @return A callable function, which will be a no-op function if an error
    *         is encountered.
    */
   this.getSafeFunction = function(name, value, context)
   {
      if (typeof context == "undefined" || context == null)
      {
         context = window;
      }

      var fn = value;

      if (fn && (typeof fn == "string" || fn instanceof String))
      {
         fn = context[fn];
      }

      if (fn && (typeof fn == "function" || fn instanceof Function))
      {
         return fn;
      }

      if (Trace_INLINEREQUEST)
      {
         Trace_println("Invalid " + name + " function specified: " + value);
      }

      return (function() {});
   }

   /********** UrlBuilder *********************************************/

   // Export the UrlBuilder constructor, making the class public
   this.UrlBuilder = UrlBuilder;

   /**
    * UrlBuilder
    *
    * Encapsulates the work of constructing a URL with a query string.
    */
   function UrlBuilder (baseUrl)
   {
      this.baseUrl = baseUrl;
      this.parameters = new wdk.lang.MultiMap();
   }

   /**
    * Set the parameters to be added to the URL. The parameters should be added
    * as a MultiMap. However, other objects will be converted as necessary.
    *
    * @param parameters The parameters as a MultiMap or other object.
    */
   UrlBuilder.prototype.setParameters = function(parameters)
   {
      if (!(parameters instanceof wdk.lang.MultiMap))
      {
         var map = new wdk.lang.MultiMap();
         map.setMap(parameters);
         parameters = map;
      }
      this.parameters = parameters;
   }

   /**
    * Add an individual parameter to the URL. If a parameter with this name
    * already exists, the new value is added to those already present.
    *
    * @param name The name of the parameter.
    * @param value The value of the parameter.
    */
   UrlBuilder.prototype.addParameter = function(name, value)
   {
      this.parameters.add(name, value);
   }

   /**
    * Retrieve the query string for this URL. Note that any query string that was
    * attached to the base URL is _not_ included.
    *
    * @return The query string for this URL.
    */
   UrlBuilder.prototype.getQueryString = function()
   {
      var queryString = "";
      var map = this.parameters.getMap();
      for (var name in map)
      {
         var values = map[name];
         for (var i = 0; i < values.length; i++)
         {
            queryString += name;
            queryString += "=";
            queryString += values[i];
            queryString += "&";
         }
      }

      return queryString.substr(0, queryString.length - 1);
   }

   /**
    * Retrieve the base URL. This is the same URL that was originally passed to
    * the constructor, without any parameters that may have been added later.
    *
    * @return The base URL.
    */
   UrlBuilder.prototype.getBaseUrl = function()
   {
      return this.baseUrl;
   }

   /**
    * Retrieve the complete URL. This is the base URL along with any parameters
    * that have been added (i.e. along with the query string).
    *
    * @return The complete URL.
    */
   UrlBuilder.prototype.getUrl = function()
   {
      var url = this.baseUrl;
      var queryString = this.getQueryString();
      if (queryString && queryString.length > 0)
      {
         url += (url.indexOf('?') == -1 ? "?" : "&");
         url += queryString;
      }

      return url;
   }

   /**
    * Timeout Object Constructor
    *
    * Provides a way to invoke a function after a fixed delay, or at repeated period intervals
    *
    * The function pointer to invoke, the period, and whether or not single or repeated invokation is required
    * are specified when creating a new Timeout object
    *
    * It supports the following methods..
    *
    *   set() - set the timeout event specified for the Timeout object
    *   cancel() - cancel a timeout event which has already been set
    *   isSet() - determine if a timeout event has already been set
    *   reset() - if already set, cancel the in-progress timeout and set it again
    *
    * @param func - Function pointer to the function which is to be invoked after the specified period
    * @param period - The period in ms, after which the function is to be invoked
    * @param bRepeating - whether or not the function is to be invoked repeatedly [ default is false ]
    */
   this.Timeout = Timeout;

   function Timeout(func, period, bRepeating)
   {
      this.fn = func;
      this.period = period;
      this.bRepeating = bRepeating;
      this.timerId = null;
   }

   Timeout.prototype.set = function()
   {
      this.timerId = (this.bRepeating) ? window.setInterval(this.fn, this.period) : window.setTimeout(this.fn, this.period);
   }

   Timeout.prototype.cancel = function()
   {
      if (this.timerId != null)
      {
         if (this.bRepeating)
         {
            window.clearInterval(this.timerId);
         }
         else
         {
            window.clearTimeout(this.timerId);
         }
         this.timerId = null;
      }
   }

   Timeout.prototype.isSet = function()
   {
      return (this.timerId != null);
   }

   Timeout.prototype.reset = function()
   {
      if (this.isSet())
      {
         this.cancel();
      }
      this.set();
   }

   /**
    * given a property in "property(.property)*" format, set val to that property
    * of the given obj.
    * @param obj  The object whose property is to be changed
    * @param prop property in "property(.property)*" format. i.e.
    *             to set the "visibility" property of the "style" object of the
    *             given object, pass in "style.visibility"
    * @param val  The value to set the property to.
    */
   this.setProperty = function (obj, prop, val)
   {
      if (!obj || !prop)
      {
         return false;
      }

      var props = prop.split('.');
      for (var i=0; i < props.length - 1; i++)
      {
         obj = obj[props[i]];
         if(!obj)
         {
            return false;
         }
      }

      obj[props[props.length - 1]] = val;
      return true;
   }

   /**
    * given a property in "property(.property)*" format, get val of the property
    * of the given obj.
    * @param obj  The object whose property is desired
    * @param prop property in "property(.property)*" format. i.e.
    *             to get the "visibility" property of the "style" object of the
    *             given object, pass in "style.visibility"
    * @return  The value of the property of the object
    */
   this.getProperty = function (obj, prop)
   {
      if (!obj || !prop)
      {
         return false;
      }

      var props = prop.split('.');
      for (var i=0; i < props.length - 1 ; i++)
      {
         obj = obj[props[i]];
         if(!obj)
         {
            return false;
         }
      }
      return obj[props[props.length - 1]];
   }
}

} // end if (typeof wdk == "undefined")


/******************** Locale specific functions ***********************************/

wdk.locale = new function()
{
   /**
    *  This function is used for finding if the application locale is Right-to-Left.
    */
   this.isRightToLeft = function()
   {
      //default to LTR mode in case the global var is not defined
      if(typeof(g_RTLLocale) == "undefined" || g_RTLLocale == null)
      {
         return false;
      }
      //convert string to boolean type
      if(g_RTLLocale == "true")
      {
         return true;
      }
      else
      {
         return false;
      }
   }
}


