# Yahoo! UI Library

### Carousel Widget 2.6.0

Yahoo! UI Library > carousel > Carousel.js (source view)

Show Private Show Protected Show Deprecated

    /**
     * The Carousel module provides a widget for browsing among a set of like
     * objects represented pictorially.
     *
     * @module carousel
     * @requires yahoo, dom, event, element
     * @optional animation
     * @namespace YAHOO.widget
     * @title Carousel Widget
     */
    (function () {

        var WidgetName;             // forward declaration

        /**
         * The Carousel widget.
         *
         * @class Carousel
         * @extends YAHOO.util.Element
         * @constructor
         * @param el {HTMLElement | String} The HTML element that represents the
         * the container that houses the Carousel.
         * @param cfg {Object} (optional) The configuration values
         */
        YAHOO.widget.Carousel = function (el, cfg) {
            YAHOO.log("Component creation", WidgetName);

            this._navBtns = {};
            this._pages = {};

            YAHOO.widget.Carousel.superclass.constructor.call(this, el, cfg);
        };

        /*
         * Private variables of the Carousel component
         */

        /* Some abbreviations to avoid lengthy typing and lookups. */
        var Carousel    = YAHOO.widget.Carousel,
            Dom         = YAHOO.util.Dom,
            Event       = YAHOO.util.Event,
            JS          = YAHOO.lang;

        /**
         * The widget name.
         * @private
         * @static
         */
        WidgetName = "Carousel";

        /**
         * The internal table of Carousel instances.
         * @private
         * @static
         */
        var instances = {};

        /*
         * Custom events of the Carousel component
         */

        /**
         * @event afterScroll
         * @description Fires when the Carousel has scrolled to the previous or
         * next page.  Passes back the index of the first and last visible items in
         * the Carousel.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var afterScrollEvent = "afterScroll";

        /**
         * @event beforeHide
         * @description Fires before the Carousel is hidden.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var beforeHideEvent = "beforeHide";

        /**
         * @event beforePageChange
         * @description Fires when the Carousel is about to scroll to the previous
         * or next page.  Passes back the page number of the current page.  Note
         * that the first page number is zero.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var beforePageChangeEvent = "beforePageChange";

        /**
         * @event beforeScroll
         * @description Fires when the Carousel is about to scroll to the previous
         * or next page.  Passes back the index of the first and last visible items
         * in the Carousel and the direction (backward/forward) of the scroll.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var beforeScrollEvent = "beforeScroll";

        /**
         * @event beforeShow
         * @description Fires when the Carousel is about to be shown.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var beforeShowEvent = "beforeShow";

        /**
         * @event blur
         * @description Fires when the Carousel loses focus.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var blurEvent = "blur";

        /**
         * @event focus
         * @description Fires when the Carousel gains focus.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var focusEvent = "focus";

        /**
         * @event hide
         * @description Fires when the Carousel is hidden.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var hideEvent = "hide";

        /**
         * @event itemAdded
         * @description Fires when an item has been added to the Carousel.  Passes
         * back the content of the item that would be added, the index at which the
         * item would be added, and the event itself.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var itemAddedEvent = "itemAdded";

        /**
         * @event itemRemoved
         * @description Fires when an item has been removed from the Carousel.
         * Passes back the content of the item that would be removed, the index
         * from which the item would be removed, and the event itself.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var itemRemovedEvent = "itemRemoved";

        /**
         * @event itemSelected
         * @description Fires when an item has been selected in the Carousel.
         * Passes back the index of the selected item in the Carousel.  Note, that
         * the index begins from zero.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var itemSelectedEvent = "itemSelected";

        /**
         * @event loadItems
         * @description Fires when the Carousel needs more items to be loaded for
         * displaying them.  Passes back the first and last visible items in the
         * Carousel, and the number of items needed to be loaded.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var loadItemsEvent = "loadItems";

        /**
         * @event navigationStateChange
         * @description Fires when the state of either one of the navigation
         * buttons are changed from enabled to disabled or vice versa.  Passes back
         * the state (true/false) of the previous and next buttons.  The value true
         * signifies the button is enabled, false signifies disabled.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var navigationStateChangeEvent = "navigationStateChange";

        /**
         * @event pageChange
         * @description Fires after the Carousel has scrolled to the previous or
         * next page.  Passes back the page number of the current page.  Note
         * that the first page number is zero.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var pageChangeEvent = "pageChange";

        /**
         * @event render
         * @description Fires when the Carousel is rendered.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var renderEvent = "render";

        /**
         * @event show
         * @description Fires when the Carousel is shown.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var showEvent = "show";

        /**
         * @event startAutoPlay
         * @description Fires when the auto play has started in the Carousel.  See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var startAutoPlayEvent = "startAutoPlay";

        /**
         * @event stopAutoPlay
         * @description Fires when the auto play has been stopped in the Carousel.
         * See
         * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
         * for more information on listening for this event.
         * @type YAHOO.util.CustomEvent
         */
        var stopAutoPlayEvent = "stopAutoPlay";

        /*
         * Private helper functions used by the Carousel component
         */

        /**
         * Automatically scroll the contents of the Carousel.
         * @method autoScroll
         * @private
         */
        function autoScroll() {
            var currIndex = this._firstItem,
                index;

            if (currIndex >= this.get("numItems") - 1) {
                if (this.get("isCircular")) {
                    index = 0;
                } else {
                    this.stopAutoPlay();
                }
            } else {
                index = currIndex + this.get("numVisible");
            }
            this.scrollTo.call(this, index);
        }

        /**
         * Create an element, set its class name and optionally install the element
         * to its parent.
         * @method createElement
         * @param el {String} The element to be created
         * @param attrs {Object} Configuration of parent, class and id attributes.
         * If the content is specified, it is inserted after creation of the
         * element. The content can also be an HTML element in which case it would
         * be appended as a child node of the created element.
         * @private
         */
        function createElement(el, attrs) {
            var newEl = document.createElement(el);

            attrs = attrs || {};
            if (attrs.className) {
                Dom.addClass(newEl, attrs.className);
            }

            if (attrs.parent) {
                attrs.parent.appendChild(newEl);
            }

            if (attrs.id) {
                newEl.setAttribute("id", attrs.id);
            }

            if (attrs.content) {
                if (attrs.content.nodeName) {
                    newEl.appendChild(attrs.content);
                } else {
                    newEl.innerHTML = attrs.content;
                }
            }

            return newEl;
        }

        /**
         * Get the computed style of an element.
         *
         * @method getStyle
         * @param el {HTMLElement} The element for which the style needs to be
         * returned.
         * @param style {String} The style attribute
         * @param type {String} "int", "float", etc. (defaults to int)
         * @private
         */
        function getStyle(el, style, type) {
            var value;

            function getStyleIntVal(el, style) {
                var val;

                val = parseInt(Dom.getStyle(el, style), 10);
                return JS.isNumber(val) ? val : 0;
            }

            function getStyleFloatVal(el, style) {
                var val;

                val = parseFloat(Dom.getStyle(el, style));
                return JS.isNumber(val) ? val : 0;
            }

            if (typeof type == "undefined") {
                type = "int";
            }

            switch (style) {
            case "height":
                value = el.offsetHeight;
                if (value > 0) {
                    value += getStyleIntVal(el, "marginTop")        +
                            getStyleIntVal(el, "marginBottom");
                } else {
                    value = getStyleFloatVal(el, "height")          +
                            getStyleIntVal(el, "marginTop")         +
                            getStyleIntVal(el, "marginBottom")      +
                            getStyleIntVal(el, "borderTopWidth")    +
                            getStyleIntVal(el, "borderBottomWidth") +
                            getStyleIntVal(el, "paddingTop")        +
                            getStyleIntVal(el, "paddingBottom");
                }
                break;
            case "width":
                value = el.offsetWidth;
                if (value > 0) {
                    value += getStyleIntVal(el, "marginLeft")       +
                            getStyleIntVal(el, "marginRight");
                } else {
                    value = getStyleFloatVal(el, "width")           +
                            getStyleIntVal(el, "marginLeft")        +
                            getStyleIntVal(el, "marginRight")       +
                            getStyleIntVal(el, "borderLeftWidth")   +
                            getStyleIntVal(el, "borderRightWidth")  +
                            getStyleIntVal(el, "paddingLeft")       +
                            getStyleIntVal(el, "paddingRight");
                }
                break;
            default:
                if (type == "int") {
                    value = getStyleIntVal(el, style);
                    // XXX: Safari calculates incorrect marginRight for an element
                    // which has its parent element style set to overflow: hidden
                    // https://bugs.webkit.org/show_bug.cgi?id=13343
                    // Let us assume marginLeft == marginRight
                    if (style == "marginRight" && YAHOO.env.ua.webkit) {
                        value = getStyleIntVal(el, "marginLeft");
                    }
                } else if (type == "float") {
                    value = getStyleFloatVal(el, style);
                } else {
                    value = Dom.getStyle(el, style);
                }
                break;
            }

            return value;
        }

        /**
         * Compute and return the height or width of a single Carousel item
         * depending upon the orientation.
         *
         * @method getCarouselItemSize
         * @param which {String} "height" or "width" to be returned.  If this is
         * passed explicitly, the calculated size is not cached.
         * @private
         */
        function getCarouselItemSize(which) {
            var child,
                size     = 0,
                vertical = false;

            if (this._itemsTable.numItems === 0) {
                return 0;
            }

            if (typeof which == "undefined") {
                if (this._itemsTable.size > 0) {
                    return this._itemsTable.size;
                }
            }

            if (JS.isUndefined(this._itemsTable.items[0])) {
                return 0;
            }

            child = Dom.get(this._itemsTable.items[0].id);

            if (typeof which == "undefined") {
                vertical = this.get("isVertical");
            } else {
                vertical = which == "height";
            }

            if (vertical) {
                size = getStyle(child, "height");
            } else {
                size = getStyle(child, "width");
            }

            if (typeof which == "undefined") {
                this._itemsTable.size = size; // save the size for later
            }

            return size;
        }

        /**
         * Return the scrolling offset size given the number of elements to
         * scroll.
         *
         * @method getScrollOffset
         * @param delta {Number} The delta number of elements to scroll by.
         * @private
         */
        function getScrollOffset(delta) {
            var itemSize = 0,
                size     = 0;

            itemSize = getCarouselItemSize.call(this);
            size     = itemSize * delta;

            // XXX: really, when the orientation is vertical, the scrolling
            // is not exactly the number of elements into element size.
            if (this.get("isVertical")) {
                size -= delta;
            }

            return size;
        }

        /**
         * The load the required set of items that are needed for display.
         *
         * @method loadItems
         * @private
         */
        function loadItems() {
            var first      = this.get("firstVisible"),
                last       = 0,
                numItems   = this.get("numItems"),
                numVisible = this.get("numVisible"),
                reveal     = this.get("revealAmount");

            last = first + numVisible - 1 + (reveal ? 1 : 0);
            last = last > numItems - 1 ? numItems - 1 : last;

            if (!this.getItem(first) || !this.getItem(last)) {
                this.fireEvent(loadItemsEvent, {
                        ev: loadItemsEvent,
                        first: first, last: last,
                        num: last - first
                });
            }
        }

        /**
         * Scroll the Carousel by a page backward.
         *
         * @method scrollPageBackward
         * @param {Event} ev The event object
         * @param {Object} obj The context object
         * @private
         */
        function scrollPageBackward(ev, obj) {
            obj.scrollPageBackward();
            Event.preventDefault(ev);
        }

        /**
         * Scroll the Carousel by a page forward.
         *
         * @method scrollPageForward
         * @param {Event} ev The event object
         * @param {Object} obj The context object
         * @private
         */
        function scrollPageForward(ev, obj) {
            obj.scrollPageForward();
            Event.preventDefault(ev);
        }

        /**
         * Set the selected item.
         *
         * @method setItemSelection
         * @param {Number} newposition The index of the new position
         * @param {Number} oldposition The index of the previous position
         * @private
         */
         function setItemSelection(newposition, oldposition) {
            var backwards,
                cssClass   = this.CLASSES,
                el,
                firstItem  = this._firstItem,
                isCircular = this.get("isCircular"),
                numItems   = this.get("numItems"),
                numVisible = this.get("numVisible"),
                position   = oldposition,
                sentinel   = firstItem + numVisible - 1;

            backwards = numVisible > 1 && !isCircular && position > newposition;

            if (position >= 0 && position < numItems) {
                if (!JS.isUndefined(this._itemsTable.items[position])) {
                    el = Dom.get(this._itemsTable.items[position].id);
                    if (el) {
                        Dom.removeClass(el, cssClass.SELECTED_ITEM);
                    }
                }
            }

            if (JS.isNumber(newposition)) {
                newposition = parseInt(newposition, 10);
                newposition = JS.isNumber(newposition) ? newposition : 0;
            } else {
                newposition = firstItem;
            }

            if (JS.isUndefined(this._itemsTable.items[newposition])) {
                this.scrollTo(newposition); // still loading the item
            }

            if (!JS.isUndefined(this._itemsTable.items[newposition])) {
                el = Dom.get(this._itemsTable.items[newposition].id);
                if (el) {
                    Dom.addClass(el, cssClass.SELECTED_ITEM);
                }
            }

            if (newposition < firstItem || newposition > sentinel) {
                // out of focus
                if (backwards) {
                    this.scrollTo(firstItem - numVisible, true);
                } else {
                    this.scrollTo(newposition);
                }
            }
        }

        /**
         * Fire custom events for enabling/disabling navigation elements.
         *
         * @method syncNavigation
         * @private
         */
        function syncNavigation() {
            var attach   = false,
                cssClass = this.CLASSES,
                i,
                navigation,
                sentinel;

            navigation = this.get("navigation");
            sentinel   = this._firstItem + this.get("numVisible");

            if (navigation.prev) {
                if (this._firstItem === 0) {
                    if (!this.get("isCircular")) {
                        Event.removeListener(navigation.prev, "click",
                                scrollPageBackward);
                        Dom.addClass(navigation.prev, cssClass.FIRST_NAV_DISABLED);
                        for (i = 0; i < this._navBtns.prev.length; i++) {
                            this._navBtns.prev[i].setAttribute("disabled", "true");
                        }
                        this._prevEnabled = false;
                    } else {
                        attach = !this._prevEnabled;
                    }
                } else {
                    attach = !this._prevEnabled;
                }

                if (attach) {
                    Event.on(navigation.prev, "click", scrollPageBackward, this);
                    Dom.removeClass(navigation.prev, cssClass.FIRST_NAV_DISABLED);
                    for (i = 0; i < this._navBtns.prev.length; i++) {
                        this._navBtns.prev[i].removeAttribute("disabled");
                    }
                    this._prevEnabled = true;
                }
            }

            attach = false;
            if (navigation.next) {
                if (sentinel >= this.get("numItems")) {
                    if (!this.get("isCircular")) {
                        Event.removeListener(navigation.next, "click",
                                scrollPageForward);
                        Dom.addClass(navigation.next, cssClass.DISABLED);
                        for (i = 0; i < this._navBtns.next.length; i++) {
                            this._navBtns.next[i].setAttribute("disabled", "true");
                        }
                        this._nextEnabled = false;
                    } else {
                        attach = !this._nextEnabled;
                    }
                } else {
                    attach = !this._nextEnabled;
                }

                if (attach) {
                    Event.on(navigation.next, "click", scrollPageForward, this);
                    Dom.removeClass(navigation.next, cssClass.DISABLED);
                    for (i = 0; i < this._navBtns.next.length; i++) {
                        this._navBtns.next[i].removeAttribute("disabled");
                    }
                    this._nextEnabled = true;
                }
            }

            this.fireEvent(navigationStateChangeEvent,
                    { next: this._nextEnabled, prev: this._prevEnabled });
        }

        /**
         * Fire custom events for synchronizing the DOM.
         *
         * @method syncUI
         * @param {Object} o The item that needs to be added or removed
         * @private
         */
        function syncUI(o) {
            var el, i, item, num, oel, pos, sibling;

            if (!JS.isObject(o)) {
                return;
            }

            switch (o.ev) {
            case itemAddedEvent:
                pos  = JS.isUndefined(o.pos) ? this._itemsTable.numItems-1 : o.pos;
                if (!JS.isUndefined(this._itemsTable.items[pos])) {
                    item = this._itemsTable.items[pos];
                    if (item && !JS.isUndefined(item.id)) {
                        oel  = Dom.get(item.id);
                    }
                }
                if (!oel) {
                    el = this._createCarouselItem({
                            className : item.className,
                            content   : item.item,
                            id        : item.id
                    });
                    if (JS.isUndefined(o.pos)) {
                        if (!JS.isUndefined(this._itemsTable.loading[pos])) {
                            oel = this._itemsTable.loading[pos];
                        }
                        if (oel) {
                            this._carouselEl.replaceChild(el, oel);
                        } else {
                            this._carouselEl.appendChild(el);
                        }
                    } else {
                        if (!JS.isUndefined(this._itemsTable.items[o.pos + 1])) {
                            sibling = Dom.get(this._itemsTable.items[o.pos + 1].id);
                        }
                        if (sibling) {
                            this._carouselEl.insertBefore(el, sibling);
                        } else {
                            YAHOO.log("Unable to find sibling","error",WidgetName);
                        }
                    }
                } else {
                    if (JS.isUndefined(o.pos)) {
                        if (!Dom.isAncestor(this._carouselEl, oel)) {
                            this._carouselEl.appendChild(oel);
                        }
                    } else {
                        if (!Dom.isAncestor(this._carouselEl, oel)) {
                            if (!JS.isUndefined(this._itemsTable.items[o.pos+1])) {
                                this._carouselEl.insertBefore(oel, Dom.get(
                                        this._itemsTable.items[o.pos+1].id));
                            }
                        }
                    }
                }

                if (this._recomputeSize) {
                    this._setClipContainerSize();
                }
                break;
            case itemRemovedEvent:
                num  = this.get("numItems");
                item = o.item;
                pos  = o.pos;

                if (item && (el = Dom.get(item.id))) {
                    if (el && Dom.isAncestor(this._carouselEl, el)) {
                        Event.purgeElement(el, true);
                        this._carouselEl.removeChild(el);
                    }

                    if (this.get("selectedItem") == pos) {
                        pos = pos >= num ? num - 1 : pos;
                        this.set("selectedItem", pos);
                    }
                } else {
                    YAHOO.log("Unable to find item", "warn", WidgetName);
                }
                break;
            case loadItemsEvent:
                for (i = o.first; i <= o.last; i++) {
                    el = this._createCarouselItem({
                            content : this.CONFIG.ITEM_LOADING,
                            id      : Dom.generateId()
                    });
                    if (el) {
                        if (!JS.isUndefined(this._itemsTable.items[o.last + 1])) {
                            sibling = Dom.get(this._itemsTable.items[o.last+1].id);
                            if (sibling) {
                                this._carouselEl.insertBefore(el, sibling);
                            } else {
                                YAHOO.log("Unable to find sibling", "error",
                                        WidgetName);
                            }
                        } else {
                            this._carouselEl.appendChild(el);
                        }
                    }
                    this._itemsTable.loading[i] = el;
                }
                break;
            }
        }

        /*
         * Static members and methods of the Carousel component
         */

        /**
         * Return the appropriate Carousel object based on the id associated with
         * the Carousel element or false if none match.
         * @method getById
         * @public
         * @static
         */
        Carousel.getById = function (id) {
            return instances[id] ? instances[id] : false;
        };

        YAHOO.extend(Carousel, YAHOO.util.Element, {

            /*
             * Internal variables used within the Carousel component
             */

            /**
             * The Carousel element.
             *
             * @property _carouselEl
             * @private
             */
            _carouselEl: null,

            /**
             * The Carousel clipping container element.
             *
             * @property _clipEl
             * @private
             */
            _clipEl: null,

            /**
             * The current first index of the Carousel.
             *
             * @property _firstItem
             * @private
             */
            _firstItem: 0,

            /**
             * Is the animation still in progress?
             *
             * @property _isAnimationInProgress
             * @private
             */
            _isAnimationInProgress: false,

            /**
             * The table of items in the Carousel.
             * The numItems is the number of items in the Carousel, items being the
             * array of items in the Carousel.  The size is the size of a single
             * item in the Carousel.  It is cached here for efficiency (to avoid
             * computing the size multiple times).
             *
             * @property _itemsTable
             * @private
             */
            _itemsTable: null,

            /**
             * The Carousel navigation buttons.
             *
             * @property _navBtns
             * @private
             */
            _navBtns: null,

            /**
             * The Carousel navigation.
             *
             * @property _navEl
             * @private
             */
            _navEl: null,

            /**
             * Status of the next navigation item.
             *
             * @property _nextEnabled
             * @private
             */
            _nextEnabled: true,

            /**
             * The Carousel pages structure.
             * This is an object of the total number of pages and the current page.
             *
             * @property _pages
             * @private
             */
            _pages: null,

            /**
             * Status of the previous navigation item.
             *
             * @property _prevEnabled
             * @private
             */
            _prevEnabled: true,

            /**
             * Whether the Carousel size needs to be recomputed or not?
             *
             * @property _recomputeSize
             * @private
             */
            _recomputeSize: true,

            /*
             * CSS classes used by the Carousel component
             */

            CLASSES: {

                /**
                 * The class name of the Carousel navigation buttons.
                 *
                 * @property BUTTON
                 * @default "yui-carousel-button"
                 */
                BUTTON: "yui-carousel-button",

                /**
                 * The class name of the Carousel element.
                 *
                 * @property CAROUSEL
                 * @default "yui-carousel"
                 */
                CAROUSEL: "yui-carousel",

                /**
                 * The class name of the container of the items in the Carousel.
                 *
                 * @property CAROUSEL_EL
                 * @default "yui-carousel-element"
                 */
                CAROUSEL_EL: "yui-carousel-element",

                /**
                 * The class name of the Carousel's container element.
                 *
                 * @property CONTAINER
                 * @default "yui-carousel-container"
                 */
                CONTAINER: "yui-carousel-container",

                /**
                 * The class name of the Carousel's container element.
                 *
                 * @property CONTENT
                 * @default "yui-carousel-content"
                 */
                CONTENT: "yui-carousel-content",

                /**
                 * The class name of a disabled navigation button.
                 *
                 * @property DISABLED
                 * @default "yui-carousel-button-disabled"
                 */
                DISABLED: "yui-carousel-button-disabled",

                /**
                 * The class name of the first Carousel navigation button.
                 *
                 * @property FIRST_NAV
                 * @default " yui-carousel-first-button"
                 */
                FIRST_NAV: " yui-carousel-first-button",

                /**
                 * The class name of a first disabled navigation button.
                 *
                 * @property FIRST_NAV_DISABLED
                 * @default "yui-carousel-first-button-disabled"
                 */
                FIRST_NAV_DISABLED: "yui-carousel-first-button-disabled",

                /**
                 * The class name of a first page element.
                 *
                 * @property FIRST_PAGE
                 * @default "yui-carousel-nav-first-page"
                 */
                FIRST_PAGE: "yui-carousel-nav-first-page",

                /**
                 * The class name of the Carousel navigation button that has focus.
                 *
                 * @property FOCUSSED_BUTTON
                 * @default "yui-carousel-button-focus"
                 */
                FOCUSSED_BUTTON: "yui-carousel-button-focus",

                /**
                 * The class name of a horizontally oriented Carousel.
                 *
                 * @property HORIZONTAL
                 * @default "yui-carousel-horizontal"
                 */
                HORIZONTAL: "yui-carousel-horizontal",

                /**
                 * The navigation element container class name.
                 *
                 * @property NAVIGATION
                 * @default "yui-carousel-nav"
                 */
                NAVIGATION: "yui-carousel-nav",

                /**
                 * The class name of the next navigation link. This variable is not
                 * only used for styling, but also for identifying the link within
                 * the Carousel container.
                 *
                 * @property NEXT_PAGE
                 * @default "yui-carousel-next"
                 */
                NEXT_PAGE: "yui-carousel-next",

                /**
                 * The class name for the navigation container for prev/next.
                 *
                 * @property NAV_CONTAINER
                 * @default "yui-carousel-buttons"
                 */
                NAV_CONTAINER: "yui-carousel-buttons",

                /**
                 * The class name of the previous navigation link. This variable
                 * is not only used for styling, but also for identifying the link
                 * within the Carousel container.
                 *
                 * @property PREV_PAGE
                 * @default "yui-carousel-prev"
                 */
                PREV_PAGE: "yui-carousel-prev",

                /**
                 * The class name of the selected item.
                 *
                 * @property SELECTED_ITEM
                 * @default "yui-carousel-item-selected"
                 */
                SELECTED_ITEM: "yui-carousel-item-selected",

                /**
                 * The class name of the selected paging navigation.
                 *
                 * @property SELECTED_NAV
                 * @default "yui-carousel-nav-page-selected"
                 */
                SELECTED_NAV: "yui-carousel-nav-page-selected",

                /**
                 * The class name of a vertically oriented Carousel.
                 *
                 * @property VERTICAL
                 * @default "yui-carousel-vertical"
                 */
                VERTICAL: "yui-carousel-vertical",

                /**
                 * The class name of the (vertical) Carousel's container element.
                 *
                 * @property VERTICAL_CONTAINER
                 * @default "yui-carousel-vertical-container"
                 */
                VERTICAL_CONTAINER: "yui-carousel-vertical-container",

                /**
                 * The class name of a visible Carousel.
                 *
                 * @property VISIBLE
                 * @default "yui-carousel-visible"
                 */
                VISIBLE: "yui-carousel-visible"

            },

            /*
             * Configuration attributes for configuring the Carousel component
             */

            CONFIG: {

                /**
                 * The offset of the first visible item in the Carousel.
                 *
                 * @property FIRST_VISIBLE
                 * @default 0
                 */
                FIRST_VISIBLE: 0,

                /**
                 * The element to be used as the progress indicator when the item
                 * is still being loaded.
                 *
                 * @property ITEM_LOADING
                 * @default The progress indicator (spinner) image
                 */
                ITEM_LOADING: "<img " +
                        "src=\"../../build/carousel/assets/ajax-loader.gif\" " +
                        "alt=\"Loading\" " +
                        "style=\"margin-top:-32px;position:relative;top:50%;\">",

                /**
                 * The tag name of the Carousel item.
                 *
                 * @property ITEM_TAG_NAME
                 * @default "LI"
                 */
                ITEM_TAG_NAME: "LI",

                /**
                 * The maximum number of pager buttons allowed beyond which the UI
                 * of the pager would be a drop-down of pages instead of buttons.
                 *
                 * @property MAX_PAGER_BUTTONS
                 * @default 5
                 */
                MAX_PAGER_BUTTONS: 5,

                /**
                 * The minimum width of the Carousel container to support the
                 * navigation buttons.
                 *
                 * @property MIN_WIDTH
                 * @default 99
                 */
                MIN_WIDTH: 99,

                /**
                 * The number of visible items in the Carousel.
                 *
                 * @property NUM_VISIBLE
                 * @default 3
                 */
                NUM_VISIBLE: 3,

                /**
                 * The tag name of the Carousel.
                 *
                 * @property TAG_NAME
                 * @default "OL"
                 */
                TAG_NAME: "OL"

            },

            /*
             * Internationalizable strings in the Carousel component
             */

            STRINGS: {

                /**
                 * The next navigation button name/text.
                 *
                 * @property NEXT_BUTTON_TEXT
                 * @default "Next Page"
                 */
                NEXT_BUTTON_TEXT: "Next Page",

                /**
                 * The prefix text for the pager in case the UI is a drop-down.
                 *
                 * @property PAGER_PREFIX_TEXT
                 * @default "Go to page "
                 */
                PAGER_PREFIX_TEXT: "Go to page ",

                /**
                 * The previous navigation button name/text.
                 *
                 * @property PREVIOUS_BUTTON_TEXT
                 * @default "Previous Page"
                 */
                PREVIOUS_BUTTON_TEXT: "Previous Page"

            },

            /*
             * Public methods of the Carousel component
             */

            /**
             * Insert or append an item to the Carousel.
             *
             * @method addItem
             * @public
             * @param item {String | Object | HTMLElement} The item to be appended
             * to the Carousel. If the parameter is a string, it is assumed to be
             * the content of the newly created item. If the parameter is an
             * object, it is assumed to supply the content and an optional class
             * and an optional id of the newly created item.
             * @param index {Number} optional The position to where in the list
             * (starts from zero).
             * @return {Boolean} Return true on success, false otherwise
             */
            addItem: function (item, index) {
                var className, content, el, elId, numItems = this.get("numItems");

                if (!item) {
                    return false;
                }

                if (JS.isString(item) || item.nodeName) {
                    content = item.nodeName ? item.innerHTML : item;
                } else if (JS.isObject(item)) {
                    content = item.content;
                } else {
                    YAHOO.log("Invalid argument to addItem", "error", WidgetName);
                    return false;
                }

                className = item.className || "";
                elId      = item.id ? item.id : Dom.generateId();

                if (JS.isUndefined(index)) {
                    this._itemsTable.items.push({
                            item      : content,
                            className : className,
                            id        : elId
                    });
                } else {
                    if (index < 0 || index >= numItems) {
                        YAHOO.log("Index out of bounds", "error", WidgetName);
                        return false;
                    }
                    this._itemsTable.items.splice(index, 0, {
                            item      : content,
                            className : className,
                            id        : elId
                    });
                }
                this._itemsTable.numItems++;

                if (numItems < this._itemsTable.items.length) {
                    this.set("numItems", this._itemsTable.items.length);
                }

                this.fireEvent(itemAddedEvent, { pos: index, ev: itemAddedEvent });

                return true;
            },

            /**
             * Insert or append multiple items to the Carousel.
             *
             * @method addItems
             * @public
             * @param items {Array} An array of items to be added with each item
             * representing an item, index pair [{item, index}, ...]
             * @return {Boolean} Return true on success, false otherwise
             */
            addItems: function (items) {
                var i, n, rv = true;

                if (!JS.isArray(items)) {
                    return false;
                }

                for (i = 0, n = items.length; i < n; i++) {
                    if (this.addItem(items[i][0], items[i][1]) === false) {
                        rv = false;
                    }
                }

                return rv;
            },

            /**
             * Remove focus from the Carousel.
             *
             * @method blur
             * @public
             */
            blur: function () {
                this._carouselEl.blur();
                this.fireEvent(blurEvent);
            },

            /**
             * Clears the items from Carousel.
             *
             * @method clearItems
             * public
             */
            clearItems: function () {
                var n = this.get("numItems");

                while (n > 0) {
                    this.removeItem(0);
                    n--;
                }
            },

            /**
             * Set focus on the Carousel.
             *
             * @method focus
             * @public
             */
            focus: function () {
                var selItem,
                    numVisible,
                    selectOnScroll,
                    selected,
                    first,
                    last,
                    isSelectionInvisible,
                    focusEl,
                    itemsTable;

                if (this._isAnimationInProgress) {
                    // this messes up real bad!
                    return;
                }

                selItem              = this.get("selectedItem");
                numVisible           = this.get("numVisible");
                selectOnScroll       = this.get("selectOnScroll");
                selected             = this.getItem(selItem);
                first                = this.get("firstVisible");
                last                 = first + numVisible - 1;
                isSelectionInvisible = (selItem < first || selItem > last);
                focusEl              = (selected && selected.id) ?
                                       Dom.get(selected.id) : null;
                itemsTable           = this._itemsTable;

                if (!selectOnScroll && isSelectionInvisible) {
                    focusEl = (itemsTable && itemsTable.items &&
                               itemsTable.items[first]) ?
                            Dom.get(itemsTable.items[first].id) : null;
                }

                if (focusEl) {
                    try {
                        focusEl.focus();
                    } catch (ex) {
                        // ignore focus errors
                    }
                }

                this.fireEvent(focusEvent);
            },

            /**
             * Hide the Carousel.
             *
             * @method hide
             * @public
             */
            hide: function () {
                if (this.fireEvent(beforeHideEvent) !== false) {
                    this.removeClass(this.CLASSES.VISIBLE);
                    this.fireEvent(hideEvent);
                }
            },

            /**
             * Initialize the Carousel.
             *
             * @method init
             * @public
             * @param el {HTMLElement | String} The html element that represents
             * the Carousel container.
             * @param attrs {Object} The set of configuration attributes for
             * creating the Carousel.
             */
            init: function (el, attrs) {
                var elId  = el,     // save for a rainy day
                    parse = false;

                if (!el) {
                    YAHOO.log(el + " is neither an HTML element, nor a string",
                            "error", WidgetName);
                    return;
                }

                this._itemsTable = { loading: {}, numItems: 0, items: [], size: 0 };
                YAHOO.log("Component initialization", WidgetName);

                if (JS.isString(el)) {
                    el = Dom.get(el);
                } else if (!el.nodeName) {
                    YAHOO.log(el + " is neither an HTML element, nor a string",
                            "error", WidgetName);
                    return;
                }

                if (el) {
                    if (!el.id) {   // in case the HTML element is passed
                        el.setAttribute("id", Dom.generateId());
                    }
                    this._parseCarousel(el);
                    parse = true;
                } else {
                    el = this._createCarousel(elId);
                }
                elId = el.id;

                Carousel.superclass.init.call(this, el, attrs);

                this.initEvents();

                if (parse) {
                    this._parseCarouselItems();
                }

                if (!attrs || typeof attrs.isVertical == "undefined") {
                    this.set("isVertical", false);
                }

                this._parseCarouselNavigation(el);
                this._navEl = this._setupCarouselNavigation();

                instances[elId] = this;

                loadItems.call(this);
            },

            /**
             * Initialize the configuration attributes used to create the Carousel.
             *
             * @method initAttributes
             * @public
             * @param attrs {Object} The set of configuration attributes for
             * creating the Carousel.
             */
            initAttributes: function (attrs) {
                attrs = attrs || {};
                Carousel.superclass.initAttributes.call(this, attrs);

                /**
                 * @attribute currentPage
                 * @description The current page number (read-only.)
                 * @type Number
                 */
                this.setAttributeConfig("currentPage", {
                        readOnly : true,
                        value    : 0
                });

                /**
                 * @attribute firstVisible
                 * @description The index to start the Carousel from (indexes begin
                 * from zero)
                 * @default 0
                 * @type Number
                 */
                this.setAttributeConfig("firstVisible", {
                        method    : this._setFirstVisible,
                        validator : this._validateFirstVisible,
                        value     : attrs.firstVisible || this.CONFIG.FIRST_VISIBLE
                });

                /**
                 * @attribute selectOnScroll
                 * @description Set this to true to automatically set focus to
                 * follow scrolling in the Carousel.
                 * @default true
                 * @type Boolean
                 */
                this.setAttributeConfig("selectOnScroll", {
                        validator : JS.isBoolean,
                        value     : attrs.selectOnScroll || true
                });

                /**
                 * @attribute numVisible
                 * @description The number of visible items in the Carousel's
                 * viewport.
                 * @default 3
                 * @type Number
                 */
                this.setAttributeConfig("numVisible", {
                        method    : this._setNumVisible,
                        validator : this._validateNumVisible,
                        value     : attrs.numVisible || this.CONFIG.NUM_VISIBLE
                });

                /**
                 * @attribute numItems
                 * @description The number of items in the Carousel.
                 * @type Number
                 */
                this.setAttributeConfig("numItems", {
                        method    : this._setNumItems,
                        validator : this._validateNumItems,
                        value     : this._itemsTable.numItems
                });

                /**
                 * @attribute scrollIncrement
                 * @description The number of items to scroll by for arrow keys.
                 * @default 1
                 * @type Number
                 */
                this.setAttributeConfig("scrollIncrement", {
                        validator : this._validateScrollIncrement,
                        value     : attrs.scrollIncrement || 1
                });

                /**
                 * @attribute selectedItem
                 * @description The index of the selected item.
                 * @type Number
                 */
                this.setAttributeConfig("selectedItem", {
                        method    : this._setSelectedItem,
                        validator : JS.isNumber,
                        value     : 0
                });

                /**
                 * @attribute revealAmount
                 * @description The percentage of the item to be revealed on each
                 * side of the Carousel (before and after the first and last item
                 * in the Carousel's viewport.)
                 * @default 0
                 * @type Number
                 */
                this.setAttributeConfig("revealAmount", {
                        method    : this._setRevealAmount,
                        validator : this._validateRevealAmount,
                        value     : attrs.revealAmount || 0
                });

                /**
                 * @attribute isCircular
                 * @description Set this to true to wrap scrolling of the contents
                 * in the Carousel.
                 * @default false
                 * @type Boolean
                 */
                this.setAttributeConfig("isCircular", {
                        validator : JS.isBoolean,
                        value     : attrs.isCircular || false
                });

                /**
                 * @attribute isVertical
                 * @description True if the orientation of the Carousel is vertical
                 * @default false
                 * @type Boolean
                 */
                this.setAttributeConfig("isVertical", {
                        method    : this._setOrientation,
                        validator : JS.isBoolean,
                        value     : attrs.isVertical || false
                });

                /**
                 * @attribute navigation
                 * @description The set of navigation controls for Carousel
                 * @default <br>
                 * { prev: null, // the previous navigation element<br>
                 *   next: null } // the next navigation element
                 * @type Object
                 */
                this.setAttributeConfig("navigation", {
                        method    : this._setNavigation,
                        validator : this._validateNavigation,
                        value     : attrs.navigation || {
                                            prev: null, next: null, page: null }
                });

                /**
                 * @attribute animation
                 * @description The optional animation attributes for the Carousel.
                 * @default <br>
                 * { speed: 0, // the animation speed (in seconds)<br>
                 *   effect: null } // the animation effect (like
                 *   YAHOO.util.Easing.easeOut)
                 * @type Object
                 */
                this.setAttributeConfig("animation", {
                        validator : this._validateAnimation,
                        value     : attrs.animation || { speed: 0, effect: null }
                });

                /**
                 * @attribute autoPlay
                 * @description Set this to time in milli-seconds to have the
                 * Carousel automatically scroll the contents.
                 * @type Number
                 */
                this.setAttributeConfig("autoPlay", {
                        validator : JS.isNumber,
                        value     : attrs.autoPlay || 0
                });
            },

            /**
             * Initialize and bind the event handlers.
             *
             * @method initEvents
             * @public
             */
            initEvents: function () {
                var cssClass = this.CLASSES;

                this.on("keydown", this._keyboardEventHandler);

                this.subscribe(afterScrollEvent, syncNavigation);
                this.on(afterScrollEvent, this.focus);

                this.subscribe(itemAddedEvent, syncUI);
                this.subscribe(itemAddedEvent, syncNavigation);

                this.subscribe(itemRemovedEvent, syncUI);
                this.subscribe(itemRemovedEvent, syncNavigation);

                this.on(itemSelectedEvent, this.focus);

                this.subscribe(loadItemsEvent, syncUI);

                this.subscribe(pageChangeEvent, this._syncPagerUI);

                this.subscribe(renderEvent, syncNavigation);
                this.subscribe(renderEvent, this._syncPagerUI);

                this.on("selectedItemChange", function (ev) {
                    setItemSelection.call(this, ev.newValue, ev.prevValue);
                    this._updateTabIndex(this.getElementForItem(ev.newValue));
                    this.fireEvent(itemSelectedEvent, ev.newValue);
                });

                this.on("firstVisibleChange", function (ev) {
                    if (!this.get("selectOnScroll")) {
                        this._updateTabIndex(this.getElementForItem(ev.newValue));
                    }
                });

                // Handle item selection on mouse click
                this.on("click", this._itemClickHandler);

                // Handle page navigation
                this.on("click", this._pagerClickHandler);

                // Restore the focus on the navigation buttons
                Event.onFocus(this.get("element"), function (ev, obj) {
                    obj._updateNavButtons(Event.getTarget(ev), true);
                }, this);

                Event.onBlur(this.get("element"), function (ev, obj) {
                    obj._updateNavButtons(Event.getTarget(ev), false);
                }, this);

            },

            /**
             * Return the ITEM_TAG_NAME at index or null if the index is not found.
             *
             * @method getElementForItem
             * @param index {Number} The index of the item to be returned
             * @return {Element} Return the item at index or null if not found
             * @public
             */
            getElementForItem: function (index) {
                if (index < 0 || index >= this.get("numItems")) {
                    YAHOO.log("Index out of bounds", "error", WidgetName);
                    return null;
                }

                // TODO: may be cache the item
                if (this._itemsTable.numItems > index) {
                    if (!JS.isUndefined(this._itemsTable.items[index])) {
                        return Dom.get(this._itemsTable.items[index].id);
                    }
                }

                return null;
            },

            /**
             * Return the ITEM_TAG_NAME for all items in the Carousel.
             *
             * @method getElementForItems
             * @return {Array} Return all the items
             * @public
             */
            getElementForItems: function () {
                var els = [], i;

                for (i = 0; i < this._itemsTable.numItems; i++) {
                    els.push(this.getElementForItem(i));
                }

                return els;
            },

            /**
             * Return the item at index or null if the index is not found.
             *
             * @method getItem
             * @param index {Number} The index of the item to be returned
             * @return {Object} Return the item at index or null if not found
             * @public
             */
            getItem: function (index) {
                if (index < 0 || index >= this.get("numItems")) {
                    YAHOO.log("Index out of bounds", "error", WidgetName);
                    return null;
                }

                if (this._itemsTable.numItems > index) {
                    if (!JS.isUndefined(this._itemsTable.items[index])) {
                        return this._itemsTable.items[index];
                    }
                }

                return null;
            },

            /**
             * Return all items as an array.
             *
             * @method getItems
             * @return {Array} Return all items in the Carousel
             * @public
             */
            getItems: function (index) {
                return this._itemsTable.items;
            },

            /**
             * Return the position of the Carousel item that has the id "id", or -1
             * if the id is not found.
             *
             * @method getItemPositionById
             * @param index {Number} The index of the item to be returned
             * @public
             */
            getItemPositionById: function (id) {
                var i = 0, n = this._itemsTable.numItems;

                while (i < n) {
                    if (!JS.isUndefined(this._itemsTable.items[i])) {
                        if (this._itemsTable.items[i].id == id) {
                            return i;
                        }
                    }
                    i++;
                }

                return -1;
            },

            /**
             * Remove an item at index from the Carousel.
             *
             * @method removeItem
             * @public
             * @param index {Number} The position to where in the list (starts from
             * zero).
             * @return {Boolean} Return true on success, false otherwise
             */
            removeItem: function (index) {
                var item, num = this.get("numItems");

                if (index < 0 || index >= num) {
                    YAHOO.log("Index out of bounds", "error", WidgetName);
                    return false;
                }

                item = this._itemsTable.items.splice(index, 1);
                if (item && item.length == 1) {
                    this.set("numItems", num - 1);

                    this.fireEvent(itemRemovedEvent,
                            { item: item[0], pos: index, ev: itemRemovedEvent });
                    return true;
                }

                return false;
            },

            /**
             * Render the Carousel.
             *
             * @method render
             * @public
             * @param appendTo {HTMLElement | String} The element to which the
             * Carousel should be appended prior to rendering.
             * @return {Boolean} Status of the operation
             */
            render: function (appendTo) {
                var config = this.CONFIG,
                    cssClass = this.CLASSES,
                    size;

                this.addClass(cssClass.CAROUSEL);

                if (!this._clipEl) {
                    this._clipEl = this._createCarouselClip();
                    this._clipEl.appendChild(this._carouselEl);
                }

                if (appendTo) {
                    this.appendChild(this._clipEl);
                    this.appendTo(appendTo);
                    this._setClipContainerSize();
                } else {
                    if (!Dom.inDocument(this.get("element"))) {
                        YAHOO.log("Nothing to render. The container should be " +
                                "within the document if appendTo is not "       +
                                "specified", "error", WidgetName);
                        return false;
                    }
                    this.appendChild(this._clipEl);
                }

                if (this.get("isVertical")) {
                    size = getCarouselItemSize.call(this);
                    size = size < config.MIN_WIDTH ? config.MIN_WIDTH : size;
                    this.setStyle("width",  size + "px");
                    this.addClass(cssClass.VERTICAL);
                } else {
                    this.addClass(cssClass.HORIZONTAL);
                }

                if (this.get("numItems") < 1) {
                    YAHOO.log("No items in the Carousel to render", "warn",
                            WidgetName);
                    return false;
                }

                // Make sure at least one item is selected
                this.set("selectedItem", this.get("firstVisible"));

                this.fireEvent(renderEvent);

                // By now, the navigation would have been rendered, so calculate
                // the container height now.
                this._setContainerSize();

                return true;
            },

            /**
             * Scroll the Carousel by an item backward.
             *
             * @method scrollBackward
             * @public
             */
            scrollBackward: function () {
                this.scrollTo(this._firstItem - this.get("scrollIncrement"));
            },

            /**
             * Scroll the Carousel by an item forward.
             *
             * @method scrollForward
             * @public
             */
            scrollForward: function () {
                this.scrollTo(this._firstItem + this.get("scrollIncrement"));
            },

            /**
             * Scroll the Carousel by a page backward.
             *
             * @method scrollPageBackward
             * @public
             */
            scrollPageBackward: function () {
                this.scrollTo(this._firstItem - this.get("numVisible"));
            },

            /**
             * Scroll the Carousel by a page forward.
             *
             * @method scrollPageForward
             * @public
             */
            scrollPageForward: function () {
                this.scrollTo(this._firstItem + this.get("numVisible"));
            },

            /**
             * Scroll the Carousel to make the item the first visible item.
             *
             * @method scrollTo
             * @public
             * @param item Number The index of the element to position at.
             * @param dontSelect Boolean True if select should be avoided
             */
            scrollTo: function (item, dontSelect) {
                var anim,
                    animate,
                    animAttrs,
                    animCfg    = this.get("animation"),
                    isCircular = this.get("isCircular"),
                    delta,
                    direction,
                    firstItem  = this._firstItem,
                    newPage,
                    numItems   = this.get("numItems"),
                    numPerPage = this.get("numVisible"),
                    offset,
                    page       = this.get("currentPage"),
                    rv,
                    sentinel,
                    which;

                if (item == firstItem) {
                    return;         // nothing to do!
                }

                if (this._isAnimationInProgress) {
                    return;         // let it take its own sweet time to complete
                }

                if (item < 0) {
                    if (isCircular) {
                        item = numItems + item;
                    } else {
                        return;
                    }
                } else if (item > numItems - 1) {
                    if (this.get("isCircular")) {
                        item = numItems - item;
                    } else {
                        return;
                    }
                }

                direction = (this._firstItem > item) ? "backward" : "forward";

                sentinel  = firstItem + numPerPage;
                sentinel  = (sentinel > numItems - 1) ? numItems - 1 : sentinel;
                rv = this.fireEvent(beforeScrollEvent,
                        { dir: direction, first: firstItem, last: sentinel });
                if (rv === false) { // scrolling is prevented
                    return;
                }

                this.fireEvent(beforePageChangeEvent, { page: page });

                delta = firstItem - item; // yes, the delta is reverse
                this._firstItem = item;
                this.set("firstVisible", item);

                YAHOO.log("Scrolling to " + item + " delta = " + delta, WidgetName);

                loadItems.call(this); // do we have all the items to display?

                sentinel  = item + numPerPage;
                sentinel  = (sentinel > numItems - 1) ? numItems - 1 : sentinel;

                which     = this.get("isVertical") ? "top" : "left";
                offset    = getScrollOffset.call(this, delta);
                YAHOO.log("Scroll offset = " + offset, WidgetName);

                animate   = animCfg.speed > 0;

                if (animate) {
                    this._isAnimationInProgress = true;
                    if (this.get("isVertical")) {
                        animAttrs = { points: { by: [0, offset] } };
                    } else {
                        animAttrs = { points: { by: [offset, 0] } };
                    }
                    anim = new YAHOO.util.Motion(this._carouselEl, animAttrs,
                            animCfg.speed, animCfg.effect);
                    anim.onComplete.subscribe(function (ev) {
                        var first = this.get("firstVisible");

                        this._isAnimationInProgress = false;
                        this.fireEvent(afterScrollEvent,
                                { first: first, last: sentinel });
                    }, null, this);
                    anim.animate();
                    anim = null;
                } else {
                    offset += getStyle(this._carouselEl, which);
                    Dom.setStyle(this._carouselEl, which, offset + "px");
                }

                newPage = parseInt(this._firstItem / numPerPage, 10);
                if (newPage != page) {
                    this.setAttributeConfig("currentPage", { value: newPage });
                    this.fireEvent(pageChangeEvent, newPage);
                }

                if (!dontSelect) {
                    if (this.get("selectOnScroll")) {
                        if (item != this._selectedItem) { // out of sync
                            this.set("selectedItem", this._getSelectedItem(item));
                        }
                    }
                }

                delete this._autoPlayTimer;
                if (this.get("autoPlay") > 0) {
                    this.startAutoPlay();
                }

                if (!animate) {
                    this.fireEvent(afterScrollEvent,
                            { first: item, last: sentinel });
                }
            },

            /**
             * Display the Carousel.
             *
             * @method show
             * @public
             */
            show: function () {
                var cssClass = this.CLASSES;

                if (this.fireEvent(beforeShowEvent) !== false) {
                    this.addClass(cssClass.VISIBLE);
                    this.fireEvent(showEvent);
                }
            },

            /**
             * Start auto-playing the Carousel.
             *
             * @method startAutoPlay
             * @public
             */
            startAutoPlay: function () {
                var self  = this,
                    timer = this.get("autoPlay");

                if (timer > 0) {
                    if (!JS.isUndefined(this._autoPlayTimer)) {
                        return;
                    }
                    this.fireEvent(startAutoPlayEvent);
                    this._autoPlayTimer = setTimeout(function () {
                        autoScroll.call(self); }, timer);
                }
            },

            /**
             * Stop auto-playing the Carousel.
             *
             * @method stopAutoPlay
             * @public
             */
            stopAutoPlay: function () {
                if (!JS.isUndefined(this._autoPlayTimer)) {
                    clearTimeout(this._autoPlayTimer);
                    delete this._autoPlayTimer;
                    this.set("autoPlay", 0);
                    this.fireEvent(stopAutoPlayEvent);
                }
            },

            /**
             * Return the string representation of the Carousel.
             *
             * @method toString
             * @public
             * @return {String}
             */
            toString: function () {
                return WidgetName + (this.get ? " (#" + this.get("id") + ")" : "");
            },

            /*
             * Protected methods of the Carousel component
             */

            /**
             * Create the Carousel.
             *
             * @method createCarousel
             * @param elId {String} The id of the element to be created
             * @protected
             */
            _createCarousel: function (elId) {
                var cssClass = this.CLASSES;

                var el = createElement("DIV", {
                        className : cssClass.CAROUSEL,
                        id        : elId
                });

                if (!this._carouselEl) {
                    this._carouselEl = createElement(this.CONFIG.TAG_NAME,
                            { className: cssClass.CAROUSEL_EL });
                }

                return el;
            },

            /**
             * Create the Carousel clip container.
             *
             * @method createCarouselClip
             * @protected
             */
            _createCarouselClip: function () {
                var el = createElement("DIV", { className: this.CLASSES.CONTENT });
                this._setClipContainerSize(el);

                return el;
            },

            /**
             * Create the Carousel item.
             *
             * @method createCarouselItem
             * @param obj {Object} The attributes of the element to be created
             * @protected
             */
            _createCarouselItem: function (obj) {
                return createElement(this.CONFIG.ITEM_TAG_NAME, {
                        className : obj.className,
                        content   : obj.content,
                        id        : obj.id
                });
            },

            /**
             * Get the value for the selected item.
             *
             * @method _getSelectedItem
             * @param val {Number} The new value for "selected" item
             * @return {Number} The new value that would be set
             * @protected
             */
            _getSelectedItem: function (val) {
                var isCircular = this.get("isCircular"),
                    numItems   = this.get("numItems"),
                    sentinel   = numItems - 1;

                if (val < 0) {
                    if (isCircular) {
                        val = numItems + val;
                    } else {
                        val = this.get("selectedItem");
                    }
                } else if (val > sentinel) {
                    if (isCircular) {
                        val = val - numItems;
                    } else {
                        val = this.get("selectedItem");
                    }
                }

                return val;
            },

            /**
             * The "click" handler for the item.
             *
             * @method _itemClickHandler
             * @param {Event} ev The event object
             * @protected
             */
            _itemClickHandler: function (ev) {
                var container = this.get("element"),
                    el,
                    item,
                    target = YAHOO.util.Event.getTarget(ev);

                while (target && target != container &&
                       target.id != this._carouselEl) {
                    el = target.nodeName;
                    if (el.toUpperCase() == this.CONFIG.ITEM_TAG_NAME) {
                        break;
                    }
                    target = target.parentNode;
                }

                if ((item = this.getItemPositionById(target.id)) >= 0) {
                    YAHOO.log("Setting selection to " + item, WidgetName);
                    this.set("selectedItem", this._getSelectedItem(item));
                }
            },

            /**
             * The keyboard event handler for Carousel.
             *
             * @method _keyboardEventHandler
             * @param ev {Event} The event that is being handled.
             * @protected
             */
            _keyboardEventHandler: function (ev) {
                var key      = Event.getCharCode(ev),
                    prevent  = false,
                    position = 0,
                    selItem;

                if (this._isAnimationInProgress) {
                    return;         // do not mess while animation is in progress
                }

                switch (key) {
                case 0x25:          // left arrow
                case 0x26:          // up arrow
                    selItem = this.get("selectedItem");
                    if (selItem == this._firstItem) {
                        position = selItem - this.get("numVisible");
                        this.scrollTo(position);
                        this.set("selectedItem", this._getSelectedItem(selItem-1));
                    } else {
                        position = this.get("selectedItem") -
                                this.get("scrollIncrement");
                        this.set("selectedItem", this._getSelectedItem(position));
                    }
                    prevent = true;
                    break;
                case 0x27:          // right arrow
                case 0x28:          // down arrow
                    position = this.get("selectedItem")+this.get("scrollIncrement");
                    this.set("selectedItem", this._getSelectedItem(position));
                    prevent = true;
                    break;
                case 0x21:          // page-up
                    this.scrollPageBackward();
                    prevent = true;
                    break;
                case 0x22:          // page-down
                    this.scrollPageForward();
                    prevent = true;
                    break;
                }

                if (prevent) {
                    Event.preventDefault(ev);
                }
            },

            /**
             * The "click" handler for the pager navigation.
             *
             * @method _pagerClickHandler
             * @param {Event} ev The event object
             * @protected
             */
            _pagerClickHandler: function (ev) {
                var pos, target, val;

                target = Event.getTarget(ev);
                val = target.href || target.value;
                if (JS.isString(val) && val) {
                    pos = val.lastIndexOf("#");
                    if (pos != -1) {
                        val = this.getItemPositionById(val.substring(pos + 1));
                        this.scrollTo(val);
                        Event.preventDefault(ev);
                    }
                }
            },

            /**
             * Find the Carousel within a container. The Carousel is identified by
             * the first element that matches the carousel element tag or the
             * element that has the Carousel class.
             *
             * @method parseCarousel
             * @param parent {HTMLElement} The parent element to look under
             * @return {Boolean} True if Carousel is found, false otherwise
             * @protected
             */
            _parseCarousel: function (parent) {
                var child, cssClass, found, node;

                cssClass = this.CLASSES;
                found    = false;

                for (child = parent.firstChild; child; child = child.nextSibling) {
                    if (child.nodeType == 1) {
                        node = child.nodeName;
                        if (node.toUpperCase() == this.CONFIG.TAG_NAME) {
                            this._carouselEl = child;
                            Dom.addClass(this._carouselEl,this.CLASSES.CAROUSEL_EL);
                            YAHOO.log("Found Carousel - " + node +
                                    (child.id ? " (#" + child.id + ")" : ""),
                                    WidgetName);
                            found = true;
                        }
                    }
                }

                return found;
            },

            /**
             * Find the items within the Carousel and add them to the items table.
             * A Carousel item is identified by elements that matches the carousel
             * item element tag.
             *
             * @method parseCarouselItems
             * @protected
             */
            _parseCarouselItems: function () {
                var child,
                    elId,
                    node,
                    parent = this._carouselEl;

                for (child = parent.firstChild; child; child = child.nextSibling) {
                    if (child.nodeType == 1) {
                        node = child.nodeName;
                        if (node.toUpperCase() == this.CONFIG.ITEM_TAG_NAME) {
                            if (child.id) {
                                elId = child.id;
                            } else {
                                elId = Dom.generateId();
                                child.setAttribute("id", elId);
                            }
                            this.addItem(child);
                        }
                    }
                }
            },

            /**
             * Find the Carousel navigation within a container. The navigation
             * elements need to match the carousel navigation class names.
             *
             * @method parseCarouselNavigation
             * @param parent {HTMLElement} The parent element to look under
             * @return {Boolean} True if at least one is found, false otherwise
             * @protected
             */
            _parseCarouselNavigation: function (parent) {
                var cfg, cssClass = this.CLASSES, el, i, j, nav, rv = false;

                nav = Dom.getElementsByClassName(cssClass.PREV_PAGE, "*", parent);
                if (nav.length > 0) {
                    for (i in nav) {
                        if (nav.hasOwnProperty(i)) {
                            el = nav[i];
                            YAHOO.log("Found Carousel previous page navigation - " +
                                    el + (el.id ? " (#" + el.id + ")" : ""),
                                    WidgetName);
                            if (el.nodeName == "INPUT" ||
                                el.nodeName == "BUTTON") {
                                if (typeof this._navBtns.prev == "undefined") {
                                    this._navBtns.prev = [];
                                }
                                this._navBtns.prev.push(el);
                            } else {
                                j = el.getElementsByTagName("INPUT");
                                if (JS.isArray(j) && j.length > 0) {
                                    this._navBtns.prev.push(j[0]);
                                } else {
                                    j = el.getElementsByTagName("BUTTON");
                                    if (JS.isArray(j) && j.length > 0) {
                                        this._navBtns.prev.push(j[0]);
                                    }
                                }
                            }
                        }
                    }
                    cfg = { prev: nav };
                }

                nav = Dom.getElementsByClassName(cssClass.NEXT_PAGE, "*", parent);
                if (nav.length > 0) {
                    for (i in nav) {
                        if (nav.hasOwnProperty(i)) {
                            el = nav[i];
                            YAHOO.log("Found Carousel next page navigation - " +
                                    el + (el.id ? " (#" + el.id + ")" : ""),
                                    WidgetName);
                            if (el.nodeName == "INPUT" ||
                                el.nodeName == "BUTTON") {
                                if (typeof this._navBtns.next == "undefined") {
                                    this._navBtns.next = [];
                                }
                                this._navBtns.next.push(el);
                            } else {
                                j = el.getElementsByTagName("INPUT");
                                if (JS.isArray(j) && j.length > 0) {
                                    this._navBtns.next.push(j[0]);
                                } else {
                                    j = el.getElementsByTagName("BUTTON");
                                    if (JS.isArray(j) && j.length > 0) {
                                        this._navBtns.next.push(j[0]);
                                    }
                                }
                            }
                        }
                    }
                    if (cfg) {
                        cfg.next = nav;
                    } else {
                        cfg = { next: nav };
                    }
                }

                if (cfg) {
                    this.set("navigation", cfg);
                    rv = true;
                }

                return rv;
            },

            /**
             * Setup/Create the Carousel navigation element (if needed).
             *
             * @method _setupCarouselNavigation
             * @protected
             */
            _setupCarouselNavigation: function () {
                var btn, cfg, cssClass, nav, navContainer, nextButton, pageEl,
                    prevButton;

                cssClass = this.CLASSES;

                navContainer = Dom.getElementsByClassName(cssClass.NAVIGATION,
                        "DIV", this.get("element"));

                if (navContainer.length === 0) {
                    navContainer = createElement("DIV",
                            { className: cssClass.NAVIGATION });
                    this.insertBefore(navContainer,
                            Dom.getFirstChild(this.get("element")));
                } else {
                    navContainer = navContainer[0];
                }

                this._pages.el = createElement("UL");
                navContainer.appendChild(this._pages.el);

                nav = this.get("navigation");
                if (nav.prev && nav.prev.length > 0) {
                    navContainer.appendChild(nav.prev[0]);
                } else {
                    // TODO: separate method for creating a navigation button
                    prevButton = createElement("SPAN",
                            { className: cssClass.BUTTON + cssClass.FIRST_NAV });
                    // XXX: for IE 6.x
                    Dom.setStyle(prevButton, "visibility", "visible");
                    btn = Dom.generateId();
                    prevButton.innerHTML = "<input type=\"button\" " +
                            "id=\"" + btn + "\" " +
                            "value=\"" + this.STRINGS.PREVIOUS_BUTTON_TEXT + "\" " +
                            "name=\"" + this.STRINGS.PREVIOUS_BUTTON_TEXT + "\">";
                    navContainer.appendChild(prevButton);
                    btn = Dom.get(btn);
                    this._navBtns.prev = [btn];
                    cfg = { prev: [prevButton] };
                }

                if (nav.next && nav.next.length > 0) {
                    navContainer.appendChild(nav.next[0]);
                } else {
                    // TODO: separate method for creating a navigation button
                    nextButton = createElement("SPAN",
                            { className: cssClass.BUTTON });
                    // XXX: for IE 6.x
                    Dom.setStyle(nextButton, "visibility", "visible");
                    btn = Dom.generateId();
                    nextButton.innerHTML = "<input type=\"button\" " +
                            "id=\"" + btn + "\" " +
                            "value=\"" + this.STRINGS.NEXT_BUTTON_TEXT + "\" " +
                            "name=\"" + this.STRINGS.NEXT_BUTTON_TEXT + "\">";
                    navContainer.appendChild(nextButton);
                    btn = Dom.get(btn);
                    this._navBtns.next = [btn];
                    if (cfg) {
                        cfg.next = [nextButton];
                    } else {
                        cfg = { next: [nextButton] };
                    }
                }

                if (cfg) {
                    this.set("navigation", cfg);
                }

                return navContainer;
            },

            /**
             * Set the clip container size (based on the new numVisible value).
             *
             * @method _setClipContainerSize
             * @param clip {HTMLElement} The clip container element.
             * @param num {Number} optional The number of items per page.
             * @protected
             */
            _setClipContainerSize: function (clip, num) {
                var attr, currVal, isVertical, itemSize, reveal, size, which;

                isVertical = this.get("isVertical");
                reveal     = this.get("revealAmount");
                which      = isVertical ? "height" : "width";
                attr       = isVertical ? "top" : "left";

                clip       = clip || this._clipEl;
                if (!clip) {
                    return;
                }

                num        = num  || this.get("numVisible");
                itemSize   = getCarouselItemSize.call(this, which);
                size       = itemSize * num;

                this._recomputeSize = (size === 0); // bleh!
                if (this._recomputeSize) {
                    return;             // no use going further, bail out!
                }

                if (reveal > 0) {
                    reveal = itemSize * (reveal / 100) * 2;
                    size += reveal;
                    // TODO: set the Carousel's initial offset somwehere
                    currVal = parseFloat(Dom.getStyle(this._carouselEl, attr));
                    currVal = JS.isNumber(currVal) ? currVal : 0;
                    Dom.setStyle(this._carouselEl, attr, currVal+(reveal/2)+"px");
                }

                if (isVertical) {
                    size += getStyle(this._carouselEl, "marginTop")     +
                            getStyle(this._carouselEl, "marginBottom")  +
                            getStyle(this._carouselEl, "paddingTop")    +
                            getStyle(this._carouselEl, "paddingBottom") +
                            getStyle(this._carouselEl, "borderTop")     +
                            getStyle(this._carouselEl, "borderBottom");
                    // XXX: for vertical Carousel
                    Dom.setStyle(clip, which, (size - (num - 1)) + "px");
                } else {
                    size += getStyle(this._carouselEl, "marginLeft")    +
                            getStyle(this._carouselEl, "marginRight")   +
                            getStyle(this._carouselEl, "paddingLeft")   +
                            getStyle(this._carouselEl, "paddingRight")  +
                            getStyle(this._carouselEl, "borderLeft")    +
                            getStyle(this._carouselEl, "borderRight");
                    Dom.setStyle(clip, which, size + "px");
                }

                this._setContainerSize(clip); // adjust the container size too
            },

            /**
             * Set the container size.
             *
             * @method _setContainerSize
             * @param clip {HTMLElement} The clip container element.
             * @param attr {String} Either set the height or width.
             * @protected
             */
            _setContainerSize: function (clip, attr) {
                var isVertical, size;

                isVertical = this.get("isVertical");
                clip       = clip || this._clipEl;
                attr       = attr || (isVertical ? "height" : "width");
                size       = parseFloat(Dom.getStyle(clip, attr), 10);

                size = JS.isNumber(size) ? size : 0;

                size += getStyle(clip, "marginLeft")   +
                        getStyle(clip, "marginRight")  +
                        getStyle(clip, "paddingLeft")  +
                        getStyle(clip, "paddingRight") +
                        getStyle(clip, "borderLeft")   +
                        getStyle(clip, "borderRight");

                if (isVertical) {
                    size += getStyle(this._navEl, "height");
                }

                this.setStyle(attr, size + "px");
            },

            /**
             * Set the value for the Carousel's first visible item.
             *
             * @method _setFirstVisible
             * @param val {Number} The new value for firstVisible
             * @return {Number} The new value that would be set
             * @protected
             */
            _setFirstVisible: function (val) {
                if (val >= 0 && val < this.get("numItems")) {
                    this.scrollTo(val);
                } else {
                    val = this.get("firstVisible");
                }
                return val;
            },

            /**
             * Set the value for the Carousel's navigation.
             *
             * @method _setNavigation
             * @param cfg {Object} The navigation configuration
             * @return {Object} The new value that would be set
             * @protected
             */
            _setNavigation: function (cfg) {
                if (cfg.prev) {
                    Event.on(cfg.prev, "click", scrollPageBackward, this);
                }
                if (cfg.next) {
                    Event.on(cfg.next, "click", scrollPageForward, this);
                }
            },

            /**
             * Set the value for the number of visible items in the Carousel.
             *
             * @method _setNumVisible
             * @param val {Number} The new value for numVisible
             * @return {Number} The new value that would be set
             * @protected
             */
            _setNumVisible: function (val) {
                if (val > 1 && val < this.get("numItems")) {
                    this._setClipContainerSize(this._clipEl, val);
                } else {
                    val = this.get("numVisible");
                }
                return val;
            },

            /**
             * Set the number of items in the Carousel.
             * Warning: Setting this to a lower number than the current removes
             * items from the end.
             *
             * @method _setNumItems
             * @param val {Number} The new value for numItems
             * @return {Number} The new value that would be set
             * @protected
             */
            _setNumItems: function (val) {
                var num = this._itemsTable.numItems;

                if (JS.isArray(this._itemsTable.items)) {
                    if (this._itemsTable.items.length != num) { // out of sync
                        num = this._itemsTable.items.length;
                        this._itemsTable.numItems = num;
                    }
                }

                if (val < num) {
                    while (num > val) {
                        this.removeItem(num - 1);
                        num--;
                    }
                }

                return val;
            },

            /**
             * Set the orientation of the Carousel.
             *
             * @method _setOrientation
             * @param val {Boolean} The new value for isVertical
             * @return {Boolean} The new value that would be set
             * @protected
             */
            _setOrientation: function (val) {
                var cssClass = this.CLASSES;

                if (val) {
                    this.replaceClass(cssClass.HORIZONTAL, cssClass.VERTICAL);
                } else {
                    this.replaceClass(cssClass.VERTICAL, cssClass.HORIZONTAL);
                }
                this._itemsTable.size = 0; // invalidate our size computation cache
                return val;
            },

            /**
             * Set the value for the reveal amount percentage in the Carousel.
             *
             * @method _setRevealAmount
             * @param val {Number} The new value for revealAmount
             * @return {Number} The new value that would be set
             * @protected
             */
            _setRevealAmount: function (val) {
                if (val >= 0 && val <= 100) {
                    val = parseInt(val, 10);
                    val = JS.isNumber(val) ? val : 0;
                    this._setClipContainerSize();
                } else {
                    val = this.get("revealAmount");
                }
                return val;
            },

            /**
             * Set the value for the selected item.
             *
             * @method _setSelectedItem
             * @param val {Number} The new value for "selected" item
             * @protected
             */
            _setSelectedItem: function (val) {
                this._selectedItem = val;
            },

            /**
             * Synchronize and redraw the Pager UI if necessary.
             *
             * @method _syncPagerUI
             * @protected
             */
            _syncPagerUI: function (page) {
                var a,
                    cssClass = this.CLASSES,
                    i,
                    markup     = "",
                    numPages,
                    numVisible = this.get("numVisible");

                page     = page || 0;
                numPages = Math.ceil(this.get("numItems") / numVisible);

                this._pages.num = numPages;
                this._pages.cur = page;

                if (numPages > this.CONFIG.MAX_PAGER_BUTTONS) {
                    markup = "<form><select>";
                } else {
                    markup = "";
                }

                for (i = 0; i < numPages; i++) {
                    if (JS.isUndefined(this._itemsTable.items[i * numVisible])) {
                        break;
                    }
                    a = this._itemsTable.items[i * numVisible].id;
                    if (numPages > this.CONFIG.MAX_PAGER_BUTTONS) {
                        markup += "<option value=\"#" + a + "\" "            +
                                (i == page ? " selected" : "") + ">"         +
                                this.STRINGS.PAGER_PREFIX_TEXT + " " + (i+1) +
                                "</option>";
                    } else {
                        markup += "<li class=\""                                   +
                                (i === 0 ? cssClass.FIRST_PAGE : "")               +
                                (i == page ? " " + cssClass.SELECTED_NAV : "")     +
                                "\"><a href=\"#" + a + "\" tabindex=\"0\"><em>"    +
                                this.STRINGS.PAGER_PREFIX_TEXT + " " + (i+1)       +
                                "</em></a></li>";
                    }
                }

                if (numPages > this.CONFIG.MAX_PAGER_BUTTONS) {
                    markup += "</select></form>";
                }

                this._pages.el.innerHTML = markup;
                markup = null;
            },

            /**
             * Set the correct class for the navigation buttons.
             *
             * @method _updateNavButtons
             * @param el {Object} The target button
             * @param setFocus {Boolean} True to set focus ring, false otherwise.
             * @protected
             */
            _updateNavButtons: function (el, setFocus) {
                var children,
                    cssClass = this.CLASSES,
                    grandParent,
                    parent   = el.parentNode;

                if (!parent) {
                    return;
                }
                grandParent = parent.parentNode;

                if (el.nodeName.toUpperCase() == "INPUT" &&
                    Dom.hasClass(parent, cssClass.BUTTON)) {
                    if (setFocus) {
                        if (grandParent) {
                            children = Dom.getChildren(grandParent);
                            if (children) {
                                Dom.removeClass(children, cssClass.FOCUSSED_BUTTON);
                            }
                        }
                        Dom.addClass(parent, cssClass.FOCUSSED_BUTTON);
                    } else {
                        Dom.removeClass(parent, cssClass.FOCUSSED_BUTTON);
                    }
                }
            },

            /**
             * Set the correct tab index for the Carousel items.
             *
             * @method _updateTabIndex
             * @param el {Object} The element to be focussed
             * @protected
             */
            _updateTabIndex: function (el) {
                if (el) {
                    if (this._focusableItemEl) {
                        this._focusableItemEl.tabIndex = -1;
                    }
                    this._focusableItemEl = el;
                    el.tabIndex = 0;
                }
            },

            /**
             * Validate animation parameters.
             *
             * @method _validateAnimation
             * @param cfg {Object} The animation configuration
             * @return {Boolean} The status of the validation
             * @protected
             */
            _validateAnimation: function (cfg) {
                var rv = true;

                if (JS.isObject(cfg)) {
                    if (cfg.speed) {
                        rv = rv && JS.isNumber(cfg.speed);
                    }
                    if (cfg.effect) {
                        rv = rv && JS.isFunction(cfg.effect);
                    } else if (!JS.isUndefined(YAHOO.util.Easing)) {
                        cfg.effect = YAHOO.util.Easing.easeOut;
                    }
                } else {
                    rv = false;
                }

                return rv;
            },

            /**
             * Validate the firstVisible value.
             *
             * @method _validateFirstVisible
             * @param val {Number} The first visible value
             * @return {Boolean} The status of the validation
             * @protected
             */
            _validateFirstVisible: function (val) {
                var rv = false;

                if (JS.isNumber(val)) {
                    rv = (val >= 0 && val < this.get("numItems"));
                }

                return rv;
            },

            /**
             * Validate and navigation parameters.
             *
             * @method _validateNavigation
             * @param cfg {Object} The navigation configuration
             * @return {Boolean} The status of the validation
             * @protected
             */
            _validateNavigation : function (cfg) {
                var i;

                if (!JS.isObject(cfg)) {
                    return false;
                }

                if (cfg.prev) {
                    if (!JS.isArray(cfg.prev)) {
                        return false;
                    }
                    for (i in cfg.prev) {
                        if (cfg.prev.hasOwnProperty(i)) {
                            if (!JS.isString(cfg.prev[i].nodeName)) {
                                return false;
                            }
                        }
                    }
                }

                if (cfg.next) {
                    if (!JS.isArray(cfg.next)) {
                        return false;
                    }
                    for (i in cfg.next) {
                        if (cfg.next.hasOwnProperty(i)) {
                            if (!JS.isString(cfg.next[i].nodeName)) {
                                return false;
                            }
                        }
                    }
                }

                return true;
            },

            /**
             * Validate the numItems value.
             *
             * @method _validateNumItems
             * @param val {Number} The numItems value
             * @return {Boolean} The status of the validation
             * @protected
             */
            _validateNumItems: function (val) {
                var rv = false;

                if (JS.isNumber(val)) {
                    rv = val > 0;
                }

                return rv;
            },

            /**
             * Validate the numVisible value.
             *
             * @method _validateNumVisible
             * @param val {Number} The numVisible value
             * @return {Boolean} The status of the validation
             * @protected
             */
            _validateNumVisible: function (val) {
                var rv = false;

                if (JS.isNumber(val)) {
                    rv = val > 0 && val < this.get("numItems");
                }

                return rv;
            },

            /**
             * Validate the revealAmount value.
             *
             * @method _validateRevealAmount
             * @param val {Number} The revealAmount value
             * @return {Boolean} The status of the validation
             * @protected
             */
            _validateRevealAmount: function (val) {
                var rv = false;

                if (JS.isNumber(val)) {
                    rv = val >= 0 && val < 100;
                }

                return rv;
            },

            /**
             * Validate the scrollIncrement value.
             *
             * @method _validateScrollIncrement
             * @param val {Number} The scrollIncrement value
             * @return {Boolean} The status of the validation
             * @protected
             */
            _validateScrollIncrement: function (val) {
                var rv = false;

                if (JS.isNumber(val)) {
                    rv = (val > 0 && val < this.get("numItems"));
                }

                return rv;
            }

        });

    })();

Copyright © 2008 Yahoo! Inc. All rights reserved.