/* 
 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
		 AAG: -+ 90 to make up for the location of the arrow on the balloon.
		*/
		switch(pos)
		{
			case 0:
				zx = this.balloonCoords.x - this.balloonDimensions[0] + 90;
				zy = this.balloonCoords.y - this.balloonDimensions[1];
				break;
			
			case 1:
				zx = this.balloonCoords.x - 90;
				zy = this.balloonCoords.y - this.balloonDimensions[1];
				break;
			
			case 2:
				zx = this.balloonCoords.x - this.balloonDimensions[0] + 90;
				zy = this.balloonCoords.y;
				break;
			
			case 3:
				zx = this.balloonCoords.x - 90;
				zy = this.balloonCoords.y;
				break;
		}
		var containerStyle = {
			/*'backgroundRepeat': 'no-repeat',
			'backgroundColor': 'transparent',
			'backgroundPosition': 'top left',*/
			'zIndex': '1000',
			'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',
				'zIndex':		'1000',
				'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,evalJS:true, 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.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);
				
				if(this.options.button.toLowerCase().indexOf('.png') > -1)
				{
					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;
				}
			}
			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',
			'bottom':		'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',
			'bottom':	'0px',
			'right': 	'0px'
		});
		
		Element.setStyle(this.content, {
			'width':		contentDimensions[0] + 'px',
			'height': 		(contentDimensions[1] - this.button.height) + 'px',
			'overflow': 	'auto',
			'position': 	'absolute',
			'top': 			'0px',
			'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?x=1504',
	
	/**
	 * 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
	
};