PageRenderTime 77ms CodeModel.GetById 13ms app.highlight 53ms RepoModel.GetById 1ms app.codeStats 0ms

/hippo/src/main/webapp/yui/carousel/carousel.js

http://hdbc.googlecode.com/
JavaScript | 2065 lines | 846 code | 269 blank | 950 comment | 176 complexity | 2e960d2d73124b5fd8b077b5c0d2c0ab MD5 | raw file
   1/*
   2Copyright (c) 2009, Yahoo! Inc. All rights reserved.
   3Code licensed under the BSD License:
   4http://developer.yahoo.net/yui/license.txt
   5version: 2.7.0
   6*/
   7/**
   8 * The Carousel module provides a widget for browsing among a set of like
   9 * objects represented pictorially.
  10 *
  11 * @module carousel
  12 * @requires yahoo, dom, event, element
  13 * @optional animation
  14 * @namespace YAHOO.widget
  15 * @title Carousel Widget
  16 * @beta
  17 */
  18(function () {
  19
  20    var WidgetName;             // forward declaration
  21
  22    /**
  23     * The Carousel widget.
  24     *
  25     * @class Carousel
  26     * @extends YAHOO.util.Element
  27     * @constructor
  28     * @param el {HTMLElement | String} The HTML element that represents the
  29     * the container that houses the Carousel.
  30     * @param cfg {Object} (optional) The configuration values
  31     */
  32    YAHOO.widget.Carousel = function (el, cfg) {
  33
  34        YAHOO.widget.Carousel.superclass.constructor.call(this, el, cfg);
  35    };
  36
  37    /*
  38     * Private variables of the Carousel component
  39     */
  40
  41    /* Some abbreviations to avoid lengthy typing and lookups. */
  42    var Carousel    = YAHOO.widget.Carousel,
  43        Dom         = YAHOO.util.Dom,
  44        Event       = YAHOO.util.Event,
  45        JS          = YAHOO.lang;
  46
  47    /**
  48     * The widget name.
  49     * @private
  50     * @static
  51     */
  52    WidgetName = "Carousel";
  53
  54    /**
  55     * The internal table of Carousel instances.
  56     * @private
  57     * @static
  58     */
  59    var instances = {},
  60
  61    /*
  62     * Custom events of the Carousel component
  63     */
  64
  65    /**
  66     * @event afterScroll
  67     * @description Fires when the Carousel has scrolled to the previous or
  68     * next page.  Passes back the index of the first and last visible items in
  69     * the Carousel.  See
  70     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
  71     * for more information on listening for this event.
  72     * @type YAHOO.util.CustomEvent
  73     */
  74    afterScrollEvent = "afterScroll",
  75
  76    /**
  77     * @event allItemsRemovedEvent
  78     * @description Fires when all items have been removed from the Carousel.
  79     * See
  80     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
  81     * for more information on listening for this event.
  82     * @type YAHOO.util.CustomEvent
  83     */
  84    allItemsRemovedEvent = "allItemsRemoved",
  85
  86    /**
  87     * @event beforeHide
  88     * @description Fires before the Carousel is hidden.  See
  89     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
  90     * for more information on listening for this event.
  91     * @type YAHOO.util.CustomEvent
  92     */
  93    beforeHideEvent = "beforeHide",
  94
  95    /**
  96     * @event beforePageChange
  97     * @description Fires when the Carousel is about to scroll to the previous
  98     * or next page.  Passes back the page number of the current page.  Note
  99     * that the first page number is zero.  See
 100     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 101     * for more information on listening for this event.
 102     * @type YAHOO.util.CustomEvent
 103     */
 104    beforePageChangeEvent = "beforePageChange",
 105
 106    /**
 107     * @event beforeScroll
 108     * @description Fires when the Carousel is about to scroll to the previous
 109     * or next page.  Passes back the index of the first and last visible items
 110     * in the Carousel and the direction (backward/forward) of the scroll.  See
 111     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 112     * for more information on listening for this event.
 113     * @type YAHOO.util.CustomEvent
 114     */
 115    beforeScrollEvent = "beforeScroll",
 116
 117    /**
 118     * @event beforeShow
 119     * @description Fires when the Carousel is about to be shown.  See
 120     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 121     * for more information on listening for this event.
 122     * @type YAHOO.util.CustomEvent
 123     */
 124    beforeShowEvent = "beforeShow",
 125
 126    /**
 127     * @event blur
 128     * @description Fires when the Carousel loses focus.  See
 129     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 130     * for more information on listening for this event.
 131     * @type YAHOO.util.CustomEvent
 132     */
 133    blurEvent = "blur",
 134
 135    /**
 136     * @event focus
 137     * @description Fires when the Carousel gains focus.  See
 138     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 139     * for more information on listening for this event.
 140     * @type YAHOO.util.CustomEvent
 141     */
 142    focusEvent = "focus",
 143
 144    /**
 145     * @event hide
 146     * @description Fires when the Carousel is hidden.  See
 147     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 148     * for more information on listening for this event.
 149     * @type YAHOO.util.CustomEvent
 150     */
 151    hideEvent = "hide",
 152
 153    /**
 154     * @event itemAdded
 155     * @description Fires when an item has been added to the Carousel.  Passes
 156     * back the content of the item that would be added, the index at which the
 157     * item would be added, and the event itself.  See
 158     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 159     * for more information on listening for this event.
 160     * @type YAHOO.util.CustomEvent
 161     */
 162    itemAddedEvent = "itemAdded",
 163
 164    /**
 165     * @event itemRemoved
 166     * @description Fires when an item has been removed from the Carousel.
 167     * Passes back the content of the item that would be removed, the index
 168     * from which the item would be removed, and the event itself.  See
 169     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 170     * for more information on listening for this event.
 171     * @type YAHOO.util.CustomEvent
 172     */
 173    itemRemovedEvent = "itemRemoved",
 174
 175    /**
 176     * @event itemSelected
 177     * @description Fires when an item has been selected in the Carousel.
 178     * Passes back the index of the selected item in the Carousel.  Note, that
 179     * the index begins from zero.  See
 180     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 181     * for more information on listening for this event.
 182     * @type YAHOO.util.CustomEvent
 183     */
 184    itemSelectedEvent = "itemSelected",
 185
 186    /**
 187     * @event loadItems
 188     * @description Fires when the Carousel needs more items to be loaded for
 189     * displaying them.  Passes back the first and last visible items in the
 190     * Carousel, and the number of items needed to be loaded.  See
 191     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 192     * for more information on listening for this event.
 193     * @type YAHOO.util.CustomEvent
 194     */
 195    loadItemsEvent = "loadItems",
 196
 197    /**
 198     * @event navigationStateChange
 199     * @description Fires when the state of either one of the navigation
 200     * buttons are changed from enabled to disabled or vice versa.  Passes back
 201     * the state (true/false) of the previous and next buttons.  The value true
 202     * signifies the button is enabled, false signifies disabled.  See
 203     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 204     * for more information on listening for this event.
 205     * @type YAHOO.util.CustomEvent
 206     */
 207    navigationStateChangeEvent = "navigationStateChange",
 208
 209    /**
 210     * @event pageChange
 211     * @description Fires after the Carousel has scrolled to the previous or
 212     * next page.  Passes back the page number of the current page.  Note
 213     * that the first page number is zero.  See
 214     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 215     * for more information on listening for this event.
 216     * @type YAHOO.util.CustomEvent
 217     */
 218    pageChangeEvent = "pageChange",
 219
 220    /*
 221     * Internal event.
 222     * @event render
 223     * @description Fires when the Carousel is rendered.  See
 224     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 225     * for more information on listening for this event.
 226     * @type YAHOO.util.CustomEvent
 227     */
 228    renderEvent = "render",
 229
 230    /**
 231     * @event show
 232     * @description Fires when the Carousel is shown.  See
 233     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 234     * for more information on listening for this event.
 235     * @type YAHOO.util.CustomEvent
 236     */
 237    showEvent = "show",
 238
 239    /**
 240     * @event startAutoPlay
 241     * @description Fires when the auto play has started in the Carousel.  See
 242     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 243     * for more information on listening for this event.
 244     * @type YAHOO.util.CustomEvent
 245     */
 246    startAutoPlayEvent = "startAutoPlay",
 247
 248    /**
 249     * @event stopAutoPlay
 250     * @description Fires when the auto play has been stopped in the Carousel.
 251     * See
 252     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 253     * for more information on listening for this event.
 254     * @type YAHOO.util.CustomEvent
 255     */
 256    stopAutoPlayEvent = "stopAutoPlay",
 257
 258    /*
 259     * Internal event.
 260     * @event uiUpdateEvent
 261     * @description Fires when the UI has been updated.
 262     * See
 263     * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
 264     * for more information on listening for this event.
 265     * @type YAHOO.util.CustomEvent
 266     */
 267    uiUpdateEvent = "uiUpdate";
 268
 269    /*
 270     * Private helper functions used by the Carousel component
 271     */
 272
 273    /**
 274     * Create an element, set its class name and optionally install the element
 275     * to its parent.
 276     * @method createElement
 277     * @param el {String} The element to be created
 278     * @param attrs {Object} Configuration of parent, class and id attributes.
 279     * If the content is specified, it is inserted after creation of the
 280     * element. The content can also be an HTML element in which case it would
 281     * be appended as a child node of the created element.
 282     * @private
 283     */
 284    function createElement(el, attrs) {
 285        var newEl = document.createElement(el);
 286
 287        attrs = attrs || {};
 288        if (attrs.className) {
 289            Dom.addClass(newEl, attrs.className);
 290        }
 291
 292        if (attrs.parent) {
 293            attrs.parent.appendChild(newEl);
 294        }
 295
 296        if (attrs.id) {
 297            newEl.setAttribute("id", attrs.id);
 298        }
 299
 300        if (attrs.content) {
 301            if (attrs.content.nodeName) {
 302                newEl.appendChild(attrs.content);
 303            } else {
 304                newEl.innerHTML = attrs.content;
 305            }
 306        }
 307
 308        return newEl;
 309    }
 310
 311    /**
 312     * Get the computed style of an element.
 313     *
 314     * @method getStyle
 315     * @param el {HTMLElement} The element for which the style needs to be
 316     * returned.
 317     * @param style {String} The style attribute
 318     * @param type {String} "int", "float", etc. (defaults to int)
 319     * @private
 320     */
 321    function getStyle(el, style, type) {
 322        var value;
 323
 324        if (!el) {
 325            return 0;
 326        }
 327
 328        function getStyleIntVal(el, style) {
 329            var val;
 330
 331            /*
 332             * XXX: Safari calculates incorrect marginRight for an element
 333             * which has its parent element style set to overflow: hidden
 334             * https://bugs.webkit.org/show_bug.cgi?id=13343
 335             * Let us assume marginLeft == marginRight
 336             */
 337            if (style == "marginRight" && YAHOO.env.ua.webkit) {
 338                val = parseInt(Dom.getStyle(el, "marginLeft"), 10);
 339            } else {
 340                val = parseInt(Dom.getStyle(el, style), 10);
 341            }
 342
 343            return JS.isNumber(val) ? val : 0;
 344        }
 345
 346        function getStyleFloatVal(el, style) {
 347            var val;
 348
 349            /*
 350             * XXX: Safari calculates incorrect marginRight for an element
 351             * which has its parent element style set to overflow: hidden
 352             * https://bugs.webkit.org/show_bug.cgi?id=13343
 353             * Let us assume marginLeft == marginRight
 354             */
 355            if (style == "marginRight" && YAHOO.env.ua.webkit) {
 356                val = parseFloat(Dom.getStyle(el, "marginLeft"));
 357            } else {
 358                val = parseFloat(Dom.getStyle(el, style));
 359            }
 360
 361            return JS.isNumber(val) ? val : 0;
 362        }
 363
 364        if (typeof type == "undefined") {
 365            type = "int";
 366        }
 367
 368        switch (style) {
 369        case "height":
 370            value = el.offsetHeight;
 371            if (value > 0) {
 372                value += getStyleIntVal(el, "marginTop")        +
 373                        getStyleIntVal(el, "marginBottom");
 374            } else {
 375                value = getStyleFloatVal(el, "height")          +
 376                        getStyleIntVal(el, "marginTop")         +
 377                        getStyleIntVal(el, "marginBottom")      +
 378                        getStyleIntVal(el, "borderTopWidth")    +
 379                        getStyleIntVal(el, "borderBottomWidth") +
 380                        getStyleIntVal(el, "paddingTop")        +
 381                        getStyleIntVal(el, "paddingBottom");
 382            }
 383            break;
 384        case "width":
 385            value = el.offsetWidth;
 386            if (value > 0) {
 387                value += getStyleIntVal(el, "marginLeft")       +
 388                        getStyleIntVal(el, "marginRight");
 389            } else {
 390                value = getStyleFloatVal(el, "width")           +
 391                        getStyleIntVal(el, "marginLeft")        +
 392                        getStyleIntVal(el, "marginRight")       +
 393                        getStyleIntVal(el, "borderLeftWidth")   +
 394                        getStyleIntVal(el, "borderRightWidth")  +
 395                        getStyleIntVal(el, "paddingLeft")       +
 396                        getStyleIntVal(el, "paddingRight");
 397            }
 398            break;
 399        default:
 400            if (type == "int") {
 401                value = getStyleIntVal(el, style);
 402            } else if (type == "float") {
 403                value = getStyleFloatVal(el, style);
 404            } else {
 405                value = Dom.getStyle(el, style);
 406            }
 407            break;
 408        }
 409
 410        return value;
 411    }
 412
 413    /**
 414     * Compute and return the height or width of a single Carousel item
 415     * depending upon the orientation.
 416     *
 417     * @method getCarouselItemSize
 418     * @param which {String} "height" or "width" to be returned.  If this is
 419     * passed explicitly, the calculated size is not cached.
 420     * @private
 421     */
 422    function getCarouselItemSize(which) {
 423        var carousel = this,
 424            child,
 425            size     = 0,
 426            vertical = false;
 427
 428        if (carousel._itemsTable.numItems === 0) {
 429            return 0;
 430        }
 431
 432        if (typeof which == "undefined") {
 433            if (carousel._itemsTable.size > 0) {
 434                return carousel._itemsTable.size;
 435            }
 436        }
 437
 438        if (JS.isUndefined(carousel._itemsTable.items[0])) {
 439            return 0;
 440        }
 441
 442        child = Dom.get(carousel._itemsTable.items[0].id);
 443
 444        if (typeof which == "undefined") {
 445            vertical = carousel.get("isVertical");
 446        } else {
 447            vertical = which == "height";
 448        }
 449
 450        if (vertical) {
 451            size = getStyle(child, "height");
 452        } else {
 453            size = getStyle(child, "width");
 454        }
 455
 456        if (typeof which == "undefined") {
 457            carousel._itemsTable.size = size; // save the size for later
 458        }
 459
 460        return size;
 461    }
 462
 463    /**
 464     * Return the index of the first item in the view port for displaying item
 465     * in "pos".
 466     *
 467     * @method getFirstVisibleForPosition
 468     * @param pos {Number} The position of the item to be displayed
 469     * @private
 470     */
 471    function getFirstVisibleForPosition(pos) {
 472        var num = this.get("numVisible");
 473
 474        return Math.floor(pos / num) * num;
 475    }
 476
 477    /**
 478     * Return the scrolling offset size given the number of elements to
 479     * scroll.
 480     *
 481     * @method getScrollOffset
 482     * @param delta {Number} The delta number of elements to scroll by.
 483     * @private
 484     */
 485    function getScrollOffset(delta) {
 486        var itemSize = 0,
 487            size     = 0;
 488
 489        itemSize = getCarouselItemSize.call(this);
 490        size     = itemSize * delta;
 491
 492        // XXX: really, when the orientation is vertical, the scrolling
 493        // is not exactly the number of elements into element size.
 494        if (this.get("isVertical")) {
 495            size -= delta;
 496        }
 497
 498        return size;
 499    }
 500
 501    /**
 502     * Scroll the Carousel by a page backward.
 503     *
 504     * @method scrollPageBackward
 505     * @param {Event} ev The event object
 506     * @param {Object} obj The context object
 507     * @private
 508     */
 509    function scrollPageBackward(ev, obj) {
 510        obj.scrollPageBackward();
 511        Event.preventDefault(ev);
 512    }
 513
 514    /**
 515     * Scroll the Carousel by a page forward.
 516     *
 517     * @method scrollPageForward
 518     * @param {Event} ev The event object
 519     * @param {Object} obj The context object
 520     * @private
 521     */
 522    function scrollPageForward(ev, obj) {
 523        obj.scrollPageForward();
 524        Event.preventDefault(ev);
 525    }
 526
 527    /**
 528     * Set the selected item.
 529     *
 530     * @method setItemSelection
 531     * @param {Number} newpos The index of the new position
 532     * @param {Number} oldpos The index of the previous position
 533     * @private
 534     */
 535     function setItemSelection(newpos, oldpos) {
 536        var carousel = this,
 537            cssClass   = carousel.CLASSES,
 538            el,
 539            firstItem  = carousel._firstItem,
 540            isCircular = carousel.get("isCircular"),
 541            numItems   = carousel.get("numItems"),
 542            numVisible = carousel.get("numVisible"),
 543            position   = oldpos,
 544            sentinel   = firstItem + numVisible - 1;
 545
 546        if (position >= 0 && position < numItems) {
 547            if (!JS.isUndefined(carousel._itemsTable.items[position])) {
 548                el = Dom.get(carousel._itemsTable.items[position].id);
 549                if (el) {
 550                    Dom.removeClass(el, cssClass.SELECTED_ITEM);
 551                }
 552            }
 553        }
 554
 555        if (JS.isNumber(newpos)) {
 556            newpos = parseInt(newpos, 10);
 557            newpos = JS.isNumber(newpos) ? newpos : 0;
 558        } else {
 559            newpos = firstItem;
 560        }
 561
 562        if (JS.isUndefined(carousel._itemsTable.items[newpos])) {
 563            newpos = getFirstVisibleForPosition.call(carousel, newpos);
 564            carousel.scrollTo(newpos); // still loading the item
 565        }
 566
 567        if (!JS.isUndefined(carousel._itemsTable.items[newpos])) {
 568            el = Dom.get(carousel._itemsTable.items[newpos].id);
 569            if (el) {
 570                Dom.addClass(el, cssClass.SELECTED_ITEM);
 571            }
 572        }
 573
 574        if (newpos < firstItem || newpos > sentinel) { // out of focus
 575            newpos = getFirstVisibleForPosition.call(carousel, newpos);
 576            carousel.scrollTo(newpos);
 577        }
 578    }
 579
 580    /**
 581     * Fire custom events for enabling/disabling navigation elements.
 582     *
 583     * @method syncNavigation
 584     * @private
 585     */
 586    function syncNavigation() {
 587        var attach   = false,
 588            carousel = this,
 589            cssClass = carousel.CLASSES,
 590            i,
 591            navigation,
 592            sentinel;
 593
 594        // Don't do anything if the Carousel is not rendered
 595        if (!carousel._hasRendered) {
 596            return;
 597        }
 598
 599        navigation = carousel.get("navigation");
 600        sentinel   = carousel._firstItem + carousel.get("numVisible");
 601
 602        if (navigation.prev) {
 603            if (carousel.get("numItems") === 0 || carousel._firstItem === 0) {
 604                if (carousel.get("numItems") === 0 ||
 605                   !carousel.get("isCircular")) {
 606                    Event.removeListener(navigation.prev, "click",
 607                            scrollPageBackward);
 608                    Dom.addClass(navigation.prev, cssClass.FIRST_NAV_DISABLED);
 609                    for (i = 0; i < carousel._navBtns.prev.length; i++) {
 610                        carousel._navBtns.prev[i].setAttribute("disabled",
 611                                "true");
 612                    }
 613                    carousel._prevEnabled = false;
 614                } else {
 615                    attach = !carousel._prevEnabled;
 616                }
 617            } else {
 618                attach = !carousel._prevEnabled;
 619            }
 620
 621            if (attach) {
 622                Event.on(navigation.prev, "click", scrollPageBackward,
 623                         carousel);
 624                Dom.removeClass(navigation.prev, cssClass.FIRST_NAV_DISABLED);
 625                for (i = 0; i < carousel._navBtns.prev.length; i++) {
 626                    carousel._navBtns.prev[i].removeAttribute("disabled");
 627                }
 628                carousel._prevEnabled = true;
 629            }
 630        }
 631
 632        attach = false;
 633        if (navigation.next) {
 634            if (sentinel >= carousel.get("numItems")) {
 635                if (!carousel.get("isCircular")) {
 636                    Event.removeListener(navigation.next, "click",
 637                            scrollPageForward);
 638                    Dom.addClass(navigation.next, cssClass.DISABLED);
 639                    for (i = 0; i < carousel._navBtns.next.length; i++) {
 640                        carousel._navBtns.next[i].setAttribute("disabled",
 641                                "true");
 642                    }
 643                    carousel._nextEnabled = false;
 644                } else {
 645                    attach = !carousel._nextEnabled;
 646                }
 647            } else {
 648                attach = !carousel._nextEnabled;
 649            }
 650
 651            if (attach) {
 652                Event.on(navigation.next, "click", scrollPageForward,
 653                         carousel);
 654                Dom.removeClass(navigation.next, cssClass.DISABLED);
 655                for (i = 0; i < carousel._navBtns.next.length; i++) {
 656                    carousel._navBtns.next[i].removeAttribute("disabled");
 657                }
 658                carousel._nextEnabled = true;
 659            }
 660        }
 661
 662        carousel.fireEvent(navigationStateChangeEvent,
 663                { next: carousel._nextEnabled, prev: carousel._prevEnabled });
 664    }
 665
 666    /**
 667     * Synchronize and redraw the Pager UI if necessary.
 668     *
 669     * @method syncPagerUi
 670     * @private
 671     */
 672    function syncPagerUi(page) {
 673        var carousel = this, numPages, numVisible;
 674
 675        // Don't do anything if the Carousel is not rendered
 676        if (!carousel._hasRendered) {
 677            return;
 678        }
 679
 680        numVisible = carousel.get("numVisible");
 681
 682        if (!JS.isNumber(page)) {
 683            page = Math.ceil(carousel.get("selectedItem") / numVisible);
 684        }
 685        numPages = Math.ceil(carousel.get("numItems") / numVisible);
 686
 687        carousel._pages.num = numPages;
 688        carousel._pages.cur = page;
 689
 690        if (numPages > carousel.CONFIG.MAX_PAGER_BUTTONS) {
 691            carousel._updatePagerMenu();
 692        } else {
 693            carousel._updatePagerButtons();
 694        }
 695    }
 696
 697    /**
 698     * Handle UI update.
 699     * Call the appropriate methods on events fired when an item is added, or
 700     * removed for synchronizing the DOM.
 701     *
 702     * @method syncUi
 703     * @param {Object} o The item that needs to be added or removed
 704     * @private
 705     */
 706    function syncUi(o) {
 707        var carousel = this;
 708
 709        if (!JS.isObject(o)) {
 710            return;
 711        }
 712
 713        switch (o.ev) {
 714        case itemAddedEvent:
 715            carousel._syncUiForItemAdd(o);
 716            break;
 717        case itemRemovedEvent:
 718            carousel._syncUiForItemRemove(o);
 719            break;
 720        case loadItemsEvent:
 721            carousel._syncUiForLazyLoading(o);
 722            break;
 723        }
 724
 725        carousel.fireEvent(uiUpdateEvent);
 726    }
 727
 728    /**
 729     * Update the state variables after scrolling the Carousel view port.
 730     *
 731     * @method updateStateAfterScroll
 732     * @param {Integer} item The index to which the Carousel has scrolled to.
 733     * @param {Integer} sentinel The last element in the view port.
 734     * @private
 735     */
 736    function updateStateAfterScroll(item, sentinel) {
 737        var carousel   = this,
 738            page       = carousel.get("currentPage"),
 739            newPage,
 740            numPerPage = carousel.get("numVisible");
 741
 742        newPage = parseInt(carousel._firstItem / numPerPage, 10);
 743        if (newPage != page) {
 744            carousel.setAttributeConfig("currentPage", { value: newPage });
 745            carousel.fireEvent(pageChangeEvent, newPage);
 746        }
 747
 748        if (carousel.get("selectOnScroll")) {
 749            if (carousel.get("selectedItem") != carousel._selectedItem) {
 750                carousel.set("selectedItem", carousel._selectedItem);
 751            }
 752        }
 753
 754        clearTimeout(carousel._autoPlayTimer);
 755        delete carousel._autoPlayTimer;
 756        if (carousel.isAutoPlayOn()) {
 757            carousel.startAutoPlay();
 758        }
 759
 760        carousel.fireEvent(afterScrollEvent,
 761                           { first: carousel._firstItem,
 762                             last: sentinel },
 763                           carousel);
 764    }
 765
 766    /*
 767     * Static members and methods of the Carousel component
 768     */
 769
 770    /**
 771     * Return the appropriate Carousel object based on the id associated with
 772     * the Carousel element or false if none match.
 773     * @method getById
 774     * @public
 775     * @static
 776     */
 777    Carousel.getById = function (id) {
 778        return instances[id] ? instances[id].object : false;
 779    };
 780
 781    YAHOO.extend(Carousel, YAHOO.util.Element, {
 782
 783        /*
 784         * Internal variables used within the Carousel component
 785         */
 786
 787        /**
 788         * The Animation object.
 789         *
 790         * @property _animObj
 791         * @private
 792         */
 793        _animObj: null,
 794
 795        /**
 796         * The Carousel element.
 797         *
 798         * @property _carouselEl
 799         * @private
 800         */
 801        _carouselEl: null,
 802
 803        /**
 804         * The Carousel clipping container element.
 805         *
 806         * @property _clipEl
 807         * @private
 808         */
 809        _clipEl: null,
 810
 811        /**
 812         * The current first index of the Carousel.
 813         *
 814         * @property _firstItem
 815         * @private
 816         */
 817        _firstItem: 0,
 818
 819        /**
 820         * Does the Carousel element have focus?
 821         *
 822         * @property _hasFocus
 823         * @private
 824         */
 825        _hasFocus: false,
 826
 827        /**
 828         * Is the Carousel rendered already?
 829         *
 830         * @property _hasRendered
 831         * @private
 832         */
 833        _hasRendered: false,
 834
 835        /**
 836         * Is the animation still in progress?
 837         *
 838         * @property _isAnimationInProgress
 839         * @private
 840         */
 841        _isAnimationInProgress: false,
 842
 843        /**
 844         * Is the auto-scrolling of Carousel in progress?
 845         *
 846         * @property _isAutoPlayInProgress
 847         * @private
 848         */
 849        _isAutoPlayInProgress: false,
 850
 851        /**
 852         * The table of items in the Carousel.
 853         * The numItems is the number of items in the Carousel, items being the
 854         * array of items in the Carousel.  The size is the size of a single
 855         * item in the Carousel.  It is cached here for efficiency (to avoid
 856         * computing the size multiple times).
 857         *
 858         * @property _itemsTable
 859         * @private
 860         */
 861        _itemsTable: null,
 862
 863        /**
 864         * The Carousel navigation buttons.
 865         *
 866         * @property _navBtns
 867         * @private
 868         */
 869        _navBtns: null,
 870
 871        /**
 872         * The Carousel navigation.
 873         *
 874         * @property _navEl
 875         * @private
 876         */
 877        _navEl: null,
 878
 879        /**
 880         * Status of the next navigation item.
 881         *
 882         * @property _nextEnabled
 883         * @private
 884         */
 885        _nextEnabled: true,
 886
 887        /**
 888         * The Carousel pages structure.
 889         * This is an object of the total number of pages and the current page.
 890         *
 891         * @property _pages
 892         * @private
 893         */
 894        _pages: null,
 895
 896        /**
 897         * Status of the previous navigation item.
 898         *
 899         * @property _prevEnabled
 900         * @private
 901         */
 902        _prevEnabled: true,
 903
 904        /**
 905         * Whether the Carousel size needs to be recomputed or not?
 906         *
 907         * @property _recomputeSize
 908         * @private
 909         */
 910        _recomputeSize: true,
 911
 912        /*
 913         * CSS classes used by the Carousel component
 914         */
 915
 916        CLASSES: {
 917
 918            /**
 919             * The class name of the Carousel navigation buttons.
 920             *
 921             * @property BUTTON
 922             * @default "yui-carousel-button"
 923             */
 924            BUTTON: "yui-carousel-button",
 925
 926            /**
 927             * The class name of the Carousel element.
 928             *
 929             * @property CAROUSEL
 930             * @default "yui-carousel"
 931             */
 932            CAROUSEL: "yui-carousel",
 933
 934            /**
 935             * The class name of the container of the items in the Carousel.
 936             *
 937             * @property CAROUSEL_EL
 938             * @default "yui-carousel-element"
 939             */
 940            CAROUSEL_EL: "yui-carousel-element",
 941
 942            /**
 943             * The class name of the Carousel's container element.
 944             *
 945             * @property CONTAINER
 946             * @default "yui-carousel-container"
 947             */
 948            CONTAINER: "yui-carousel-container",
 949
 950            /**
 951             * The class name of the Carousel's container element.
 952             *
 953             * @property CONTENT
 954             * @default "yui-carousel-content"
 955             */
 956            CONTENT: "yui-carousel-content",
 957
 958            /**
 959             * The class name of a disabled navigation button.
 960             *
 961             * @property DISABLED
 962             * @default "yui-carousel-button-disabled"
 963             */
 964            DISABLED: "yui-carousel-button-disabled",
 965
 966            /**
 967             * The class name of the first Carousel navigation button.
 968             *
 969             * @property FIRST_NAV
 970             * @default " yui-carousel-first-button"
 971             */
 972            FIRST_NAV: " yui-carousel-first-button",
 973
 974            /**
 975             * The class name of a first disabled navigation button.
 976             *
 977             * @property FIRST_NAV_DISABLED
 978             * @default "yui-carousel-first-button-disabled"
 979             */
 980            FIRST_NAV_DISABLED: "yui-carousel-first-button-disabled",
 981
 982            /**
 983             * The class name of a first page element.
 984             *
 985             * @property FIRST_PAGE
 986             * @default "yui-carousel-nav-first-page"
 987             */
 988            FIRST_PAGE: "yui-carousel-nav-first-page",
 989
 990            /**
 991             * The class name of the Carousel navigation button that has focus.
 992             *
 993             * @property FOCUSSED_BUTTON
 994             * @default "yui-carousel-button-focus"
 995             */
 996            FOCUSSED_BUTTON: "yui-carousel-button-focus",
 997
 998            /**
 999             * The class name of a horizontally oriented Carousel.
1000             *
1001             * @property HORIZONTAL
1002             * @default "yui-carousel-horizontal"
1003             */
1004            HORIZONTAL: "yui-carousel-horizontal",
1005
1006            /**
1007             * The element to be used as the progress indicator when the item
1008             * is still being loaded.
1009             *
1010             * @property ITEM_LOADING
1011             * @default The progress indicator (spinner) image CSS class
1012             */
1013            ITEM_LOADING: "yui-carousel-item-loading",
1014
1015            /**
1016             * The class name that will be set if the Carousel adjusts itself
1017             * for a minimum width.
1018             *
1019             * @property MIN_WIDTH
1020             * @default "yui-carousel-min-width"
1021             */
1022            MIN_WIDTH: "yui-carousel-min-width",
1023
1024            /**
1025             * The navigation element container class name.
1026             *
1027             * @property NAVIGATION
1028             * @default "yui-carousel-nav"
1029             */
1030            NAVIGATION: "yui-carousel-nav",
1031
1032            /**
1033             * The class name of the next Carousel navigation button.
1034             *
1035             * @property NEXT_NAV
1036             * @default " yui-carousel-next-button"
1037             */
1038            NEXT_NAV: " yui-carousel-next-button",
1039
1040            /**
1041             * The class name of the next navigation link. This variable is
1042             * not only used for styling, but also for identifying the link
1043             * within the Carousel container.
1044             *
1045             * @property NEXT_PAGE
1046             * @default "yui-carousel-next"
1047             */
1048            NEXT_PAGE: "yui-carousel-next",
1049
1050            /**
1051             * The class name for the navigation container for prev/next.
1052             *
1053             * @property NAV_CONTAINER
1054             * @default "yui-carousel-buttons"
1055             */
1056            NAV_CONTAINER: "yui-carousel-buttons",
1057
1058            /**
1059             * The class name of the focussed page navigation.  This class is
1060             * specifically used for the ugly focus handling in Opera.
1061             *
1062             * @property PAGE_FOCUS
1063             * @default "yui-carousel-nav-page-focus"
1064             */
1065            PAGE_FOCUS: "yui-carousel-nav-page-focus",
1066
1067            /**
1068             * The class name of the previous navigation link. This variable
1069             * is not only used for styling, but also for identifying the link
1070             * within the Carousel container.
1071             *
1072             * @property PREV_PAGE
1073             * @default "yui-carousel-prev"
1074             */
1075            PREV_PAGE: "yui-carousel-prev",
1076
1077            /**
1078             * The class name of the selected item.
1079             *
1080             * @property SELECTED_ITEM
1081             * @default "yui-carousel-item-selected"
1082             */
1083            SELECTED_ITEM: "yui-carousel-item-selected",
1084
1085            /**
1086             * The class name of the selected paging navigation.
1087             *
1088             * @property SELECTED_NAV
1089             * @default "yui-carousel-nav-page-selected"
1090             */
1091            SELECTED_NAV: "yui-carousel-nav-page-selected",
1092
1093            /**
1094             * The class name of a vertically oriented Carousel.
1095             *
1096             * @property VERTICAL
1097             * @default "yui-carousel-vertical"
1098             */
1099            VERTICAL: "yui-carousel-vertical",
1100
1101            /**
1102             * The class name of the (vertical) Carousel's container element.
1103             *
1104             * @property VERTICAL_CONTAINER
1105             * @default "yui-carousel-vertical-container"
1106             */
1107            VERTICAL_CONTAINER: "yui-carousel-vertical-container",
1108
1109            /**
1110             * The class name of a visible Carousel.
1111             *
1112             * @property VISIBLE
1113             * @default "yui-carousel-visible"
1114             */
1115            VISIBLE: "yui-carousel-visible"
1116
1117        },
1118
1119        /*
1120         * Configuration attributes for configuring the Carousel component
1121         */
1122
1123        CONFIG: {
1124
1125            /**
1126             * The offset of the first visible item in the Carousel.
1127             *
1128             * @property FIRST_VISIBLE
1129             * @default 0
1130             */
1131            FIRST_VISIBLE: 0,
1132
1133            /**
1134             * The minimum width of the horizontal Carousel container to support
1135             * the navigation buttons.
1136             *
1137             * @property HORZ_MIN_WIDTH
1138             * @default 180
1139             */
1140            HORZ_MIN_WIDTH: 180,
1141
1142            /**
1143             * The maximum number of pager buttons allowed beyond which the UI
1144             * of the pager would be a drop-down of pages instead of buttons.
1145             *
1146             * @property MAX_PAGER_BUTTONS
1147             * @default 5
1148             */
1149            MAX_PAGER_BUTTONS: 5,
1150
1151            /**
1152             * The minimum width of the vertical Carousel container to support
1153             * the navigation buttons.
1154             *
1155             * @property VERT_MIN_WIDTH
1156             * @default 99
1157             */
1158            VERT_MIN_WIDTH: 99,
1159
1160            /**
1161             * The number of visible items in the Carousel.
1162             *
1163             * @property NUM_VISIBLE
1164             * @default 3
1165             */
1166            NUM_VISIBLE: 3
1167
1168        },
1169
1170        /*
1171         * Internationalizable strings in the Carousel component
1172         */
1173
1174        STRINGS: {
1175
1176            /**
1177             * The content to be used as the progress indicator when the item
1178             * is still being loaded.
1179             *
1180             * @property ITEM_LOADING_CONTENT
1181             * @default "Loading"
1182             */
1183            ITEM_LOADING_CONTENT: "Loading",
1184
1185            /**
1186             * The next navigation button name/text.
1187             *
1188             * @property NEXT_BUTTON_TEXT
1189             * @default "Next Page"
1190             */
1191            NEXT_BUTTON_TEXT: "Next Page",
1192
1193            /**
1194             * The prefix text for the pager in case the UI is a drop-down.
1195             *
1196             * @property PAGER_PREFIX_TEXT
1197             * @default "Go to page "
1198             */
1199            PAGER_PREFIX_TEXT: "Go to page ",
1200
1201            /**
1202             * The previous navigation button name/text.
1203             *
1204             * @property PREVIOUS_BUTTON_TEXT
1205             * @default "Previous Page"
1206             */
1207            PREVIOUS_BUTTON_TEXT: "Previous Page"
1208
1209        },
1210
1211        /*
1212         * Public methods of the Carousel component
1213         */
1214
1215        /**
1216         * Insert or append an item to the Carousel.
1217         *
1218         * @method addItem
1219         * @public
1220         * @param item {String | Object | HTMLElement} The item to be appended
1221         * to the Carousel. If the parameter is a string, it is assumed to be
1222         * the content of the newly created item. If the parameter is an
1223         * object, it is assumed to supply the content and an optional class
1224         * and an optional id of the newly created item.
1225         * @param index {Number} optional The position to where in the list
1226         * (starts from zero).
1227         * @return {Boolean} Return true on success, false otherwise
1228         */
1229        addItem: function (item, index) {
1230            var carousel = this,
1231                className,
1232                content,
1233                elId,
1234                numItems = carousel.get("numItems");
1235
1236            if (!item) {
1237                return false;
1238            }
1239
1240            if (JS.isString(item) || item.nodeName) {
1241                content = item.nodeName ? item.innerHTML : item;
1242            } else if (JS.isObject(item)) {
1243                content = item.content;
1244            } else {
1245                return false;
1246            }
1247
1248            className = item.className || "";
1249            elId      = item.id ? item.id : Dom.generateId();
1250
1251            if (JS.isUndefined(index)) {
1252                carousel._itemsTable.items.push({
1253                        item      : content,
1254                        className : className,
1255                        id        : elId
1256                });
1257            } else {
1258                if (index < 0 || index >= numItems) {
1259                    return false;
1260                }
1261                carousel._itemsTable.items.splice(index, 0, {
1262                        item      : content,
1263                        className : className,
1264                        id        : elId
1265                });
1266            }
1267            carousel._itemsTable.numItems++;
1268
1269            if (numItems < carousel._itemsTable.items.length) {
1270                carousel.set("numItems", carousel._itemsTable.items.length);
1271            }
1272
1273            carousel.fireEvent(itemAddedEvent, { pos: index, ev: itemAddedEvent });
1274
1275            return true;
1276        },
1277
1278        /**
1279         * Insert or append multiple items to the Carousel.
1280         *
1281         * @method addItems
1282         * @public
1283         * @param items {Array} An array of items to be added with each item
1284         * representing an item, index pair [{item, index}, ...]
1285         * @return {Boolean} Return true on success, false otherwise
1286         */
1287        addItems: function (items) {
1288            var i, n, rv = true;
1289
1290            if (!JS.isArray(items)) {
1291                return false;
1292            }
1293
1294            for (i = 0, n = items.length; i < n; i++) {
1295                if (this.addItem(items[i][0], items[i][1]) === false) {
1296                    rv = false;
1297                }
1298            }
1299
1300            return rv;
1301        },
1302
1303        /**
1304         * Remove focus from the Carousel.
1305         *
1306         * @method blur
1307         * @public
1308         */
1309        blur: function () {
1310            this._carouselEl.blur();
1311            this.fireEvent(blurEvent);
1312        },
1313
1314        /**
1315         * Clears the items from Carousel.
1316         *
1317         * @method clearItems
1318         * public
1319         */
1320        clearItems: function () {
1321            var carousel = this, n = carousel.get("numItems");
1322
1323            while (n > 0) {
1324                if (!carousel.removeItem(0)) {
1325                }
1326                /*
1327                    For dynamic loading, the numItems may be much larger than
1328                    the actual number of items in the table.  So, set the
1329                    numItems to zero, and break out of the loop if the table
1330                    is already empty.
1331                 */
1332                if (carousel._itemsTable.numItems === 0) {
1333                    carousel.set("numItems", 0);
1334                    break;
1335                }
1336                n--;
1337            }
1338
1339            carousel.fireEvent(allItemsRemovedEvent);
1340        },
1341
1342        /**
1343         * Set focus on the Carousel.
1344         *
1345         * @method focus
1346         * @public
1347         */
1348        focus: function () {
1349            var carousel = this,
1350                first,
1351                focusEl,
1352                isSelectionInvisible,
1353                itemsTable,
1354                last,
1355                numVisible,
1356                selectOnScroll,
1357                selected,
1358                selItem;
1359
1360            // Don't do anything if the Carousel is not rendered
1361            if (!carousel._hasRendered) {
1362                return;
1363            }
1364
1365            if (carousel.isAnimating()) {
1366                // this messes up real bad!
1367                return;
1368            }
1369
1370            selItem              = carousel.get("selectedItem");
1371            numVisible           = carousel.get("numVisible");
1372            selectOnScroll       = carousel.get("selectOnScroll");
1373            selected             = (selItem >= 0) ?
1374                                   carousel.getItem(selItem) : null;
1375            first                = carousel.get("firstVisible");
1376            last                 = first + numVisible - 1;
1377            isSelectionInvisible = (selItem < first || selItem > last);
1378            focusEl              = (selected && selected.id) ?
1379                                   Dom.get(selected.id) : null;
1380            itemsTable           = carousel._itemsTable;
1381
1382            if (!selectOnScroll && isSelectionInvisible) {
1383                focusEl = (itemsTable && itemsTable.items &&
1384                           itemsTable.items[first]) ?
1385                        Dom.get(itemsTable.items[first].id) : null;
1386            }
1387
1388            if (focusEl) {
1389                try {
1390                    focusEl.focus();
1391                } catch (ex) {
1392                    // ignore focus errors
1393                }
1394            }
1395
1396            carousel.fireEvent(focusEvent);
1397        },
1398
1399        /**
1400         * Hide the Carousel.
1401         *
1402         * @method hide
1403         * @public
1404         */
1405        hide: function () {
1406            var carousel = this;
1407
1408            if (carousel.fireEvent(beforeHideEvent) !== false) {
1409                carousel.removeClass(carousel.CLASSES.VISIBLE);
1410                carousel.fireEvent(hideEvent);
1411            }
1412        },
1413
1414        /**
1415         * Initialize the Carousel.
1416         *
1417         * @method init
1418         * @public
1419         * @param el {HTMLElement | String} The html element that represents
1420         * the Carousel container.
1421         * @param attrs {Object} The set of configuration attributes for
1422         * creating the Carousel.
1423         */
1424        init: function (el, attrs) {
1425            var carousel = this,
1426                elId     = el,  // save for a rainy day
1427                parse    = false;
1428
1429            if (!el) {
1430                return;
1431            }
1432
1433            carousel._hasRendered = false;
1434            carousel._navBtns     = { prev: [], next: [] };
1435            carousel._pages       = { el: null, num: 0, cur: 0 };
1436            carousel._itemsTable  = { loading: {}, numItems: 0,
1437                                      items: [], size: 0 };
1438
1439
1440            if (JS.isString(el)) {
1441                el = Dom.get(el);
1442            } else if (!el.nodeName) {
1443                return;
1444            }
1445
1446            Carousel.superclass.init.call(carousel, el, attrs);
1447
1448            if (el) {
1449                if (!el.id) {   // in case the HTML element is passed
1450                    el.setAttribute("id", Dom.generateId());
1451                }
1452                parse = carousel._parseCarousel(el);
1453                if (!parse) {
1454                    carousel._createCarousel(elId);
1455                }
1456            } else {
1457                el = carousel._createCarousel(elId);
1458            }
1459            elId = el.id;
1460
1461            carousel.initEvents();
1462
1463            if (parse) {
1464                carousel._parseCarouselItems();
1465            }
1466
1467            if (!attrs || typeof attrs.isVertical == "undefined") {
1468                carousel.set("isVertical", false);
1469            }
1470
1471            carousel._parseCarouselNavigation(el);
1472            carousel._navEl = carousel._setupCarouselNavigation();
1473
1474            instances[elId] = { object: carousel };
1475
1476            carousel._loadItems();
1477        },
1478
1479        /**
1480         * Initialize the configuration attributes used to create the Carousel.
1481         *
1482         * @method initAttributes
1483         * @public
1484         * @param attrs {Object} The set of configuration attributes for
1485         * creating the Carousel.
1486         */
1487        initAttributes: function (attrs) {
1488            var carousel = this;
1489
1490            attrs = attrs || {};
1491            Carousel.superclass.initAttributes.call(carousel, attrs);
1492
1493            /**
1494             * @attribute carouselEl
1495             * @description The type of the Carousel element.
1496             * @default OL
1497             * @type Boolean
1498             */
1499            carousel.setAttributeConfig("carouselEl", {
1500                    validator : JS.isString,
1501                    value     : attrs.carouselEl || "OL"
1502            });
1503
1504            /**
1505             * @attribute carouselItemEl
1506             * @description The type of the list of items within the Carousel.
1507             * @default LI
1508             * @type Boolean
1509             */
1510            carousel.setAttributeConfig("carouselItemEl", {
1511                    validator : JS.isString,
1512                    value     : attrs.carouselItemEl || "LI"
1513            });
1514
1515            /**
1516             * @attribute currentPage
1517             * @description The current page number (read-only.)
1518             * @type Number
1519             */
1520            carousel.setAttributeConfig("currentPage", {
1521                    readOnly : true,
1522                    value    : 0
1523            });
1524
1525            /**
1526             * @attribute firstVisible
1527             * @description The index to start the Carousel from (indexes begin
1528             * from zero)
1529             * @default 0
1530             * @type Number
1531             */
1532            carousel.setAttributeConfig("firstVisible", {
1533                    method    : carousel._setFirstVisible,
1534                    validator : carousel._validateFirstVisible,
1535                    value     :
1536                        attrs.firstVisible || carousel.CONFIG.FIRST_VISIBLE
1537            });
1538
1539            /**
1540             * @attribute selectOnScroll
1541             * @description Set this to true to automatically set focus to
1542             * follow scrolling in the Carousel.
1543             * @default true
1544             * @type Boolean
1545             */
1546            carousel.setAttributeConfig("selectOnScroll", {
1547                    validator : JS.isBoolean,
1548                    value     : attrs.selectOnScroll || true
1549            });
1550
1551            /**
1552             * @attribute numVisible
1553             * @description The number of visible items in the Carousel's
1554             * viewport.
1555             * @default 3
1556             * @type Number
1557             */
1558            carousel.setAttributeConfig("numVisible", {
1559                    method    : carousel._setNumVisible,
1560                    validator : carousel._validateNumVisible,
1561                    value     : attrs.numVisible || carousel.CONFIG.NUM_VISIBLE
1562            });
1563
1564            /**
1565             * @attribute numItems
1566             * @description The number of items in the Carousel.
1567             * @type Number
1568             */
1569            carousel.setAttributeConfig("numItems", {
1570                    method    : carousel._setNumItems,
1571                    validator : carousel._validateNumItems,
1572                    value     : carousel._itemsTable.numItems
1573            });
1574
1575            /**
1576             * @attribute scrollIncrement
1577             * @description The number of items to scroll by for arrow keys.
1578             * @default 1
1579             * @type Number
1580             */
1581            carousel.setAttributeConfig("scrollIncrement", {
1582                    validator : carousel._validateScrollIncrement,
1583                    value     : attrs.scrollIncrement || 1
1584            });
1585
1586            /**
1587             * @attribute selectedItem
1588             * @description The index of the selected item.
1589             * @type Number
1590             */
1591            carousel.setAttributeConfig("selectedItem", {
1592                    method    : carousel._setSelectedItem,
1593                    validator : JS.isNumber,
1594                    value     : -1
1595            });
1596
1597            /**
1598             * @attribute revealAmount
1599             * @description The percentage of the item to be revealed on each
1600             * side of the Carousel (before and after the first and last item
1601             * in the Carousel's viewport.)
1602             * @default 0
1603             * @type Number
1604             */
1605            carousel.setAttributeConfig("revealAmount", {
1606                    method    : carousel._setRevealAmount,
1607                    validator : carousel._validateRevealAmount,
1608                    value     : attrs.revealAmount || 0
1609            });
1610
1611            /**
1612             * @attribute isCircular
1613             * @description Set this to true to wrap scrolling of the contents
1614             * in the Carousel.
1615             * @default false
1616             * @type Boolean
1617             */
1618            carousel.setAttributeConfig("isCircular", {
1619                    validator : JS.isBoolean,
1620                    value     : attrs.isCircular || false
1621            });
1622
1623            /**
1624             * @attribute isVertical
1625             * @description True if the orientation of the Carousel is vertical
1626             * @default false
1627             * @type Boolean
1628             */
1629            carousel.setAttributeConfig("isVertical", {
1630                    method    : carousel._setOrientation,
1631                    validator : JS.isBoolean,
1632                    value     : attrs.isVertical || false
1633            });
1634
1635            /**
1636             * @attribute navigation
1637             * @description The set of navigation controls for Carousel
1638             * @default <br>
1639             * { prev: null, // the previous navigation element<br>
1640             *   next: null } // the next navigation element
1641             * @type Object
1642             */
1643            carousel.setAttributeConfig("navigation", {
1644                    method    : carousel._setNavigation,
1645                    validator : carousel._validateNavigation,
1646                    value     :
1647                        attrs.navigation || {prev: null,next: null,page: null}
1648            });
1649
1650            /**
1651             * @attribute animation
1652             * @description The optional animation attributes for the Carousel.
1653             * @default <br>
1654             * { speed: 0, // the animation speed (in seconds)<br>
1655             *   effect: null } // the animation effect (like
1656             *   YAHOO.util.Easing.easeOut)
1657             * @type Object
1658             */
1659            carousel.setAttributeConfig("animation", {
1660                    validator : carousel._validateAnimation,
1661                    value     : attrs.animation || { speed: 0, effect: null }
1662            });
1663
1664            /**
1665             * @attribute autoPlay
1666             * @description Set this to time in milli-seconds to have the
1667             * Carousel automatically scroll the contents.
1668             * @type Number
1669             * @deprecated Use autoPlayInterval instead.
1670             */
1671            carousel.setAttributeConfig("autoPlay", {
1672                    validator : JS.isNumber,
1673                    value     : attrs.autoPlay || 0
1674            });
1675
1676            /**
1677             * @attribute autoPlayInterval
1678             * @description The delay in milli-seconds for scrolling the
1679             * Carousel during auto-play.
1680             * Note: The startAutoPlay() method needs to be invoked to trigger
1681             * automatic scrolling of Carousel.
1682             * @type Number
1683             */
1684            carousel.setAttributeConfig("autoPlayInterval", {
1685                    validator : JS.isNumber,
1686                    value     : attrs.autoPlayInterval || 0
1687            });
1688        },
1689
1690        /**
1691         * Initialize and bind the event handlers.
1692         *
1693         * @method initEvents
1694         * @public
1695         */
1696        initEvents: function () {
1697            var carousel = this,
1698                cssClass = carousel.CLASSES,
1699                focussedLi;
1700
1701            carousel.on("keydown", carousel._keyboardEventHandler);
1702
1703            carousel.on(afterScrollEvent, syncNavigation);
1704
1705            carousel.on(itemAddedEvent, syncUi);
1706
1707            carousel.on(itemRemovedEvent, syncUi);
1708
1709            carousel.on(itemSelectedEvent, function () {
1710                if (carousel._hasFocus) {
1711                    carousel.focus();
1712                }
1713            });
1714
1715            carousel.on(loadItemsEvent, syncUi);
1716
1717            carousel.on(allItemsRemovedEvent, function (ev) {
1718                carousel.scrollTo(0);
1719                syncNavigation.call(carousel);
1720                syncPagerUi.call(carousel);
1721            });
1722
1723            carousel.on(pageChangeEvent, syncPagerUi, carousel);
1724
1725            carousel.on(renderEvent, function (ev) {
1726                carousel.set("selectedItem", carousel.get("firstVisible"));
1727                syncNavigation.call(carousel, ev);
1728                syncPagerUi.call(carousel, ev);
1729                carousel._setClipContainerSize();
1730            });
1731
1732            carousel.on("selectedItemChange", function (ev) {
1733                setItemSelection.call(carousel, ev.newValue, ev.prevValue);
1734                if (ev.newValue >= 0) {
1735                    carousel._updateTabIndex(
1736                            carousel.getElementForItem(ev.newValue));
1737                }
1738                carousel.fireEvent(itemSelectedEvent, ev.newValue);
1739            });
1740
1741            carousel.on(uiUpdateEvent, function (ev) {
1742                syncNavigation.call(carousel, ev);
1743                syncPagerUi.call(carousel, ev);
1744            });
1745
1746            carousel.on("firstVisibleChange", function (ev) {
1747                if (!carousel.get("selectOnScroll")) {
1748                    if (ev.newValue >= 0) {
1749                        carousel._updateTabIndex(
1750                                carousel.getElementForItem(ev.newValue));
1751                    }
1752                }
1753            });
1754
1755            // Handle item selection on mouse click
1756            carousel.on("click", function (ev) {
1757                if (carousel.isAutoPlayOn()) {
1758                    carousel.stopAutoPlay();
1759                }
1760                carousel._itemClickHandler(ev);
1761                carousel._pagerClickHandler(ev);
1762            });
1763
1764            // Restore the focus on the navigation buttons
1765
1766            Event.onFocus(carousel.get("element"), function (ev, obj) {
1767                var target = Event.getTarget(ev);
1768
1769                if (target && target.nodeName.toUpperCase() == "A" &&
1770                    Dom.getAncestorByClassName(target, cssClass.NAVIGATION)) {
1771                    if (focussedLi) {
1772                        Dom.removeClass(focussedLi, cssClass.PAGE_FOCUS);
1773                    }
1774                    focussedLi = target.parentNode;
1775                    Dom.addClass(focussedLi, cssClass.PAGE_FOCUS);
1776                } else {
1777                    if (focussedLi) {
1778                        Dom.removeClass(focussedLi, cssClass.PAGE_FOCUS);
1779                    }
1780                }
1781
1782                obj._hasFocus = true;
1783                obj._updateNavButtons(Event.getTarget(ev), true);
1784            }, carousel);
1785
1786            Event.onBlur(carousel.get("element"), function (ev, obj) {
1787                obj._hasFocus = false;
1788                obj._updateNavButtons(Event.getTarget(ev), false);
1789            }, carousel);
1790        },
1791
1792        /**
1793         * Return true if the Carousel is still animating, or false otherwise.
1794         *
1795         * @method isAnimating
1796         * @return {Boolean} Return true if animation is still in progress, or
1797         * false otherwise.
1798         * @public
1799         */
1800        isAnimating: function () {
1801            return this._isAnimationInProgress;
1802        },
1803
1804        /**
1805         * Return true if the auto-scrolling of Carousel is "on", or false
1806         * otherwise.
1807         *
1808         * @method isAutoPlayOn
1809         * @return {Boolean} Return true if autoPlay is "on", or false
1810         * otherwise.
1811         * @public
1812         */
1813        isAutoPlayOn: function () {
1814            return this._isAutoPlayInProgress;
1815        },
1816
1817        /**
1818         * Return the carouselItemEl at index or null if the index is not
1819         * found.
1820         *
1821         * @method getElementForItem
1822         * @param index {Number} The index of the item to be returned
1823         * @return {Element} Return the item at index or null if not found
1824         * @public
1825         */
1826        getElementForItem: function (index) {
1827            var carousel = this;
1828
1829            if (index < 0 || index >= carousel.get("numItems")) {
1830                return null;
1831            }
1832
1833            // TODO: may be cache the item
1834            if (carousel._itemsTable.numItems > index) {
1835                if (!JS.isUndefined(carousel._itemsTable.items[index])) {
1836                    return Dom.get(carousel._itemsTable.items[index].id);
1837                }
1838            }
1839
1840            return null;
1841        },
1842
1843        /**
1844         * Return the carouselItemEl for all items in the Carousel.
1845         *
1846         * @method getElementForItems
1847         * @return {Array} Return all the items
1848         * @public
1849         */
1850        getElementForItems: function () {
1851            var carousel = this, els = [], i;
1852
1853            for (i = 0; i < carousel._itemsTable.numItems; i++) {
1854                els.push(carousel.getElementForItem(i));
1855            }
1856
1857            return els;
1858        },
1859
1860        /**
1861         * Return the item at index or null if the index is not found.
1862         *
1863         * @method getItem
1864         * @param index {Number} The index of the item to be returned
1865         * @return {Object} Return the item at index or null if not found
1866         * @public
1867         */
1868        getItem: function (index) {
1869            var carousel = this;
1870
1871            if (index < 0 || index >= carousel.get("numItems")) {
1872                return null;
1873            }
1874
1875            if (carousel._itemsTable.numItems > index) {
1876                if (!JS.isUndefined(carousel._itemsTable.items[index])) {
1877                    return carousel._itemsTable.items[index];
1878                }
1879            }
1880
1881            return null;
1882        },
1883
1884        /**
1885         * Return all items as an array.
1886         *
1887         * @method getItems
1888         * @return {Array} Return all items in the Carousel
1889         * @public
1890         */
1891        getItems: function (index) {
1892            return this._itemsTable.items;
1893        },
1894
1895        /**
1896         * Return the position of the Carousel item that has the id "id", or -1
1897         * if the id is not found.
1898         *
1899         * @method getItemPositionById
1900         * @param index {Number} The index of the item to be returned
1901         * @public
1902         */
1903        getItemPositionById: function (id) {
1904            var carousel = this, i = 0, n = carousel._itemsTable.numItems;
1905
1906            while (i < n) {
1907                if (!JS.isUndefined(carousel._itemsTable.items[i])) {
1908                    if (carousel._itemsTable.items[i].id == id) {
1909                        return i;
1910                    }
1911                }
1912                i++;
1913            }
1914
1915            return -1;
1916        },
1917
1918        /**
1919         * Return all visible items as an array.
1920         *
1921         * @method getVisibleItems
1922         * @return {Array} The array of visible items
1923         * @public
1924         */
1925        getVisibleItems: function () {
1926            var carousel = this,
1927                i        = carousel.get("firstVisible"),
1928                n        = i + carousel.get("numVisible"),
1929                r        = [];
1930
1931            while (i < n) {
1932                r.push(carousel.getElementForItem(i));
1933                i++;
1934            }
1935
1936            return r;
1937        },
1938
1939        /**
1940         * Remove an item at index from the Carousel.
1941         *
1942         * @method removeItem
1943         * @public
1944         * @param index {Number} The position to where in the list (starts from
1945         * zero).
1946         * @return {Boolean} Return true on success, false otherwise
1947         */
1948        removeItem: function (index) {
1949            var carousel = this,
1950                item,
1951                num      = carousel.get("numItems");
1952
1953            if (index < 0 || index >= num) {
1954                return false;
1955            }
1956
1957            item = carousel._itemsTable.items.splice(index, 1);
1958            if (item && item.length == 1) {
1959                carousel._itemsTable.numItems--;
1960                carousel.set("numItems", num - 1);
1961
1962                carousel.fireEvent(itemRemovedEvent,
1963                        { item: item[0], pos: index, ev: itemRemovedEvent });
1964                return true;
1965            }
1966
1967            return false;
1968        },
1969
1970        /**
1971         * Render the Carousel.
1972         *
1973         * @method render
1974         * @public
1975         * @param appendTo {HTMLElement | String} The element to which the
1976         * Carousel should be appended prior to rendering.
1977         * @return {Boolean} Status of the operation
1978         */
1979        render: function (appendTo) {
1980            var carousel = this,
1981                cssClass = carousel.CLASSES;
1982
1983            carousel.addClass(cssClass.CAROUSEL);
1984
1985            if (!carousel._clipEl) {
1986                carousel._clipEl = carousel._createCarouselClip();
1987                carousel._clipEl.appendChild(carousel._carouselEl);
1988            }
1989
1990            if (appendTo) {
1991                carousel.appendChild(carousel._clipEl);
1992                carousel.appendTo(appendTo);
1993            } else {
1994                if (!Dom.inDocument(carousel.get("element"))) {
1995                    return false;
1996                }
1997                carousel.appendChild(carousel._clipEl);
1998            }
1999
2000            if (carousel.get("isVertical")) {
2001                carousel.addClass(cssClass.VERTICAL);
2002            } else {
2003                carousel.addClass(cssClass.HORIZONTAL);
2004            }
2005
2006            if (carousel.get("numItems") < 1) {
2007                return false;
2008            }
2009
2010            carousel._refreshUi();
2011
2012            return true;
2013        },
2014
2015        /**
2016         * Scroll the Carousel by an item backward.
2017         *
2018         * @method scrollBackward
2019         * @public
2020         */
2021        scrollBackward: function () {
2022            var carousel = this;
2023
2024            carousel.scrollTo(carousel._firstItem -
2025                              carousel.get("scrollIncrement"));
2026        },
2027
2028        /**
2029         * Scroll the Carousel by an item forward.
2030         *
2031         * @method scrollForward
2032         * @public
2033         */
2034        scrollForward: function () {
2035            var carousel = this;
2036
2037            carousel.scrollTo(carousel._firstItem +
2038                              carousel.get("scrollIncrement"));
2039        },
2040
2041        /**
2042         * Scroll the Carousel by a page backward.
2043         *
2044         * @method scrollPageBackward
2045         * @public
2046         */
2047        scrollPageBackward: function () {
2048            var carousel = this,
2049                item     = carousel._firstItem - carousel.get("numVisible");
2050
2051            if (carousel.get("selectOnScroll")) {
2052                carousel._selectedItem = carousel._getSelectedItem(item);
2053            } else {
2054                item = carousel._getValidIndex(item);
2055            }
2056            carousel.scrollTo(item);
2057        },
2058
2059        /**
2060         * Scroll the Carousel by a page forward.
2061         *
2062         * @method scrollPageForward
2063         * @public
2064         */
2065        scrollPageForwa