// 
// 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': 	'11px',
			'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': '16px',
		'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
	
};