/**
 *
 *  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;
    }

};

