/**
*
*  Copyright 2005 Sabre Airline Solutions
*
*  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
*  file except in compliance with the License. You may obtain a copy of the License at
*
*         http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software distributed under the
*  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
*  either express or implied. See the License for the specific language governing permissions
*  and limitations under the License.
**/


//-------------------- rico.js
var Rico = {
	Version: '1.1.2',
	prototypeVersion: parseFloat(Prototype.Version.split(".")[0] + "." + Prototype.Version.split(".")[1])
}

if((typeof Prototype=='undefined') || Rico.prototypeVersion < 1.3)
throw("Rico requires the Prototype JavaScript framework >= 1.3");

Rico.ArrayExtensions = new Array();

if (Object.prototype.extend) {
	Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Object.prototype.extend;
}else{
	Object.prototype.extend = function(object) {
		return Object.extend.apply(this, [this, object]);
	}
	Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Object.prototype.extend;
}

if (Array.prototype.push) {
	Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.push;
}

if (!Array.prototype.remove) {
	Array.prototype.remove = function(dx) {
		if( isNaN(dx) || dx > this.length )
		return false;
		for( var i=0,n=0; i<this.length; i++ )
		if( i != dx )
		this[n++]=this[i];
		this.length-=1;
	};
	Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.remove;
}

if (!Array.prototype.removeItem) {
	Array.prototype.removeItem = function(item) {
		for ( var i = 0 ; i < this.length ; i++ )
		if ( this[i] == item ) {
			this.remove(i);
			break;
		}
	};
	Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.removeItem;
}

if (!Array.prototype.indices) {
	Array.prototype.indices = function() {
		var indexArray = new Array();
		for ( index in this ) {
			var ignoreThis = false;
			for ( var i = 0 ; i < Rico.ArrayExtensions.length ; i++ ) {
				if ( this[index] == Rico.ArrayExtensions[i] ) {
					ignoreThis = true;
					break;
				}
			}
			if ( !ignoreThis )
			indexArray[ indexArray.length ] = index;
		}
		return indexArray;
	}
	Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.indices;
}

// Create the loadXML method and xml getter for Mozilla
if ( window.DOMParser &&
window.XMLSerializer &&
window.Node && Node.prototype && Node.prototype.__defineGetter__ ) {

	if (!Document.prototype.loadXML) {
		Document.prototype.loadXML = function (s) {
			var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
			while (this.hasChildNodes())
			this.removeChild(this.lastChild);

			for (var i = 0; i < doc2.childNodes.length; i++) {
				this.appendChild(this.importNode(doc2.childNodes[i], true));
			}
		};
	}

	Document.prototype.__defineGetter__( "xml",
	function () {
		return (new XMLSerializer()).serializeToString(this);
	}
	);
}

document.getElementsByTagAndClassName = function(tagName, className) {
	if ( tagName == null )
	tagName = '*';

	var children = document.getElementsByTagName(tagName) || document.all;
	var elements = new Array();

	if ( className == null )
	return children;

	for (var i = 0; i < children.length; i++) {
		var child = children[i];
		var classNames = child.className.split(' ');
		for (var j = 0; j < classNames.length; j++) {
			if (classNames[j] == className) {
				elements.push(child);
				break;
			}
		}
	}

	return elements;
}


//-------------------- ricoAccordion.js
Rico.Accordion = Class.create();

Rico.Accordion.prototype = {

	initialize: function(container, options) {
		this.container            = $(container);
		this.lastExpandedTab      = null;
		this.accordionTabs        = new Array();
		this.setOptions(options);
		this._attachBehaviors();
		if(!container) return;

		this.container.style.borderBottom = '1px solid ' + this.options.borderColor;
		// validate onloadShowTab
		if (this.options.onLoadShowTab >= this.accordionTabs.length)
		this.options.onLoadShowTab = 0;

		// set the initial visual state...
		for ( var i=0 ; i < this.accordionTabs.length ; i++ )
		{
			if (i != this.options.onLoadShowTab){
				this.accordionTabs[i].collapse();
				this.accordionTabs[i].content.style.display = 'none';
			}
		}
		this.lastExpandedTab = this.accordionTabs[this.options.onLoadShowTab];
		if (this.options.panelHeight == 'auto'){
			var tabToCheck = (this.options.onloadShowTab === 0)? 1 : 0;
			var titleBarSize = parseInt(RicoUtil.getElementsComputedStyle(this.accordionTabs[tabToCheck].titleBar, 'height'));
			if (isNaN(titleBarSize))
			titleBarSize = this.accordionTabs[tabToCheck].titleBar.offsetHeight;

			var totalTitleBarSize = this.accordionTabs.length * titleBarSize;
			var parentHeight = parseInt(RicoUtil.getElementsComputedStyle(this.container.parentNode, 'height'));
			if (isNaN(parentHeight))
			parentHeight = this.container.parentNode.offsetHeight;

			this.options.panelHeight = parentHeight - totalTitleBarSize-2;
		}

		this.lastExpandedTab.content.style.height = this.options.panelHeight + "px";
		this.lastExpandedTab.showExpanded();
		this.lastExpandedTab.titleBar.style.fontWeight = this.options.expandedFontWeight;

	},

	setOptions: function(options) {
		this.options = {
			expandedBg          : '#63699c',
			hoverBg             : '#63699c',
			collapsedBg         : '#6b79a5',
			expandedTextColor   : '#ffffff',
			expandedFontWeight  : 'bold',
			hoverTextColor      : '#ffffff',
			collapsedTextColor  : '#ced7ef',
			collapsedFontWeight : 'normal',
			hoverTextColor      : '#ffffff',
			borderColor         : '#1f669b',
			panelHeight         : 200,
			onHideTab           : null,
			onShowTab           : null,
			onLoadShowTab       : 0
		}
		Object.extend(this.options, options || {});
	},

	showTabByIndex: function( anIndex, animate ) {
		var doAnimate = arguments.length == 1 ? true : animate;
		this.showTab( this.accordionTabs[anIndex], doAnimate );
	},

	showTab: function( accordionTab, animate ) {
		if ( this.lastExpandedTab == accordionTab )
		return;

		var doAnimate = arguments.length == 1 ? true : animate;

		if ( this.options.onHideTab )
		this.options.onHideTab(this.lastExpandedTab);

		this.lastExpandedTab.showCollapsed();
		var accordion = this;
		var lastExpandedTab = this.lastExpandedTab;

		this.lastExpandedTab.content.style.height = (this.options.panelHeight - 1) + 'px';
		accordionTab.content.style.display = '';

		accordionTab.titleBar.style.fontWeight = this.options.expandedFontWeight;

		if ( doAnimate ) {
			new Rico.Effect.AccordionSize( this.lastExpandedTab.content,
			accordionTab.content,
			1,
			this.options.panelHeight,
			100, 10,
			{ complete: function() {accordion.showTabDone(lastExpandedTab)} } );
			this.lastExpandedTab = accordionTab;
		}
		else {
			this.lastExpandedTab.content.style.height = "1px";
			accordionTab.content.style.height = this.options.panelHeight + "px";
			this.lastExpandedTab = accordionTab;
			this.showTabDone(lastExpandedTab);
		}
	},

	showTabDone: function(collapsedTab) {
		collapsedTab.content.style.display = 'none';
		this.lastExpandedTab.showExpanded();
		if ( this.options.onShowTab )
		this.options.onShowTab(this.lastExpandedTab);
	},

	_attachBehaviors: function() {
		var panels = this._getDirectChildrenByTag(this.container, 'DIV');
		for ( var i = 0 ; i < panels.length ; i++ ) {

			var tabChildren = this._getDirectChildrenByTag(panels[i],'DIV');
			if ( tabChildren.length != 2 )
			continue; // unexpected

			var tabTitleBar   = tabChildren[0];
			var tabContentBox = tabChildren[1];
			this.accordionTabs.push( new Rico.Accordion.Tab(this,tabTitleBar,tabContentBox) );
		}
	},

	_getDirectChildrenByTag: function(e, tagName) {
		var kids = new Array();
		var allKids = e.childNodes;
		for( var i = 0 ; i < allKids.length ; i++ )
		if ( allKids[i] && allKids[i].tagName && allKids[i].tagName == tagName )
		kids.push(allKids[i]);
		return kids;
	}

};

Rico.Accordion.Tab = Class.create();

Rico.Accordion.Tab.prototype = {

	initialize: function(accordion, titleBar, content) {
		this.accordion = accordion;
		this.titleBar  = titleBar;
		this.content   = content;
		this._attachBehaviors();
	},

	collapse: function() {
		this.showCollapsed();
		this.content.style.height = "1px";
	},

	showCollapsed: function() {
		this.expanded = false;
		this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
		this.titleBar.style.color           = this.accordion.options.collapsedTextColor;
		this.titleBar.style.fontWeight      = this.accordion.options.collapsedFontWeight;
		this.content.style.overflow = "hidden";
	},

	showExpanded: function() {
		this.expanded = true;
		this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
		this.titleBar.style.color           = this.accordion.options.expandedTextColor;
		this.content.style.overflow         = "auto";
	},

	titleBarClicked: function(e) {
		if ( this.accordion.lastExpandedTab == this )
		return;
		this.accordion.showTab(this);
	},

	hover: function(e) {
		this.titleBar.style.backgroundColor = this.accordion.options.hoverBg;
		this.titleBar.style.color           = this.accordion.options.hoverTextColor;
	},

	unhover: function(e) {
		if ( this.expanded ) {
			this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
			this.titleBar.style.color           = this.accordion.options.expandedTextColor;
		}
		else {
			this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
			this.titleBar.style.color           = this.accordion.options.collapsedTextColor;
		}
	},

	_attachBehaviors: function() {
		this.content.style.border = "1px solid " + this.accordion.options.borderColor;
		this.content.style.borderTopWidth    = "0px";
		this.content.style.borderBottomWidth = "0px";
		this.content.style.margin            = "0px";

		this.titleBar.onclick     = this.titleBarClicked.bindAsEventListener(this);
		this.titleBar.onmouseover = this.hover.bindAsEventListener(this);
		this.titleBar.onmouseout  = this.unhover.bindAsEventListener(this);
	}

};


//-------------------- ricoAjaxEngine.js
Rico.AjaxEngine = Class.create();

Rico.AjaxEngine.prototype = {

	initialize: function() {
		this.ajaxElements = new Array();
		this.ajaxObjects  = new Array();
		this.requestURLS  = new Array();
		this.options = {};
	},

	registerAjaxElement: function( anId, anElement ) {
		if ( !anElement )
		anElement = $(anId);
		this.ajaxElements[anId] = anElement;
	},

	registerAjaxObject: function( anId, anObject ) {
		this.ajaxObjects[anId] = anObject;
	},

	registerRequest: function (requestLogicalName, requestURL) {
		this.requestURLS[requestLogicalName] = requestURL;
	},

	sendRequest: function(requestName, options) {
		// Allow for backwards Compatibility
		if ( arguments.length >= 2 )
		if (typeof arguments[1] == 'string')
		options = {parameters: this._createQueryString(arguments, 1)};
		this.sendRequestWithData(requestName, null, options);
	},

	sendRequestWithData: function(requestName, xmlDocument, options) {
		var requestURL = this.requestURLS[requestName];
		if ( requestURL == null )
		return;

		// Allow for backwards Compatibility
		if ( arguments.length >= 3 )
		if (typeof arguments[2] == 'string')
		options.parameters = this._createQueryString(arguments, 2);

		new Ajax.Request(requestURL, this._requestOptions(options,xmlDocument));
	},

	sendRequestAndUpdate: function(requestName,container,options) {
		// Allow for backwards Compatibility
		if ( arguments.length >= 3 )
		if (typeof arguments[2] == 'string')
		options.parameters = this._createQueryString(arguments, 2);

		this.sendRequestWithDataAndUpdate(requestName, null, container, options);
	},

	sendRequestWithDataAndUpdate: function(requestName,xmlDocument,container,options) {
		var requestURL = this.requestURLS[requestName];
		if ( requestURL == null )
		return;

		// Allow for backwards Compatibility
		if ( arguments.length >= 4 )
		if (typeof arguments[3] == 'string')
		options.parameters = this._createQueryString(arguments, 3);

		var updaterOptions = this._requestOptions(options,xmlDocument);

		new Ajax.Updater(container, requestURL, updaterOptions);
	},

	// Private -- not part of intended engine API --------------------------------------------------------------------

	_requestOptions: function(options,xmlDoc) {
		var requestHeaders = ['X-Rico-Version', Rico.Version ];
		var sendMethod = 'post';
		if ( xmlDoc == null )
		if (Rico.prototypeVersion < 1.4)
		requestHeaders.push( 'Content-type', 'text/xml' );
		else
		sendMethod = 'get';
		(!options) ? options = {} : '';

		if (!options._RicoOptionsProcessed){
			// Check and keep any user onComplete functions
			if (options.onComplete)
			options.onRicoComplete = options.onComplete;
			// Fix onComplete
			if (options.overrideOnComplete)
			options.onComplete = options.overrideOnComplete;
			else
			options.onComplete = this._onRequestComplete.bind(this);
			options._RicoOptionsProcessed = true;
		}

		// Set the default options and extend with any user options
		this.options = {
			requestHeaders: requestHeaders,
			parameters:     options.parameters,
			postBody:       xmlDoc,
			method:         sendMethod,
			onComplete:     options.onComplete
		};
		// Set any user options:
		Object.extend(this.options, options);
		return this.options;
	},

	_createQueryString: function( theArgs, offset ) {
		var queryString = ""
		for ( var i = offset ; i < theArgs.length ; i++ ) {
			if ( i != offset )
			queryString += "&";

			var anArg = theArgs[i];

			if ( anArg.name != undefined && anArg.value != undefined ) {
				queryString += anArg.name +  "=" + escape(anArg.value);
			}
			else {
				var ePos  = anArg.indexOf('=');
				var argName  = anArg.substring( 0, ePos );
				var argValue = anArg.substring( ePos + 1 );
				queryString += argName + "=" + escape(argValue);
			}
		}
		return queryString;
	},

	_onRequestComplete : function(request) {
		if(!request)
		return;
		// User can set an onFailure option - which will be called by prototype
		if (request.status != 200)
		return;

		var response = request.responseXML.getElementsByTagName("ajax-response");
		if (response == null || response.length != 1)
		return;
		this._processAjaxResponse( response[0].childNodes );

		// Check if user has set a onComplete function
		var onRicoComplete = this.options.onRicoComplete;
		if (onRicoComplete != null)
		onRicoComplete();
	},

	_processAjaxResponse: function( xmlResponseElements ) {
		for ( var i = 0 ; i < xmlResponseElements.length ; i++ ) {
			var responseElement = xmlResponseElements[i];

			// only process nodes of type element.....
			if ( responseElement.nodeType != 1 )
			continue;

			var responseType = responseElement.getAttribute("type");
			var responseId   = responseElement.getAttribute("id");

			if ( responseType == "object" )
			this._processAjaxObjectUpdate( this.ajaxObjects[ responseId ], responseElement );
			else if ( responseType == "element" )
			this._processAjaxElementUpdate( this.ajaxElements[ responseId ], responseElement );
			else
			alert('unrecognized AjaxResponse type : ' + responseType );
		}
	},

	_processAjaxObjectUpdate: function( ajaxObject, responseElement ) {
		ajaxObject.ajaxUpdate( responseElement );
	},

	_processAjaxElementUpdate: function( ajaxElement, responseElement ) {
		ajaxElement.innerHTML = RicoUtil.getContentAsString(responseElement);
	}

}

var ajaxEngine = new Rico.AjaxEngine();


//-------------------- ricoColor.js
Rico.Color = Class.create();

Rico.Color.prototype = {

	initialize: function(red, green, blue) {
		this.rgb = { r: red, g : green, b : blue };
	},

	setRed: function(r) {
		this.rgb.r = r;
	},

	setGreen: function(g) {
		this.rgb.g = g;
	},

	setBlue: function(b) {
		this.rgb.b = b;
	},

	setHue: function(h) {

		// get an HSB model, and set the new hue...
		var hsb = this.asHSB();
		hsb.h = h;

		// convert back to RGB...
		this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
	},

	setSaturation: function(s) {
		// get an HSB model, and set the new hue...
		var hsb = this.asHSB();
		hsb.s = s;

		// convert back to RGB and set values...
		this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
	},

	setBrightness: function(b) {
		// get an HSB model, and set the new hue...
		var hsb = this.asHSB();
		hsb.b = b;

		// convert back to RGB and set values...
		this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
	},

	darken: function(percent) {
		var hsb  = this.asHSB();
		this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
	},

	brighten: function(percent) {
		var hsb  = this.asHSB();
		this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
	},

	blend: function(other) {
		this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
		this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
		this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
	},

	isBright: function() {
		var hsb = this.asHSB();
		return this.asHSB().b > 0.5;
	},

	isDark: function() {
		return ! this.isBright();
	},

	asRGB: function() {
		return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
	},

	asHex: function() {
		return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
	},

	asHSB: function() {
		return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
	},

	toString: function() {
		return this.asHex();
	}

};

Rico.Color.createFromHex = function(hexCode) {
	if(hexCode.length==4) {
		var shortHexCode = hexCode;
		var hexCode = '#';
		for(var i=1;i<4;i++) hexCode += (shortHexCode.charAt(i) +
		shortHexCode.charAt(i));
	}
	if ( hexCode.indexOf('#') == 0 )
	hexCode = hexCode.substring(1);
	var red   = hexCode.substring(0,2);
	var green = hexCode.substring(2,4);
	var blue  = hexCode.substring(4,6);
	return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
}

/**
* Factory method for creating a color from the background of
* an HTML element.
*/
Rico.Color.createColorFromBackground = function(elem) {

	var actualColor = RicoUtil.getElementsComputedStyle($(elem), "backgroundColor", "background-color");

	if ( actualColor == "transparent" && elem.parentNode )
	return Rico.Color.createColorFromBackground(elem.parentNode);

	if ( actualColor == null )
	return new Rico.Color(255,255,255);

	if ( actualColor.indexOf("rgb(") == 0 ) {
		var colors = actualColor.substring(4, actualColor.length - 1 );
		var colorArray = colors.split(",");
		return new Rico.Color( parseInt( colorArray[0] ),
		parseInt( colorArray[1] ),
		parseInt( colorArray[2] )  );

	}
	else if ( actualColor.indexOf("#") == 0 ) {
		return Rico.Color.createFromHex(actualColor);
	}
	else
	return new Rico.Color(255,255,255);
}

Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {

	var red   = 0;
	var green = 0;
	var blue  = 0;

	if (saturation == 0) {
		red = parseInt(brightness * 255.0 + 0.5);
		green = red;
		blue = red;
	}
	else {
		var h = (hue - Math.floor(hue)) * 6.0;
		var f = h - Math.floor(h);
		var p = brightness * (1.0 - saturation);
		var q = brightness * (1.0 - saturation * f);
		var t = brightness * (1.0 - (saturation * (1.0 - f)));

		switch (parseInt(h)) {
			case 0:
			red   = (brightness * 255.0 + 0.5);
			green = (t * 255.0 + 0.5);
			blue  = (p * 255.0 + 0.5);
			break;
			case 1:
			red   = (q * 255.0 + 0.5);
			green = (brightness * 255.0 + 0.5);
			blue  = (p * 255.0 + 0.5);
			break;
			case 2:
			red   = (p * 255.0 + 0.5);
			green = (brightness * 255.0 + 0.5);
			blue  = (t * 255.0 + 0.5);
			break;
			case 3:
			red   = (p * 255.0 + 0.5);
			green = (q * 255.0 + 0.5);
			blue  = (brightness * 255.0 + 0.5);
			break;
			case 4:
			red   = (t * 255.0 + 0.5);
			green = (p * 255.0 + 0.5);
			blue  = (brightness * 255.0 + 0.5);
			break;
			case 5:
			red   = (brightness * 255.0 + 0.5);
			green = (p * 255.0 + 0.5);
			blue  = (q * 255.0 + 0.5);
			break;
		}
	}

	return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
}

Rico.Color.RGBtoHSB = function(r, g, b) {

	var hue;
	var saturation;
	var brightness;

	var cmax = (r > g) ? r : g;
	if (b > cmax)
	cmax = b;

	var cmin = (r < g) ? r : g;
	if (b < cmin)
	cmin = b;

	brightness = cmax / 255.0;
	if (cmax != 0)
	saturation = (cmax - cmin)/cmax;
	else
	saturation = 0;

	if (saturation == 0)
	hue = 0;
	else {
		var redc   = (cmax - r)/(cmax - cmin);
		var greenc = (cmax - g)/(cmax - cmin);
		var bluec  = (cmax - b)/(cmax - cmin);

		if (r == cmax)
		hue = bluec - greenc;
		else if (g == cmax)
		hue = 2.0 + redc - bluec;
		else
		hue = 4.0 + greenc - redc;

		hue = hue / 6.0;
		if (hue < 0)
		hue = hue + 1.0;
	}

	return { h : hue, s : saturation, b : brightness };
}


//-------------------- ricoCorner.js
Rico.Corner = {

	round: function(e, options) {
		var e = $(e);
		this._setOptions(options);

		var color = this.options.color;
		if ( this.options.color == "fromElement" )
		color = this._background(e);

		var bgColor = this.options.bgColor;
		if ( this.options.bgColor == "fromParent" )
		bgColor = this._background(e.offsetParent);

		this._roundCornersImpl(e, color, bgColor);
	},

	_roundCornersImpl: function(e, color, bgColor) {
		if(this.options.border)
		this._renderBorder(e,bgColor);
		if(this._isTopRounded())
		this._roundTopCorners(e,color,bgColor);
		if(this._isBottomRounded())
		this._roundBottomCorners(e,color,bgColor);
	},

	_renderBorder: function(el,bgColor) {
		var borderValue = "1px solid " + this._borderColor(bgColor);
		var borderL = "border-left: "  + borderValue;
		var borderR = "border-right: " + borderValue;
		var style   = "style='" + borderL + ";" + borderR +  "'";
		el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>"
	},

	_roundTopCorners: function(el, color, bgColor) {
		var corner = this._createCorner(bgColor);
		for(var i=0 ; i < this.options.numSlices ; i++ )
		corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
		el.style.paddingTop = 0;
		el.insertBefore(corner,el.firstChild);
	},

	_roundBottomCorners: function(el, color, bgColor) {
		var corner = this._createCorner(bgColor);
		for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- )
		corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
		el.style.paddingBottom = 0;
		el.appendChild(corner);
	},

	_createCorner: function(bgColor) {
		var corner = document.createElement("div");
		corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
		return corner;
	},

	_createCornerSlice: function(color,bgColor, n, position) {
		var slice = document.createElement("span");

		var inStyle = slice.style;
		inStyle.backgroundColor = color;
		inStyle.display  = "block";
		inStyle.height   = "1px";
		inStyle.overflow = "hidden";
		inStyle.fontSize = "1px";

		var borderColor = this._borderColor(color,bgColor);
		if ( this.options.border && n == 0 ) {
			inStyle.borderTopStyle    = "solid";
			inStyle.borderTopWidth    = "1px";
			inStyle.borderLeftWidth   = "0px";
			inStyle.borderRightWidth  = "0px";
			inStyle.borderBottomWidth = "0px";
			inStyle.height            = "0px"; // assumes css compliant box model
			inStyle.borderColor       = borderColor;
		}
		else if(borderColor) {
			inStyle.borderColor = borderColor;
			inStyle.borderStyle = "solid";
			inStyle.borderWidth = "0px 1px";
		}

		if ( !this.options.compact && (n == (this.options.numSlices-1)) )
		inStyle.height = "2px";

		this._setMargin(slice, n, position);
		this._setBorder(slice, n, position);
		return slice;
	},

	_setOptions: function(options) {
		this.options = {
			corners : "all",
			color   : "fromElement",
			bgColor : "fromParent",
			blend   : true,
			border  : false,
			compact : false
		}
		Object.extend(this.options, options || {});

		this.options.numSlices = this.options.compact ? 2 : 4;
		if ( this._isTransparent() )
		this.options.blend = false;
	},

	_whichSideTop: function() {
		if ( this._hasString(this.options.corners, "all", "top") )
		return "";

		if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 )
		return "";

		if (this.options.corners.indexOf("tl") >= 0)
		return "left";
		else if (this.options.corners.indexOf("tr") >= 0)
		return "right";
		return "";
	},

	_whichSideBottom: function() {
		if ( this._hasString(this.options.corners, "all", "bottom") )
		return "";

		if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 )
		return "";

		if(this.options.corners.indexOf("bl") >=0)
		return "left";
		else if(this.options.corners.indexOf("br")>=0)
		return "right";
		return "";
	},

	_borderColor : function(color,bgColor) {
		if ( color == "transparent" )
		return bgColor;
		else if ( this.options.border )
		return this.options.border;
		else if ( this.options.blend )
		return this._blend( bgColor, color );
		else
		return "";
	},


	_setMargin: function(el, n, corners) {
		var marginSize = this._marginSize(n);
		var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();

		if ( whichSide == "left" ) {
			el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
		}
		else if ( whichSide == "right" ) {
			el.style.marginRight = marginSize + "px"; el.style.marginLeft  = "0px";
		}
		else {
			el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
		}
	},

	_setBorder: function(el,n,corners) {
		var borderSize = this._borderSize(n);
		var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
		if ( whichSide == "left" ) {
			el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
		}
		else if ( whichSide == "right" ) {
			el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth  = "0px";
		}
		else {
			el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
		}
		if (this.options.border != false)
		el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
	},

	_marginSize: function(n) {
		if ( this._isTransparent() )
		return 0;

		var marginSizes          = [ 5, 3, 2, 1 ];
		var blendedMarginSizes   = [ 3, 2, 1, 0 ];
		var compactMarginSizes   = [ 2, 1 ];
		var smBlendedMarginSizes = [ 1, 0 ];

		if ( this.options.compact && this.options.blend )
		return smBlendedMarginSizes[n];
		else if ( this.options.compact )
		return compactMarginSizes[n];
		else if ( this.options.blend )
		return blendedMarginSizes[n];
		else
		return marginSizes[n];
	},

	_borderSize: function(n) {
		var transparentBorderSizes = [ 5, 3, 2, 1 ];
		var blendedBorderSizes     = [ 2, 1, 1, 1 ];
		var compactBorderSizes     = [ 1, 0 ];
		var actualBorderSizes      = [ 0, 2, 0, 0 ];

		if ( this.options.compact && (this.options.blend || this._isTransparent()) )
		return 1;
		else if ( this.options.compact )
		return compactBorderSizes[n];
		else if ( this.options.blend )
		return blendedBorderSizes[n];
		else if ( this.options.border )
		return actualBorderSizes[n];
		else if ( this._isTransparent() )
		return transparentBorderSizes[n];
		return 0;
	},

	_hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) return true; return false; },
	_blend: function(c1, c2) { var cc1 = Rico.Color.createFromHex(c1); cc1.blend(Rico.Color.createFromHex(c2)); return cc1; },
	_background: function(el) { try { return Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
	_isTransparent: function() { return this.options.color == "transparent"; },
	_isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
	_isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
	_hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
}


//-------------------- ricoDragAndDrop.js
Rico.DragAndDrop = Class.create();

Rico.DragAndDrop.prototype = {

	initialize: function() {
		this.dropZones                = new Array();
		this.draggables               = new Array();
		this.currentDragObjects       = new Array();
		this.dragElement              = null;
		this.lastSelectedDraggable    = null;
		this.currentDragObjectVisible = false;
		this.interestedInMotionEvents = false;
		this._mouseDown = this._mouseDownHandler.bindAsEventListener(this);
		this._mouseMove = this._mouseMoveHandler.bindAsEventListener(this);
		this._mouseUp = this._mouseUpHandler.bindAsEventListener(this);
	},

	registerDropZone: function(aDropZone) {
		this.dropZones[ this.dropZones.length ] = aDropZone;
	},

	deregisterDropZone: function(aDropZone) {
		var newDropZones = new Array();
		var j = 0;
		for ( var i = 0 ; i < this.dropZones.length ; i++ ) {
			if ( this.dropZones[i] != aDropZone )
			newDropZones[j++] = this.dropZones[i];
		}

		this.dropZones = newDropZones;
	},

	clearDropZones: function() {
		this.dropZones = new Array();
	},

	registerDraggable: function( aDraggable ) {
		this.draggables[ this.draggables.length ] = aDraggable;
		this._addMouseDownHandler( aDraggable );
	},

	clearSelection: function() {
		for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
		this.currentDragObjects[i].deselect();
		this.currentDragObjects = new Array();
		this.lastSelectedDraggable = null;
	},

	hasSelection: function() {
		return this.currentDragObjects.length > 0;
	},

	setStartDragFromElement: function( e, mouseDownElement ) {
		this.origPos = RicoUtil.toDocumentPosition(mouseDownElement);
		this.startx = e.screenX - this.origPos.x
		this.starty = e.screenY - this.origPos.y
		//this.startComponentX = e.layerX ? e.layerX : e.offsetX;
		//this.startComponentY = e.layerY ? e.layerY : e.offsetY;
		//this.adjustedForDraggableSize = false;

		this.interestedInMotionEvents = this.hasSelection();
		this._terminateEvent(e);
	},

	updateSelection: function( draggable, extendSelection ) {
		if ( ! extendSelection )
		this.clearSelection();

		if ( draggable.isSelected() ) {
			this.currentDragObjects.removeItem(draggable);
			draggable.deselect();
			if ( draggable == this.lastSelectedDraggable )
			this.lastSelectedDraggable = null;
		}
		else {
			this.currentDragObjects[ this.currentDragObjects.length ] = draggable;
			draggable.select();
			this.lastSelectedDraggable = draggable;
		}
	},

	_mouseDownHandler: function(e) {
		if ( arguments.length == 0 )
		e = event;

		// if not button 1 ignore it...
		var nsEvent = e.which != undefined;
		if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
		return;

		var eventTarget      = e.target ? e.target : e.srcElement;
		var draggableObject  = eventTarget.draggable;

		var candidate = eventTarget;
		while (draggableObject == null && candidate.parentNode) {
			candidate = candidate.parentNode;
			draggableObject = candidate.draggable;
		}

		if ( draggableObject == null )
		return;

		this.updateSelection( draggableObject, e.ctrlKey );

		// clear the drop zones postion cache...
		if ( this.hasSelection() )
		for ( var i = 0 ; i < this.dropZones.length ; i++ )
		this.dropZones[i].clearPositionCache();

		this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() );
	},


	_mouseMoveHandler: function(e) {
		var nsEvent = e.which != undefined;
		if ( !this.interestedInMotionEvents ) {
			//this._terminateEvent(e);
			return;
		}

		if ( ! this.hasSelection() )
		return;

		if ( ! this.currentDragObjectVisible )
		this._startDrag(e);

		if ( !this.activatedDropZones )
		this._activateRegisteredDropZones();

		//if ( !this.adjustedForDraggableSize )
		//   this._adjustForDraggableSize(e);

		this._updateDraggableLocation(e);
		this._updateDropZonesHover(e);

		this._terminateEvent(e);
	},

	_makeDraggableObjectVisible: function(e)
	{
		if ( !this.hasSelection() )
		return;

		var dragElement;
		if ( this.currentDragObjects.length > 1 )
		dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects);
		else
		dragElement = this.currentDragObjects[0].getSingleObjectDragGUI();

		// go ahead and absolute position it...
		if ( RicoUtil.getElementsComputedStyle(dragElement, "position")  != "absolute" )
		dragElement.style.position = "absolute";

		// need to parent him into the document...
		if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 )
		document.body.appendChild(dragElement);

		this.dragElement = dragElement;
		this._updateDraggableLocation(e);

		this.currentDragObjectVisible = true;
	},

	/**
	_adjustForDraggableSize: function(e) {
	var dragElementWidth  = this.dragElement.offsetWidth;
	var dragElementHeight = this.dragElement.offsetHeight;
	if ( this.startComponentX > dragElementWidth )
	this.startx -= this.startComponentX - dragElementWidth + 2;
	if ( e.offsetY ) {
	if ( this.startComponentY > dragElementHeight )
	this.starty -= this.startComponentY - dragElementHeight + 2;
	}
	this.adjustedForDraggableSize = true;
	},
	**/

	_leftOffset: function(e) {
		return e.offsetX ? document.body.scrollLeft : 0
	},

	_topOffset: function(e) {
		return e.offsetY ? document.body.scrollTop:0
	},


	_updateDraggableLocation: function(e) {
		var dragObjectStyle = this.dragElement.style;
		dragObjectStyle.left = (e.screenX + this._leftOffset(e) - this.startx) + "px"
		dragObjectStyle.top  = (e.screenY + this._topOffset(e) - this.starty) + "px";
	},

	_updateDropZonesHover: function(e) {
		var n = this.dropZones.length;
		for ( var i = 0 ; i < n ; i++ ) {
			if ( ! this._mousePointInDropZone( e, this.dropZones[i] ) )
			this.dropZones[i].hideHover();
		}

		for ( var i = 0 ; i < n ; i++ ) {
			if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
				if ( this.dropZones[i].canAccept(this.currentDragObjects) )
				this.dropZones[i].showHover();
			}
		}
	},

	_startDrag: function(e) {
		for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
		this.currentDragObjects[i].startDrag();

		this._makeDraggableObjectVisible(e);
	},

	_mouseUpHandler: function(e) {
		if ( ! this.hasSelection() )
		return;

		var nsEvent = e.which != undefined;
		if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
		return;

		this.interestedInMotionEvents = false;

		if ( this.dragElement == null ) {
			this._terminateEvent(e);
			return;
		}

		if ( this._placeDraggableInDropZone(e) )
		this._completeDropOperation(e);
		else {
			this._terminateEvent(e);
			new Rico.Effect.Position( this.dragElement,
			this.origPos.x,
			this.origPos.y,
			200,
			20,
			{ complete : this._doCancelDragProcessing.bind(this) } );
		}

		Event.stopObserving(document.body, "mousemove", this._mouseMove);
		Event.stopObserving(document.body, "mouseup",  this._mouseUp);
	},

	_retTrue: function () {
		return true;
	},

	_completeDropOperation: function(e) {
		if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
			if ( this.dragElement.parentNode != null )
			this.dragElement.parentNode.removeChild(this.dragElement);
		}

		this._deactivateRegisteredDropZones();
		this._endDrag();
		this.clearSelection();
		this.dragElement = null;
		this.currentDragObjectVisible = false;
		this._terminateEvent(e);
	},

	_doCancelDragProcessing: function() {
		this._cancelDrag();

		if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() && this.dragElement)
		if ( this.dragElement.parentNode != null )
		this.dragElement.parentNode.removeChild(this.dragElement);


		this._deactivateRegisteredDropZones();
		this.dragElement = null;
		this.currentDragObjectVisible = false;
	},

	_placeDraggableInDropZone: function(e) {
		var foundDropZone = false;
		var n = this.dropZones.length;
		for ( var i = 0 ; i < n ; i++ ) {
			if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
				if ( this.dropZones[i].canAccept(this.currentDragObjects) ) {
					this.dropZones[i].hideHover();
					this.dropZones[i].accept(this.currentDragObjects);
					foundDropZone = true;
					break;
				}
			}
		}

		return foundDropZone;
	},

	_cancelDrag: function() {
		for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
		this.currentDragObjects[i].cancelDrag();
	},

	_endDrag: function() {
		for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
		this.currentDragObjects[i].endDrag();
	},

	_mousePointInDropZone: function( e, dropZone ) {

		var absoluteRect = dropZone.getAbsoluteRect();

		return e.clientX  > absoluteRect.left + this._leftOffset(e) &&
		e.clientX  < absoluteRect.right + this._leftOffset(e) &&
		e.clientY  > absoluteRect.top + this._topOffset(e)   &&
		e.clientY  < absoluteRect.bottom + this._topOffset(e);
	},

	_addMouseDownHandler: function( aDraggable )
	{
		htmlElement  = aDraggable.getMouseDownHTMLElement();
		if ( htmlElement  != null ) {
			htmlElement.draggable = aDraggable;
			Event.observe(htmlElement , "mousedown", this._onmousedown.bindAsEventListener(this));
			Event.observe(htmlElement, "mousedown", this._mouseDown);
		}
	},

	_activateRegisteredDropZones: function() {
		var n = this.dropZones.length;
		for ( var i = 0 ; i < n ; i++ ) {
			var dropZone = this.dropZones[i];
			if ( dropZone.canAccept(this.currentDragObjects) )
			dropZone.activate();
		}

		this.activatedDropZones = true;
	},

	_deactivateRegisteredDropZones: function() {
		var n = this.dropZones.length;
		for ( var i = 0 ; i < n ; i++ )
		this.dropZones[i].deactivate();
		this.activatedDropZones = false;
	},

	_onmousedown: function () {
		Event.observe(document.body, "mousemove", this._mouseMove);
		Event.observe(document.body, "mouseup",  this._mouseUp);
	},

	_terminateEvent: function(e) {
		if ( e.stopPropagation != undefined )
		e.stopPropagation();
		else if ( e.cancelBubble != undefined )
		e.cancelBubble = true;

		if ( e.preventDefault != undefined )
		e.preventDefault();
		else
		e.returnValue = false;
	},


	initializeEventHandlers: function() {
		if ( typeof document.implementation != "undefined" &&
		document.implementation.hasFeature("HTML",   "1.0") &&
		document.implementation.hasFeature("Events", "2.0") &&
		document.implementation.hasFeature("CSS",    "2.0") ) {
			document.addEventListener("mouseup",   this._mouseUpHandler.bindAsEventListener(this),  false);
			document.addEventListener("mousemove", this._mouseMoveHandler.bindAsEventListener(this), false);
		}
		else {
			document.attachEvent( "onmouseup",   this._mouseUpHandler.bindAsEventListener(this) );
			document.attachEvent( "onmousemove", this._mouseMoveHandler.bindAsEventListener(this) );
		}
	}
}

var dndMgr = new Rico.DragAndDrop();
dndMgr.initializeEventHandlers();


//-------------------- ricoDraggable.js
Rico.Draggable = Class.create();

Rico.Draggable.prototype = {

	initialize: function( type, htmlElement ) {
		this.type          = type;
		this.htmlElement   = $(htmlElement);
		this.selected      = false;
	},

	/**
	*   Returns the HTML element that should have a mouse down event
	*   added to it in order to initiate a drag operation
	*
	**/
	getMouseDownHTMLElement: function() {
		return this.htmlElement;
	},

	select: function() {
		this.selected = true;

		if ( this.showingSelected )
		return;

		var htmlElement = this.getMouseDownHTMLElement();

		var color = Rico.Color.createColorFromBackground(htmlElement);
		color.isBright() ? color.darken(0.033) : color.brighten(0.033);

		this.saveBackground = RicoUtil.getElementsComputedStyle(htmlElement, "backgroundColor", "background-color");
		htmlElement.style.backgroundColor = color.asHex();
		this.showingSelected = true;
	},

	deselect: function() {
		this.selected = false;
		if ( !this.showingSelected )
		return;

		var htmlElement = this.getMouseDownHTMLElement();

		htmlElement.style.backgroundColor = this.saveBackground;
		this.showingSelected = false;
	},

	isSelected: function() {
		return this.selected;
	},

	startDrag: function() {
	},

	cancelDrag: function() {
	},

	endDrag: function() {
	},

	getSingleObjectDragGUI: function() {
		return this.htmlElement;
	},

	getMultiObjectDragGUI: function( draggables ) {
		return this.htmlElement;
	},

	getDroppedGUI: function() {
		return this.htmlElement;
	},

	toString: function() {
		return this.type + ":" + this.htmlElement + ":";
	}

}


//-------------------- ricoDropzone.js
Rico.Dropzone = Class.create();

Rico.Dropzone.prototype = {

	initialize: function( htmlElement ) {
		this.htmlElement  = $(htmlElement);
		this.absoluteRect = null;
	},

	getHTMLElement: function() {
		return this.htmlElement;
	},

	clearPositionCache: function() {
		this.absoluteRect = null;
	},

	getAbsoluteRect: function() {
		if ( this.absoluteRect == null ) {
			var htmlElement = this.getHTMLElement();
			var pos = RicoUtil.toViewportPosition(htmlElement);

			this.absoluteRect = {
				top:    pos.y,
				left:   pos.x,
				bottom: pos.y + htmlElement.offsetHeight,
				right:  pos.x + htmlElement.offsetWidth
			};
		}
		return this.absoluteRect;
	},

	activate: function() {
		var htmlElement = this.getHTMLElement();
		if (htmlElement == null  || this.showingActive)
		return;

		this.showingActive = true;
		this.saveBackgroundColor = htmlElement.style.backgroundColor;

		var fallbackColor = "#ffea84";
		var currentColor = Rico.Color.createColorFromBackground(htmlElement);
		if ( currentColor == null )
		htmlElement.style.backgroundColor = fallbackColor;
		else {
			currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
			htmlElement.style.backgroundColor = currentColor.asHex();
		}
	},

	deactivate: function() {
		var htmlElement = this.getHTMLElement();
		if (htmlElement == null || !this.showingActive)
		return;

		htmlElement.style.backgroundColor = this.saveBackgroundColor;
		this.showingActive = false;
		this.saveBackgroundColor = null;
	},

	showHover: function() {
		var htmlElement = this.getHTMLElement();
		if ( htmlElement == null || this.showingHover )
		return;

		this.saveBorderWidth = htmlElement.style.borderWidth;
		this.saveBorderStyle = htmlElement.style.borderStyle;
		this.saveBorderColor = htmlElement.style.borderColor;

		this.showingHover = true;
		htmlElement.style.borderWidth = "1px";
		htmlElement.style.borderStyle = "solid";
		//htmlElement.style.borderColor = "#ff9900";
		htmlElement.style.borderColor = "#ffff00";
	},

	hideHover: function() {
		var htmlElement = this.getHTMLElement();
		if ( htmlElement == null || !this.showingHover )
		return;

		htmlElement.style.borderWidth = this.saveBorderWidth;
		htmlElement.style.borderStyle = this.saveBorderStyle;
		htmlElement.style.borderColor = this.saveBorderColor;
		this.showingHover = false;
	},

	canAccept: function(draggableObjects) {
		return true;
	},

	accept: function(draggableObjects) {
		var htmlElement = this.getHTMLElement();
		if ( htmlElement == null )
		return;

		n = draggableObjects.length;
		for ( var i = 0 ; i < n ; i++ )
		{
			var theGUI = draggableObjects[i].getDroppedGUI();
			if ( RicoUtil.getElementsComputedStyle( theGUI, "position" ) == "absolute" )
			{
				theGUI.style.position = "static";
				theGUI.style.top = "";
				theGUI.style.top = "";
			}
			htmlElement.appendChild(theGUI);
		}
	}
}


//-------------------- ricoEffects.js

Rico.Effect = {};

Rico.Effect.SizeAndPosition = Class.create();
Rico.Effect.SizeAndPosition.prototype = {

	initialize: function(element, x, y, w, h, duration, steps, options) {
		this.element = $(element);
		this.x = x;
		this.y = y;
		this.w = w;
		this.h = h;
		this.duration = duration;
		this.steps    = steps;
		this.options  = arguments[7] || {};

		this.sizeAndPosition();
	},

	sizeAndPosition: function() {
		if (this.isFinished()) {
			if(this.options.complete) this.options.complete(this);
			return;
		}

		if (this.timer)
		clearTimeout(this.timer);

		var stepDuration = Math.round(this.duration/this.steps) ;

		// Get original values: x,y = top left corner;  w,h = width height
		var currentX = this.element.offsetLeft;
		var currentY = this.element.offsetTop;
		var currentW = this.element.offsetWidth;
		var currentH = this.element.offsetHeight;

		// If values not set, or zero, we do not modify them, and take original as final as well
		this.x = (this.x) ? this.x : currentX;
		this.y = (this.y) ? this.y : currentY;
		this.w = (this.w) ? this.w : currentW;
		this.h = (this.h) ? this.h : currentH;

		// how much do we need to modify our values for each step?
		var difX = this.steps >  0 ? (this.x - currentX)/this.steps : 0;
		var difY = this.steps >  0 ? (this.y - currentY)/this.steps : 0;
		var difW = this.steps >  0 ? (this.w - currentW)/this.steps : 0;
		var difH = this.steps >  0 ? (this.h - currentH)/this.steps : 0;

		this.moveBy(difX, difY);
		this.resizeBy(difW, difH);

		this.duration -= stepDuration;
		this.steps--;

		this.timer = setTimeout(this.sizeAndPosition.bind(this), stepDuration);
	},

	isFinished: function() {
		return this.steps <= 0;
	},

	moveBy: function( difX, difY ) {
		var currentLeft = this.element.offsetLeft;
		var currentTop  = this.element.offsetTop;
		var intDifX     = parseInt(difX);
		var intDifY     = parseInt(difY);

		var style = this.element.style;
		if ( intDifX != 0 )
		style.left = (currentLeft + intDifX) + "px";
		if ( intDifY != 0 )
		style.top  = (currentTop + intDifY) + "px";
	},

	resizeBy: function( difW, difH ) {
		var currentWidth  = this.element.offsetWidth;
		var currentHeight = this.element.offsetHeight;
		var intDifW       = parseInt(difW);
		var intDifH       = parseInt(difH);

		var style = this.element.style;
		if ( intDifW != 0 )
		style.width   = (currentWidth  + intDifW) + "px";
		if ( intDifH != 0 )
		style.height  = (currentHeight + intDifH) + "px";
	}
}

Rico.Effect.Size = Class.create();
Rico.Effect.Size.prototype = {

	initialize: function(element, w, h, duration, steps, options) {
		new Rico.Effect.SizeAndPosition(element, null, null, w, h, duration, steps, options);
	}
}

Rico.Effect.Position = Class.create();
Rico.Effect.Position.prototype = {

	initialize: function(element, x, y, duration, steps, options) {
		new Rico.Effect.SizeAndPosition(element, x, y, null, null, duration, steps, options);
	}
}

Rico.Effect.Round = Class.create();
Rico.Effect.Round.prototype = {

	initialize: function(tagName, className, options) {
		var elements = document.getElementsByTagAndClassName(tagName,className);
		for ( var i = 0 ; i < elements.length ; i++ )
		Rico.Corner.round( elements[i], options );
	}
};

Rico.Effect.FadeTo = Class.create();
Rico.Effect.FadeTo.prototype = {

	initialize: function( element, opacity, duration, steps, options) {
		this.element  = $(element);
		this.opacity  = opacity;
		this.duration = duration;
		this.steps    = steps;
		this.options  = arguments[4] || {};
		this.fadeTo();
	},

	fadeTo: function() {
		if (this.isFinished()) {
			if(this.options.complete) this.options.complete(this);
			return;
		}

		if (this.timer)
		clearTimeout(this.timer);

		var stepDuration = Math.round(this.duration/this.steps) ;
		var currentOpacity = this.getElementOpacity();
		var delta = this.steps > 0 ? (this.opacity - currentOpacity)/this.steps : 0;

		this.changeOpacityBy(delta);
		this.duration -= stepDuration;
		this.steps--;

		this.timer = setTimeout(this.fadeTo.bind(this), stepDuration);
	},

	changeOpacityBy: function(v) {
		var currentOpacity = this.getElementOpacity();
		var newOpacity = Math.max(0, Math.min(currentOpacity+v, 1));
		this.element.ricoOpacity = newOpacity;

		this.element.style.filter = "alpha(opacity:"+Math.round(newOpacity*100)+")";
		this.element.style.opacity = newOpacity; /*//*/;
	},

	isFinished: function() {
		return this.steps <= 0;
	},

	getElementOpacity: function() {
		if ( this.element.ricoOpacity == undefined ) {
			var opacity = RicoUtil.getElementsComputedStyle(this.element, 'opacity');
			this.element.ricoOpacity = opacity != undefined ? opacity : 1.0;
		}
		return parseFloat(this.element.ricoOpacity);
	}
}

Rico.Effect.AccordionSize = Class.create();

Rico.Effect.AccordionSize.prototype = {

	initialize: function(e1, e2, start, end, duration, steps, options) {
		this.e1       = $(e1);
		this.e2       = $(e2);
		this.start    = start;
		this.end      = end;
		this.duration = duration;
		this.steps    = steps;
		this.options  = arguments[6] || {};

		this.accordionSize();
	},

	accordionSize: function() {

		if (this.isFinished()) {
			// just in case there are round errors or such...
			this.e1.style.height = this.start + "px";
			this.e2.style.height = this.end + "px";

			if(this.options.complete)
			this.options.complete(this);
			return;
		}

		if (this.timer)
		clearTimeout(this.timer);

		var stepDuration = Math.round(this.duration/this.steps) ;

		var diff = this.steps > 0 ? (parseInt(this.e1.offsetHeight) - this.start)/this.steps : 0;
		this.resizeBy(diff);

		this.duration -= stepDuration;
		this.steps--;

		this.timer = setTimeout(this.accordionSize.bind(this), stepDuration);
	},

	isFinished: function() {
		return this.steps <= 0;
	},

	resizeBy: function(diff) {
		var h1Height = this.e1.offsetHeight;
		var h2Height = this.e2.offsetHeight;
		var intDiff = parseInt(diff);
		if ( diff != 0 ) {
			this.e1.style.height = (h1Height - intDiff) + "px";
			this.e2.style.height = (h2Height + intDiff) + "px";
		}
	}

};


//-------------------- ricoLiveGrid.js
// Rico.LiveGridMetaData -----------------------------------------------------

Rico.LiveGridMetaData = Class.create();

Rico.LiveGridMetaData.prototype = {

	initialize: function( pageSize, totalRows, columnCount, options ) {
		this.pageSize  = pageSize;
		this.totalRows = totalRows;
		this.setOptions(options);
		this.ArrowHeight = 16;
		this.columnCount = columnCount;
	},

	setOptions: function(options) {
		this.options = {
			largeBufferSize    : 7.0,   // 7 pages
			nearLimitFactor    : 0.2    // 20% of buffer
		};
		Object.extend(this.options, options || {});
	},

	getPageSize: function() {
		return this.pageSize;
	},

	getTotalRows: function() {
		return this.totalRows;
	},

	setTotalRows: function(n) {
		this.totalRows = n;
	},

	getLargeBufferSize: function() {
		return parseInt(this.options.largeBufferSize * this.pageSize);
	},

	getLimitTolerance: function() {
		return parseInt(this.getLargeBufferSize() * this.options.nearLimitFactor);
	}
};

// Rico.LiveGridScroller -----------------------------------------------------

Rico.LiveGridScroller = Class.create();

Rico.LiveGridScroller.prototype = {

	initialize: function(liveGrid, viewPort) {
		this.isIE = navigator.userAgent.toLowerCase().indexOf("msie") >= 0;
		this.liveGrid = liveGrid;
		this.metaData = liveGrid.metaData;
		this.createScrollBar();
		this.scrollTimeout = null;
		this.lastScrollPos = 0;
		this.viewPort = viewPort;
		this.rows = new Array();
	},

	isUnPlugged: function() {
		return this.scrollerDiv.onscroll == null;
	},

	plugin: function() {
		this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
	},

	unplug: function() {
		this.scrollerDiv.onscroll = null;
	},

	sizeIEHeaderHack: function() {
		if ( !this.isIE ) return;
		var headerTable = $(this.liveGrid.tableId + "_header");
		if ( headerTable )
		headerTable.rows[0].cells[0].style.width =
		(headerTable.rows[0].cells[0].offsetWidth + 1) + "px";
	},

	createScrollBar: function() {
		var visibleHeight = this.liveGrid.viewPort.visibleHeight();
		// create the outer div...
		this.scrollerDiv  = document.createElement("div");
		var scrollerStyle = this.scrollerDiv.style;
		scrollerStyle.borderRight = this.liveGrid.options.scrollerBorderRight;
		scrollerStyle.position    = "relative";
		scrollerStyle.left        = this.isIE ? "-6px" : "-3px";
		scrollerStyle.width       = "19px";
		scrollerStyle.height      = visibleHeight + "px";
		scrollerStyle.overflow    = "auto";

		// create the inner div...
		this.heightDiv = document.createElement("div");
		this.heightDiv.style.width  = "1px";

		this.heightDiv.style.height = parseInt(visibleHeight *
		this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px" ;
		this.scrollerDiv.appendChild(this.heightDiv);
		this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);

		var table = this.liveGrid.table;
		table.parentNode.parentNode.insertBefore( this.scrollerDiv, table.parentNode.nextSibling );
		var eventName = this.isIE ? "mousewheel" : "DOMMouseScroll";
		Event.observe(table, eventName,
		function(evt) {
			if (evt.wheelDelta>=0 || evt.detail < 0) //wheel-up
			this.scrollerDiv.scrollTop -= (2*this.viewPort.rowHeight);
			else
			this.scrollerDiv.scrollTop += (2*this.viewPort.rowHeight);
			this.handleScroll(false);
		}.bindAsEventListener(this),
		false);
	},

	updateSize: function() {
		var table = this.liveGrid.table;
		var visibleHeight = this.viewPort.visibleHeight();
		this.heightDiv.style.height = parseInt(visibleHeight *
		this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px";
	},

	rowToPixel: function(rowOffset) {
		return (rowOffset / this.metaData.getTotalRows()) * this.heightDiv.offsetHeight
	},

	moveScroll: function(rowOffset) {
		this.scrollerDiv.scrollTop = this.rowToPixel(rowOffset);
		if ( this.metaData.options.onscroll )
		this.metaData.options.onscroll( this.liveGrid, rowOffset );
	},

	handleScroll: function() {
		if ( this.scrollTimeout )
		clearTimeout( this.scrollTimeout );

		var scrollDiff = this.lastScrollPos-this.scrollerDiv.scrollTop;
		if (scrollDiff != 0.00) {
			var r = this.scrollerDiv.scrollTop % this.viewPort.rowHeight;
			if (r != 0) {
				this.unplug();
				if ( scrollDiff < 0 ) {
					this.scrollerDiv.scrollTop += (this.viewPort.rowHeight-r);
				} else {
					this.scrollerDiv.scrollTop -= r;
				}
				this.plugin();
			}
		}
		var contentOffset = parseInt(this.scrollerDiv.scrollTop / this.viewPort.rowHeight);
		this.liveGrid.requestContentRefresh(contentOffset);
		this.viewPort.scrollTo(this.scrollerDiv.scrollTop);

		if ( this.metaData.options.onscroll )
		this.metaData.options.onscroll( this.liveGrid, contentOffset );

		this.scrollTimeout = setTimeout(this.scrollIdle.bind(this), 1200 );
		this.lastScrollPos = this.scrollerDiv.scrollTop;

	},

	scrollIdle: function() {
		if ( this.metaData.options.onscrollidle )
		this.metaData.options.onscrollidle();
	}
};

// Rico.LiveGridBuffer -----------------------------------------------------

Rico.LiveGridBuffer = Class.create();

Rico.LiveGridBuffer.prototype = {

	initialize: function(metaData, viewPort) {
		this.startPos = 0;
		this.size     = 0;
		this.metaData = metaData;
		this.rows     = new Array();
		this.updateInProgress = false;
		this.viewPort = viewPort;
		this.maxBufferSize = metaData.getLargeBufferSize() * 2;
		this.maxFetchSize = metaData.getLargeBufferSize();
		this.lastOffset = 0;
	},

	getBlankRow: function() {
		if (!this.blankRow ) {
			this.blankRow = new Array();
			for ( var i=0; i < this.metaData.columnCount ; i++ )
			this.blankRow[i] = "&nbsp;";
		}
		return this.blankRow;
	},

	loadRows: function(ajaxResponse) {
		var rowsElement = ajaxResponse.getElementsByTagName('rows')[0];
		this.updateUI = rowsElement.getAttribute("update_ui") == "true"
		var newRows = new Array()
		var trs = rowsElement.getElementsByTagName("tr");
		for ( var i=0 ; i < trs.length; i++ ) {
			var row = newRows[i] = new Array();
			var cells = trs[i].getElementsByTagName("td");
			for ( var j=0; j < cells.length ; j++ ) {
				var cell = cells[j];
				var convertSpaces = cell.getAttribute("convert_spaces") == "true";
				var cellContent = RicoUtil.getContentAsString(cell);
				row[j] = convertSpaces ? this.convertSpaces(cellContent) : cellContent;
				if (!row[j])
				row[j] = '&nbsp;';
			}
		}
		return newRows;
	},

	update: function(ajaxResponse, start) {
		var newRows = this.loadRows(ajaxResponse);
		if (this.rows.length == 0) { // initial load
			this.rows = newRows;
			this.size = this.rows.length;
			this.startPos = start;
			return;
		}
		if (start > this.startPos) { //appending
			if (this.startPos + this.rows.length < start) {
				this.rows =  newRows;
				this.startPos = start;//
			} else {
				this.rows = this.rows.concat( newRows.slice(0, newRows.length));
				if (this.rows.length > this.maxBufferSize) {
					var fullSize = this.rows.length;
					this.rows = this.rows.slice(this.rows.length - this.maxBufferSize, this.rows.length)
					this.startPos = this.startPos +  (fullSize - this.rows.length);
				}
			}
		} else { //prepending
			if (start + newRows.length < this.startPos) {
				this.rows =  newRows;
			} else {
				this.rows = newRows.slice(0, this.startPos).concat(this.rows);
				if (this.rows.length > this.maxBufferSize)
				this.rows = this.rows.slice(0, this.maxBufferSize)
			}
			this.startPos =  start;
		}
		this.size = this.rows.length;
	},

	clear: function() {
		this.rows = new Array();
		this.startPos = 0;
		this.size = 0;
	},

	isOverlapping: function(start, size) {
		return ((start < this.endPos()) && (this.startPos < start + size)) || (this.endPos() == 0)
	},

	isInRange: function(position) {
		return (position >= this.startPos) && (position + this.metaData.getPageSize() <= this.endPos());
		//&& this.size()  != 0;
	},

	isNearingTopLimit: function(position) {
		return position - this.startPos < this.metaData.getLimitTolerance();
	},

	endPos: function() {
		return this.startPos + this.rows.length;
	},

	isNearingBottomLimit: function(position) {
		return this.endPos() - (position + this.metaData.getPageSize()) < this.metaData.getLimitTolerance();
	},

	isAtTop: function() {
		return this.startPos == 0;
	},

	isAtBottom: function() {
		return this.endPos() == this.metaData.getTotalRows();
	},

	isNearingLimit: function(position) {
		return ( !this.isAtTop()    && this.isNearingTopLimit(position)) ||
		( !this.isAtBottom() && this.isNearingBottomLimit(position) )
	},

	getFetchSize: function(offset) {
		var adjustedOffset = this.getFetchOffset(offset);
		var adjustedSize = 0;
		if (adjustedOffset >= this.startPos) { //apending
			var endFetchOffset = this.maxFetchSize  + adjustedOffset;
			if (endFetchOffset > this.metaData.totalRows)
			endFetchOffset = this.metaData.totalRows;
			adjustedSize = endFetchOffset - adjustedOffset;
			if(adjustedOffset == 0 && adjustedSize < this.maxFetchSize){
				adjustedSize = this.maxFetchSize;
			}
		} else {//prepending
			var adjustedSize = this.startPos - adjustedOffset;
			if (adjustedSize > this.maxFetchSize)
			adjustedSize = this.maxFetchSize;
		}
		return adjustedSize;
	},

	getFetchOffset: function(offset) {
		var adjustedOffset = offset;
		if (offset > this.startPos)  //apending
		adjustedOffset = (offset > this.endPos()) ? offset :  this.endPos();
		else { //prepending
			if (offset + this.maxFetchSize >= this.startPos) {
				var adjustedOffset = this.startPos - this.maxFetchSize;
				if (adjustedOffset < 0)
				adjustedOffset = 0;
			}
		}
		this.lastOffset = adjustedOffset;
		return adjustedOffset;
	},

	getRows: function(start, count) {
		var begPos = start - this.startPos
		var endPos = begPos + count

		// er? need more data...
		if ( endPos > this.size )
		endPos = this.size

		var results = new Array()
		var index = 0;
		for ( var i=begPos ; i < endPos; i++ ) {
			results[index++] = this.rows[i]
		}
		return results
	},

	convertSpaces: function(s) {
		return s.split(" ").join("&nbsp;");
	}

};


//Rico.GridViewPort --------------------------------------------------
Rico.GridViewPort = Class.create();

Rico.GridViewPort.prototype = {

	initialize: function(table, rowHeight, visibleRows, buffer, liveGrid) {
		this.lastDisplayedStartPos = 0;
		this.div = table.parentNode;
		this.table = table
		this.rowHeight = rowHeight;
		this.div.style.height = (this.rowHeight * visibleRows) + "px";
		this.div.style.overflow = "hidden";
		this.buffer = buffer;
		this.liveGrid = liveGrid;
		this.visibleRows = visibleRows + 1;
		this.lastPixelOffset = 0;
		this.startPos = 0;
	},

	populateRow: function(htmlRow, row) {
		for (var j=0; j < row.length; j++) {
			htmlRow.cells[j].innerHTML = row[j]
		}
	},

	bufferChanged: function() {
		this.refreshContents( parseInt(this.lastPixelOffset / this.rowHeight));
	},

	clearRows: function() {
		if (!this.isBlank) {
			this.liveGrid.table.className = this.liveGrid.options.loadingClass;
			for (var i=0; i < this.visibleRows; i++)
			this.populateRow(this.table.rows[i], this.buffer.getBlankRow());
			this.isBlank = true;
		}
	},

	clearContents: function() {
		this.clearRows();
		this.scrollTo(0);
		this.startPos = 0;
		this.lastStartPos = -1;
	},

	refreshContents: function(startPos) {
		if (startPos == this.lastRowPos && !this.isPartialBlank && !this.isBlank) {
			return;
		}
		if ((startPos + this.visibleRows < this.buffer.startPos)
		|| (this.buffer.startPos + this.buffer.size < startPos)
		|| (this.buffer.size == 0)) {
			this.clearRows();
			return;
		}
		this.isBlank = false;
		var viewPrecedesBuffer = this.buffer.startPos > startPos
		var contentStartPos = viewPrecedesBuffer ? this.buffer.startPos: startPos;
		var contentEndPos = (this.buffer.startPos + this.buffer.size < startPos + this.visibleRows)
		? this.buffer.startPos + this.buffer.size
		: startPos + this.visibleRows;
		var rowSize = contentEndPos - contentStartPos;
		var rows = this.buffer.getRows(contentStartPos, rowSize );
		var blankSize = this.visibleRows - rowSize;
		var blankOffset = viewPrecedesBuffer ? 0: rowSize;
		var contentOffset = viewPrecedesBuffer ? blankSize: 0;

		for (var i=0; i < rows.length; i++) {//initialize what we have
			this.populateRow(this.table.rows[i + contentOffset], rows[i]);
		}
		for (var i=0; i < blankSize; i++) {// blank out the rest
			this.populateRow(this.table.rows[i + blankOffset], this.buffer.getBlankRow());
		}
		this.isPartialBlank = blankSize > 0;
		this.lastRowPos = startPos;

		this.liveGrid.table.className = this.liveGrid.options.tableClass;
		// Check if user has set a onRefreshComplete function
		var onRefreshComplete = this.liveGrid.options.onRefreshComplete;
		if (onRefreshComplete != null)
		onRefreshComplete();
	},

	scrollTo: function(pixelOffset) {
		if (this.lastPixelOffset == pixelOffset)
		return;

		this.refreshContents(parseInt(pixelOffset / this.rowHeight))
		this.div.scrollTop = pixelOffset % this.rowHeight

		this.lastPixelOffset = pixelOffset;
	},

	visibleHeight: function() {
		return parseInt(RicoUtil.getElementsComputedStyle(this.div, 'height'));
	}

};


Rico.LiveGridRequest = Class.create();
Rico.LiveGridRequest.prototype = {
	initialize: function( requestOffset, options ) {
		this.requestOffset = requestOffset;
	}
};

// Rico.LiveGrid -----------------------------------------------------

Rico.LiveGrid = Class.create();

Rico.LiveGrid.prototype = {

	initialize: function( tableId, visibleRows, totalRows, url, options, ajaxOptions ) {

		this.options = {
			tableClass:           $(tableId).className,
			loadingClass:         $(tableId).className,
			scrollerBorderRight: '1px solid #ababab',
			bufferTimeout:        20000,
			sortAscendImg:        'images/sort_asc.gif',
			sortDescendImg:       'images/sort_desc.gif',
			sortImageWidth:       9,
			sortImageHeight:      5,
			ajaxSortURLParms:     [],
			onRefreshComplete:    null,
			requestParameters:    null,
			inlineStyles:         true
		};
		Object.extend(this.options, options || {});

		this.ajaxOptions = {parameters: null};
		Object.extend(this.ajaxOptions, ajaxOptions || {});

		this.tableId     = tableId;
		this.table       = $(tableId);

		this.addLiveGridHtml();

		var columnCount  = this.table.rows[0].cells.length;
		this.metaData    = new Rico.LiveGridMetaData(visibleRows, totalRows, columnCount, options);
		this.buffer      = new Rico.LiveGridBuffer(this.metaData);

		var rowCount = this.table.rows.length;
		this.viewPort =  new Rico.GridViewPort(this.table,
		this.table.offsetHeight/rowCount,
		visibleRows,
		this.buffer, this);
		this.scroller    = new Rico.LiveGridScroller(this,this.viewPort);
		this.options.sortHandler = this.sortHandler.bind(this);

		if ( $(tableId + '_header') )
		this.sort = new Rico.LiveGridSort(tableId + '_header', this.options)

		this.processingRequest = null;
		this.unprocessedRequest = null;

		this.initAjax(url);
		if ( this.options.prefetchBuffer || this.options.prefetchOffset > 0) {
			var offset = 0;
			if (this.options.offset ) {
				offset = this.options.offset;
				this.scroller.moveScroll(offset);
				this.viewPort.scrollTo(this.scroller.rowToPixel(offset));
			}
			if (this.options.sortCol) {
				this.sortCol = options.sortCol;
				this.sortDir = options.sortDir;
			}
			this.requestContentRefresh(offset);
		}
	},

	addLiveGridHtml: function() {
		// Check to see if need to create a header table.
		if (this.table.getElementsByTagName("thead").length > 0){
			// Create Table this.tableId+'_header'
			var tableHeader = this.table.cloneNode(true);
			tableHeader.setAttribute('id', this.tableId+'_header');
			tableHeader.setAttribute('class', this.table.className+'_header');

			// Clean up and insert
			for( var i = 0; i < tableHeader.tBodies.length; i++ )
			tableHeader.removeChild(tableHeader.tBodies[i]);
			this.table.deleteTHead();
			this.table.parentNode.insertBefore(tableHeader,this.table);
		}

		new Insertion.Before(this.table, "<div id='"+this.tableId+"_container'></div>");
		this.table.previousSibling.appendChild(this.table);
		new Insertion.Before(this.table,"<div id='"+this.tableId+"_viewport' style='float:left;'></div>");
		this.table.previousSibling.appendChild(this.table);
	},


	resetContents: function() {
		this.scroller.moveScroll(0);
		this.buffer.clear();
		this.viewPort.clearContents();
	},

	sortHandler: function(column) {
		if(!column) return ;
		this.sortCol = column.name;
		this.sortDir = column.currentSort;

		this.resetContents();
		this.requestContentRefresh(0)
	},

	adjustRowSize: function() {

	},

	setTotalRows: function( newTotalRows ) {
		this.resetContents();
		this.metaData.setTotalRows(newTotalRows);
		this.scroller.updateSize();
	},

	initAjax: function(url) {
		ajaxEngine.registerRequest( this.tableId + '_request', url );
		ajaxEngine.registerAjaxObject( this.tableId + '_updater', this );
	},

	invokeAjax: function() {
	},

	handleTimedOut: function() {
		//server did not respond in 4 seconds... assume that there could have been
		//an error or something, and allow requests to be processed again...
		this.processingRequest = null;
		this.processQueuedRequest();
	},

	fetchBuffer: function(offset) {
		if ( this.buffer.isInRange(offset) &&
		!this.buffer.isNearingLimit(offset)) {
			return;
		}
		if (this.processingRequest) {
			this.unprocessedRequest = new Rico.LiveGridRequest(offset);
			return;
		}
		var bufferStartPos = this.buffer.getFetchOffset(offset);
		this.processingRequest = new Rico.LiveGridRequest(offset);
		this.processingRequest.bufferOffset = bufferStartPos;
		var fetchSize = this.buffer.getFetchSize(offset);
		var partialLoaded = false;

		var queryString
		if (this.options.requestParameters)
		queryString = this._createQueryString(this.options.requestParameters, 0);

		queryString = (queryString == null) ? '' : queryString+'&';
		queryString  = queryString+'id='+this.tableId+'&page_size='+fetchSize+'&offset='+bufferStartPos;
		if (this.sortCol)
		queryString = queryString+'&sort_col='+escape(this.sortCol)+'&sort_dir='+this.sortDir;

		this.ajaxOptions.parameters = queryString;

		ajaxEngine.sendRequest( this.tableId + '_request', this.ajaxOptions );

		this.timeoutHandler = setTimeout( this.handleTimedOut.bind(this), this.options.bufferTimeout);

	},

	setRequestParams: function() {
		this.options.requestParameters = [];
		for ( var i=0 ; i < arguments.length ; i++ )
		this.options.requestParameters[i] = arguments[i];
	},

	requestContentRefresh: function(contentOffset) {
		this.fetchBuffer(contentOffset);
	},

	ajaxUpdate: function(ajaxResponse) {
		try {
			clearTimeout( this.timeoutHandler );
			this.buffer.update(ajaxResponse,this.processingRequest.bufferOffset);
			this.viewPort.bufferChanged();
		}
		catch(err) {}
		finally {this.processingRequest = null; }
		this.processQueuedRequest();
	},

	_createQueryString: function( theArgs, offset ) {
		var queryString = ""
		if (!theArgs)
		return queryString;

		for ( var i = offset ; i < theArgs.length ; i++ ) {
			if ( i != offset )
			queryString += "&";

			var anArg = theArgs[i];

			if ( anArg.name != undefined && anArg.value != undefined ) {
				queryString += anArg.name +  "=" + escape(anArg.value);
			}
			else {
				var ePos  = anArg.indexOf('=');
				var argName  = anArg.substring( 0, ePos );
				var argValue = anArg.substring( ePos + 1 );
				queryString += argName + "=" + escape(argValue);
			}
		}
		return queryString;
	},

	processQueuedRequest: function() {
		if (this.unprocessedRequest != null) {
			this.requestContentRefresh(this.unprocessedRequest.requestOffset);
			this.unprocessedRequest = null
		}
	}
};


//-------------------- ricoLiveGridSort.js
Rico.LiveGridSort = Class.create();

Rico.LiveGridSort.prototype = {

	initialize: function(headerTableId, options) {
		this.headerTableId = headerTableId;
		this.headerTable   = $(headerTableId);
		this.options = options;
		this.setOptions();
		this.applySortBehavior();

		if ( this.options.sortCol ) {
			this.setSortUI( this.options.sortCol, this.options.sortDir );
		}
	},

	setSortUI: function( columnName, sortDirection ) {
		var cols = this.options.columns;
		for ( var i = 0 ; i < cols.length ; i++ ) {
			if ( cols[i].name == columnName ) {
				this.setColumnSort(i, sortDirection);
				break;
			}
		}
	},

	setOptions: function() {
		// preload the images...
		new Image().src = this.options.sortAscendImg;
		new Image().src = this.options.sortDescendImg;

		this.sort = this.options.sortHandler;
		if ( !this.options.columns )
		this.options.columns = this.introspectForColumnInfo();
		else {
			// allow client to pass { columns: [ ["a", true], ["b", false] ] }
			// and convert to an array of Rico.TableColumn objs...
			this.options.columns = this.convertToTableColumns(this.options.columns);
		}
	},

	applySortBehavior: function() {
		var headerRow   = this.headerTable.rows[0];
		var headerCells = headerRow.cells;
		for ( var i = 0 ; i < headerCells.length ; i++ ) {
			this.addSortBehaviorToColumn( i, headerCells[i] );
		}
	},

	addSortBehaviorToColumn: function( n, cell ) {
		if ( this.options.columns[n].isSortable() ) {
			cell.id            = this.headerTableId + '_' + n;
			cell.style.cursor  = 'pointer';
			cell.onclick       = this.headerCellClicked.bindAsEventListener(this);
			cell.innerHTML     = cell.innerHTML + '<span id="' + this.headerTableId + '_img_' + n + '">'
			+ '&nbsp;&nbsp;&nbsp;</span>';
		}
	},

	// event handler....
	headerCellClicked: function(evt) {
		var eventTarget = evt.target ? evt.target : evt.srcElement;
		var cellId = eventTarget.id;
		var columnNumber = parseInt(cellId.substring( cellId.lastIndexOf('_') + 1 ));
		var sortedColumnIndex = this.getSortedColumnIndex();
		if ( sortedColumnIndex != -1 ) {
			if ( sortedColumnIndex != columnNumber ) {
				this.removeColumnSort(sortedColumnIndex);
				this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
			}
			else
			this.toggleColumnSort(sortedColumnIndex);
		}
		else
		this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);

		if (this.options.sortHandler) {
			this.options.sortHandler(this.options.columns[columnNumber]);
		}
	},

	removeColumnSort: function(n) {
		this.options.columns[n].setUnsorted();
		this.setSortImage(n);
	},

	setColumnSort: function(n, direction) {
		if(isNaN(n)) return ;
		this.options.columns[n].setSorted(direction);
		this.setSortImage(n);
	},

	toggleColumnSort: function(n) {
		this.options.columns[n].toggleSort();
		this.setSortImage(n);
	},

	setSortImage: function(n) {
		var sortDirection = this.options.columns[n].getSortDirection();

		var sortImageSpan = $( this.headerTableId + '_img_' + n );
		if ( sortDirection == Rico.TableColumn.UNSORTED )
		sortImageSpan.innerHTML = '&nbsp;&nbsp;';
		else if ( sortDirection == Rico.TableColumn.SORT_ASC )
		sortImageSpan.innerHTML = '&nbsp;&nbsp;<img width="'  + this.options.sortImageWidth    + '" ' +
		'height="'+ this.options.sortImageHeight   + '" ' +
		'src="'   + this.options.sortAscendImg + '"/>';
		else if ( sortDirection == Rico.TableColumn.SORT_DESC )
		sortImageSpan.innerHTML = '&nbsp;&nbsp;<img width="'  + this.options.sortImageWidth    + '" ' +
		'height="'+ this.options.sortImageHeight   + '" ' +
		'src="'   + this.options.sortDescendImg + '"/>';
	},

	getSortedColumnIndex: function() {
		var cols = this.options.columns;
		for ( var i = 0 ; i < cols.length ; i++ ) {
			if ( cols[i].isSorted() )
			return i;
		}

		return -1;
	},

	introspectForColumnInfo: function() {
		var columns = new Array();
		var headerRow   = this.headerTable.rows[0];
		var headerCells = headerRow.cells;
		for ( var i = 0 ; i < headerCells.length ; i++ )
		columns.push( new Rico.TableColumn( this.deriveColumnNameFromCell(headerCells[i],i), true ) );
		return columns;
	},

	convertToTableColumns: function(cols) {
		var columns = new Array();
		for ( var i = 0 ; i < cols.length ; i++ )
		columns.push( new Rico.TableColumn( cols[i][0], cols[i][1] ) );
		return columns;
	},

	deriveColumnNameFromCell: function(cell,columnNumber) {
		var cellContent = cell.innerText != undefined ? cell.innerText : cell.textContent;
		return cellContent ? cellContent.toLowerCase().split(' ').join('_') : "col_" + columnNumber;
	}
};

Rico.TableColumn = Class.create();

Rico.TableColumn.UNSORTED  = 0;
Rico.TableColumn.SORT_ASC  = "ASC";
Rico.TableColumn.SORT_DESC = "DESC";

Rico.TableColumn.prototype = {
	initialize: function(name, sortable) {
		this.name        = name;
		this.sortable    = sortable;
		this.currentSort = Rico.TableColumn.UNSORTED;
	},

	isSortable: function() {
		return this.sortable;
	},

	isSorted: function() {
		return this.currentSort != Rico.TableColumn.UNSORTED;
	},

	getSortDirection: function() {
		return this.currentSort;
	},

	toggleSort: function() {
		if ( this.currentSort == Rico.TableColumn.UNSORTED || this.currentSort == Rico.TableColumn.SORT_DESC )
		this.currentSort = Rico.TableColumn.SORT_ASC;
		else if ( this.currentSort == Rico.TableColumn.SORT_ASC )
		this.currentSort = Rico.TableColumn.SORT_DESC;
	},

	setUnsorted: function(direction) {
		this.setSorted(Rico.TableColumn.UNSORTED);
	},

	setSorted: function(direction) {
		// direction must by one of Rico.TableColumn.UNSORTED, .SORT_ASC, or .SORT_DESC...
		this.currentSort = direction;
	}

};


//-------------------- ricoUtil.js
var RicoUtil = {

	getElementsComputedStyle: function ( htmlElement, cssProperty, mozillaEquivalentCSS) {
		if ( arguments.length == 2 )
		mozillaEquivalentCSS = cssProperty;

		var el = $(htmlElement);
		if ( el.currentStyle )
		return el.currentStyle[cssProperty];
		else
		return document.defaultView.getComputedStyle(el, null).getPropertyValue(mozillaEquivalentCSS);
	},

	createXmlDocument : function() {
		if (document.implementation && document.implementation.createDocument) {
			var doc = document.implementation.createDocument("", "", null);

			if (doc.readyState == null) {
				doc.readyState = 1;
				doc.addEventListener("load", function () {
					doc.readyState = 4;
					if (typeof doc.onreadystatechange == "function")
					doc.onreadystatechange();
				}, false);
			}

			return doc;
		}

		if (window.ActiveXObject)
		return Try.these(
		function() { return new ActiveXObject('MSXML2.DomDocument')   },
		function() { return new ActiveXObject('Microsoft.DomDocument')},
		function() { return new ActiveXObject('MSXML.DomDocument')    },
		function() { return new ActiveXObject('MSXML3.DomDocument')   }
		) || false;

		return null;
	},

	getContentAsString: function( parentNode ) {
		return parentNode.xml != undefined ?
		this._getContentAsStringIE(parentNode) :
		this._getContentAsStringMozilla(parentNode);
	},

	_getContentAsStringIE: function(parentNode) {
		var contentStr = "";
		for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
			var n = parentNode.childNodes[i];
			if (n.nodeType == 4) {
				contentStr += n.nodeValue;
			}
			else {
				contentStr += n.xml;
			}
		}
		return contentStr;
	},

	_getContentAsStringMozilla: function(parentNode) {
		var xmlSerializer = new XMLSerializer();
		var contentStr = "";
		for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
			var n = parentNode.childNodes[i];
			if (n.nodeType == 4) { // CDATA node
				contentStr += n.nodeValue;
			}
			else {
				contentStr += xmlSerializer.serializeToString(n);
			}
		}
		return contentStr;
	},

	toViewportPosition: function(element) {
		return this._toAbsolute(element,true);
	},

	toDocumentPosition: function(element) {
		return this._toAbsolute(element,false);
	},

	/**
	*  Compute the elements position in terms of the window viewport
	*  so that it can be compared to the position of the mouse (dnd)
	*  This is additions of all the offsetTop,offsetLeft values up the
	*  offsetParent hierarchy, ...taking into account any scrollTop,
	*  scrollLeft values along the way...
	*
	* IE has a bug reporting a correct offsetLeft of elements within a
	* a relatively positioned parent!!!
	**/
	_toAbsolute: function(element,accountForDocScroll) {

		if ( navigator.userAgent.toLowerCase().indexOf("msie") == -1 )
		return this._toAbsoluteMozilla(element,accountForDocScroll);

		var x = 0;
		var y = 0;
		var parent = element;
		while ( parent ) {

			var borderXOffset = 0;
			var borderYOffset = 0;
			if ( parent != element ) {
				var borderXOffset = parseInt(this.getElementsComputedStyle(parent, "borderLeftWidth" ));
				var borderYOffset = parseInt(this.getElementsComputedStyle(parent, "borderTopWidth" ));
				borderXOffset = isNaN(borderXOffset) ? 0 : borderXOffset;
				borderYOffset = isNaN(borderYOffset) ? 0 : borderYOffset;
			}

			x += parent.offsetLeft - parent.scrollLeft + borderXOffset;
			y += parent.offsetTop - parent.scrollTop + borderYOffset;
			parent = parent.offsetParent;
		}

		if ( accountForDocScroll ) {
			x -= this.docScrollLeft();
			y -= this.docScrollTop();
		}

		return { x:x, y:y };
	},

	/**
	*  Mozilla did not report all of the parents up the hierarchy via the
	*  offsetParent property that IE did.  So for the calculation of the
	*  offsets we use the offsetParent property, but for the calculation of
	*  the scrollTop/scrollLeft adjustments we navigate up via the parentNode
	*  property instead so as to get the scroll offsets...
	*
	**/
	_toAbsoluteMozilla: function(element,accountForDocScroll) {
		var x = 0;
		var y = 0;
		var parent = element;
		while ( parent ) {
			x += parent.offsetLeft;
			y += parent.offsetTop;
			parent = parent.offsetParent;
		}

		parent = element;
		while ( parent &&
		parent != document.body &&
		parent != document.documentElement ) {
			if ( parent.scrollLeft  )
			x -= parent.scrollLeft;
			if ( parent.scrollTop )
			y -= parent.scrollTop;
			parent = parent.parentNode;
		}

		if ( accountForDocScroll ) {
			x -= this.docScrollLeft();
			y -= this.docScrollTop();
		}

		return { x:x, y:y };
	},

	docScrollLeft: function() {
		if ( window.pageXOffset )
		return window.pageXOffset;
		else if ( document.documentElement && document.documentElement.scrollLeft )
		return document.documentElement.scrollLeft;
		else if ( document.body )
		return document.body.scrollLeft;
		else
		return 0;
	},

	docScrollTop: function() {
		if ( window.pageYOffset )
		return window.pageYOffset;
		else if ( document.documentElement && document.documentElement.scrollTop )
		return document.documentElement.scrollTop;
		else if ( document.body )
		return document.body.scrollTop;
		else
		return 0;
	}

};
