
	/**
	 * @version
	 *   $Id: rollover.js,v 1.9 2007/10/08 20:41:14 cabernet Exp $
	 * @author
	 *   F. Zaslavskiy
	 * @copyright
	 *   Copyright (c) 2006 Zerve, Inc.
	 * 
	 * ZRollover class. 
	 * Generic rollover manager for the webpage.
	 * 
	 * Each element on the page that needs to have a rollover needs to be given 
	 * a class attribute of "ro_target". Each element will also need to have a 
	 * custom attribute ro_id and ro_type.
	 *
	 * Example of a rollover target.  A rollover target is any div or span that 
	 * is rolled over. In this case the user will rollover an event date and get a
	 * popup with more information about the event. For ro_type we used "event" which
	 * is a good name because we may be displaying many event dates on one page and
	 * they would all be grouped under a type of event. An ro_id value has to be unique
	 * within the type so an event's id would be a good value to use here.
	 *
	 *  <div class="ro_target" ro_type="event" ro_id="123344" > Dec 26th, 07 </div>
	 * 
	 * Any html may be contained inside this a target div or span.<b> 
	 *
	 * Each type of rollover will have to have a data source object that will be
	 * responsible for providing the data and the html that will be displayed in the 
	 * popup that comes up on a rollover.
	 *
	 * Here is an example of an a data source used on amp2.4.  All the data is 
	 * passed into teh constructor of ZRolloverAmpActivity as a json object.
	 * 
	 *  var ro = new ZRollover( );
	 *  ro.addDataSource( 'act', new ZRolloverAmpActivity( { .... } ) );
	 *  ro.addDisplayDisplayProperty( 'act', 'width', '300px' );
	 *
	 * The addDisplayProperty() method specifies one of the style attributes
	 * to be used for the rollover popup.
	 * 
	 * The ro object will set everything up on the page in the onload event of the window.
	 *
	 * You must also include util.js and json.js before rollover.js
	 */
	
	var open_id = '';
	
	/**
	 * This is a private class of ZRollover. 
	 * It helps with managing all the Rollover targets.
	 * 
	 * @param HTMLElement elem The target element of the rollover.
	 * @param int id user String the uniquely represents the target element (corresponds to "ro_id")
	 * @param string type Type of the target (corresponds to "ro_type")
	 */
	function ZRolloverTarget( elem, id, type ) {

		this.displayed = false;
		this.waiting_on_show = false;
		this.waiting_on_hide = false;

		this.ro_id = '';
		if( id ) {
			this.ro_id = id;
		}
		
		this.ro_type = '';
		if( type ) {
			this.ro_type = type;
		}
		
		this.ro_target = null;
		if( elem ) {
			this.ro_target = elem;
		}

		this.rollover = null;

	}
	
	ZRolloverTarget.prototype._cleanup = function( ) {

		if( this.ro_target && ZUtil.isIE ) {
			this.ro_target.onmouseover = null;
			this.ro_target.onmouseout = null;
		}
		this.ro_target = null;
		this.rollover = null;

	}
	
	function ZRollover( ) {

		this._ro_targets = [];
		this._data_sources = { };
		this._display_properties = { };
		this._show_delay = 200;
		this._hide_delay = 230;

		// this variabled will be set to true as soon as _init method has finished.
		this._was_initialized = false;
		
		ZUtil.addEvent( window, 'load', ZUtil.bind( this, this._init )  );
		ZUtil.addEvent( window, 'unload', ZUtil.bind( this, this._cleanup )  );

	}
	
	/**
	 * Add datasource for a type on a page. Datasource has to be an object
	 * that meets a specific interface. Take a look at rollover_amp_activity.js 
	 * for an example.
	 */
	ZRollover.prototype.addDataSource = function( type, dsource ) {

		if( typeof dsource != 'object' ) {
			throw Error( "Data source must be an object" );
		}

		this._data_sources[type] = dsource;

	}

	/**
	 * Customize the way the rollover behaves for a specific type.
	 */	
	ZRollover.prototype.addDisplayProperty = function( type, property, value ) {

		// Add properties object if it does not exist for the type yet.
		if( !( type in this._display_properties ) ) {
			this._display_properties[type] = {
				"width":null
			}
		}

		if( !( property in this._display_properties[type] ) ) {
			throw Error( 'Property : ' + property + ' is not supported' );
		}
		
		this._display_properties[type][property] = value;

	}
	
	/**
	 * Callback that is called for the windows onload event.
	 */
	ZRollover.prototype._init = function( ) {

		// Build array of roll over targets 
		var divs = document.getElementsByTagName( 'div' );
		var spans = document.getElementsByTagName( 'span' );
		
		// We only want to add items from data source types that are known to us
		
		for( var i = 0; i < divs.length; i++ ) {
			if( divs[i].className.indexOf( 'ro_target' ) != -1 
				&& ( divs[i].getAttribute( 'ro_type' ) in this._data_sources ) ) {
				this._ro_targets.push( new ZRolloverTarget( divs[i], divs[i].getAttribute( 'ro_id' ), divs[i].getAttribute( 'ro_type' ) ) );
			}
		}
		for( var i = 0; i < spans.length; i++ ) {
			if( spans[i].className.indexOf( 'ro_target' ) != -1 
				&& ( spans[i].getAttribute( 'ro_type' ) in this._data_sources ) ) {
				this._ro_targets.push( new ZRolloverTarget( spans[i], spans[i].getAttribute( 'ro_id' ), spans[i].getAttribute( 'ro_type' ) ) );
			}
		}

		// For each target add the mouse over/out events
		// These events stay in effect until the page is unloaded
		for( var i = 0; i < this._ro_targets.length; i++ ) {

			var ro_target = this._ro_targets[i].ro_target;
			
			if( ZUtil.isIE ) {
				ro_target.onmouseover = ZUtil.bindForIEeventListener( this, this._mouseOverTarget );
				ro_target.onmouseout = ZUtil.bindForIEeventListener( this, this._mouseOutTarget );
			} else {
				ZUtil.addEvent( ro_target, 'mouseover', ZUtil.bind( this, this._mouseOverTarget ) );
				ZUtil.addEvent( ro_target, 'mouseout',  ZUtil.bind( this, this._mouseOutTarget ) );
			}

		}

		this._was_initialized = true;

	}
	
	ZRollover.prototype._cleanup = function ( ) {

		if(! this._was_initialized ) {
			return;
		}

		for( var i = 0; i < this._ro_targets.length; i++ ) {
			this._ro_targets[i]._cleanup( );
		}

		this._ro_targets = null;

	}

	/**
	 * Handles the mouse over event for a rollover target element.
	 */
	ZRollover.prototype._mouseOverTarget = function( e ) {

		if(! this._was_initialized ) {
			return;
		}

		var id = this._checkMouseEvent( e , 'target' );
		if( id == -1 ) {
			return;
		}

		// Cancel any hide actions
		this._ro_targets[id].waiting_on_hide = false;

		// If we are waiting to show this rollover or
		// already displaying it then ignore this action
		if( this._ro_targets[id].waiting_on_show 
			|| this._ro_targets[id].displayed ) {
			return;
		}

		// Initiate the show action
		this._ro_targets[id].waiting_on_show = true;
		var tmp_this = this;

		// For now just determine the x,y position of cursor
		var mX, mY;
		if( e.pageX && e.pageY ) {
			mX = e.pageX;
			mY = e.pageY;
		} else if( e.clientX && e.clientY ) {
			mX = e.clientX;
			mY = e.clientY;
			if( ZUtil.isIE ) {
				mX += document.body.scrollLeft;
				mY += document.body.scrollTop;	
			}	
		}

		// For non-Safari browsers, we can use the window position.
		var adj_up = false;
		var adj_left = false;
		if( e.clientX && e.clientY && !ZUtil.isSafari ) {
			adj_up   = e.clientY > ZUtil.getInnerHeight( ) / 2; // if we are on bottom side of page to up
			adj_left = e.clientX > ZUtil.getInnerWidth( ) / 2; // if we are on the right side of page go left			
		} 
		
		// For Safari, we use the document position and adjust for scrolling.
		if( ZUtil.isSafari  ) {
			adj_up   = mY - document.body.scrollTop  > ZUtil.getInnerHeight( ) / 2;
			adj_left = mX - document.body.scrollLeft > ZUtil.getInnerWidth( ) / 2;
		}

		// Figure out which page 
		setTimeout( function( ) { 
						if( tmp_this._ro_targets[id].waiting_on_show ) {
							if( open_id != '' ) { 
								tmp_this._hideRollover( open_id );
							}
							tmp_this._showRollover( id , mX, mY, adj_left, adj_up );
							open_id = id;
						}
					} , this._show_delay );

	}

	/**
	 * Handles the mouse out event for the rollover target element.
	 */
	ZRollover.prototype._mouseOutTarget = function( e ) {

		if(! this._was_initialized ) {
			return;
		}

		var id = this._checkMouseEvent( e , 'target' );
		if( id == -1 ) {
			return;
		}
		
		// Cancel any show actions
		this._ro_targets[id].waiting_on_show = false;

		// If we are waiting to hide this rollover or
		// already not displaying it then ignore this action
		if( this._ro_targets[id].waiting_on_hide 
			|| !this._ro_targets[id].displayed ) {
			return;
		}

		// Initiate the hide action
		this._ro_targets[id].waiting_on_hide = true;
		var tmp_this = this;
		setTimeout( function( ) { 
						if( tmp_this._ro_targets[id].waiting_on_hide ) {
							tmp_this._hideRollover( id );
						}
					} , this._hide_delay );

	}

	/**
	 * Handles the mouse over event for a rollover element itself.
	 */
	ZRollover.prototype._mouseOverRollover = function( e ) {

		if(! this._was_initialized ) {
			return;
		}

		var id = this._checkMouseEvent( e , 'rollover' );
		if( id == -1 ) {
			return;
		}

		// Cancel any hide actions
		this._ro_targets[id].waiting_on_hide = false;
	
	}

	/**
	 * Handles the mouse out event for the rollover element itself.
	 */
	ZRollover.prototype._mouseOutRollover = function( e ) {
		if(! this._was_initialized ) {
			return;
		}

		var id = this._checkMouseEvent( e , 'rollover' );
		if( id == -1 ) {
			return;
		}

		// Cancel any show actions
		this._ro_targets[id].waiting_on_show = false;

		// If we are waiting to hide this rollover or
		// already not displaying it then ignore this action
		if( this._ro_targets[id].waiting_on_hide 
			|| !this._ro_targets[id].displayed ) {
			return;
		}

		// Initiate the hide action
		this._ro_targets[id].waiting_on_hide = true;
		var tmp_this = this;
		setTimeout( function( ) { 
						if( tmp_this._ro_targets[id].waiting_on_hide ) {
							tmp_this._hideRollover( id );
						}
					} , this._hide_delay );
	}

	/**
	 *  This is a generic check that makes sure the mouse out/over events
	 *  should be handled or ignored. If an event should be handled
	 *  the id of the target element is returned
	 * 
	 *  @param type If we are checking an event on rollover or target
	 */
	ZRollover.prototype._checkMouseEvent = function( e, type ) {

		// If we used bindForIEeventListener() then currentTarget should be setup for IE for us
	  	if ( e && e.currentTarget ) {
	  		// currentTarget refers to the element handling the event not the original target
			var curtg = e.currentTarget;
		} else {
			return -1;
		}

		// locate the target element first 
		var id;
		for( id = 0; id < this._ro_targets.length; id++ ) {
			if( type == 'rollover' ) {
				if( this._ro_targets[id].rollover === curtg ) {
					break;
				}
			} else { // target
				if( this._ro_targets[id].ro_target === curtg ) {
					break;	
				}			
			}
		}

		ZUtil.stopPropagation( e );
		
		var reltg = ZUtil.getRelatedTarget( e );
		
		if(! reltg ) {
			return -1;
		}

		// Walk the tree from related target up until body is reached or 
		// curtg is reached. If curtg is reached then we know the mouseover event 
		// was inside the curtg since both the target and related target is under the curtg
		var tmp = reltg;
		while( tmp.nodeName != 'BODY' && tmp.nodeName != 'HTML' ) {
			if( tmp == curtg ) {
				return -1;
			}
			tmp = tmp.parentNode;
			if(! tmp ) {
				return -1;
			}
		}	

		return id;

	}

	/**
	 * Creates the rollover and displayes it.
	 * ro_id is the internal integer identifier.
	 */
	ZRollover.prototype._showRollover = function( ro_id, x, y, adj_left, adj_up ) {

		var rollover = document.createElement( 'div' );		

		rollover.style.top =  y + 'px';
		rollover.style.left = x + 'px';
		rollover.style.padding = '1px';
		rollover.style.zIndex = 3;
		rollover.style.border= "1px solid black";
		rollover.style.backgroundColor = '#ffffee';
		rollover.style.position = "absolute";

		var d      = document.createElement( 'div' );
		d.align    = "right";
		var img    = document.createElement( 'img' );
		img.src    = '/images/close-small.gif';
		img.width  = '33';
		img.height = '7';
		img.border = '0';
		ZUtil.addEvent( img, 'click', ZUtil.isIE ? ZUtil.bindForIEeventListener( this, this._closeRollover ) : ZUtil.bind( this, this._closeRollover ) );
				
		d.appendChild( img );

		var cont = document.createElement( 'div' );
		cont.style.paddingRight   = "5px";
		cont.style.paddingLeft    = "5px";
		cont.style.marginBottom   = "3px";
		cont.style.textAlign = "left";

		// Get the contents of the rollover
		var content_error = false;
		var rollover_id = this._ro_targets[ro_id].ro_id;
		if( rollover_id == '' ) {
			content_error = 'No content id is missing for the element';
		}

		var rollover_type = this._ro_targets[ro_id].ro_type;
		if( !content_error && rollover_type == '' ) {
			content_error = 'No content type is specified for the element';
		} else {
			if( ( rollover_type in this._display_properties ) && this._display_properties[rollover_type]['width'] != null ) {
				cont.style.width = this._display_properties[rollover_type]['width'];
			}
		}

		if( !content_error	&& !( rollover_type in this._data_sources ) ) {
			content_error = 'Data source for content type: ' + rollover_type + ' does not exist';
		}
		
		var content_data = null;
		if( !content_error ) {
			try {
				content_data = this._data_sources[rollover_type].getData( rollover_id );
				if( content_data == null ) {
					// No data to show so bail 
					img.onclick = null; // clear event handler for IE memory leak
					return;
				}
			} catch( e ) {
				content_error = e.name + ': ' + e.message;
			}
		}

		if(! content_error ) {
			cont.innerHTML = this._data_sources[rollover_type].getHtml( content_data ); 
		} else {
			cont.innerHTML = content_error;
		}
		
		rollover.appendChild( d );
		rollover.appendChild( cont );
		
		// We have to hide it first before adding to document
		// This is done because the position has not been determined yet.
		rollover.style.visibility = 'hidden';
		document.body.appendChild( rollover );

		if( adj_left ) {
			rollover.style.left = ( x - rollover.offsetWidth ) + 'px';
		}

		if( adj_up ) {
			rollover.style.top =  ( y - rollover.offsetHeight ) + 'px';
		}

		// Unhide here once element is positioned correctly
		rollover.style.visibility = 'visible';

		// add to target
		this._ro_targets[ro_id].rollover = rollover;
		
		// Update status flags
		this._ro_targets[ro_id].waiting_on_show = false;
		this._ro_targets[ro_id].displayed = true;

		// Setup the show/hide events for the rollover itself.
		if( ZUtil.isIE ) {
			rollover.onmouseover = ZUtil.bindForIEeventListener( this, this._mouseOverRollover );
			rollover.onmouseout = ZUtil.bindForIEeventListener( this, this._mouseOutRollover );
		} else {
			ZUtil.addEvent( rollover, 'mouseover', ZUtil.bind( this, this._mouseOverRollover ) );
			ZUtil.addEvent( rollover, 'mouseout',  ZUtil.bind( this, this._mouseOutRollover ) );
		}
	}
	
	/**
	 * Hides the rollover by removing its element.
	 * ro_id is the internal integer identifyer
	 */
	ZRollover.prototype._hideRollover = function( ro_id ) {

		var ro = this._ro_targets[ro_id];
		if( ZUtil.isIE ) {
			ro.rollover.onmouseover = null;
			ro.rollover.onmouseout = null;
		}
		if( ro.rollover ) {
			ro.rollover.parentNode.removeChild( ro.rollover );
		}
		ro.rollover = null;

		// Update status flags
		this._ro_targets[ro_id].waiting_on_hide = false;
		this._ro_targets[ro_id].displayed = false;

		open_id = '';

	}
	
	/**
	 * This is event handler for the close button of the rollover.
	 */
	ZRollover.prototype._closeRollover = function( e ) {

		var t = ZUtil.getTarget( e );
		if( !t ) {
			return;
		}

		// Go up two elements in the dom tree to the div
		// containing the rollover.
		t = t.parentNode.parentNode;

		// Locate which target 
		var id;
		for( id = 0; id < this._ro_targets.length; id++ ) {
			if( this._ro_targets[id].rollover === t ) {
				break;
			}
		}
		
		this._hideRollover( id );

	}