/* Javascript Scroller (JSS) library starts here. */
/* Code copyright 2005, JPortal, All Rights Reserved. 
   Code may be released under the GPL license - see www.jportalhome.com for more information
*/

// This is just an array of all the scrollers on the page.
var jss_scrollers = [];

/**
 * Hash library function. Creates a new Hash object, and uses passed arguments
 * to populate the Hash.
 * Examples:
 * new Hash('key1', 'value1', 'key2', 'value2', 3, 'value3', 4, 'value4', 5, 6);
 * new Hash({'key1': 'value1', 'key2': 'value2'});
 * new Hash(new Array('val1', 'val2', 'val3'));
 * new Hash(existing_hash_object);
 */
function Hash()
{
	this.length = 0;
	this.numericLength = 0; 
	this.elementData = [];
	
	// If the first passed argument is a hash...
	if (arguments[0] && arguments[0].elementData)
	{
		this.length = arguments[0].length;
		this.numericLength = arguments[0].numericLength;
		this.elementData = arguments[0].elementData;
		return;
	}
	
	// Load hash data from an existing array, if it was passed
	if (typeof(arguments[0]) == 'object')
	{
		for (var i in arguments[0])
		{
			this.set(i, arguments[0][i]);
		}
		return;
	}
	
	// For regular Hash operations - load hash object from passed parameters
	for (var i = 0; i < arguments.length; i += 2)
	{
		if (typeof(arguments[i + 1]) != 'undefined')
		{
			this.set(arguments[i], arguments[i+1]);
		}
	}
};


/**
 * Hash prototype - get. Gets a Hash value that is associated with the passed in_key.
 */
Hash.prototype.get = function(in_key)
{
	return this.elementData[in_key];
};


/**
 * Hash prototype - set. Sets a Hash value that is paired with the passed in_key.
 */
Hash.prototype.set = function(in_key, in_value)
{
	// Allow "auto" to be a valid key, it will be converted to an integer here
	in_key = (in_key == 'auto' ? this.numericLength + 1 : in_key);
	
	if (typeof(in_value) != 'undefined')
	{
		if (typeof(this.elementData[in_key]) == 'undefined')
		{
			this.length++;
			if (parseInt(in_key) == in_key)
			{
				this.numericLength++;
			}
		}

		return this.elementData[in_key] = in_value;
	}

	return false;
};


/**
 * Hash prototype - remove. Removes an element from the Hash associated with the passed in_key.
 */
Hash.prototype.remove = function(in_key)
{
	var tmp_value;
	if (typeof(this.elementData[in_key]) != 'undefined')
	{
		this.length--;
		if (in_key == parseInt(in_key)) 
		{
			this.numericLength--;
		}

		tmp_value = this.elementData[in_key];
		delete this.elementData[in_key];
	}

	return tmp_value;
};


/**
 * Hash prototype - size. Returns the number of elements in the Hash.
 */
Hash.prototype.size = function()
{
	return this.length;
};


/**
 * Hash prototype - has. Checks to see if a key has a value in the Hash.
 */
Hash.prototype.has = function(in_key)
{
	return typeof(this.elementData[in_key]) != 'undefined';
};


/**
 * Hash prototype - find. Searches the Hash for a key that corresponds to the passed
 * value. 
 */
Hash.prototype.find = function(in_obj)
{
	for (var tmp_key in this.elementData) 
	{
		if (this.elementData[tmp_key] == in_obj) 
		{
			return tmp_key;
		}
	}
	return null;
};


/**
 * Hash prototype - merge. Merges the passed Hash into the current Hash.
 */
Hash.prototype.merge = function(in_hash)
{
	for (var tmp_key in in_hash.elementData) 
	{
		if (typeof(this.elementData[tmp_key]) == 'undefined') 
		{
			this.length++;
			if (tmp_key == parseInt(tmp_key)) 
			{
				this.numericLength++;
			}
		}

		this.elementData[tmp_key] = in_hash.elementData[tmp_key];
	}
};


/**
 * Hash prototype - compare. Compares the passed Hash with the current Hash.
 * Returns false if any elements are different, or true if all are the same.
 */
Hash.prototype.compare = function(in_hash)
{
	if (this.length != in_hash.length) 
	{
		return false;
	}

	for (var tmp_key in this.elementData) 
	{
		if (this.elementData[tmp_key] != in_hash.elementData[tmp_key]) 
		{
			return false;
		}
	}
	
	return true;
};


/**
 * "hashify" arrays. Converts an array to a Hash object.
 */
function hashify (in_array)
{
	// Loop through array and convert all array members to Hashes
	if (typeof(in_array) == 'object')
	{
		for (var i in in_array)
		{
			if (typeof(in_array[i]) == 'object')
			{
				in_array[i] = hashify(in_array[i]);
			}
		}
		
		// Make the array a hash
		in_array = new Hash(in_array);
	}
	
	return in_array;
};

/**
 * JSS constructor. Call like this:
 *
 * var my_scroller = new JSS('div_id', { ... array of arguments ... });
 */
function JSS (div_id, in_arguments)
{
	// Put this scroller in the array of scrollers for this page
	jss_scrollers[div_id] = this;
	
	// Save div ID for future use
	this.div_id = div_id;

	// Save all arguments
	this.options = hashify(in_arguments);

	// Save the element of the scroller. Create element where all the scrolling HTML will be placed,
	// and save that too.
	this.scroller_element = jss_get_element(div_id);
	this.scroller_element.innerHTML = '<div id="'+div_id+'internal"style=" position: absolute; top: 0px; left: 0px;"></div>';
	this.scroller_element_inner = jss_get_element(div_id+'internal');
	
	// Set scroller div attributes
	this.scroller_element.style.position = 'relative';
	this.scroller_element.style.overflow = 'hidden';
	this.scroller_element.style.height = this.options.get('visible_height') + 'px';
	this.scroller_element.style.width = this.options.get('visible_width') + 'px';
	this.scroller_element.style.margin = '0px';
	this.scroller_element.style.padding = '0px';
	
	// Initialize variables that the scroll function will use
	this.scroll_one_row = false;
	this.pos_top = 0;
	this.current_row = (this.options.get('scroll_delta') > 0 ? 0 : this.options.get('rows').size() - 1);
	this.row_scrolled = 0;
	this.row_heights_set = false;
	this.pausing = false;

	window.onload = function () { jss_start_all_scrollers(); };
}

/**
 * The scroller function. 
 * do_not_scroll is to start automatically scrolling on onload.
 */
JSS.prototype.scroll = function (do_not_scroll)
{
	// Should we pause right away?
	if (this.pausing && !this.options.get('row_smooth_pause'))
	{
		this.pausing = false;
		this.timeout = false;
		return;
	}

	// If we have not done so already, put all the rows into HTML and
	// get the height of each row.
	if (!this.row_heights_set)
	{
		// Build CSS attributes that each row's table will have
		var row_table_css = '';
		for (var i in this.options.get('row_table_styles').elementData)
		{
			if (typeof(this.options.get('row_table_styles').get(i)) != 'function')
			{
				row_table_css += i + ':' + this.options.get('row_table_styles').get(i) + ';';
			}
		}

		// Now loop through all rows, output the HTML and save data about them (namely,
		// how tall the rows are)
		this.row_heights = [];
		this.row_heights_set = true;
		this.total_rows_height = 0;
		this.tallest_row_height = 0;
		for (var i = 0; i < this.options.get('rows').size(); ++i)
		{
			// Build HTML for this row
			var row_html = '<div id="' + this.div_id + 'tmprow' + i + '" style="width: '+this.options.get('visible_width')+'px;"><table cellpadding="0" cellspacing="0" style="width: 100%; margin: 0px; padding: 0px;"><tr><td style="width: 100%;">'
					+ '<table cellpadding="0" cellspacing="0" style="width: 100%; margin: 0px; padding: 0xp; '+row_table_css+'"><tr><td>'
					+ this.options.get('rows').get(i)
					+ '</td></tr></table>' 
				+ '</td></tr></table></div>';

			// Add each row to the HTML, with appropriate padding etc.
			this.scroller_element_inner.innerHTML += row_html;
			
			// Get the row height
			this.row_heights[i] = jss_get_element(this.div_id+'tmprow'+i).offsetHeight;
			
			// Is this the tallest row?
			if (this.row_heights[i] > this.tallest_row_height)
			{
				this.tallest_row_height = this.row_heights[i];
			}
			// Update total row height
			this.total_rows_height += this.row_heights[i];
		}

		// If the user options dictate it, make sure all rows are the
		// same height.
		if (this.options.get('force_same_row_height'))
		{
			for (var i = 0; i < this.options.get('rows').size(); ++i)
			{
				jss_get_element(this.div_id+'tmprow'+i).style.height = this.tallest_row_height + 'px';
				this.row_heights[i] = this.tallest_row_height;
			}
			this.total_rows_height = this.tallest_row_height * (this.options.get('rows').size());
		}
	
		// Duplicate the scroller HTML, so we have two version of the scrolling
		// HTML to create a smooth scrolling experience.
		this.scroller_element_inner.innerHTML += this.scroller_element_inner.innerHTML;

		// If we're supposed to... resize the visible area to
		// accomodate the tallest row.
		if (this.options.get('accomodate_tallest_row'))
		{
            if (!this.options.get('rows_to_show_at_once'))
			{
				this.options.set('rows_to_show_at_once', 1);
			}
			this.scroller_element.style.height = ((this.tallest_row_height
				+ (this.options.get('accomodation_padding') * 2)) * this.options.get('rows_to_show_at_once')) + 'px';
		}

		// Workaround for weird rendering in some browsers
		this.scroller_element_inner.style.top = (this.pos_top - 15) + 'px';
		setTimeout('jss_get_element("'+this.scroller_element_inner.id+'").style.top = "'+this.pos_top+'px";', 10);

		// Exit if we're not supposed to scroll right away
		do_not_scroll = typeof(do_not_scroll) != 'undefined' ? do_not_scroll : false;
		if (do_not_scroll)
		{
			return;
		}

		// Delay before beginning scroll?
		if (this.options.get('initial_pause'))
		{
			setTimeout('jss_scrollers["'+this.div_id+'"].scroll();', this.options.get('initial_pause'));
			return;
		}
	}
	
	// Update positions
	this.pos_top -= this.options.get('scroll_delta');
	this.row_scrolled += Math.abs(this.options.get('scroll_delta'));
	if (this.row_scrolled >= this.row_heights[this.current_row])
	{
		// Make sure we stop at the top/botttom of a row, if we're supposed to
		// stop after scrolling a full row.
		if (this.options.get('row_scroll_pause'))
		{
			if (this.options.get('scroll_delta') > 0)
			{
				this.pos_top += (this.row_scrolled - this.row_heights[this.current_row]);
			}
			else
			{
				this.pos_top -= (this.row_scrolled - this.row_heights[this.current_row]);
			}
		}
	}

	// Scroll a little more
	this.scroller_element_inner.style.top = this.pos_top + 'px';

	// Should we delay longer before scrolling again?
	if ((this.options.get('row_scroll_pause') || (this.options.get('row_smooth_pause') && this.pausing))
		&& this.row_scrolled >= this.row_heights[this.current_row])
	{
		// Do we have to pause?
		if (this.pausing || this.scroll_one_row)
		{
			this.pausing = false;
			this.timeout = false;
			this.scroll_one_row = false;
		}
		// We do not need to pause, scroll after a specialized delay
		else
		{
			this.timeout = setTimeout('jss_scrollers["'+this.div_id+'"].scroll();', this.options.get('row_scroll_pause'));
		}
	}
	// Set delay to scroll again normally
	else
	{
		this.timeout = setTimeout('jss_scrollers["'+this.div_id+'"].scroll();', this.options.get('scroll_delay'));
	}

	// Have we scrolled a full row?
	if (this.row_scrolled >= this.row_heights[this.current_row])
	{
		// Reset current row scrolled counter
		this.row_scrolled = 0;	
	
		// Update row counter
		// Are we scrolling up?
		if (this.options.get('scroll_delta') > 0)
		{
			this.current_row++;
		}
		// We're scrolling down.
		else
		{
			this.current_row--;
		}	

		// Reset row counter if needed
		if (typeof(this.row_heights[this.current_row]) == 'undefined')
		{
			if (this.options.get('scroll_delta') > 0)
			{
				this.current_row = 0;
			}
			else
			{
				this.current_row = this.row_heights.length - 1;
			}
		}
	}
	
	// Reset top positions for a continuous scroll
	// Are we scrolling up?
	if (this.options.get('scroll_delta') > 0)
	{
		// Restart the scroller, if needed, to create a smooth continuous scroll
		if (Math.abs(this.pos_top) >= this.total_rows_height)
		{
			this.pos_top = this.pos_top + this.total_rows_height;
		}
	}
	// We're scrolling down.
	else
	{	
		// Restart the scroller, if needed, to create a smooth continuous scroll
		if (this.pos_top > 0)
		{
			this.pos_top = this.pos_top - this.total_rows_height;
		}
	}	
};

/**
 * Get a DOM element with the specified ID.
 */
function jss_get_element (dom_id)
{
	return document.all ? document.all[dom_id] : document.getElementById(dom_id);
}

/**
 * Resume scrolling
 */
function jss_play (div_id)
{
	jss_scrollers[div_id].scroll();
}

/**
 * Pause scrolling
 */
function jss_pause (div_id)
{
	if (jss_scrollers[div_id].timeout)
	{
		jss_scrollers[div_id].pausing = true;
		if (!jss_scrollers[div_id].row_scrolled)
		{
			clearTimeout(jss_scrollers[div_id].timeout);
			jss_scrollers[div_id].timeout = false;
		}
	}
}

/**
 * Toggle the playing status.
 */
function jss_toggle_playing (div_id)
{
	if (jss_scrollers[div_id].pausing || !jss_scrollers[div_id].timeout)
	{
        clearTimeout(jss_scrollers[div_id].timeout);
		jss_scrollers[div_id].timeout = false;
		jss_play(div_id);
	}
	else if (jss_scrollers[div_id].timeout)
	{
		jss_pause(div_id);
	}
}

/**
 * Change direction and speed of vertical scrolling. If you call with a direction of
 * 1, the scroller will scroll UP. If you call with a direction of -1, it will scroll
 * down. You can also call with values like 2 or 0.5 to speed up or slow down scrolling.
 */
function jss_vertical_scrolling (div_id, direction)
{
	// Change direction/speed
	var previous_direction = (jss_scrollers[div_id].options.get('scroll_delta') > 0 ? 1 : -1);
	jss_scrollers[div_id].options.set('scroll_delta', Math.abs(jss_scrollers[div_id].options.get('scroll_delta')) * direction);
	
	// Adjust various values if we are changing direction
	if (previous_direction != direction)
	{
		// If a full row has been scrolled, we'll have to change it for the new direction
		if (!jss_scrollers[div_id].row_scrolled)
		{
			if (direction > 0)
			{
				jss_scrollers[div_id].current_row++;
			}
			else
			{
				jss_scrollers[div_id].current_row--;
			}
		}
		
		// Reset row counter if needed
		if (typeof(jss_scrollers[div_id].row_heights[jss_scrollers[div_id].current_row]) == 'undefined')
		{
			if (jss_scrollers[div_id].options.get('scroll_delta') > 0)
			{
				jss_scrollers[div_id].current_row = 0;
			}
			else
			{
				jss_scrollers[div_id].current_row = jss_scrollers[div_id].row_heights.length - 1;
			}
		}
	
		// Since we are reversing direction, we must also essentially reverse the
		// number of pixels scrolled in the current row.
		if (jss_scrollers[div_id].row_scrolled > 0)
		{
			jss_scrollers[div_id].row_scrolled = Math.abs(jss_scrollers[div_id].row_heights[jss_scrollers[div_id].current_row] - jss_scrollers[div_id].row_scrolled);
		}
	}
	
	// Scroll one row even if paused?
	if (jss_scrollers[div_id].options.get('allow_forced_scroll_when_paused') && !jss_scrollers[div_id].timeout)
	{
		jss_scrollers[div_id].scroll_one_row = true;
		jss_play(div_id);
	}
	// Restart scroller immediately?
	else if (jss_scrollers[div_id].options.get('show_direction_change_immediately'))
	{
		clearTimeout(jss_scrollers[div_id].timeout);
		jss_scrollers[div_id].timeout = false;
		jss_play(div_id);
	}
}

/**
 * This function unconditionally starts all JSS scrollers in motion.
 */
function jss_start_all_scrollers ()
{
	for (var i in jss_scrollers)
	{
		if (jss_scrollers[i].scroll)
		{
			if (jss_scrollers[i].options.has('start_scrolling_automatically') && jss_scrollers[i].options.get('start_scrolling_automatically') == false)
			{
				jss_scrollers[i].scroll(true);
			}
			else
			{
				jss_scrollers[i].scroll();
			}
		}
	}
}
/* Javascript Scroller library ends here */
