﻿/*--------------------------------------------------------------------------*/
/*	Utility routines
	
	Certain concepts and methods taken from Prototype by Sam Stephenson,
	under the terms of the MIT-style license -- see below.
/*--------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/
/*  Prototype JavaScript framework, version 1.5.0
 *  (c) 2005-2007 Sam Stephenson
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://prototype.conio.net/
 *
/*--------------------------------------------------------------------------*/


// Crad Namespace
if ( !window.Crad )
	var Crad = new Object();

// Allow Element to be extended
if (!window.Element)
  var Element = function () {};


// $
//
// returns an Element given an Element or an element's id string.
function $(e)
{
	var elem = e;
	if ( typeof e == 'string')
		elem = document.getElementById(e);
	return Element.extend(elem);
}

// Element.extend
//
// Extend the Element object with the methods in Element.Methods
// The addMethods function, below, attempts to add all the methods in
// Element.Methods to the Element prototype for browsers that create
// HTML elements using the Element prototype.
// Element.extend first checks to see if the browser supports the Element prototype
// (e.g., _nativeExtensions). If not, it just adds all the methods in
// Element.Methods to the given element.
Element.extend = function(element)
{
	if (!element || _nativeExtensions || element.nodeType == 3)
		return element;
		
	if ( !element._extended )
	{
		for ( var p in Element.Methods )
		{
			var value = Element.Methods[p];
			if ( typeof value == 'function' && !( p in element))
			{
				element[p] = value;
			}
		}
		element._extended = true;
	}
	return element;
}

// Element.addMethods
//
// add the methods under Element.Methods to the HTML prototype
var _nativeExtensions = false;

Element.addMethods = function(methods) {
	function copy(methods, destination, onlyIfAbsent)
	{
		onlyIfAbsent = onlyIfAbsent || false;
		for (var property in methods)
		{
			var value = methods[property];
			if (!onlyIfAbsent || !(property in destination))
				destination[property] = value;
		}
	}

	if (typeof HTMLElement != 'undefined') {
		copy(Element.Methods, HTMLElement.prototype);
		_nativeExtensions = true;
	}
}

Element.Methods = function() {}

Element.Methods.addClassName = function(name)
{
	this.className += " " + name;
}
Element.Methods.removeClassName = function(name)
{
	var temp = new Array();
	temp = this.className.split(" ");
	var len = temp.length;
	var classes = "";
	for ( var i=0; i<len; i++ )
	{
		if ( temp[i] != name )
		{
			classes += " " + temp[i];
		}
	}
	this.className = classes;
}
Element.Methods.hide = function()
{
    this.style.display = 'none';
}
Element.Methods.show = function()
{
    this.style.display = '';
}



// getEventDelegator
// Return the Crad.EventDelegator for the given element, creating one if necessary.
Element.Methods.getEventDelegator = function()
{
	if ( this.delegator == null )
	{
		this.delegator = new Crad.EventDelegator(this);
	}
	return this.delegator;
}

// mouseWithin
// Return true if the mouse is inside the element.
Element.Methods.mouseWithin = function(e, border)
{
	border = border || 0;
	var isWithin = false;
	var mousePos = eventPositionWithinElementXY( e, this );
	if ( mousePos.y > border
		&& (mousePos.y+border) < parseInt(this.clientHeight,10)
		&& mousePos.x > border
		&& (mousePos.x+border) < parseInt(this.clientWidth,10)
		)
		isWithin = true;
		
	return isWithin;
}

// getImmediateChildren
// Return an array of nodes that are the immediate childNode of the given element.
// Note that textNodes are excluded
Element.Methods.getImmediateChildren = function()
{
	var elem = this;
	var nodes = elem.getElementsByTagName('*');	// avoids text nodes
	var children = new Array();
	var len = nodes.length;
	for ( var i=0; i<len; i++ )
	{
		var node = nodes[i];
		if ( node.parentNode != elem ) 
			continue;
		children.push($(node));
	}
	return children;
}

// getChildIndexInParent
// return the index of the given element in its parent element
Element.Methods.getIndexInParent = function()
{
	var childElem = this;
	var parentNode = childElem.parentNode;
	var nodes = parentNode.getElementsByTagName('*');	// avoids text nodes
	var index = 0;
	var len = nodes.length;
	for ( var i=0; i<len; i++ )
	{
		var node = nodes[i];
		// only count immediate children
		if ( node.parentNode != parentNode ) 
			continue;
		
		if ( node == childElem )
			break;
		index++;
	}
	return index;
}

// positionWithinPageXY, positionWithinElementXY
// return x,y offsets within page or element
Element.Methods.positionWithinPageXY = function()
{
	var elem = this;
	var left = elem.offsetLeft;
	var top = elem.offsetTop;
	var parent = elem.offsetParent;
	while ( parent != null ) 
	{
		left += parent.offsetLeft;
		top += parent.offsetTop;
		parent = parent.offsetParent;
	}
	return { x:left, y:top };
}
Element.Methods.positionRelativeToElementXY = function(elem)
{
	elem = $(elem);
	var pos = this.positionWithinPageXY();
	pos.x -= elem.offsetLeft;
	pos.y -= elem.offsetTop;
	return pos;
}


Element.addMethods();


// Function.bind()
//
// returns a function object that will apply a call to the function in question in the context of
// the object passed in.
// E.g.,
//	 x = {value:100};
//	 foo = function() { var value=555; alert(this.value); }
//	 y = foo.bind(x);
//	 foo(); // will alert: "undefined", since this.value is not defined (although, value is defined)
//	 y(); // will alert: "100"
Function.prototype.bind = function(obj) { 
	var _method = this; 
	temp = function() { 
		return _method.apply(obj, arguments); 
	}; 
	return temp; 
}


// stopEventBubble
// (cross-browser)
// Stops the current event from bubbling up to other event handlers.
function stopEventBubble(e)
{
	if (!e)
		if (window.event)
			e = window.event;
		else
			return false;
	if (e.cancelBubble != null)
		e.cancelBubble = true;
	if (e.stopPropagation)
		e.stopPropagation();
	if (e.preventDefault)
		e.preventDefault();
	if (window.event)
		e.returnValue = false;
	if (e.cancel != null)
		e.cancel = true;
}  // StopEvent


// preventTextSelection
//
// Prevent text inside the given elements from being selected by cancelling event bubbling for two events:
//	onselectstart (IE)
//	onmousedown
function preventTextSelection()
{
	var len = arguments.length;
	for ( var i=0; i<len; i++ )
	{
		var elem = $(arguments[i]);
		elem.onselectstart = stopEventBubble;	// IE
		elem.onmousedown = stopEventBubble;	// everything
	}
}


// shallowCopy, shallowCompare
//
// utility functions that make a shallow copy of an object and perform a shallow compare of two object.
function shallowCopy(obj)
{
	var temp = new Object();
	for ( var i in obj )
	{
		temp[i] = obj[i];
	}
	return temp;
}
function shallowCompare(obj1, obj2)
{
	if ( obj1 == null || obj2 == null )
		return (obj1 == null && obj2 == null);
	if ( obj1.length != obj2.length )
		return false;
	for ( var i in obj1 )
	{
		if ( obj1[i] != obj2[i] )
			return false
	}
	return true;
}


// pagePosition, elementPosition
//
function eventPositionWithinPageXY(evt)
{
	var tempX = evt.pageX; var tempY = evt.pageY;
	if (tempX == undefined) {
		tempX = event.clientX + document.body.scrollLeft;
		tempY = event.clientY + document.body.scrollTop;
	}
	return { x:tempX, y:tempY };
}
function eventPositionWithinElementXY(evt, elem)
{
	var pos = eventPositionWithinPageXY(evt);
	var elemPos = elem.positionWithinPageXY();
	pos.x -= elemPos.x;
	pos.y -= elemPos.y;
	return pos;
}


// sets the 'src' property of an image element.
//
// cross-browser (code to work around a bug in safari).
//
// The safari bug:  When an image source is changed, Safari will keep the dimensions from the old image,
// so the new image gets distorted. The workaround used here is to remove the image from the DOM altogether,
// then change the source and put it back.
function setImageSrc(image, src)
{
	image = $(image);
	var isSafari = (navigator.userAgent.indexOf('Safari') > 0);
	if ( isSafari )
	{
		var parent = image.parentNode;
		parent.removeChild(image);
		image.src = src;
		parent.appendChild(image);
	}
	else
	{
		image.src = src;
	}
}

// BackgroundDownloader
//
// Takes an array of paths to images and downloads them in a background method.
Crad.BackgroundDownloader = function(imagePaths)
{
	var delay = 200;	// delay between each download (ms)
	var i = 0;
	var len = imagePaths.length;
	var imageArray = new Array(len);
	
	// To make this work in Firefox, we insert a delay after each image load --
	// without the delay, Firefox appears to just stop the downloads; perhaps
	// it won't allow the ports to be overloaded. Who knows?
	setTimeout(backgroundDownload, delay);
	
	function backgroundDownload()
	{
		if ( i<len )
		{
			imageArray[i] = new Image();
			imageArray[i].src = imagePaths[i];
			i++;
			setTimeout(backgroundDownload, delay);
		}
	}
}

