//
// Copyright (c) 2008 Beau D. Scott | http://www.beauscott.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//

/**
 * HelpBalloon.js
 * Prototype/Scriptaculous based help balloons / dialog balloons
 * @version 2.0.1
 * @requires prototype.js <http://www.prototypejs.org/>
 * @author Beau D. Scott <beau_scott@hotmail.com>
 * 4/10/2008
 */
var HelpBalloon = Object.extend(Class.create(), {
        /**
         * Enumerated value for dynamic rendering position.
         * @static
         */
        POS_DYNAMIC: -1,
        /**
         * Enumerated value for the top-left rendering position.
         * @static
         */
        POS_TOP_LEFT: 0,
        /**
         * Enumerated value for the top-right rendering position.
         * @static
         */
        POS_TOP_RIGHT: 1,
        /**
         * Enumerated value for the bottom-left rendering position.
         * @static
         */
        POS_BOTTOM_LEFT: 2,
        /**
         * Enumerated value for the bottom-right rendering position.
         * @static
         */
        POS_BOTTOM_RIGHT: 3,
        /**
         * CSS Classname to look for when doing auto link associations.
         * @static
         */
        ELEMENT_CLASS_NAME: 'HelpBalloon',
        /**
         * Global array of all HelpBalloon instances.
         * (Cheaper than document.getElementByClassName with a property check)
         * @static
         * @private
         */
        _balloons: [],
        /**
         * Event listener that auto-associates anchors with a dynamic HelpBalloon.
         * Also begins mouse movement registration
         * @static
         */
        registerClassLinks: function(e) {
                $A(document.getElementsByClassName(HelpBalloon.ELEMENT_CLASS_NAME))
                        .each(function(obj){
                        // Only apply any element with an href tag
                        if(obj && obj.tagName && obj.href && obj.href != '')
                        {
                                new HelpBalloon({
                                        icon:obj,
                                        method: 'get'
                                });
                        }
                });

                Event.observe(document, 'mousemove', HelpBalloon._trackMousePosition);

        },

        /**
         * Private cache of the client's mouseX position
         */
        _mouseX: 0,

        /**
         * Private cache of the client's mouseY position
         */
        _mouseY: 0,

        /**
         * @param {Event} e
         */
        _trackMousePosition: function(e) {
                if(!e) e = window.event;
                HelpBalloon._mouseX = e.clientX;
                HelpBalloon._mouseY = e.clientY;
        }
});

//
// Event for activating HelpBalloon classed links
//
Event.observe(window, 'load', HelpBalloon.registerClassLinks);

HelpBalloon.prototype = {

//
// Properties
//
        /**
         * Configuration options
         * @var {HelpBalloon.Options}
         */
        options: null,

        /**
         * Containing element of the balloon
         * @var {Element}
         */
        container: null,
        /**
         * Inner content container
         * @var {Element}
         */
        inner: null,
        /**
         * A reference to the anchoring element/icon
         * @var {Element}
         */
        icon: null,
        /**
         * Content container
         * @var {Element}
         */
        content: null,
        /**
         * Closing button element
         * @var {Element}
         */
        button: null,
        /**
         * The closer object. This can be the same as button, but could
         * also be a div with a png loaded as the back ground, browser dependent.
         * @var {Element}
         */
        closer: null,
        /**
         * Title container
         * @var {Element}
         */
        titleContainer: null,
        /**
         * Background container (houses the balloon images
         * @var {Element}
         */
        bgContainer: null,
        /**
         * Array of balloon image references
         * @var {Array}
         */
        balloons: null,

        /**
         * The local store of 'title'. Will change if the balloon is making a remote call
         * unless options.title is specified
         * @var {String}
         */
        _titleString: null,

        /**
         * The balloons visibility state.
         * @var {Boolean}
         */
        visible: false,

        /**
         * Rendering status
         * @var {Boolean}
         */
        drawn: false,

        /**
         * Stores the balloon coordinates
         * @var {Object}
         */
        balloonCoords: null,

        /**
         * Width,height of the balloons
         * @var {Array}
         */
        balloonDimensions: null,

        /**
         * ID for HelpBalloon
         * @var {String}
         */
        id: null,

        /**
         * Used at render time to measure the dimensions of the loaded balloon
         * @private
         */
        _lastBalloon: null,

//
// Methods
//

        /**
         * @param {Object} options
         * @see HelpBalloon.Options
         * @constructor
         */
        initialize: function(options)
        {

                this.options = new HelpBalloon.Options();
                Object.extend(this.options, options || {});

                this._titleString = this.options.title;
                this.balloonDimensions = [0,0];

                //
                // Preload the balloon and button images so they're ready
                // at render time
                //
                // 0 1
                //  X
                // 2 3
                //
                this.balloons = [];
                for(var i = 0; i < 4; i++)
                {
                        var balloon = new Element('img', {
                                src: this.options.balloonPrefix + i + this.options.balloonSuffix
                        });
                        this.balloons.push(balloon.src);
                }

                this._lastBalloon = balloon;

                this.button = new Element('img', {
                        src: this.options.button
                });

                //
                // Create the anchoring icon, or attach the balloon to the given icon element
                // If a string is passed in, assume it's a URL, if it's an object, assume it's
                // a DOM member.
                //
                if(typeof this.options.icon == 'string')
                {
                        this.icon = new Element('img', {
                                src: this.options.icon,
                                id: this.id + "_icon"
                        });
                        Element.setStyle(this.icon, this.options.iconStyle);
                }
                else
                {
                        //
                        // Not a string given (most likely an object. Do not append the element
                        // Kind of a hack for now, but I'll fix it in the next version.
                        //
                        this.icon = this.options.icon;
                        this.options.returnElement = true;
                }

                this.icon._HelpBalloon = this;

                //
                // Attach rendering events
                //

                for(i = 0; i < this.options.useEvent.length; i++)
                        Event.observe(this.icon, this.options.useEvent[i], this.toggle.bindAsEventListener(this));

                this.container = new Element('div');
                this.container._HelpBalloon = this;

                this.id = 'HelpBalloon_' + Element.identify(this.container);

                HelpBalloon._balloons.push(this);

                //
                // If we are not relying on other javascript to attach the anchoring icon
                // to the DOM, we'll just do where the script is called from. Default behavior.
                //
                // If you want to use external JavaScript to attach it to the DOM, attach this.icon
                //
                if(!this.options.returnElement)
                {
                        document.write('<span id="' + this.id + '"></span>');
                        var te = $(this.id);
                        var p = te.parentNode;
                        p.insertBefore(this.icon, te);
                        p.removeChild(te);
                }
        },

        /**
         * Toggles the help balloon
         * @param {Object} e Event
         */
        toggle: function(event)
        {
                if(!event) event = window.event || {type: this.options.useEvent, target: this.icon};
                var icon = Event.element(event);
                Event.stop(event);
                if(event.type == this.options.useEvent && !this.visible && icon == this.icon)
                {
                        this.show(event);
                }
                else
                        this.hide();
        },

        /**
         * Triggers the balloon to appear
         */
        show: function(event)
        {
                if(!this.visible){
                        if(!event) event = window.event;
                        if(!this.drawn || !this.options.cacheRemoteContent) this._draw();
                        this._reposition(event);
                        this._hideOtherHelps();
                        if(this.options.showEffect)
                        {
                                this.options.showEffect(this.container, Object.extend(this.options.showEffectOptions, {
                                        afterFinish: this._afterShow.bindAsEventListener(this)
                                }));
                        }
                        else
                        {
                                this._afterShow();
                        }
                        Event.observe(window, 'resize', this._reposition.bindAsEventListener(this));
                }
        },

        /**
         * Sets the container to block styling and hides the elements below the
         * container (if in IE)
         * @private
         */
        _afterShow: function()
        {
                Element.setStyle(this.container, {
                        'display': 'block'
                });
                this._hideLowerElements();
                this.visible = true;
                if(this.options.autoHideTimeout)
                {
                        setTimeout(this._hideQueue.bind(this), this.options.autoHideTimeout);
                }
        },

        /**
         * Checks the mouse position and triggers a hide after the time specified in autoHideTimeout
         * if the mouse is not currently over the balloon, otherwise it requeue's a hide for later.
         */
        _hideQueue: function()
        {
                if(Position.within(this.container, HelpBalloon._mouseX, HelpBalloon._mouseY))
                        setTimeout(this._hideQueue.bind(this), this.options.autoHideTimeout);
                else
                        this.hide();
        },

        /**
         * Hides the balloon
         */
        hide: function()
        {
                if(this.visible)
                {
                        this._showLowerElements();
                        if(this.options.hideEffect)
                        {
                                this.options.hideEffect(this.container, Object.extend(this.options.hideEffectOptions, {
                                        afterFinish: this._afterHide.bindAsEventListener(this)
                                }));
                        }
                        else
                        {
                                this._afterHide();
                        }
                        Event.stopObserving(window, 'resize', this._reposition.bindAsEventListener(this));
                }
        },

        /**
         * Sets the container's display to block
         * @private
         */
        _afterHide: function()
        {
                Element.setStyle(this.container, {
                        'display': 'none'
                });
                this.visible = false;
        },

        /**
         * Redraws the balloon based on the current coordinates of the icon.
         * @private
         */
        _reposition: function(event)
        {
                if(this.icon.tagName.toLowerCase() == 'area' || !!this.icon.isMap)
                {
                        this.balloonCoords = Event.pointer(event);
                }
                else
                {
                        this.balloonCoords = this._getXY(this.icon);
                        //Horizontal and vertical offsets in relation to the icon's 0,0 position.
                        // Default is the middle of the object
                        var ho = this.icon.offsetWidth / 2;
                        var vo = this.icon.offsetHeight / 2;

                        var offsets = this.options.anchorPosition.split(/\s+/gi);
                        // Only use the first two specified values
                        if(offsets.length > 2)
                                offsets.length = 2;

                        for(var i = 0; i < offsets.length; i++)
                        {
                                switch(offsets[i].toLowerCase())
                                {
                                        case 'left':
                                                        ho = 0;
                                                break;
                                        case 'right':
                                                        ho = this.icon.offsetWidth;
                                                break;
                                        case 'center':
                                                        ho = this.icon.offsetWidth / 2;
                                                break;
                                        case 'top':
                                                        vo = 0;
                                                break;
                                        case 'middle':
                                                        vo = this.icon.offsetHeight / 2;
                                                break;
                                        case 'bottom':
                                                        vo = this.icon.offsetHeight;
                                                break;
                                        default:
                                                var numVal = parseInt(offsets[i]);
                                                if(!isNaN(numVal))
                                                {
                                                        // 0 = width, 1 = height (WxH)
                                                        if(i == 0)
                                                        {
                                                                if(numVal < 0)
                                                                {
                                                                        ho = 0;
                                                                }
                                                                else
                                                                {
                                                                        if(numVal > this.icon.offsetWidth)
                                                                                ho = this.icon.offsetWidth;
                                                                        else
                                                                                ho = numVal
                                                                }
                                                        }
                                                        else
                                                        {
                                                                if(numVal < 0)
                                                                {
                                                                        vo = 0;
                                                                }
                                                                else
                                                                {
                                                                        if(numVal > this.icon.offsetHeight)
                                                                                vo = this.icon.offsetHeight;
                                                                        else
                                                                                vo = numVal
                                                                }
                                                        }
                                                }
                                                break;
                                }
                        }
                        this.balloonCoords.x += ho;
                        this.balloonCoords.y += vo;
                }

                //
                // Figure out what position to show based on available realestate
                // unless
                // 0 1
                //  X
                // 2 3
                // Number indicates position of corner opposite anchor
                //
                var pos = 1;
                if(this.options.fixedPosition == HelpBalloon.POS_DYNAMIC)
                {
                        var offsetHeight = this.balloonCoords.y - this.balloonDimensions[1];
                        if(offsetHeight < 0)
                                pos += 2;

                        var offsetWidth = this.balloonCoords.x + this.balloonDimensions[0];
                        var ww = Prototype.Browser.IE ? document.body.clientWidth : window.outerWidth;
                        if(offsetWidth > ww)
                                pos -- ;
                }
                else
                        pos = this.options.fixedPosition;

                var zx = 0;
                var zy = 0;

                //
                // 0 1
                //  X
                // 2 3
                //
                switch(pos)
                {
                        case 0:
                                zx = this.balloonCoords.x - this.balloonDimensions[0];
                                zy = this.balloonCoords.y - this.balloonDimensions[1];
                                break;

                        case 1:
                                zx = this.balloonCoords.x;
                                zy = this.balloonCoords.y - this.balloonDimensions[1];
                                break;

                        case 2:
                                zx = this.balloonCoords.x - this.balloonDimensions[0];
                                zy = this.balloonCoords.y;
                                break;

                        case 3:
                                zx = this.balloonCoords.x;
                                zy = this.balloonCoords.y;
                                break;
                }
                var containerStyle = {
                        /*'backgroundRepeat': 'no-repeat',
                        'backgroundColor': 'transparent',
                        'backgroundPosition': 'top left',*/
                        'left'         : zx + "px",
                        'top'        : zy + "px",
                        'width' : this.balloonDimensions[0] + 'px',
                        'height' : this.balloonDimensions[1] + 'px'
                }
                if(Prototype.Browser.IE)
                {
                        //
                        // Fix for IE alpha transparencies
                        //
                        if(this.balloons[pos].toLowerCase().indexOf('.png') > -1)
                        {
                                Element.setStyle(this.bgContainer, {
                                        'left'                 : '0px',
                                        'top'                : '0px',
                                        'filter'        : "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.balloons[pos] + "', sizingMethod='scale')",
                                        'width'         : this.balloonDimensions[0] + 'px',
                                        'height'         : this.balloonDimensions[1] + 'px',
                                        'position'        : 'absolute'
                                });
                        }
                        else
                                containerStyle['background'] = 'transparent url(' + this.balloons[pos] + ') top left no-repeat';
                }
                else
                {
                                containerStyle['background'] = 'transparent url(' + this.balloons[pos] + ') top left no-repeat';
                }
                Element.setStyle(this.container, containerStyle);
        },

        /**
         * Renders the Balloon
         * @private
         */
        _draw: function()
        {
                Element.setStyle(
                        this.container,
                        Object.extend(this.options.balloonStyle, {
                                'position':         'absolute',
                                'display':                 'none'
                        })
                );

                var url = this.options.dataURL;

                //
                // Play nicely with anchor tags being used as the icon. Use it's specified href as our
                // data URL unless one has already been used specified in this.options.dataURL.
                // We'll also force a new request with this as it may be an image map.
                //
                if(this.icon.className == 'a')
                {
                        if(!this.options.dataURL && this.icon.href != ''){
                                url = this.icon.href;
                                this.options.cacheRemoteContent = false;
                        }
                }

                if(url && (!this.drawn || !this.options.cacheRemoteContent))
                {
                        var cont = new Ajax.Request(this.options.dataURL, {asynchronous: false, method: this.options.method});
                        //
                        // Expects the following XML format:
                        // <HelpBalloon>
                        //                 <title>My Title</title>
                        //                 <content>My content</content>
                        // </HelpBaloon>
                        //
                        var doHTML = false;
                        if(cont.transport.responseXML)
                        {
                                var xml = cont.transport.responseXML.getElementsByTagName('HelpBalloon')[0];

                                if(xml)
                                {
                                        if(!this.options.title)
                                        {
                                                xmlTitle = xml.getElementsByTagName('title')[0];
                                                if(xmlTitle) this._titleString = xmlTitle.firstChild.nodeValue;
                                        }

                                        xmlContent = xml.getElementsByTagName('content')[0];
                                        if(xmlContent) this.options.content = xmlContent.firstChild.nodeValue;
                                }
                                else
                                        doHTML = true;
                        }
                        else
                                doHTML = true;

                        if(doHTML)
                        {
                                // Attempt to get the title from a <title/> HTML tag, unless the title option has been set. If so, use that.
                                if(!this.options.title)
                                {
                                        var htmlTitle = cont.transport.responseText.match(/\<title\>([^\<]+)\<\/title\>/gi);
                                        if(htmlTitle)
                                        {
                                                htmlTitle = htmlTitle.toString().replace(/\<title\>|\<\/title\>/gi, '');
                                                this._titleString = htmlTitle;
                                        }
                                }
                                this.options.content = cont.transport.responseText;
                        }
                }

                this.balloonDimensions[0] = this._lastBalloon.width;
                this.balloonDimensions[1] = this._lastBalloon.height;

                var contentDimensions = [
                        this.balloonDimensions[0] - (2 * this.options.contentMargin),
                        this.balloonDimensions[1] - (2 * this.options.contentMargin)
                ];

                var buttonDimensions = [
                        this.button.width,
                        this.button.height
                ];

                //
                // Create all the elements on demand if they haven't been created yet
                //
                if(!this.drawn)
                {
                        this.inner = new Element('div');

                        this.titleContainer = new Element('div');
                        this.inner.appendChild(this.titleContainer);

                        // PNG fix for IE
                        if(Prototype.Browser.IE && this.options.button.toLowerCase().indexOf('.png') > -1)
                        {
                                this.bgContainer = new Element('div');

                                // Have to create yet-another-child of container to house the background for IE... when it was set in
                                // the main container, it for some odd reason prevents child components from being clickable.
                                this.container.appendChild(this.bgContainer);

                                this.closer =  new Element('div');
                                Element.setStyle(this.closer, {
                                        'filter':
                                                "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.options.button + "', sizingMethod='scale')"
                                });
                        }
                        else
                        {
                                this.closer = this.button;
                        }

                        Event.observe(this.closer, 'click', this.toggle.bindAsEventListener(this));
                        this.inner.appendChild(this.closer);

                        this.content =  new Element('div');
                        this.inner.appendChild(this.content);

                        this.container.appendChild(this.inner);

                        document.getElementsByTagName('body')[0].appendChild(this.container);

                        this.drawn = true;
                }

                // Reset the title element and reappend the title value (could have changed with a new URL)
                this.titleContainer.innerHTML = '';
                this.titleContainer.appendChild(document.createTextNode(this._titleString));

                // Reset content value:
                this.content.innerHTML = this.options.content;

                //
                // Reapply styling to components as values might have changed
                //

                Element.setStyle(this.inner, {
                        'position':         'absolute',
                        'top':                        this.options.contentMargin + 'px',
                        'left':                        this.options.contentMargin + 'px',
                        'width':                 contentDimensions[0] + 'px',
                        'height':                 contentDimensions[1] + 'px'
                });

                Element.setStyle(this.titleContainer, {
                        'width':                (contentDimensions[0] - buttonDimensions[0]) + 'px',
                        'height':                buttonDimensions[1] + 'px',
                        'position':                'absolute',
                        'overflow':                'hidden',
                        'top':                         '0px',
                        'left':                 '0px'
                });

                Element.setStyle(this.titleContainer, this.options.titleStyle);

                Element.setStyle(this.closer, {
                        'width': buttonDimensions[0] + 'px',
                        'height': buttonDimensions[1] + 'px',
                        'cursor':         'pointer',
                        'position':        'absolute',
                        'top':                 '0px',
                        'right':         '0px'
                });

                Element.setStyle(this.content, {
                        'width':                contentDimensions[0] + 'px',
                        'height':                 (contentDimensions[1] - this.button.height) + 'px',
                        'overflow':         'auto',
                        'position':         'absolute',
                        'top':                         buttonDimensions[1] + 'px',
                        'left':                 '0px',
                        'fontFamily':         'verdana',
                        'fontSize':         '10px',
                        'fontWeight':         'normal',
                        'color':                 'black'
                });

        },

        /**
         * Gets the current position of the obj
         * @param {Element} element to get position of
         * @return Object of (x, y, x2, y2)
         */
        _getXY: function(obj)
        {
                var pos = Position.cumulativeOffset(obj)
                var y = pos[1];
                var x = pos[0];
                var x2 = x + parseInt(obj.offsetWidth);
                var y2 = y + parseInt(obj.offsetHeight);
                return {'x':x, 'y':y, 'x2':x2, 'y2':y2};

        },

        /**
         * Determins if the object is a child of the balloon element
         * @param {Element} Element to check parentage
         * @return {Boolean}
         * @private
         */
        _isChild: function(obj)
        {
                var i = 15;
                do{
                        if(obj == this.container)
                                return true;
                        obj = obj.parentNode;
                }while(obj && i--);
                return false
        },

        /**
         * Determines if the balloon is over this_obj object
         * @param {Element} Object to look under
         * @return {Boolean}
         * @private
         */
        _isOver: function(this_obj)
        {
                if(!this.visible) return false;
                if(this_obj == this.container || this._isChild(this_obj)) return false;
                var this_coords = this._getXY(this_obj);
                var that_coords = this._getXY(this.container);
                if(
                        (
                         (
                          (this_coords.x >= that_coords.x && this_coords.x <= that_coords.x2)
                           ||
                          (this_coords.x2 >= that_coords.x &&  this_coords.x2 <= that_coords.x2)
                         )
                         &&
                         (
                          (this_coords.y >= that_coords.y && this_coords.y <= that_coords.y2)
                           ||
                          (this_coords.y2 >= that_coords.y && this_coords.y2 <= that_coords.y2)
                         )
                        )

                  ){
                        return true;
                }
                else
                        return false;
        },

        /**
         * Restores visibility of elements under the balloon
         * (For IE)
         * TODO: suck yourself
         * @private
         */
        _showLowerElements: function()
        {
                if(this.options.hideUnderElementsInIE)
                {
                        var elements = this._getWeirdAPIElements();
                        for(var i = 0; i < elements.length; i++)
                        {
                                if(this._isOver(elements[i]))
                                {
                                        if(elements[i].style.visibility != 'visible' && elements[i].hiddenBy == this)
                                        {
                                                elements[i].style.visibility = 'visible';
                                                elements[i].hiddenBy = null;
                                        }
                                }
                        }
                }
        },

        /**
         * Hides elements below the balloon
         * (For IE)
         * @private
         */
        _hideLowerElements: function()
        {
                if(this.options.hideUnderElementsInIE)
                {
                        var elements = this._getWeirdAPIElements();
                        for(var i = 0; i < elements.length; i++)
                        {
                                if(this._isOver(elements[i]))
                                {
                                        if(elements[i].style.visibility != 'hidden')
                                        {
                                                elements[i].style.visibility = 'hidden';
                                                elements[i].hiddenBy = this;
                                        }
                                }
                        }
                }
        },

        /**
         * Determines which elements need to be hidden
         * (For IE)
         * @return {Array} array of elements
         */
        _getWeirdAPIElements: function()
        {
                if(!Prototype.Browser.IE) return [];
                var objs = ['select', 'input', 'object'];
                var elements = [];
                for(var i = 0; i < objs.length; i++)
                {
                        var e = document.getElementsByTagName(objs[i]);
                        for(var j = 0; j < e.length; j++)
                        {
                                elements.push(e[j]);
                        }
                }
                return elements;
        },

        /**
         * Hides the other visible help balloons
         * @param {Event} e
         */
        _hideOtherHelps: function(e)
        {
                if(this.options.hideOtherBalloonsOnDisplay)
                {
                        $A(HelpBalloon._balloons).each(function(obj){
                                if(obj != this)
                                {
                                        obj.hide();
                                }
                        }.bind(this));
                }
        }
};

/**
 * HelpBalloon.Options
 * Helper class for defining options for the HelpBalloon object
 * @author Beau D. Scott <beau_scott@hotmail.com>
 */
HelpBalloon.Options = Class.create();
HelpBalloon.Options.prototype = {

        /**
         * @constructor
         * @param {Object} overriding options
         */
        initialize: function(values){
                // Apply the overriding values to this
                Object.extend(this, values || {});
        },

        /**
         * Show Effect
         * The Scriptaculous (or compatible) showing effect function
         * @var Function
         */
        showEffect: window.Scriptaculous ? Effect.Appear : null,

        /**
         * Show Effect options
         */
        showEffectOptions: {duration: 0.2},

        /**
         * Hide Effect
         * The Scriptaculous (or compatible) hiding effect function
         * @var Function
         */
        hideEffect: window.Scriptaculous ? Effect.Fade : null,

        /**
         * Show Effect options
         */
        hideEffectOptions: {duration: 0.2},

        /**
         * For use with embedding this object into another. If true, the icon is not created
         * and not appeneded to the DOM at construction.
         * Default is false
         * @var {Boolean}
         */
        returnElement: false,

        /**
         * URL to the anchoring icon image file to use. This can also be a direct reference
         * to an existing element if you're using that as your anchoring icon.
         * @var {Object}
         */
        icon: 'images/icon.gif',

        /**
         * Alt text of the help icon
         * @var {String}
         */
        altText: 'Click here for help with this topic.',

        /**
         * URL to pull the title/content XML
         * @var {String}
         */
        dataURL: null,

        /**
         * Static title of the balloon
         * @var {String}
         */
        title: null,

        /**
         * Static content of the balloon
         * @var {String}
         */
        content: null,

        /**
         * The event type to listen for on the icon to show the balloon.
         * Default 'click'
         * @var {String}
         */
        useEvent: ['click'],

        /**
         * Request method for dynamic content. (get, post)
         * Default 'get'
         * @var {String}
         */
        method:        'get',

        /**
         * Flag indicating cache the request result. If this is false, every
         * time the balloon is shown, it will retrieve the remote url and parse it
         * before the balloon appears, updating the content. Otherwise, it will make
         * the call once and use the same content with each subsequent showing.
         * Default true
         * @var {Boolean}
         */
        cacheRemoteContent: true,

        /**
         * Vertical and horizontal margin of the content pane
         * @var {Number}
         */
        contentMargin: 35,

        /**
         * X coordinate of the closing button
         * @var {Number}
         */
        buttonX: 246,

        /**
         * Y coordinate of the closing button
         * @var {Number}
         */
        buttonY: 35,

        /**
         * Closing button image path
         * @var {String}
         */
        button: 'images/button.png',

        /**
         * Balloon image path prefix. There are 4 button images, numerically named, starting with 0.
         * 0 1
         *  X
         * 2 3
         * X indicates the anchor corner
         * @var {String}
         */
        balloonPrefix: 'images/balloon-',

        /**
         * The image filename suffix, including the file extension
         * @var {String}
         */
        balloonSuffix: '.png',

        /**
         * Position of the balloon's anchor relative to the icon element.
         * Combine one horizontal indicator (left, center, right) and one vertical indicator (top, middle, bottom).
         * Numeric values can also be used in an X Y order. So a value of 9 13 would place the anchor 9 pixels from
         * the left and 13 pixels below the top. (0,0 is top left). If values are greater than the width or height
         * the width or height of the anchor are used instead. If less than 0, 0 is used.
         * Default is 'center middle'
         * @var {String}
         */
        anchorPosition: 'center middle',

        /**
         * Flag indicating whether to hide the elements under the balloon in IE.
         * Setting this to false can cause rendering issues in Internet Explorer
         * as some elements appear on top of the balloon if they're not hidden.
         * Default is true.
         * @var {Boolean}
         */
        hideUnderElementsInIE: true,

        /**
         * Default Balloon styling
         * @var {Object}
         */
        balloonStyle: {},

        /**
         * Default Title Bar style
         * @var {Object}
         */
        titleStyle: {
                'color': 'black',
                'fontSize': '12px',
                'fontWeight': 'bold',
                'fontFamily': 'Verdana'
        },

        /**
         * Icon custom styling
         * @var {Object}
         */
        iconStyle: {
                'cursor': 'pointer'
        },

        /**
         * Flag indication whether to automatically hide any other visible HelpBalloon on the page before showing the current one.
         * @var {Boolean}
         */
        hideOtherBalloonsOnDisplay: true,

        /**
         * If you want the balloon to always display in a particular location, set this
         */
        fixedPosition: HelpBalloon.POS_DYNAMIC,

        /**
         * Number of milliseconds to hide the balloon after showing and after the mouse is not over the balloon.
         * A value of 0 means it will not auto-hide
         * @var {Number}
         */
        autoHideTimeout: 0

};