/** Copyright (c) 2001-2010 Laszlo Systems, Inc. All Rights Reserved. */
function Webtop() {
    //error msg that should be used in case of a javascript error
    this.initError = "The application encountered an error and some " +
    "functionality may not work. Please report the problem to your help " +
    "desk or system administrator. Error code: ";

    this.originalPageTitle = document.title;
    this.isDHTML = false;

    //used by resizer
    this.lastWidthUnder;
    this.lastHeightUnder;

    //just default values, these should be set from server configuration
    this.minWidth = 300;
    this.minHeight = 300;
    this.preferredLocale;

    //reference to the div that contains the wrapper table. Used to detect
    //when to apply scollbars
    this.container = null;

    //attach init to onload
    this.init = function(isDHTML) {
        this.isDHTML = isDHTML;
        this.container = document.getElementById("container");
        
        var me = this;
        //browsers except IE support min-width and min-height CSS
        if (wgBrowser.isie) {
            window.onresize = function() {
                me.handleResize();
            }
        } else if (wgBrowser.issafari) {//fix the safari height 100% issue
            var appDiv = document.getElementById("appDiv");
            appDiv.style.minWidth = this.minWidth;
            
            //fix EM-15158. 
            var footerCell = document.getElementById("footerCell");
            var headerCell = document.getElementById("headerCell");
            appDiv.style.minHeight = this.minHeight - footerCell.parentNode.clientHeight - headerCell.parentNode.clientHeight;
        } else {
            this.container.style.minWidth = this.minWidth;
            this.container.style.minHeight = this.minHeight;
        }
        
        //To minimize a platform issue in FF that causes the browser to get
        //confused whether the swf or the browser has focus.
        //Capture backspace keypresses outside of the swf so they don't
        //navigate the user back
        if (!this.isDHTML) window.document.onkeydown = function(e){me.captureBackspace(e);}
        
        //perform resize to initialize lastWidthUnder and lastHeightUnder
        if (wgBrowser.isie) this.handleResize();
    }

    //capture backspace keypresses outside of the swf so they don't navigate the user back
    //attach this as a handler to the document.onkeydown event (window.document.onkeydown = captureBackspace;)
    this.captureBackspace = function(e) {
        // Getting this.parentWindow.event instead of window.event so this handler works when attached
        // to the iframe.document as well as the window.document
        // since this handler forms a closure "window" will always be the window of the parent page
        // and not the iframe. this.parentWindow will resolve to the window of whatever document
        // the event is attached to.
        if (!e) e = this.parentWindow.event;
        var k = e['keyCode'];
        if (k >= 0) {
            if (k == 8) {
                this.returnFocus();
                e.cancelBubble = true;
                e.returnValue = false;
                return false;
            }
        }
    }

    //Apply scrollbars in IE when window is sized down under min width and height
    //Other browsers support min-width and min-height in CSS
    this.handleResize = function() {
        var minW = this.minWidth;
        var minH = this.minHeight;
        if (document.body.offsetWidth <= minW && !this.lastWidthUnder) {
            this.lastWidthUnder = true;
            this.container.style.width = minW + 'px';
        } else if (document.body.offsetWidth > minW && this.lastWidthUnder) {
            this.lastWidthUnder = false;
            this.container.style.width = "100%";
        }
        
        if (document.body.offsetHeight <= minH && !this.lastHeightUnder) {
            this.lastHeightUnder = true;
            this.container.style.height = minH + 'px';
        } else if (document.body.offsetHeight > minH && this.lastHeightUnder) {
            this.lastHeightUnder = false;
            this.container.style.height = "100%";
        }
    }

    //return focus to the <embed> or to an attachment window if one is on top
    this.returnFocus = function() {
        if (this.isDHTML) return;
        var theSwf = document.getElementById('lzapp');
        if (theSwf) theSwf.focus();
        this.forceIframesVisible();
    }

    //call this after the swf takes focus to make the iframe visible above it again
    this.forceIframesVisible = function() {
        //we only need to do this in IE
        if (!wgBrowser.isie) return;
        for (var id in Lz.iframemanager.__frames) {
            var iframe = Lz.iframemanager.__frames[id];
            if (iframe) {
                //this forces the iframe to be shown if it should be visible
                if (iframe.style.display == 'block') {
                    iframe.style.display = 'none';
                    iframe.style.display = 'block';
                }
            }
        }
    }

    //call this function to reload an adFrame.
    this.reloadAdFrame = function(adFrame) {
        adFrame.location.reload();
    }

    //set the title of the page, title will be a concatenation of preTitle, 
    //browserTitle, postTitle where browserTitle is the original title of the page
    //stored when the app is initialized
    this.setTitle = function(preTitle, postTitle) {
        if (preTitle == null || preTitle == undefined) {
            preTitle = "";
        }
        if (postTitle == null || postTitle == undefined) {
            postTitle = "";
        }
        var newTitle = preTitle + this.originalPageTitle + postTitle;
        document.title = newTitle;
    }

    this.writeApp = function(swfConfig, lzr, debug, backtrace, baseUrl) {
        if (swfConfig['locale']) {
            //baseUrl = "main-" + swfConfig.locale + ".lzr=swf8.swf";
        }

        // build the app url
        var lzt = lzr == 'dhtml' ? 'object' : 'swf';
        var timer = wgBrowser.getQueryVariable("timer");

        var wmode = 'opaque';
        // in IE the wmode must be window or mouse events won't get to the swf
        if (wgBrowser.isie || !wgMail['useiframe']) wmode = 'window';

        var appUrl = baseUrl +
        "?lzt=" + lzt +
        "&lzr=" + lzr +
        "&debug=" + debug +
        "&lzbacktrace=" + debug +
        (timer ? "&timer=" + timer : "");

        // add config parameters 
        for (var k in swfConfig) {
            var value = swfConfig[k];
            appUrl += "&lzmc_" + typeof(value) + "_" + k + "=" + escape(value);
        }

        //pass in the wrapper url to be used in reload
        appUrl += '&lzmc_string_reloadurl=' + escape(window.top.location.href);

        if (lzr == 'dhtml') {
            Lz.dhtmlEmbed({
                url: appUrl,
                bgcolor: '#898989',
                width: '100%',
                height: '100%',
                id: 'lzapp',
                appenddivid: 'appDiv',
                history: false
            });
        } else {
            var lzCanvasRuntimeVersion = 9 * 1;
            if (dojo.flash.info.version == 9.16) {
                //Note this is fragile. This relies on the fact that the dojo.flash 
                //module in the platform does a string comparison between the 
                //current version and the version specified. This is why in this 
                //particular case we are setting lzCanvasRuntimeVersion to a string
                //rather than an int.
                lzCanvasRuntimeVersion = '9.0.124';
            }
            if (swfConfig['locale']) {
               appUrl += '&locale='+swfConfig.locale;
            }

            var args = {
                url: appUrl,
                bgcolor: '#898989',
                width: '100%',
                height: '100%',
                id: 'lzapp',
                accessible: 'false',
                wmode: wmode,
                appenddivid: 'appDiv',
                history: false
            };
            Lz.swfEmbed(args, lzCanvasRuntimeVersion);
        }
    }
}

/** Helper object for data detection.
  * @param type - The type of object.
  * @param value - The object's value.
  */
function Model(type, value) {
    this.type = type;
    this.value = value;
}

/** Creates the gDataDetection object.
  *
  * @author dwheeler
  */
var gDataDetection = new function() {
    // Private variables
    var actionDropDowns = {};
    var actionText = {};
    
    // Public variables
    this.actionDropDown = null;
    this.mousedOverElement = null;
    // Not moused over state
    this.borderWidth = '1px';
    this.borderStyle = 'dotted';
    this.borderColor = '#999999';
    this.backgroundColor = '#FFFFFF';
    // Moused over state
    this.borderStyleOver = 'solid';
    this.borderColorOver = '#999999';
    this.backgroundColorOver = '#EEEEEE';


    /*//// METHODS ///////////////////////////////////////////////////////////*/
    
    /** Calls a data detection method in Webtop's LZX.
        @keywords private
        @param params - Object containing the name of the method */
    var _callDataDetectionMethod = function(methodName) {
        var smo = gDataDetection.mousedOverElement.wt_smo;
        var winId = gDataDetection.getWindowByElement(gDataDetection.mousedOverElement).frameElement.id;
        var type = (!smo['type']) ? '' : smo.type;
        var value = (!smo['value']) ? '' : smo.value.replace(/'/g, "\\'");
        Lz.setCanvasAttribute('gDataDetection', [winId, methodName, type, value].join(':'));
    }
    
    /** Creates the drop-down arrow view.
        @keywords private */
    var _createArrowView = function(theWindow) {
        var self = gDataDetection;
        theWindow.wgDataDetection = {
            view: theWindow.document.createElement('div'),
            img:  theWindow.document.createElement('div')
        };
        
        // Default view attributes
        var iStyle = theWindow.wgDataDetection.view.style;
        iStyle.position = 'absolute';
        iStyle.left = '-9999px';
        iStyle.top = '-9999px';
        iStyle.borderStyle = self.borderStyleOver;
        iStyle.backgroundColor = self.backgroundColorOver;
        iStyle.borderWidth = [self.borderWidth, self.borderWidth,
            self.borderWidth, '0px'].join(' ');
        iStyle.width = '15px';
        iStyle.height = '15px';
        iStyle.borderColor = self.borderColor;
        
        // Round corners via CSS for browsers that support it
        if(iStyle.MozBorderRadius !== undefined) {
            iStyle.MozBorderRadiusTopright = '2em';
            iStyle.MozBorderRadiusBottomright = '2em';
        } else if(iStyle.WebkitBorderRadius !== undefined) {
            iStyle.WebkitBorderTopRightRadius = '2em';
            iStyle.WebkitBorderBottomRightRadius = '2em';
        } else {
            // TODO: Round corners for other browsers via images
        }
        
        // Image properties
        iStyle = theWindow.wgDataDetection.img.style;
        iStyle.width = '15px';
        iStyle.height = '15px';
        iStyle.position = 'relative';
        iStyle.backgroundPosition = '0 0';
        iStyle.cursor = 'pointer';
        iStyle.backgroundImage = 'url(images/smartobject.png)';
        theWindow.wgDataDetection.img.appendChild(theWindow.document.createTextNode(' '));
        
        // Append image view to parent
        theWindow.wgDataDetection.view.appendChild(theWindow.wgDataDetection.img);
        
        // Events
        theWindow.wgDataDetection.view.onmouseover = viewMouseover;
        theWindow.wgDataDetection.view.onmouseout = self.mousedOut;
        theWindow.wgDataDetection.img.onmouseover = imgMouseover;
        theWindow.wgDataDetection.img.onmouseout = imgMouseout;
        theWindow.wgDataDetection.img.onmousedown = imgMousedown;
        theWindow.wgDataDetection.img.onmouseup = imgMouseup;
        
        theWindow.document.body.appendChild(theWindow.wgDataDetection.view);
    }
    
    /** Returns the parent window object of the given element.
        @param theElement - DOM node */
    this.getWindowByElement = function(theElement) {
        if(wgBrowser.isie) {
            return theElement.ownerDocument.parentWindow;
        } else {
            return theElement.ownerDocument.defaultView;
        }
    }
    
    /** Initializes the data detection spans.
        @param theWindow - Window containing the data detection spans. 
        TODO: Hide any open drop downs. */
    this.initDataItems = function(theWindow) {
        if(!theWindow) return;
        
        // Create drop-down arrow and append it to the document
        _createArrowView(theWindow);
        
        var dataElems = theWindow.document.getElementsByTagName('span');
        for (var i=0; dataElems.length > i; i++) {
            if (dataElems[i].className.indexOf('wt_') == -1) continue;
            
            var iStyle = dataElems[i].style;
            iStyle.backgroundColor = this.backgroundColor;
            iStyle.borderStyle = this.borderStyle;
            iStyle.borderColor = this.borderColor;
            iStyle.borderWidth = this.borderWidth;
            iStyle.padding = '0 2px';
            
            // Mouseover function
            dataElems[i].onmouseover = function() {
                top.gDataDetection.handleMouseOver(this);
                
                // Clear timer for 'this'
                var selfTimer = this['wt_mouseoutTimer'];
                if(selfTimer) top.clearTimeout(selfTimer);
                
                // Clear timer for the menu arrow
                var arrowTimer = gDataDetection.getWindowByElement(this).wgDataDetection.view['wt_mouseoutTimer'];
                if(arrowTimer) top.clearTimeout(arrowTimer);
                
                // Clear timer for the actionDropDown
                if(!gDataDetection['actionDropDown']) return;
                var menuTimer = gDataDetection.actionDropDown.view['wt_mouseoutTimer'];
                if(menuTimer) top.clearTimeout(menuTimer);

            }
            
            // Mouseout function
            dataElems[i].onmouseout = gDataDetection.mousedOut;
        }
    }
    
    /** Adds an item to the given menu type and creates the menu if it does not exist.
        @param String lzxMethodName - The lzx method to call in gDataDetectionCallback when this item is clicked.
        @param Array types - Array of menu types to add the item to.
        @param String text - The text to display for this item. */
    this.addMenuItems = function(lzxMethodName, types, text) {
        actionText[lzxMethodName] = {};
        for(var i=0; i < types.length; i++) {
            var theMenu = actionDropDowns[types[i]];
            if(!theMenu) {
                theMenu = actionDropDowns[types[i]] = new JMenu();
            }
            var jItem = new JMenuItem(theMenu, text,
                _callDataDetectionMethod, lzxMethodName
            );
            actionText[lzxMethodName][types[i]] = jItem;
        }
    }
    
    /** Updates the text string for the given lzx method name.
        @param String lzxMethodName - The lzx method associated with this action.
        @param String text - The new text to set for the item. */
    this.updateMenuText = function(lzxMethodName, text) {
        for(var key in actionText[lzxMethodName]) {
            actionText[lzxMethodName][key].setText(text);
        }
    }
    
    /** Handles the mouseout event for element associated with data detection.
        We need to delay the actual mouseout handlers to allow time for the 
        mouseover events to fire.
        @param theView - DOM element on which the event occured */
    this.mousedOut = function() {
        var dataElem = gDataDetection.mousedOverElement;
        this.wt_mouseoutTimer = top.setTimeout(function() {
            top.gDataDetection.handleMouseOut(dataElem);
        }, 10);
    }
    
    /** Handles the onmouseover event for data detector elements.
        @param theView - DOM element on which the event occured */
    this.handleMouseOver = function(dhtmlElem) {
        if(this['actionDropDown']) {
            this.getWindowByElement(dhtmlElem).wgDataDetection.img.style.backgroundPosition =
                (this.actionDropDown.visible) ? '0 15px' : '0 0';
        } 
        
        this.mousedOverElement = dhtmlElem;
        var iStyle = dhtmlElem.style;
        iStyle.borderStyle = this.borderStyleOver;
        iStyle.borderColor = this.borderColorOver;
        iStyle.backgroundColor = this.backgroundColorOver;
        
        if (!dhtmlElem['wt_smo']) {
            // TODO dwheeler: Look into why we need the split.
            var constr = dhtmlElem.className.split(',')[0].substring(3);
            // Standards compliant way vs the IE way
            var content = (dhtmlElem['textContent']) ? 
                dhtmlElem.textContent : dhtmlElem.innerText;
            dhtmlElem.wt_smo = new Model(constr, content);
        }
        this.summonDropDownArrow(dhtmlElem);
    }
    
    /** Handles the onmouseout event for data detector elements. */
    this.handleMouseOut = function(dataElem) {
        // Reset data element
        var iStyle = dataElem.style;
        iStyle.borderStyle = gDataDetection.borderStyle;
        iStyle.borderColor = gDataDetection.borderColor;
        iStyle.backgroundColor = gDataDetection.backgroundColor;
        
        var theWindow = this.getWindowByElement(dataElem);
        
        // Reset drop-down arrow
        theWindow.wgDataDetection.img.style.backgroundPosition = '0 0';
        
        // Reset drop-down arrow, if we "own" it
        if(this.mousedOverElement == dataElem) {
            var iStyle = theWindow.wgDataDetection.view.style;
            iStyle.backgroundColor = this.backgroundColorOver;
            iStyle.left = '-9999px';
            iStyle.top = '-9999px';
        }
        
        // Reset menu
        if(this['actionDropDown']) {
            this.actionDropDown.hide();
            this.actionDropDown = null;
        }
    }
    
    
    /** Calculates the position of the dropdown arrow and summons it.
        @param dhtmlElem - the dhtml element that holds the data
        @param windowElem - the window that contains the dhtmlElem */
    this.summonDropDownArrow = function(dhtmlElem) {
        var dropDownId = dhtmlElem.wt_smo.type;
        this.actionDropDown = actionDropDowns[dropDownId];
        
        if (this.actionDropDown.visible) return;
        
        var siblingSpan = dhtmlElem.nextSibling;
        // Set the siblings to the same height
        siblingSpan.style.fontSize = dhtmlElem.style.fontSize;
        
        var pos = this.getAbsolutePosition(siblingSpan);
        var borderWidthNum =
            this.borderWidth.substring(0,this.borderWidth.length-2) * 1;
        // This is to fix "quirks mode" issues in IE7 or less.
        // IE8 will ignore this statement since it has documentMode.
        if(wgBrowser.isie && !document['documentMode']) {
            borderWidthNum = borderWidthNum * 3;
        }
        var h = siblingSpan.offsetHeight;
        var iStyle = this.getWindowByElement(dhtmlElem).wgDataDetection.view.style;
        iStyle.height = h + 'px';
        iStyle.width = '15px';
        
        var doc = dhtmlElem.ownerDocument;
        iStyle.left = pos.x - borderWidthNum + doc.documentElement.scrollLeft -
            document.body.scrollLeft + 'px';
        iStyle.top = pos.y - borderWidthNum + doc.documentElement.scrollTop -
            document.body.scrollTop + 'px';
        this.getWindowByElement(dhtmlElem).wgDataDetection.img.style.top = 
            (h - 15)/2 + 'px'; // Vertically center the drop-down arrow
    }
    
    /** Finds the absolute x and y position of an element.
        Note: This is a duplicate method of lz.embed.getAbsolutePosition.
        It is included here because the method does not exist in Diamond.
        @param theElem - DOM element find the absolute position of 
        @returns object - Contains the x and y of the element */
    this.getAbsolutePosition = function(el) {
        var parent = null;
        var pos = {};
        var box;

        if (!(wgBrowser.isff && el == document.body) && el.getBoundingClientRect ) { // IE and FF3
            box = el.getBoundingClientRect();
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
            return {x: Math.floor(box.left + scrollLeft), y: Math.floor(box.top + scrollTop)};
        } else if (document.getBoxObjectFor) { // gecko
            box = document.getBoxObjectFor(el);
            pos = {x: box.x, y: box.y};
        } else { // safari/opera
            pos = {x: el.offsetLeft, y: el.offsetTop};
            parent = el.offsetParent;
            if (parent != el) {
                while (parent) {
                    pos.x += parent.offsetLeft;
                    pos.y += parent.offsetTop;
                    parent = parent.offsetParent;
                }
            }

            // look up computed style for Safari
            if (wgBrowser.issafari && document.defaultView && document.defaultView.getComputedStyle) {
                var styles = document.defaultView.getComputedStyle(el, '');
            }
                
            // opera & (safari absolute) incorrectly account for body offsetTop
            // used quirks.absolute_position_accounts_for_offset before...
            if ( wgBrowser.isopera || (wgBrowser.issafari && styles && styles['position'] == 'absolute' ) ) {
                pos.y -= document.body.offsetTop;
            }
        }

        if (el.parentNode) {
            parent = el.parentNode;
        } else {
            return pos;
        }

        while (parent && parent.tagName != 'BODY' && parent.tagName != 'HTML') {
            pos.x -= parent.scrollLeft;
            pos.y -= parent.scrollTop;

            if (parent.parentNode) {
                parent = parent.parentNode;
            } else {
                return pos;
            }
        } 
        return pos;
    }


    /*//// HANDLERS //////////////////////////////////////////////////////////*/
    
    // Onmouseover event for the drop-down arrow's container
    var viewMouseover = function() {
        // Clear timer for 'this'
        var selfTimer = this['wt_mouseoutTimer'];
        if(selfTimer) top.clearTimeout(selfTimer);
        
        // Clear timer for the detected data element
        var dataTimer = gDataDetection['mousedOverElement']['wt_mouseoutTimer'];
        if(dataTimer) top.clearTimeout(dataTimer);
        
        // Clear timer for the actionDropDown
        if(!gDataDetection['actionDropDown']) return;
        var menuTimer = gDataDetection.actionDropDown.view['wt_mouseoutTimer'];
        if(menuTimer) top.clearTimeout(menuTimer);
    }
    
    // Onmouseover event for the drop-down arrow
    var imgMouseover = function() {
        if(gDataDetection['actionDropDown']) {
            this.style.backgroundPosition =
                (gDataDetection.actionDropDown.visible) ? '-15px -15px' : '-15px 0';
        } 
    }
    
    // Onclick event for the drop-down arrow
    var imgMousedown = function() {
        // Close drop-down if open
        if(gDataDetection.actionDropDown.visible) {
            this.style.backgroundPosition = '-30px -15px';
            gDataDetection.actionDropDown.hide();
        // Open drop-down if closed
        } else {
            this.style.backgroundPosition = '-30px 0';
            var pos = gDataDetection.getAbsolutePosition(this);
            var iframePos = gDataDetection.getAbsolutePosition(
                gDataDetection.getWindowByElement(this).frameElement
            );
            gDataDetection.actionDropDown.summon(
                pos.x + iframePos.x + this.offsetWidth/2 - top.document.body.scrollLeft,
                pos.y + iframePos.y + this.offsetHeight/2 - top.document.body.scrollTop
            );
        }
    }
    
    // Onmouseup event for the drop-down arrow
    var imgMouseup = function() {
        if(gDataDetection.actionDropDown.visible) {
            this.style.backgroundPosition = '-15px -15px';
        } else {
            this.style.backgroundPosition = '-15px 0';
        }
    }
    
    // Onmouseout event for the drop-down arrow
    var imgMouseout = function() {
        if(gDataDetection['actionDropDown']) {
            this.style.backgroundPosition =
                (gDataDetection.actionDropDown.visible) ? '0 -15px' : '0 0';
        }
    }
}

/** JMenu Class
  * @param theParent - DOM element to attach this instance's view 
  *
  * @author dwheeler
  */
function JMenu(parent) {
    // Class variables
    JMenu.hiliteColor = '#D5EFF5';
    JMenu.backgroundColor = '#FFFFFF';
    JMenu.borderColor = '#9D9D9D';
    JMenu.fontFamily = 'Verdana, Arial, sans-serif';
    JMenu.fontSize = '11px';

    // Public variables
    this.view = document.createElement('div');
    this.hide();
    this.width = 200;
    this.visible = false;
    
    // Set default view attributes
    var iStyle = this.view.style;
    iStyle.width = this.width + 'px';
    iStyle.fontFamily = JMenu.fontFamily;
    iStyle.fontSize = JMenu.fontSize;
    iStyle.backgroundColor = JMenu.backgroundColor;
    iStyle.borderColor = JMenu.borderColor;
    iStyle.borderWidth = '1px';
    iStyle.borderStyle = 'solid';
    iStyle.position = 'absolute';
    iStyle.zIndex = 99998; // Must be greater than iframe z-index
    
    // Append view to parent
    if(!parent) { parent = document.body; }
    parent.appendChild(this.view);
    
    this.view.onmouseover = function() {
        // Clear timer for 'this' (actionDropDown)
        var selfTimer = this['wt_mouseoutTimer'];
        if(selfTimer) top.clearTimeout(selfTimer);
        
        // Clear timer for the menu arrow
        var arrowTimer = gDataDetection.getWindowByElement(gDataDetection.mousedOverElement).
            wgDataDetection.view['wt_mouseoutTimer'];
        if(arrowTimer) top.clearTimeout(arrowTimer);
        
        // Clear timer for the detected data element
        var dataTimer = gDataDetection['mousedOverElement']['wt_mouseoutTimer'];
        if(dataTimer) top.clearTimeout(dataTimer);
    }
        
    this.view.onmouseout = gDataDetection.mousedOut;
}

/** Sets the x and y of the JMenu within the boundaries of the page.
    This also makes it visible because of the way visibility is controlled.
    @param theX - x position
    @param theY - y position */
JMenu.prototype.summon = function(theX, theY) {
    this.visible = true;
    var domChildren = this.view.childNodes;
    var padding = 10; // Padding is the minimum number of pixels to display between a menu and the edge of the document.
    
    // Determine actionDropDown width (all children are the same width)
    var maxX = document.body.clientWidth - domChildren[0].clientWidth - padding;
    
    // Determine actionDropDown height
    var parentHeight = 0;
    for(var i = 0; i < domChildren.length; i++) {
        parentHeight += domChildren[i].clientHeight;
    }
    var maxY = document.body.clientHeight - parentHeight - padding;
    
    // Adjust x and y so the menu stays in the bounds of the document
    if(theX > maxX) theX = maxX;
    if(theY > maxY) {
        theY = theY - gDataDetection.actionDropDown.view.clientHeight;
    }
    
    this.view.style.left = theX + 'px';
    this.view.style.top = theY + 'px';
}

/** Hides the JMenu if it's visible.
    We cannot use display:hidden here because it makes
    the clientHeight and clientWidth of the menu 0. */
JMenu.prototype.hide = function() {
    this.visible = false;
    this.view.style.left = '-9999px';
    this.view.style.top = '-9999px';
}


/** JMenuItem Class
  * @param JMenu parent - JMenu to attach this instance's view to
  * @param String text - Text to display on item
  * @param Function callbackMethod - Method to call when this item is clicked
  * @param callbackParams - Object of name/value pairs to pass to the callback function
  * @param Function canActOnMethod - Method to check whether this is enabled (optional)
  * @author dwheeler
  */
function JMenuItem(parent, text, callbackMethod, callbackParams, canActOnMethod) {
    // Parent must be a JMenu object
    if(typeof parent != 'object' || !parent['view']) return;

    // Private variables
    var disabledTextColor = '#666';
    
    // Public variables
    this.view = document.createElement('div');
    
    // Set default view attributes
    this.view.style.padding = '2px 4px';
    this.view.style.cursor = 'pointer';
    this.view['wt_enabled'] = true;
    this.view.appendChild(document.createTextNode(text));
    
    // Append view to parent
    parent.view.appendChild(this.view);
    
    // Set the callback function, if it exists.
    if(callbackMethod != undefined && typeof callbackMethod == 'function') {
        this.view.onclick = function() {
            if(this['wt_enabled']) {
                callbackMethod(callbackParams);
                gDataDetection.handleMouseOut(gDataDetection.mousedOverElement);
            }
        }
    }
    
    // Set the canActOnMethod, if it exists.
    // Otherwise, assign it to a function that returns true.
    if(canActOnMethod != undefined && typeof canActOnMethod == 'function') {
        this.canActOn = canActionMethod;
    } else {
        this.canActOn = function() {
            return true;
        }
    }
    
    // View events
    this.view.onmouseover = function() {
        this.style.backgroundColor = JMenu.hiliteColor;
    }
    this.view.onmouseout = function() {
        this.style.backgroundColor = '#ffffff';
    }
}

/** Set the text of this item. 
    @param String text - The text to use for this item. */
JMenuItem.prototype.setText = function(text) {
    var textNode = this.view.firstChild;
    textNode.replaceData(0, textNode.length, text);
}

