2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 * The Carousel module provides a widget for browsing among a set of like
9 * objects represented pictorially.
12 * @requires yahoo, dom, event, element
14 * @namespace YAHOO.widget
15 * @title Carousel Widget
20 var WidgetName; // forward declaration
23 * The Carousel widget.
26 * @extends YAHOO.util.Element
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
32 YAHOO.widget.Carousel = function (el, cfg) {
34 YAHOO.widget.Carousel.superclass.constructor.call(this, el, cfg);
38 * Private variables of the Carousel component
41 /* Some abbreviations to avoid lengthy typing and lookups. */
42 var Carousel = YAHOO.widget.Carousel,
44 Event = YAHOO.util.Event,
52 WidgetName = "Carousel";
55 * The internal table of Carousel instances.
62 * Custom events of the Carousel component
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
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
74 afterScrollEvent = "afterScroll",
77 * @event allItemsRemovedEvent
78 * @description Fires when all items have been removed from the Carousel.
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
84 allItemsRemovedEvent = "allItemsRemoved",
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
93 beforeHideEvent = "beforeHide",
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
104 beforePageChangeEvent = "beforePageChange",
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
115 beforeScrollEvent = "beforeScroll",
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
124 beforeShowEvent = "beforeShow",
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
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
142 focusEvent = "focus",
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
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
162 itemAddedEvent = "itemAdded",
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
173 itemRemovedEvent = "itemRemoved",
176 * @event itemReplaced
177 * @description Fires when an item has been replaced in the Carousel.
178 * Passes back the content of the item that was replaced, the content
179 * of the new item, the index where the replacement occurred, and the event
181 * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
182 * for more information on listening for this event.
183 * @type YAHOO.util.CustomEvent
185 itemReplacedEvent = "itemReplaced",
188 * @event itemSelected
189 * @description Fires when an item has been selected in the Carousel.
190 * Passes back the index of the selected item in the Carousel. Note, that
191 * the index begins from zero. See
192 * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
193 * for more information on listening for this event.
194 * @type YAHOO.util.CustomEvent
196 itemSelectedEvent = "itemSelected",
200 * @description Fires when the Carousel needs more items to be loaded for
201 * displaying them. Passes back the first and last visible items in the
202 * Carousel, and the number of items needed to be loaded. 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
207 loadItemsEvent = "loadItems",
210 * @event navigationStateChange
211 * @description Fires when the state of either one of the navigation
212 * buttons are changed from enabled to disabled or vice versa. Passes back
213 * the state (true/false) of the previous and next buttons. The value true
214 * signifies the button is enabled, false signifies disabled. See
215 * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
216 * for more information on listening for this event.
217 * @type YAHOO.util.CustomEvent
219 navigationStateChangeEvent = "navigationStateChange",
223 * @description Fires after the Carousel has scrolled to the previous or
224 * next page. Passes back the page number of the current page. Note
225 * that the first page number is zero. See
226 * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
227 * for more information on listening for this event.
228 * @type YAHOO.util.CustomEvent
230 pageChangeEvent = "pageChange",
235 * @description Fires when the Carousel is rendered. See
236 * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
237 * for more information on listening for this event.
238 * @type YAHOO.util.CustomEvent
240 renderEvent = "render",
244 * @description Fires when the Carousel is shown. See
245 * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
246 * for more information on listening for this event.
247 * @type YAHOO.util.CustomEvent
252 * @event startAutoPlay
253 * @description Fires when the auto play has started in the Carousel. See
254 * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
255 * for more information on listening for this event.
256 * @type YAHOO.util.CustomEvent
258 startAutoPlayEvent = "startAutoPlay",
261 * @event stopAutoPlay
262 * @description Fires when the auto play has been stopped in the Carousel.
264 * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
265 * for more information on listening for this event.
266 * @type YAHOO.util.CustomEvent
268 stopAutoPlayEvent = "stopAutoPlay",
272 * @event uiUpdateEvent
273 * @description Fires when the UI has been updated.
275 * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
276 * for more information on listening for this event.
277 * @type YAHOO.util.CustomEvent
279 uiUpdateEvent = "uiUpdate";
282 * Private helper functions used by the Carousel component
286 * Set multiple styles on one element.
288 * @param el {HTMLElement} The element to set styles on
289 * @param style {Object} top:"10px", left:"0px", etc.
292 function setStyles(el, styles) {
295 for (which in styles) {
296 if (styles.hasOwnProperty(which)) {
297 Dom.setStyle(el, which, styles[which]);
303 * Create an element, set its class name and optionally install the element
305 * @method createElement
306 * @param el {String} The element to be created
307 * @param attrs {Object} Configuration of parent, class and id attributes.
308 * If the content is specified, it is inserted after creation of the
309 * element. The content can also be an HTML element in which case it would
310 * be appended as a child node of the created element.
313 function createElement(el, attrs) {
314 var newEl = document.createElement(el);
317 if (attrs.className) {
318 Dom.addClass(newEl, attrs.className);
322 setStyles(newEl, attrs.styles);
326 attrs.parent.appendChild(newEl);
330 newEl.setAttribute("id", attrs.id);
334 if (attrs.content.nodeName) {
335 newEl.appendChild(attrs.content);
337 newEl.innerHTML = attrs.content;
345 * Get the computed style of an element.
348 * @param el {HTMLElement} The element for which the style needs to be
350 * @param style {String} The style attribute
351 * @param type {String} "int", "float", etc. (defaults to int)
354 function getStyle(el, style, type) {
361 function getStyleIntVal(el, style) {
365 * XXX: Safari calculates incorrect marginRight for an element
366 * which has its parent element style set to overflow: hidden
367 * https://bugs.webkit.org/show_bug.cgi?id=13343
368 * Let us assume marginLeft == marginRight
370 if (style == "marginRight" && YAHOO.env.ua.webkit) {
371 val = parseInt(Dom.getStyle(el, "marginLeft"), 10);
373 val = parseInt(Dom.getStyle(el, style), 10);
376 return JS.isNumber(val) ? val : 0;
379 function getStyleFloatVal(el, style) {
383 * XXX: Safari calculates incorrect marginRight for an element
384 * which has its parent element style set to overflow: hidden
385 * https://bugs.webkit.org/show_bug.cgi?id=13343
386 * Let us assume marginLeft == marginRight
388 if (style == "marginRight" && YAHOO.env.ua.webkit) {
389 val = parseFloat(Dom.getStyle(el, "marginLeft"));
391 val = parseFloat(Dom.getStyle(el, style));
394 return JS.isNumber(val) ? val : 0;
397 if (typeof type == "undefined") {
403 value = el.offsetHeight;
405 value += getStyleIntVal(el, "marginTop") +
406 getStyleIntVal(el, "marginBottom");
408 value = getStyleFloatVal(el, "height") +
409 getStyleIntVal(el, "marginTop") +
410 getStyleIntVal(el, "marginBottom") +
411 getStyleIntVal(el, "borderTopWidth") +
412 getStyleIntVal(el, "borderBottomWidth") +
413 getStyleIntVal(el, "paddingTop") +
414 getStyleIntVal(el, "paddingBottom");
418 value = el.offsetWidth;
420 value += getStyleIntVal(el, "marginLeft") +
421 getStyleIntVal(el, "marginRight");
423 value = getStyleFloatVal(el, "width") +
424 getStyleIntVal(el, "marginLeft") +
425 getStyleIntVal(el, "marginRight") +
426 getStyleIntVal(el, "borderLeftWidth") +
427 getStyleIntVal(el, "borderRightWidth") +
428 getStyleIntVal(el, "paddingLeft") +
429 getStyleIntVal(el, "paddingRight");
434 value = getStyleIntVal(el, style);
435 } else if (type == "float") {
436 value = getStyleFloatVal(el, style);
438 value = Dom.getStyle(el, style);
447 * Compute and return the height or width of a single Carousel item
448 * depending upon the orientation.
450 * @method getCarouselItemSize
451 * @param which {String} "height" or "width" to be returned. If this is
452 * passed explicitly, the calculated size is not cached.
455 function getCarouselItemSize(which) {
460 first = carousel.get("firstVisible"),
463 if (carousel._itemsTable.numItems === 0) {
467 item = carousel._itemsTable.items[first] ||
468 carousel._itemsTable.loading[first];
470 if (JS.isUndefined(item)) {
474 child = Dom.get(item.id);
476 if (typeof which == "undefined") {
477 vertical = carousel.get("isVertical");
479 vertical = which == "height";
482 if (this._itemAttrCache[which]) {
483 return this._itemAttrCache[which];
487 size = getStyle(child, "height");
489 size = getStyle(child, "width");
492 this._itemAttrCache[which] = size;
498 * Return the size of a part of the item (reveal).
500 * @method getRevealSize
503 function getRevealSize() {
504 var carousel = this, isVertical, sz;
506 isVertical = carousel.get("isVertical");
507 sz = getCarouselItemSize.call(carousel,
508 isVertical ? "height" : "width");
509 return (sz * carousel.get("revealAmount") / 100);
513 * Compute and return the position of a Carousel item based on its
516 * @method getCarouselItemPosition
517 * @param position {Number} The position of the Carousel item.
520 function getCarouselItemPosition(pos) {
522 itemsPerRow = carousel._cols,
523 itemsPerCol = carousel._rows,
536 itemsTable = carousel._itemsTable,
537 items = itemsTable.items,
538 loading = itemsTable.loading;
540 isVertical = carousel.get("isVertical");
541 sz = getCarouselItemSize.call(carousel,
542 isVertical ? "height" : "width");
543 rsz = getRevealSize.call(carousel);
545 // adjust for items not yet loaded
546 while (index < pos) {
547 if (!items[index] && !loading[index]) {
555 page = this.getPageForItem(pos);
557 itemsRow = Math.floor(pos/itemsPerRow);
560 styles.top = (top + rsz) + "px";
562 sz = getCarouselItemSize.call(carousel, "width");
564 itemsCol = pos % itemsPerRow;
567 styles.left = left + "px";
569 itemsCol = pos % itemsPerRow;
570 sentinel = (page - 1) * itemsPerRow;
571 delta = itemsCol + sentinel;
573 styles.left = (left + rsz) + "px";
575 sz = getCarouselItemSize.call(carousel, "height");
577 itemsRow = Math.floor(pos/itemsPerRow);
578 sentinel = (page - 1) * itemsPerCol;
579 delta = itemsRow - sentinel;
582 styles.top = top + "px";
587 styles.top = ((pos * sz) + rsz) + "px";
590 styles.left = ((pos * sz) + rsz) + "px";
598 * Return the index of the first item in the view port for displaying item
601 * @method getFirstVisibleForPosition
602 * @param pos {Number} The position of the item to be displayed
605 function getFirstVisibleForPosition(pos) {
606 var num = this.get("numVisible");
607 return Math.floor(pos / num) * num;
611 * Return the scrolling offset size given the number of elements to
614 * @method getScrollOffset
615 * @param delta {Number} The delta number of elements to scroll by.
618 function getScrollOffset(delta) {
622 itemSize = getCarouselItemSize.call(this);
623 size = itemSize * delta;
629 * Scroll the Carousel by a page backward.
631 * @method scrollPageBackward
632 * @param {Event} ev The event object
633 * @param {Object} obj The context object
636 function scrollPageBackward(ev, obj) {
637 obj.scrollPageBackward();
638 Event.preventDefault(ev);
642 * Scroll the Carousel by a page forward.
644 * @method scrollPageForward
645 * @param {Event} ev The event object
646 * @param {Object} obj The context object
649 function scrollPageForward(ev, obj) {
650 obj.scrollPageForward();
651 Event.preventDefault(ev);
655 * Set the selected item.
657 * @method setItemSelection
658 * @param {Number} newpos The index of the new position
659 * @param {Number} oldpos The index of the previous position
662 function setItemSelection(newpos, oldpos) {
664 cssClass = carousel.CLASSES,
666 firstItem = carousel._firstItem,
667 isCircular = carousel.get("isCircular"),
668 numItems = carousel.get("numItems"),
669 numVisible = carousel.get("numVisible"),
671 sentinel = firstItem + numVisible - 1;
673 if (position >= 0 && position < numItems) {
674 if (!JS.isUndefined(carousel._itemsTable.items[position])) {
675 el = Dom.get(carousel._itemsTable.items[position].id);
677 Dom.removeClass(el, cssClass.SELECTED_ITEM);
682 if (JS.isNumber(newpos)) {
683 newpos = parseInt(newpos, 10);
684 newpos = JS.isNumber(newpos) ? newpos : 0;
689 if (JS.isUndefined(carousel._itemsTable.items[newpos])) {
690 newpos = getFirstVisibleForPosition.call(carousel, newpos);
691 carousel.scrollTo(newpos); // still loading the item
694 if (!JS.isUndefined(carousel._itemsTable.items[newpos])) {
695 el = Dom.get(carousel._itemsTable.items[newpos].id);
697 Dom.addClass(el, cssClass.SELECTED_ITEM);
701 if (newpos < firstItem || newpos > sentinel) { // out of focus
702 newpos = getFirstVisibleForPosition.call(carousel, newpos);
703 carousel.scrollTo(newpos);
708 * Fire custom events for enabling/disabling navigation elements.
710 * @method syncNavigation
713 function syncNavigation() {
716 cssClass = carousel.CLASSES,
721 // Don't do anything if the Carousel is not rendered
722 if (!carousel._hasRendered) {
726 navigation = carousel.get("navigation");
727 sentinel = carousel._firstItem + carousel.get("numVisible");
729 if (navigation.prev) {
730 if (carousel.get("numItems") === 0 || carousel._firstItem === 0) {
731 if (carousel.get("numItems") === 0 ||
732 !carousel.get("isCircular")) {
733 Event.removeListener(navigation.prev, "click",
735 Dom.addClass(navigation.prev, cssClass.FIRST_NAV_DISABLED);
736 for (i = 0; i < carousel._navBtns.prev.length; i++) {
737 carousel._navBtns.prev[i].setAttribute("disabled",
740 carousel._prevEnabled = false;
742 attach = !carousel._prevEnabled;
745 attach = !carousel._prevEnabled;
749 Event.on(navigation.prev, "click", scrollPageBackward,
751 Dom.removeClass(navigation.prev, cssClass.FIRST_NAV_DISABLED);
752 for (i = 0; i < carousel._navBtns.prev.length; i++) {
753 carousel._navBtns.prev[i].removeAttribute("disabled");
755 carousel._prevEnabled = true;
760 if (navigation.next) {
761 if (sentinel >= carousel.get("numItems")) {
762 if (!carousel.get("isCircular")) {
763 Event.removeListener(navigation.next, "click",
765 Dom.addClass(navigation.next, cssClass.DISABLED);
766 for (i = 0; i < carousel._navBtns.next.length; i++) {
767 carousel._navBtns.next[i].setAttribute("disabled",
770 carousel._nextEnabled = false;
772 attach = !carousel._nextEnabled;
775 attach = !carousel._nextEnabled;
779 Event.on(navigation.next, "click", scrollPageForward,
781 Dom.removeClass(navigation.next, cssClass.DISABLED);
782 for (i = 0; i < carousel._navBtns.next.length; i++) {
783 carousel._navBtns.next[i].removeAttribute("disabled");
785 carousel._nextEnabled = true;
789 carousel.fireEvent(navigationStateChangeEvent,
790 { next: carousel._nextEnabled, prev: carousel._prevEnabled });
794 * Synchronize and redraw the Pager UI if necessary.
796 * @method syncPagerUi
799 function syncPagerUi(page) {
800 var carousel = this, numPages, numVisible;
802 // Don't do anything if the Carousel is not rendered
803 if (!carousel._hasRendered) {
807 numVisible = carousel.get("numVisible");
809 if (!JS.isNumber(page)) {
810 page = Math.floor(carousel.get("selectedItem") / numVisible);
813 numPages = Math.ceil(carousel.get("numItems") / numVisible);
815 carousel._pages.num = numPages;
816 carousel._pages.cur = page;
818 if (numPages > carousel.CONFIG.MAX_PAGER_BUTTONS) {
819 carousel._updatePagerMenu();
821 carousel._updatePagerButtons();
826 * Get full dimensions of an element.
828 * @method getDimensions
829 * @param {Object} el The element to get the dimensions of
830 * @param {String} which Get the height or width of an element
833 function getDimensions(el, which) {
836 return getStyle(el, "marginTop") +
837 getStyle(el, "marginBottom") +
838 getStyle(el, "paddingTop") +
839 getStyle(el, "paddingBottom") +
840 getStyle(el, "borderTopWidth") +
841 getStyle(el, "borderBottomWidth");
843 return getStyle(el, "marginLeft") +
844 getStyle(el, "marginRight") +
845 getStyle(el, "paddingLeft") +
846 getStyle(el, "paddingRight") +
847 getStyle(el, "borderLeftWidth") +
848 getStyle(el, "borderRightWidth");
853 return getStyle(el, which);
858 * Call the appropriate methods on events fired when an item is added, or
859 * removed for synchronizing the DOM.
862 * @param {Object} o The item that needs to be added or removed
868 if (!JS.isObject(o)) {
874 carousel._syncUiForItemAdd(o);
876 case itemRemovedEvent:
877 carousel._syncUiForItemRemove(o);
879 case itemReplacedEvent:
880 carousel._syncUiForItemReplace(o);
883 carousel._syncUiForLazyLoading(o);
887 carousel.fireEvent(uiUpdateEvent);
891 * Update the state variables after scrolling the Carousel view port.
893 * @method updateStateAfterScroll
894 * @param {Integer} item The index to which the Carousel has scrolled to.
895 * @param {Integer} sentinel The last element in the view port.
898 function updateStateAfterScroll(item, sentinel) {
900 page = carousel.get("currentPage"),
902 numPerPage = carousel.get("numVisible");
904 newPage = parseInt(carousel._firstItem / numPerPage, 10);
905 if (newPage != page) {
906 carousel.setAttributeConfig("currentPage", { value: newPage });
907 carousel.fireEvent(pageChangeEvent, newPage);
910 if (carousel.get("selectOnScroll")) {
911 if (carousel.get("selectedItem") != carousel._selectedItem) {
912 carousel.set("selectedItem", carousel._selectedItem);
916 clearTimeout(carousel._autoPlayTimer);
917 delete carousel._autoPlayTimer;
918 if (carousel.isAutoPlayOn()) {
919 carousel.startAutoPlay();
922 carousel.fireEvent(afterScrollEvent,
923 { first: carousel._firstItem,
929 * Static members and methods of the Carousel component
933 * Return the appropriate Carousel object based on the id associated with
934 * the Carousel element or false if none match.
939 Carousel.getById = function (id) {
940 return instances[id] ? instances[id].object : false;
943 YAHOO.extend(Carousel, YAHOO.util.Element, {
946 * Internal variables used within the Carousel component
950 * Number of rows for a multirow carousel.
958 * Number of cols for a multirow carousel.
966 * The Animation object.
974 * The Carousel element.
976 * @property _carouselEl
982 * The Carousel clipping container element.
990 * The current first index of the Carousel.
992 * @property _firstItem
998 * Does the Carousel element have focus?
1000 * @property _hasFocus
1006 * Is the Carousel rendered already?
1008 * @property _hasRendered
1011 _hasRendered: false,
1014 * Is the animation still in progress?
1016 * @property _isAnimationInProgress
1019 _isAnimationInProgress: false,
1022 * Is the auto-scrolling of Carousel in progress?
1024 * @property _isAutoPlayInProgress
1027 _isAutoPlayInProgress: false,
1030 * The table of items in the Carousel.
1031 * The numItems is the number of items in the Carousel, items being the
1032 * array of items in the Carousel. The size is the size of a single
1033 * item in the Carousel. It is cached here for efficiency (to avoid
1034 * computing the size multiple times).
1036 * @property _itemsTable
1042 * The Carousel navigation buttons.
1044 * @property _navBtns
1050 * The Carousel navigation.
1058 * Status of the next navigation item.
1060 * @property _nextEnabled
1066 * The Carousel pages structure.
1067 * This is an object of the total number of pages and the current page.
1075 * The Carousel pagination structure.
1077 * @property _pagination
1083 * Status of the previous navigation item.
1085 * @property _prevEnabled
1091 * Whether the Carousel size needs to be recomputed or not?
1093 * @property _recomputeSize
1096 _recomputeSize: true,
1099 * Cache the Carousel item attributes.
1101 * @property _itemAttrCache
1107 * CSS classes used by the Carousel component
1113 * The class name of the Carousel navigation buttons.
1116 * @default "yui-carousel-button"
1118 BUTTON: "yui-carousel-button",
1121 * The class name of the Carousel element.
1123 * @property CAROUSEL
1124 * @default "yui-carousel"
1126 CAROUSEL: "yui-carousel",
1129 * The class name of the container of the items in the Carousel.
1131 * @property CAROUSEL_EL
1132 * @default "yui-carousel-element"
1134 CAROUSEL_EL: "yui-carousel-element",
1137 * The class name of the Carousel's container element.
1139 * @property CONTAINER
1140 * @default "yui-carousel-container"
1142 CONTAINER: "yui-carousel-container",
1145 * The class name of the Carousel's container element.
1148 * @default "yui-carousel-content"
1150 CONTENT: "yui-carousel-content",
1153 * The class name of a disabled navigation button.
1155 * @property DISABLED
1156 * @default "yui-carousel-button-disabled"
1158 DISABLED: "yui-carousel-button-disabled",
1161 * The class name of the first Carousel navigation button.
1163 * @property FIRST_NAV
1164 * @default " yui-carousel-first-button"
1166 FIRST_NAV: " yui-carousel-first-button",
1169 * The class name of a first disabled navigation button.
1171 * @property FIRST_NAV_DISABLED
1172 * @default "yui-carousel-first-button-disabled"
1174 FIRST_NAV_DISABLED: "yui-carousel-first-button-disabled",
1177 * The class name of a first page element.
1179 * @property FIRST_PAGE
1180 * @default "yui-carousel-nav-first-page"
1182 FIRST_PAGE: "yui-carousel-nav-first-page",
1185 * The class name of the Carousel navigation button that has focus.
1187 * @property FOCUSSED_BUTTON
1188 * @default "yui-carousel-button-focus"
1190 FOCUSSED_BUTTON: "yui-carousel-button-focus",
1193 * The class name of a horizontally oriented Carousel.
1195 * @property HORIZONTAL
1196 * @default "yui-carousel-horizontal"
1198 HORIZONTAL: "yui-carousel-horizontal",
1201 * The element to be used as the progress indicator when the item
1202 * is still being loaded.
1204 * @property ITEM_LOADING
1205 * @default The progress indicator (spinner) image CSS class
1207 ITEM_LOADING: "yui-carousel-item-loading",
1210 * The class name that will be set if the Carousel adjusts itself
1211 * for a minimum width.
1213 * @property MIN_WIDTH
1214 * @default "yui-carousel-min-width"
1216 MIN_WIDTH: "yui-carousel-min-width",
1219 * The navigation element container class name.
1221 * @property NAVIGATION
1222 * @default "yui-carousel-nav"
1224 NAVIGATION: "yui-carousel-nav",
1227 * The class name of the next Carousel navigation button.
1229 * @property NEXT_NAV
1230 * @default " yui-carousel-next-button"
1232 NEXT_NAV: " yui-carousel-next-button",
1235 * The class name of the next navigation link. This variable is
1236 * not only used for styling, but also for identifying the link
1237 * within the Carousel container.
1239 * @property NEXT_PAGE
1240 * @default "yui-carousel-next"
1242 NEXT_PAGE: "yui-carousel-next",
1245 * The class name for the navigation container for prev/next.
1247 * @property NAV_CONTAINER
1248 * @default "yui-carousel-buttons"
1250 NAV_CONTAINER: "yui-carousel-buttons",
1253 * The class name for an item in the pager UL or dropdown menu.
1255 * @property PAGER_ITEM
1256 * @default "yui-carousel-pager-item"
1258 PAGER_ITEM: "yui-carousel-pager-item",
1261 * The class name for the pagination container
1263 * @property PAGINATION
1264 * @default "yui-carousel-pagination"
1266 PAGINATION: "yui-carousel-pagination",
1269 * The class name of the focussed page navigation. This class is
1270 * specifically used for the ugly focus handling in Opera.
1272 * @property PAGE_FOCUS
1273 * @default "yui-carousel-nav-page-focus"
1275 PAGE_FOCUS: "yui-carousel-nav-page-focus",
1278 * The class name of the previous navigation link. This variable
1279 * is not only used for styling, but also for identifying the link
1280 * within the Carousel container.
1282 * @property PREV_PAGE
1283 * @default "yui-carousel-prev"
1285 PREV_PAGE: "yui-carousel-prev",
1288 * The class name of the selected item.
1290 * @property SELECTED_ITEM
1291 * @default "yui-carousel-item-selected"
1293 SELECTED_ITEM: "yui-carousel-item-selected",
1296 * The class name of the selected paging navigation.
1298 * @property SELECTED_NAV
1299 * @default "yui-carousel-nav-page-selected"
1301 SELECTED_NAV: "yui-carousel-nav-page-selected",
1304 * The class name of a vertically oriented Carousel.
1306 * @property VERTICAL
1307 * @default "yui-carousel-vertical"
1309 VERTICAL: "yui-carousel-vertical",
1312 * The class name of a multirow Carousel.
1314 * @property MULTI_ROW
1315 * @default "yui-carousel-multi-row"
1317 MULTI_ROW: "yui-carousel-multi-row",
1320 * The class name of a row in a multirow Carousel.
1323 * @default "yui-carousel-new-row"
1325 ROW: "yui-carousel-row",
1328 * The class name of a vertical Carousel's container element.
1330 * @property VERTICAL_CONTAINER
1331 * @default "yui-carousel-vertical-container"
1333 VERTICAL_CONTAINER: "yui-carousel-vertical-container",
1336 * The class name of a visible Carousel.
1339 * @default "yui-carousel-visible"
1341 VISIBLE: "yui-carousel-visible"
1346 * Configuration attributes for configuring the Carousel component
1352 * The offset of the first visible item in the Carousel.
1354 * @property FIRST_VISIBLE
1360 * The minimum width of the horizontal Carousel container to support
1361 * the navigation buttons.
1363 * @property HORZ_MIN_WIDTH
1366 HORZ_MIN_WIDTH: 180,
1369 * The maximum number of pager buttons allowed beyond which the UI
1370 * of the pager would be a drop-down of pages instead of buttons.
1372 * @property MAX_PAGER_BUTTONS
1375 MAX_PAGER_BUTTONS: 5,
1378 * The minimum width of the vertical Carousel container to support
1379 * the navigation buttons.
1381 * @property VERT_MIN_WIDTH
1384 VERT_MIN_WIDTH: 115,
1387 * The number of visible items in the Carousel.
1389 * @property NUM_VISIBLE
1397 * Internationalizable strings in the Carousel component
1403 * The content to be used as the progress indicator when the item
1404 * is still being loaded.
1406 * @property ITEM_LOADING_CONTENT
1407 * @default "Loading"
1409 ITEM_LOADING_CONTENT: "Loading",
1412 * The next navigation button name/text.
1414 * @property NEXT_BUTTON_TEXT
1415 * @default "Next Page"
1417 NEXT_BUTTON_TEXT: "Next Page",
1420 * The prefix text for the pager in case the UI is a drop-down.
1422 * @property PAGER_PREFIX_TEXT
1423 * @default "Go to page "
1425 PAGER_PREFIX_TEXT: "Go to page ",
1428 * The previous navigation button name/text.
1430 * @property PREVIOUS_BUTTON_TEXT
1431 * @default "Previous Page"
1433 PREVIOUS_BUTTON_TEXT: "Previous Page"
1438 * Public methods of the Carousel component
1442 * Insert or append an item to the Carousel.
1443 * E.g. if Object: ({content:"Your Content", id:"", className:""}, index)
1447 * @param item {String | Object | HTMLElement} The item to be appended
1448 * to the Carousel. If the parameter is a string, it is assumed to be
1449 * the content of the newly created item. If the parameter is an
1450 * object, it is assumed to supply the content and an optional class
1451 * and an optional id of the newly created item.
1452 * @param index {Number} optional The position to where in the list
1453 * (starts from zero).
1454 * @return {Boolean} Return true on success, false otherwise
1456 addItem: function (item, index) {
1457 var carousel = this,
1462 newIndex, // Add newIndex as workaround for undefined pos
1463 numItems = carousel.get("numItems");
1469 if (JS.isString(item) || item.nodeName) {
1470 content = item.nodeName ? item.innerHTML : item;
1471 } else if (JS.isObject(item)) {
1472 content = item.content;
1477 className = item.className || "";
1478 elId = item.id ? item.id : Dom.generateId();
1480 if (JS.isUndefined(index)) {
1481 carousel._itemsTable.items.push({
1483 className : className,
1486 // Add newIndex as workaround for undefined pos
1487 newIndex = carousel._itemsTable.items.length-1;
1489 if (index < 0 || index > numItems) {
1493 // make sure we splice into the correct position
1494 if(!carousel._itemsTable.items[index]){
1495 carousel._itemsTable.items[index] = undefined;
1499 carousel._itemsTable.items.splice(index, replaceItems, {
1501 className : className,
1505 carousel._itemsTable.numItems++;
1507 if (numItems < carousel._itemsTable.items.length) {
1508 carousel.set("numItems", carousel._itemsTable.items.length);
1511 // Add newPos as workaround for undefined pos
1512 carousel.fireEvent(itemAddedEvent, { pos: index, ev: itemAddedEvent, newPos:newIndex });
1518 * Insert or append multiple items to the Carousel.
1522 * @param items {Array} An array containing an array of new items each linked to the
1523 * index where the insertion should take place.
1524 * E.g. [[{content:'<img/>'}, index1], [{content:'<img/>'}, index2]]
1525 * NOTE: An item at index must already exist.
1526 * @return {Boolean} Return true on success, false otherwise
1528 addItems: function (items) {
1529 var i, n, rv = true;
1531 if (!JS.isArray(items)) {
1535 for (i = 0, n = items.length; i < n; i++) {
1536 if (this.addItem(items[i][0], items[i][1]) === false) {
1545 * Remove focus from the Carousel.
1551 this._carouselEl.blur();
1552 this.fireEvent(blurEvent);
1556 * Clears the items from Carousel.
1558 * @method clearItems
1561 clearItems: function () {
1562 var carousel = this, n = carousel.get("numItems");
1565 if (!carousel.removeItem(0)) {
1568 For dynamic loading, the numItems may be much larger than
1569 the actual number of items in the table. So, set the
1570 numItems to zero, and break out of the loop if the table
1573 if (carousel._itemsTable.numItems === 0) {
1574 carousel.set("numItems", 0);
1580 carousel.fireEvent(allItemsRemovedEvent);
1584 * Set focus on the Carousel.
1589 focus: function () {
1590 var carousel = this,
1593 isSelectionInvisible,
1601 // Don't do anything if the Carousel is not rendered
1602 if (!carousel._hasRendered) {
1606 if (carousel.isAnimating()) {
1607 // this messes up real bad!
1611 selItem = carousel.get("selectedItem");
1612 numVisible = carousel.get("numVisible");
1613 selectOnScroll = carousel.get("selectOnScroll");
1614 selected = (selItem >= 0) ?
1615 carousel.getItem(selItem) : null;
1616 first = carousel.get("firstVisible");
1617 last = first + numVisible - 1;
1618 isSelectionInvisible = (selItem < first || selItem > last);
1619 focusEl = (selected && selected.id) ?
1620 Dom.get(selected.id) : null;
1621 itemsTable = carousel._itemsTable;
1623 if (!selectOnScroll && isSelectionInvisible) {
1624 focusEl = (itemsTable && itemsTable.items &&
1625 itemsTable.items[first]) ?
1626 Dom.get(itemsTable.items[first].id) : null;
1633 // ignore focus errors
1637 carousel.fireEvent(focusEvent);
1641 * Hide the Carousel.
1647 var carousel = this;
1649 if (carousel.fireEvent(beforeHideEvent) !== false) {
1650 carousel.removeClass(carousel.CLASSES.VISIBLE);
1651 carousel.fireEvent(hideEvent);
1656 * Initialize the Carousel.
1660 * @param el {HTMLElement | String} The html element that represents
1661 * the Carousel container.
1662 * @param attrs {Object} The set of configuration attributes for
1663 * creating the Carousel.
1665 init: function (el, attrs) {
1666 var carousel = this,
1667 elId = el, // save for a rainy day
1675 carousel._hasRendered = false;
1676 carousel._navBtns = { prev: [], next: [] };
1677 carousel._pages = { el: null, num: 0, cur: 0 };
1678 carousel._pagination = {};
1679 carousel._itemAttrCache = {};
1681 carousel._itemsTable = { loading: {}, numItems: 0,
1682 items: [], size: 0 };
1685 if (JS.isString(el)) {
1687 } else if (!el.nodeName) {
1691 Carousel.superclass.init.call(carousel, el, attrs);
1693 // check if we're starting somewhere in the middle
1694 selected = carousel.get("selectedItem");
1696 carousel.set("firstVisible",getFirstVisibleForPosition.call(carousel,selected));
1700 if (!el.id) { // in case the HTML element is passed
1701 el.setAttribute("id", Dom.generateId());
1703 parse = carousel._parseCarousel(el);
1705 carousel._createCarousel(elId);
1708 el = carousel._createCarousel(elId);
1712 carousel.initEvents();
1715 carousel._parseCarouselItems();
1718 // add the selected class
1720 setItemSelection.call(carousel,selected,0);
1723 if (!attrs || typeof attrs.isVertical == "undefined") {
1724 carousel.set("isVertical", false);
1727 carousel._parseCarouselNavigation(el);
1728 carousel._navEl = carousel._setupCarouselNavigation();
1730 instances[elId] = { object: carousel };
1731 carousel._loadItems(Math.min(carousel.get("firstVisible")+carousel.get("numVisible"),carousel.get("numItems"))-1);
1735 * Initialize the configuration attributes used to create the Carousel.
1737 * @method initAttributes
1739 * @param attrs {Object} The set of configuration attributes for
1740 * creating the Carousel.
1742 initAttributes: function (attrs) {
1743 var carousel = this;
1745 attrs = attrs || {};
1746 Carousel.superclass.initAttributes.call(carousel, attrs);
1749 * @attribute carouselEl
1750 * @description The type of the Carousel element.
1754 carousel.setAttributeConfig("carouselEl", {
1755 validator : JS.isString,
1756 value : attrs.carouselEl || "OL"
1760 * @attribute carouselItemEl
1761 * @description The type of the list of items within the Carousel.
1765 carousel.setAttributeConfig("carouselItemEl", {
1766 validator : JS.isString,
1767 value : attrs.carouselItemEl || "LI"
1771 * @attribute currentPage
1772 * @description The current page number (read-only.)
1775 carousel.setAttributeConfig("currentPage", {
1781 * @attribute firstVisible
1782 * @description The index to start the Carousel from (indexes begin
1787 carousel.setAttributeConfig("firstVisible", {
1788 method : carousel._setFirstVisible,
1789 validator : carousel._validateFirstVisible,
1791 attrs.firstVisible || carousel.CONFIG.FIRST_VISIBLE
1795 * @attribute selectOnScroll
1796 * @description Set this to true to automatically set focus to
1797 * follow scrolling in the Carousel.
1801 carousel.setAttributeConfig("selectOnScroll", {
1802 validator : JS.isBoolean,
1803 value : attrs.selectOnScroll || true
1807 * @attribute numVisible
1808 * @description The number of visible items in the Carousel's
1813 carousel.setAttributeConfig("numVisible", {
1814 setter : carousel._numVisibleSetter,
1815 method : carousel._setNumVisible,
1816 validator : carousel._validateNumVisible,
1817 value : attrs.numVisible || carousel.CONFIG.NUM_VISIBLE
1821 * @attribute numItems
1822 * @description The number of items in the Carousel.
1825 carousel.setAttributeConfig("numItems", {
1826 method : carousel._setNumItems,
1827 validator : carousel._validateNumItems,
1828 value : carousel._itemsTable.numItems
1832 * @attribute scrollIncrement
1833 * @description The number of items to scroll by for arrow keys.
1837 carousel.setAttributeConfig("scrollIncrement", {
1838 validator : carousel._validateScrollIncrement,
1839 value : attrs.scrollIncrement || 1
1843 * @attribute selectedItem
1844 * @description The index of the selected item.
1847 carousel.setAttributeConfig("selectedItem", {
1848 setter : carousel._selectedItemSetter,
1849 method : carousel._setSelectedItem,
1850 validator : JS.isNumber,
1855 * @attribute revealAmount
1856 * @description The percentage of the item to be revealed on each
1857 * side of the Carousel (before and after the first and last item
1858 * in the Carousel's viewport.)
1862 carousel.setAttributeConfig("revealAmount", {
1863 method : carousel._setRevealAmount,
1864 validator : carousel._validateRevealAmount,
1865 value : attrs.revealAmount || 0
1869 * @attribute isCircular
1870 * @description Set this to true to wrap scrolling of the contents
1875 carousel.setAttributeConfig("isCircular", {
1876 validator : JS.isBoolean,
1877 value : attrs.isCircular || false
1881 * @attribute isVertical
1882 * @description True if the orientation of the Carousel is vertical
1886 carousel.setAttributeConfig("isVertical", {
1887 method : carousel._setOrientation,
1888 validator : JS.isBoolean,
1889 value : attrs.isVertical || false
1893 * @attribute navigation
1894 * @description The set of navigation controls for Carousel
1896 * { prev: null, // the previous navigation element<br>
1897 * next: null } // the next navigation element
1900 carousel.setAttributeConfig("navigation", {
1901 method : carousel._setNavigation,
1902 validator : carousel._validateNavigation,
1904 attrs.navigation || {prev: null,next: null,page: null}
1908 * @attribute animation
1909 * @description The optional animation attributes for the Carousel.
1911 * { speed: 0, // the animation speed (in seconds)<br>
1912 * effect: null } // the animation effect (like
1913 * YAHOO.util.Easing.easeOut)
1916 carousel.setAttributeConfig("animation", {
1917 validator : carousel._validateAnimation,
1918 value : attrs.animation || { speed: 0, effect: null }
1922 * @attribute autoPlay
1923 * @description Set this to time in milli-seconds to have the
1924 * Carousel automatically scroll the contents.
1926 * @deprecated Use autoPlayInterval instead.
1928 carousel.setAttributeConfig("autoPlay", {
1929 validator : JS.isNumber,
1930 value : attrs.autoPlay || 0
1934 * @attribute autoPlayInterval
1935 * @description The delay in milli-seconds for scrolling the
1936 * Carousel during auto-play.
1937 * Note: The startAutoPlay() method needs to be invoked to trigger
1938 * automatic scrolling of Carousel.
1941 carousel.setAttributeConfig("autoPlayInterval", {
1942 validator : JS.isNumber,
1943 value : attrs.autoPlayInterval || 0
1947 * @attribute numPages
1948 * @description The number of pages in the carousel.
1951 carousel.setAttributeConfig("numPages", {
1953 getter : carousel._getNumPages
1957 * @attribute lastVisible
1958 * @description The last item visible in the carousel.
1961 carousel.setAttributeConfig("lastVisible", {
1963 getter : carousel._getLastVisible
1968 * Initialize and bind the event handlers.
1970 * @method initEvents
1973 initEvents: function () {
1974 var carousel = this,
1975 cssClass = carousel.CLASSES,
1978 carousel.on("keydown", carousel._keyboardEventHandler);
1980 carousel.on(afterScrollEvent, syncNavigation);
1982 carousel.on(itemAddedEvent, syncUi);
1984 carousel.on(itemRemovedEvent, syncUi);
1986 carousel.on(itemReplacedEvent, syncUi);
1988 carousel.on(itemSelectedEvent, function () {
1989 if (carousel._hasFocus) {
1994 carousel.on(loadItemsEvent, syncUi);
1996 carousel.on(allItemsRemovedEvent, function (ev) {
1997 carousel.scrollTo(0);
1998 syncNavigation.call(carousel);
1999 syncPagerUi.call(carousel);
2002 carousel.on(pageChangeEvent, syncPagerUi, carousel);
2004 carousel.on(renderEvent, function (ev) {
2005 if (carousel.get("selectedItem") === null ||
2006 carousel.get("selectedItem") <= 0) { //in either case
2007 carousel.set("selectedItem", carousel.get("firstVisible"));
2009 syncNavigation.call(carousel, ev);
2010 syncPagerUi.call(carousel, ev);
2011 carousel._setClipContainerSize();
2015 carousel.on("selectedItemChange", function (ev) {
2016 setItemSelection.call(carousel, ev.newValue, ev.prevValue);
2017 if (ev.newValue >= 0) {
2018 carousel._updateTabIndex(
2019 carousel.getElementForItem(ev.newValue));
2021 carousel.fireEvent(itemSelectedEvent, ev.newValue);
2024 carousel.on(uiUpdateEvent, function (ev) {
2025 syncNavigation.call(carousel, ev);
2026 syncPagerUi.call(carousel, ev);
2029 carousel.on("firstVisibleChange", function (ev) {
2030 if (!carousel.get("selectOnScroll")) {
2031 if (ev.newValue >= 0) {
2032 carousel._updateTabIndex(
2033 carousel.getElementForItem(ev.newValue));
2038 // Handle item selection on mouse click
2039 carousel.on("click", function (ev) {
2040 if (carousel.isAutoPlayOn()) {
2041 carousel.stopAutoPlay();
2043 carousel._itemClickHandler(ev);
2044 carousel._pagerClickHandler(ev);
2047 // Restore the focus on the navigation buttons
2049 Event.onFocus(carousel.get("element"), function (ev, obj) {
2050 var target = Event.getTarget(ev);
2052 if (target && target.nodeName.toUpperCase() == "A" &&
2053 Dom.getAncestorByClassName(target, cssClass.NAVIGATION)) {
2055 Dom.removeClass(focussedLi, cssClass.PAGE_FOCUS);
2057 focussedLi = target.parentNode;
2058 Dom.addClass(focussedLi, cssClass.PAGE_FOCUS);
2061 Dom.removeClass(focussedLi, cssClass.PAGE_FOCUS);
2065 obj._hasFocus = true;
2066 obj._updateNavButtons(Event.getTarget(ev), true);
2069 Event.onBlur(carousel.get("element"), function (ev, obj) {
2070 obj._hasFocus = false;
2071 obj._updateNavButtons(Event.getTarget(ev), false);
2076 * Return true if the Carousel is still animating, or false otherwise.
2078 * @method isAnimating
2079 * @return {Boolean} Return true if animation is still in progress, or
2083 isAnimating: function () {
2084 return this._isAnimationInProgress;
2088 * Return true if the auto-scrolling of Carousel is "on", or false
2091 * @method isAutoPlayOn
2092 * @return {Boolean} Return true if autoPlay is "on", or false
2096 isAutoPlayOn: function () {
2097 return this._isAutoPlayInProgress;
2101 * Return the carouselItemEl at index or null if the index is not
2104 * @method getElementForItem
2105 * @param index {Number} The index of the item to be returned
2106 * @return {Element} Return the item at index or null if not found
2109 getElementForItem: function (index) {
2110 var carousel = this;
2112 if (index < 0 || index >= carousel.get("numItems")) {
2116 if (carousel._itemsTable.items[index]) {
2117 return Dom.get(carousel._itemsTable.items[index].id);
2124 * Return the carouselItemEl for all items in the Carousel.
2126 * @method getElementForItems
2127 * @return {Array} Return all the items
2130 getElementForItems: function () {
2131 var carousel = this, els = [], i;
2133 for (i = 0; i < carousel._itemsTable.numItems; i++) {
2134 els.push(carousel.getElementForItem(i));
2141 * Return the item at index or null if the index is not found.
2144 * @param index {Number} The index of the item to be returned
2145 * @return {Object} Return the item at index or null if not found
2148 getItem: function (index) {
2149 var carousel = this;
2151 if (index < 0 || index >= carousel.get("numItems")) {
2155 if (carousel._itemsTable.numItems > index) {
2156 if (!JS.isUndefined(carousel._itemsTable.items[index])) {
2157 return carousel._itemsTable.items[index];
2165 * Return all items as an array.
2168 * @return {Array} Return all items in the Carousel
2171 getItems: function () {
2172 return this._itemsTable.items;
2176 * Return all loading items as an array.
2178 * @method getLoadingItems
2179 * @return {Array} Return all items that are loading in the Carousel.
2182 getLoadingItems: function () {
2183 return this._itemsTable.loading;
2187 * For a multirow carousel, return the number of rows specified by user.
2190 * @return {Number} Number of rows
2193 getRows: function () {
2198 * For a multirow carousel, return the number of cols specified by user.
2201 * @return {Array} Return all items in the Carousel
2204 getCols: function () {
2209 * Return the position of the Carousel item that has the id "id", or -1
2210 * if the id is not found.
2212 * @method getItemPositionById
2213 * @param index {Number} The index of the item to be returned
2216 getItemPositionById: function (id) {
2217 var carousel = this,
2218 n = carousel.get("numItems"),
2220 items = carousel._itemsTable.items,
2224 item = items[i] || {};
2235 * Return all visible items as an array.
2237 * @method getVisibleItems
2238 * @return {Array} The array of visible items
2241 getVisibleItems: function () {
2242 var carousel = this,
2243 i = carousel.get("firstVisible"),
2244 n = i + carousel.get("numVisible"),
2248 r.push(carousel.getElementForItem(i));
2256 * Remove an item at index from the Carousel.
2258 * @method removeItem
2260 * @param index {Number} The position to where in the list (starts from
2262 * @return {Boolean} Return true on success, false otherwise
2264 removeItem: function (index) {
2265 var carousel = this,
2267 num = carousel.get("numItems");
2269 if (index < 0 || index >= num) {
2273 item = carousel._itemsTable.items.splice(index, 1);
2274 if (item && item.length == 1) {
2275 carousel._itemsTable.numItems--;
2276 carousel.set("numItems", num - 1);
2278 carousel.fireEvent(itemRemovedEvent,
2279 { item: item[0], pos: index, ev: itemRemovedEvent });
2287 * Replace an item at index witin Carousel.
2289 * @method replaceItem
2291 * @param item {String | Object | HTMLElement} The item to be appended
2292 * to the Carousel. If the parameter is a string, it is assumed to be
2293 * the content of the newly created item. If the parameter is an
2294 * object, it is assumed to supply the content and an optional class
2295 * and an optional id of the newly created item.
2296 * @param index {Number} The position to where in the list (starts from
2298 * @return {Boolean} Return true on success, false otherwise
2300 replaceItem: function (item, index) {
2301 var carousel = this,
2305 numItems = carousel.get("numItems"),
2313 if (JS.isString(item) || item.nodeName) {
2314 content = item.nodeName ? item.innerHTML : item;
2315 } else if (JS.isObject(item)) {
2316 content = item.content;
2321 if (JS.isUndefined(index)) {
2324 if (index < 0 || index >= numItems) {
2328 oel = carousel._itemsTable.items[index];
2330 oel = carousel._itemsTable.loading[index];
2331 carousel._itemsTable.items[index] = undefined;
2334 carousel._itemsTable.items.splice(index, 1, {
2336 className : item.className || "",
2337 id : Dom.generateId()
2340 el = carousel._itemsTable.items[index];
2342 carousel.fireEvent(itemReplacedEvent,
2343 { newItem: el, oldItem: oel, pos: index, ev: itemReplacedEvent });
2349 * Replace multiple items at specified indexes.
2350 * NOTE: item at index must already exist.
2352 * @method replaceItems
2354 * @param items {Array} An array containing an array of replacement items each linked to the
2355 * index where the substitution should take place.
2356 * E.g. [[{content:'<img/>'}, index1], [{content:'<img/>'}, index2]]
2357 * @return {Boolean} Return true on success, false otherwise
2359 replaceItems: function (items) {
2360 var i, n, rv = true;
2362 if (!JS.isArray(items)) {
2366 for (i = 0, n = items.length; i < n; i++) {
2367 if (this.replaceItem(items[i][0], items[i][1]) === false) {
2376 * Render the Carousel.
2380 * @param appendTo {HTMLElement | String} The element to which the
2381 * Carousel should be appended prior to rendering.
2382 * @return {Boolean} Status of the operation
2384 render: function (appendTo) {
2385 var carousel = this,
2386 cssClass = carousel.CLASSES,
2387 rows = carousel._rows;
2389 carousel.addClass(cssClass.CAROUSEL);
2391 if (!carousel._clipEl) {
2392 carousel._clipEl = carousel._createCarouselClip();
2393 carousel._clipEl.appendChild(carousel._carouselEl);
2397 carousel.appendChild(carousel._clipEl);
2398 carousel.appendTo(appendTo);
2400 if (!Dom.inDocument(carousel.get("element"))) {
2403 carousel.appendChild(carousel._clipEl);
2407 Dom.addClass(carousel._clipEl, cssClass.MULTI_ROW);
2410 if (carousel.get("isVertical")) {
2411 carousel.addClass(cssClass.VERTICAL);
2413 carousel.addClass(cssClass.HORIZONTAL);
2416 if (carousel.get("numItems") < 1) {
2420 carousel._refreshUi();
2426 * Scroll the Carousel by an item backward.
2428 * @method scrollBackward
2431 scrollBackward: function () {
2432 var carousel = this;
2433 carousel.scrollTo(carousel._firstItem -
2434 carousel.get("scrollIncrement"));
2438 * Scroll the Carousel by an item forward.
2440 * @method scrollForward
2443 scrollForward: function () {
2444 var carousel = this;
2445 carousel.scrollTo(carousel._firstItem +
2446 carousel.get("scrollIncrement"));
2450 * Scroll the Carousel by a page backward.
2452 * @method scrollPageBackward
2455 scrollPageBackward: function () {
2456 var carousel = this,
2457 isVertical = carousel.get("isVertical"),
2458 cols = carousel._cols,
2459 item = carousel._firstItem - carousel.get("numVisible");
2461 if (item < 0) { // only account for multi-row when scrolling backwards from item 0
2463 item = carousel._firstItem - cols;
2467 if (carousel.get("selectOnScroll")) {
2468 carousel._selectedItem = carousel._getSelectedItem(item);
2471 carousel.scrollTo(item);
2475 * Scroll the Carousel by a page forward.
2477 * @method scrollPageForward
2480 scrollPageForward: function () {
2481 var carousel = this,
2482 item = carousel._firstItem + carousel.get("numVisible");
2484 if (item > carousel.get("numItems")) {
2488 if (carousel.get("selectOnScroll")) {
2489 carousel._selectedItem = carousel._getSelectedItem(item);
2492 carousel.scrollTo(item);
2496 * Scroll the Carousel to make the item the first visible item.
2500 * @param item Number The index of the element to position at.
2501 * @param dontSelect Boolean True if select should be avoided
2503 scrollTo: function (item, dontSelect) {
2504 var carousel = this, animate, animCfg, isCircular, isVertical,
2505 rows, delta, direction, firstItem, lastItem, itemsPerRow,
2506 itemsPerCol, numItems, numPerPage, offset, page, rv, sentinel,
2507 index, stopAutoScroll,
2508 itemsTable = carousel._itemsTable,
2509 items = itemsTable.items,
2510 loading = itemsTable.loading;
2512 if (JS.isUndefined(item) || item == carousel._firstItem ||
2513 carousel.isAnimating()) {
2514 return; // nothing to do!
2517 animCfg = carousel.get("animation");
2518 isCircular = carousel.get("isCircular");
2519 isVertical = carousel.get("isVertical");
2520 itemsPerRow = carousel._cols;
2521 itemsPerCol = carousel._rows;
2522 firstItem = carousel._firstItem;
2523 numItems = carousel.get("numItems");
2524 numPerPage = carousel.get("numVisible");
2525 page = carousel.get("currentPage");
2527 stopAutoScroll = function () {
2528 if (carousel.isAutoPlayOn()) {
2529 carousel.stopAutoPlay();
2535 item = numItems + item;
2537 stopAutoScroll.call(carousel);
2540 } else if (numItems > 0 && item > numItems - 1) {
2542 if (carousel.get("isCircular")) {
2543 item = numItems - item;
2545 stopAutoScroll.call(carousel);
2554 direction = (carousel._firstItem > item) ? "backward" : "forward";
2556 sentinel = firstItem + numPerPage;
2557 sentinel = (sentinel > numItems - 1) ? numItems - 1 : sentinel;
2558 rv = carousel.fireEvent(beforeScrollEvent,
2559 { dir: direction, first: firstItem, last: sentinel });
2560 if (rv === false) { // scrolling is prevented
2564 carousel.fireEvent(beforePageChangeEvent, { page: page });
2566 // call loaditems to check if we have all the items to display
2567 lastItem = item + numPerPage - 1;
2568 carousel._loadItems(lastItem > numItems-1 ? numItems-1 : lastItem);
2570 // Calculate the delta relative to the first item, the delta is
2575 // offset calculations for multirow Carousel
2577 delta = parseInt(delta / itemsPerRow, 10);
2579 delta = parseInt(delta / itemsPerCol, 10);
2583 // adjust for items not yet loaded
2585 while (delta < 0 && index < item+numPerPage-1 && index < numItems) {
2586 if (!items[index] && !loading[index]) {
2589 index += itemsPerCol ? itemsPerCol : 1;
2592 carousel._firstItem = item;
2593 carousel.set("firstVisible", item);
2596 sentinel = item + numPerPage;
2597 sentinel = (sentinel > numItems - 1) ? numItems - 1 : sentinel;
2599 offset = getScrollOffset.call(carousel, delta);
2601 animate = animCfg.speed > 0;
2604 carousel._animateAndSetCarouselOffset(offset, item, sentinel,
2607 carousel._setCarouselOffset(offset);
2608 updateStateAfterScroll.call(carousel, item, sentinel);
2613 * Get the page an item is on within carousel.
2615 * @method getPageForItem
2617 * @param index {Number} Index of item
2618 * @return {Number} Page item is on
2620 getPageForItem : function(item) {
2622 (item+1) / parseInt(this.get("numVisible"),10)
2627 * Get the first visible item's index on any given page.
2629 * @method getFirstVisibleOnpage
2631 * @param page {Number} Page
2632 * @return {Number} First item's index
2634 getFirstVisibleOnPage : function(page) {
2635 return (page - 1) * this.get("numVisible");
2639 * Select the previous item in the Carousel.
2641 * @method selectPreviousItem
2644 selectPreviousItem: function () {
2645 var carousel = this,
2647 selected = carousel.get("selectedItem");
2649 if (selected == this._firstItem) {
2650 newpos = selected - carousel.get("numVisible");
2651 carousel._selectedItem = carousel._getSelectedItem(selected-1);
2652 carousel.scrollTo(newpos);
2654 newpos = carousel.get("selectedItem") -
2655 carousel.get("scrollIncrement");
2656 carousel.set("selectedItem",carousel._getSelectedItem(newpos));
2661 * Select the next item in the Carousel.
2663 * @method selectNextItem
2666 selectNextItem: function () {
2667 var carousel = this, newpos = 0;
2669 newpos = carousel.get("selectedItem") +
2670 carousel.get("scrollIncrement");
2671 carousel.set("selectedItem", carousel._getSelectedItem(newpos));
2675 * Display the Carousel.
2681 var carousel = this,
2682 cssClass = carousel.CLASSES;
2684 if (carousel.fireEvent(beforeShowEvent) !== false) {
2685 carousel.addClass(cssClass.VISIBLE);
2686 carousel.fireEvent(showEvent);
2691 * Start auto-playing the Carousel.
2693 * @method startAutoPlay
2696 startAutoPlay: function () {
2697 var carousel = this, timer;
2699 if (JS.isUndefined(carousel._autoPlayTimer)) {
2700 timer = carousel.get("autoPlayInterval");
2704 carousel._isAutoPlayInProgress = true;
2705 carousel.fireEvent(startAutoPlayEvent);
2706 carousel._autoPlayTimer = setTimeout(function () {
2707 carousel._autoScroll();
2713 * Stop auto-playing the Carousel.
2715 * @method stopAutoPlay
2718 stopAutoPlay: function () {
2719 var carousel = this;
2721 if (!JS.isUndefined(carousel._autoPlayTimer)) {
2722 clearTimeout(carousel._autoPlayTimer);
2723 delete carousel._autoPlayTimer;
2724 carousel._isAutoPlayInProgress = false;
2725 carousel.fireEvent(stopAutoPlayEvent);
2730 * Update interface's pagination data within a registered template.
2732 * @method updatePagination
2735 updatePagination: function () {
2736 var carousel = this,
2737 pagination = carousel._pagination;
2738 if(!pagination.el){ return false; }
2740 var numItems = carousel.get('numItems'),
2741 numVisible = carousel.get('numVisible'),
2742 firstVisible = carousel.get('firstVisible')+1,
2743 currentPage = carousel.get('currentPage')+1,
2744 numPages = carousel.get('numPages'),
2746 'numVisible' : numVisible,
2747 'numPages' : numPages,
2748 'numItems' : numItems,
2749 'selectedItem' : carousel.get('selectedItem')+1,
2750 'currentPage' : currentPage,
2751 'firstVisible' : firstVisible,
2752 'lastVisible' : carousel.get("lastVisible")+1
2754 cb = pagination.callback || {},
2755 scope = cb.scope && cb.obj ? cb.obj : carousel;
2757 pagination.el.innerHTML = JS.isFunction(cb.fn) ? cb.fn.apply(scope, [pagination.template, replacements]) : YAHOO.lang.substitute(pagination.template, replacements);
2761 * Register carousels pagination template, append to interface, and populate.
2763 * @method registerPagination
2764 * @param template {String} Pagination template as passed to lang.substitute
2767 registerPagination: function (tpl, pos, cb) {
2768 var carousel = this;
2770 carousel._pagination.template = tpl;
2771 carousel._pagination.callback = cb || {};
2773 if(!carousel._pagination.el){
2774 carousel._pagination.el = createElement('DIV', {className:carousel.CLASSES.PAGINATION});
2776 if(pos == "before"){
2777 carousel._navEl.insertBefore(carousel._pagination.el, carousel._navEl.firstChild);
2779 carousel._navEl.appendChild(carousel._pagination.el);
2782 carousel.on('itemSelected', carousel.updatePagination);
2783 carousel.on('pageChange', carousel.updatePagination);
2786 carousel.updatePagination();
2790 * Return the string representation of the Carousel.
2796 toString: function () {
2797 return WidgetName + (this.get ? " (#" + this.get("id") + ")" : "");
2801 * Protected methods of the Carousel component
2805 * Set the Carousel offset to the passed offset after animating.
2807 * @method _animateAndSetCarouselOffset
2808 * @param {Integer} offset The offset to which the Carousel has to be
2810 * @param {Integer} item The index to which the Carousel will scroll.
2811 * @param {Integer} sentinel The last element in the view port.
2814 _animateAndSetCarouselOffset: function (offset, item, sentinel) {
2815 var carousel = this,
2816 animCfg = carousel.get("animation"),
2819 if (carousel.get("isVertical")) {
2820 animObj = new YAHOO.util.Motion(carousel._carouselEl,
2821 { top: { to: offset } },
2822 animCfg.speed, animCfg.effect);
2824 animObj = new YAHOO.util.Motion(carousel._carouselEl,
2825 { left: { to: offset } },
2826 animCfg.speed, animCfg.effect);
2829 carousel._isAnimationInProgress = true;
2830 animObj.onComplete.subscribe(carousel._animationCompleteHandler,
2831 { scope: carousel, item: item,
2837 * Handle the animation complete event.
2839 * @method _animationCompleteHandler
2840 * @param {Event} ev The event.
2841 * @param {Array} p The event parameters.
2842 * @param {Object} o The object that has the state of the Carousel
2845 _animationCompleteHandler: function (ev, p, o) {
2846 o.scope._isAnimationInProgress = false;
2847 updateStateAfterScroll.call(o.scope, o.item, o.last);
2851 * Automatically scroll the contents of the Carousel.
2852 * @method _autoScroll
2855 _autoScroll: function() {
2856 var carousel = this,
2857 currIndex = carousel._firstItem,
2860 if (currIndex >= carousel.get("numItems") - 1) {
2861 if (carousel.get("isCircular")) {
2864 carousel.stopAutoPlay();
2867 index = currIndex + carousel.get("numVisible");
2870 carousel._selectedItem = carousel._getSelectedItem(index);
2871 carousel.scrollTo.call(carousel, index);
2875 * Create the Carousel.
2877 * @method createCarousel
2878 * @param elId {String} The id of the element to be created
2881 _createCarousel: function (elId) {
2882 var carousel = this,
2883 cssClass = carousel.CLASSES,
2887 el = createElement("DIV", {
2888 className : cssClass.CAROUSEL,
2893 if (!carousel._carouselEl) {
2894 carousel._carouselEl=createElement(carousel.get("carouselEl"),
2895 { className: cssClass.CAROUSEL_EL });
2902 * Create the Carousel clip container.
2904 * @method createCarouselClip
2907 _createCarouselClip: function () {
2908 return createElement("DIV", { className: this.CLASSES.CONTENT });
2912 * Create the Carousel item.
2914 * @method createCarouselItem
2915 * @param obj {Object} The attributes of the element to be created
2918 _createCarouselItem: function (obj) {
2919 var attr, carousel = this,
2920 styles = getCarouselItemPosition.call(carousel, obj.pos);
2922 return createElement(carousel.get("carouselItemEl"), {
2923 className : obj.className,
2924 styles : obj.styles,
2925 content : obj.content,
2931 * Return a valid item for a possibly out of bounds index considering
2932 * the isCircular property.
2934 * @method _getValidIndex
2935 * @param index {Number} The index of the item to be returned
2936 * @return {Object} Return a valid item index
2939 _getValidIndex: function (index) {
2940 var carousel = this,
2941 isCircular = carousel.get("isCircular"),
2942 numItems = carousel.get("numItems"),
2943 numVisible = carousel.get("numVisible"),
2944 sentinel = numItems - 1;
2947 index = isCircular ?
2948 Math.ceil(numItems/numVisible)*numVisible + index : 0;
2949 } else if (index > sentinel) {
2950 index = isCircular ? 0 : sentinel;
2957 * Get the value for the selected item.
2959 * @method _getSelectedItem
2960 * @param val {Number} The new value for "selected" item
2961 * @return {Number} The new value that would be set
2964 _getSelectedItem: function (val) {
2965 var carousel = this,
2966 isCircular = carousel.get("isCircular"),
2967 numItems = carousel.get("numItems"),
2968 sentinel = numItems - 1;
2972 val = numItems + val;
2974 val = carousel.get("selectedItem");
2976 } else if (val > sentinel) {
2978 val = val - numItems;
2980 val = carousel.get("selectedItem");
2987 * The "click" handler for the item.
2989 * @method _itemClickHandler
2990 * @param {Event} ev The event object
2993 _itemClickHandler: function (ev) {
2994 var carousel = this,
2995 carouselItem = carousel.get("carouselItemEl"),
2996 container = carousel.get("element"),
2999 target = Event.getTarget(ev),
3000 tag = target.tagName.toUpperCase();
3002 if(tag === "INPUT" ||
3004 tag === "TEXTAREA") {
3008 while (target && target != container &&
3009 target.id != carousel._carouselEl) {
3010 el = target.nodeName;
3011 if (el.toUpperCase() == carouselItem) {
3014 target = target.parentNode;
3017 if ((item = carousel.getItemPositionById(target.id)) >= 0) {
3018 carousel.set("selectedItem", carousel._getSelectedItem(item));
3024 * The keyboard event handler for Carousel.
3026 * @method _keyboardEventHandler
3027 * @param ev {Event} The event that is being handled.
3030 _keyboardEventHandler: function (ev) {
3031 var carousel = this,
3032 key = Event.getCharCode(ev),
3033 target = Event.getTarget(ev),
3036 // do not mess while animation is in progress or naving via select
3037 if (carousel.isAnimating() || target.tagName.toUpperCase() === "SELECT") {
3042 case 0x25: // left arrow
3043 case 0x26: // up arrow
3044 carousel.selectPreviousItem();
3047 case 0x27: // right arrow
3048 case 0x28: // down arrow
3049 carousel.selectNextItem();
3052 case 0x21: // page-up
3053 carousel.scrollPageBackward();
3056 case 0x22: // page-down
3057 carousel.scrollPageForward();
3063 if (carousel.isAutoPlayOn()) {
3064 carousel.stopAutoPlay();
3066 Event.preventDefault(ev);
3071 * The load the required set of items that are needed for display.
3073 * @method _loadItems
3076 _loadItems: function(last) {
3077 var carousel = this,
3078 numItems = carousel.get("numItems"),
3079 numVisible = carousel.get("numVisible"),
3080 reveal = carousel.get("revealAmount"),
3081 first = carousel._itemsTable.items.length,
3082 lastVisible = carousel.get("lastVisible");
3084 // adjust if going backwards
3085 if(first > last && last+1 >= numVisible){
3086 // need to get first a bit differently for the last page
3087 first = last % numVisible || last == lastVisible ? last - last % numVisible : last - numVisible + 1;
3090 if(reveal && last < numItems - 1){ last++; }
3092 if (last >= first && (!carousel.getItem(first) || !carousel.getItem(last))) {
3093 carousel.fireEvent(loadItemsEvent, {
3094 ev: loadItemsEvent, first: first, last: last,
3095 num: last - first + 1
3102 * The "onchange" handler for select box pagination.
3104 * @method _pagerChangeHandler
3105 * @param {Event} ev The event object
3108 _pagerChangeHandler: function (ev) {
3109 var carousel = this,
3110 target = Event.getTarget(ev),
3111 page = target.value,
3115 item = carousel.getFirstVisibleOnPage(page);
3116 carousel._selectedItem = item;
3117 carousel.scrollTo(item);
3122 * The "click" handler for anchor pagination.
3124 * @method _pagerClickHandler
3125 * @param {Event} ev The event object
3128 _pagerClickHandler: function (ev) {
3129 var carousel = this,
3130 css = carousel.CLASSES,
3131 target = Event.getTarget(ev),
3132 elNode = target.nodeName.toUpperCase(),
3138 if (Dom.hasClass(target, css.PAGER_ITEM) || Dom.hasClass(target.parentNode, css.PAGER_ITEM)) {
3139 if (elNode == "EM") {
3140 target = target.parentNode;// item is an em and not an anchor (when text is visible)
3143 stringIndex = val.lastIndexOf("#");
3144 page = parseInt(val.substring(stringIndex+1), 10);
3146 item = carousel.getFirstVisibleOnPage(page);
3147 carousel._selectedItem = item;
3148 carousel.scrollTo(item);
3151 Event.preventDefault(ev);
3156 * Find the Carousel within a container. The Carousel is identified by
3157 * the first element that matches the carousel element tag or the
3158 * element that has the Carousel class.
3160 * @method parseCarousel
3161 * @param parent {HTMLElement} The parent element to look under
3162 * @return {Boolean} True if Carousel is found, false otherwise
3165 _parseCarousel: function (parent) {
3166 var carousel = this, child, cssClass, domEl, found, node;
3168 cssClass = carousel.CLASSES;
3169 domEl = carousel.get("carouselEl");
3172 for (child = parent.firstChild; child; child = child.nextSibling) {
3173 if (child.nodeType == 1) {
3174 node = child.nodeName;
3175 if (node.toUpperCase() == domEl) {
3176 carousel._carouselEl = child;
3177 Dom.addClass(carousel._carouselEl,
3178 carousel.CLASSES.CAROUSEL_EL);
3188 * Find the items within the Carousel and add them to the items table.
3189 * A Carousel item is identified by elements that matches the carousel
3192 * @method parseCarouselItems
3195 _parseCarouselItems: function () {
3196 var carousel = this,
3197 cssClass = carousel.CLASSES,
3204 index = carousel.get("firstVisible"),
3205 parent = carousel._carouselEl;
3207 rows = carousel._rows;
3208 domItemEl = carousel.get("carouselItemEl");
3210 for (child = parent.firstChild; child; child = child.nextSibling) {
3211 if (child.nodeType == 1) {
3212 node = child.nodeName;
3213 if (node.toUpperCase() == domItemEl) {
3217 elId = Dom.generateId();
3218 child.setAttribute("id", elId);
3220 carousel.addItem(child,index);
3228 * Find the Carousel navigation within a container. The navigation
3229 * elements need to match the carousel navigation class names.
3231 * @method parseCarouselNavigation
3232 * @param parent {HTMLElement} The parent element to look under
3233 * @return {Boolean} True if at least one is found, false otherwise
3236 _parseCarouselNavigation: function (parent) {
3237 var carousel = this,
3239 cssClass = carousel.CLASSES,
3246 nav = Dom.getElementsByClassName(cssClass.PREV_PAGE, "*", parent);
3247 if (nav.length > 0) {
3249 if (nav.hasOwnProperty(i)) {
3251 if (el.nodeName == "INPUT" ||
3252 el.nodeName == "BUTTON" ||
3253 el.nodeName == "A") {// Anchor support in Nav (for SEO)
3254 carousel._navBtns.prev.push(el);
3256 j = el.getElementsByTagName("INPUT");
3257 if (JS.isArray(j) && j.length > 0) {
3258 carousel._navBtns.prev.push(j[0]);
3260 j = el.getElementsByTagName("BUTTON");
3261 if (JS.isArray(j) && j.length > 0) {
3262 carousel._navBtns.prev.push(j[0]);
3268 cfg = { prev: nav };
3271 nav = Dom.getElementsByClassName(cssClass.NEXT_PAGE, "*", parent);
3272 if (nav.length > 0) {
3274 if (nav.hasOwnProperty(i)) {
3276 if (el.nodeName == "INPUT" ||
3277 el.nodeName == "BUTTON" ||
3278 el.nodeName == "A") {// Anchor support in Nav (for SEO)
3279 carousel._navBtns.next.push(el);
3281 j = el.getElementsByTagName("INPUT");
3282 if (JS.isArray(j) && j.length > 0) {
3283 carousel._navBtns.next.push(j[0]);
3285 j = el.getElementsByTagName("BUTTON");
3286 if (JS.isArray(j) && j.length > 0) {
3287 carousel._navBtns.next.push(j[0]);
3296 cfg = { next: nav };
3301 carousel.set("navigation", cfg);
3309 * Refresh the widget UI if it is not already rendered, on first item
3312 * @method _refreshUi
3315 _refreshUi: function () {
3316 var carousel = this, i, isVertical = carousel.get("isVertical"), firstVisible = carousel.get("firstVisible"), item, n, rsz, sz;
3318 if (carousel._itemsTable.numItems < 1) {
3322 sz = getCarouselItemSize.call(carousel,
3323 isVertical ? "height" : "width");
3324 // This fixes the widget to auto-adjust height/width for absolute
3325 // positioned children.
3326 item = carousel._itemsTable.items[firstVisible].id;
3328 sz = isVertical ? getStyle(item, "width") :
3329 getStyle(item, "height");
3331 Dom.setStyle(carousel._carouselEl,
3332 isVertical ? "width" : "height", sz + "px");
3334 // Set the rendered state appropriately.
3335 carousel._hasRendered = true;
3336 carousel.fireEvent(renderEvent);
3340 * Set the Carousel offset to the passed offset.
3342 * @method _setCarouselOffset
3345 _setCarouselOffset: function (offset) {
3346 var carousel = this, which;
3348 which = carousel.get("isVertical") ? "top" : "left";
3349 Dom.setStyle(carousel._carouselEl, which, offset + "px");
3353 * Setup/Create the Carousel navigation element (if needed).
3355 * @method _setupCarouselNavigation
3358 _setupCarouselNavigation: function () {
3359 var carousel = this,
3360 btn, cfg, cssClass, nav, navContainer, nextButton, prevButton;
3362 cssClass = carousel.CLASSES;
3364 // TODO: can the _navBtns be tested against instead?
3365 navContainer = Dom.getElementsByClassName(cssClass.NAVIGATION,
3366 "DIV", carousel.get("element"));
3368 if (navContainer.length === 0) {
3369 navContainer = createElement("DIV",
3370 { className: cssClass.NAVIGATION });
3371 carousel.insertBefore(navContainer,
3372 Dom.getFirstChild(carousel.get("element")));
3374 navContainer = navContainer[0];
3377 carousel._pages.el = createElement("UL");
3378 navContainer.appendChild(carousel._pages.el);
3380 nav = carousel.get("navigation");
3381 if (JS.isString(nav.prev) || JS.isArray(nav.prev)) {
3382 if (JS.isString(nav.prev)) {
3383 nav.prev = [nav.prev];
3385 for (btn in nav.prev) {
3386 if (nav.prev.hasOwnProperty(btn)) {
3387 carousel._navBtns.prev.push(Dom.get(nav.prev[btn]));
3391 // TODO: separate method for creating a navigation button
3392 prevButton = createElement("SPAN",
3393 { className: cssClass.BUTTON + cssClass.FIRST_NAV });
3395 Dom.setStyle(prevButton, "visibility", "visible");
3396 btn = Dom.generateId();
3397 prevButton.innerHTML = "<button type=\"button\" " +
3398 "id=\"" + btn + "\" name=\"" +
3399 carousel.STRINGS.PREVIOUS_BUTTON_TEXT + "\">" +
3400 carousel.STRINGS.PREVIOUS_BUTTON_TEXT + "</button>";
3401 navContainer.appendChild(prevButton);
3403 carousel._navBtns.prev = [btn];
3404 cfg = { prev: [prevButton] };
3407 if (JS.isString(nav.next) || JS.isArray(nav.next)) {
3408 if (JS.isString(nav.next)) {
3409 nav.next = [nav.next];
3411 for (btn in nav.next) {
3412 if (nav.next.hasOwnProperty(btn)) {
3413 carousel._navBtns.next.push(Dom.get(nav.next[btn]));
3417 // TODO: separate method for creating a navigation button
3418 nextButton = createElement("SPAN",
3419 { className: cssClass.BUTTON + cssClass.NEXT_NAV });
3421 Dom.setStyle(nextButton, "visibility", "visible");
3422 btn = Dom.generateId();
3423 nextButton.innerHTML = "<button type=\"button\" " +
3424 "id=\"" + btn + "\" name=\"" +
3425 carousel.STRINGS.NEXT_BUTTON_TEXT + "\">" +
3426 carousel.STRINGS.NEXT_BUTTON_TEXT + "</button>";
3427 navContainer.appendChild(nextButton);
3429 carousel._navBtns.next = [btn];
3431 cfg.next = [nextButton];
3433 cfg = { next: [nextButton] };
3438 carousel.set("navigation", cfg);
3441 return navContainer;
3445 * Set the clip container size (based on the new numVisible value).
3447 * @method _setClipContainerSize
3448 * @param clip {HTMLElement} The clip container element.
3449 * @param num {Number} optional The number of items per page.
3452 _setClipContainerSize: function (clip, num) {
3453 var carousel = this,
3454 isVertical = carousel.get("isVertical"),
3455 rows = carousel._rows,
3456 cols = carousel._cols,
3457 reveal = carousel.get("revealAmount"),
3458 itemHeight = getCarouselItemSize.call(carousel, "height"),
3459 itemWidth = getCarouselItemSize.call(carousel, "width"),
3463 clip = clip || carousel._clipEl;
3466 containerHeight = itemHeight * rows;
3467 containerWidth = itemWidth * cols;
3469 num = num || carousel.get("numVisible");
3471 containerHeight = itemHeight * num;
3473 containerWidth = itemWidth * num;
3477 // TODO: try to re-use the _hasRendered indicator
3479 carousel._recomputeSize = (containerHeight === 0); // bleh!
3480 if (carousel._recomputeSize) {
3481 carousel._hasRendered = false;
3482 return; // no use going further, bail out!
3485 reveal = getRevealSize.call(carousel);
3487 containerHeight += (reveal * 2);
3489 containerWidth += (reveal * 2);
3493 containerHeight += getDimensions(carousel._carouselEl,"height");
3494 Dom.setStyle(clip, "height", containerHeight + "px");
3495 // For multi-row Carousel
3497 containerWidth += getDimensions(carousel._carouselEl,
3499 Dom.setStyle(clip, "width", containerWidth + (0) + "px");
3502 containerWidth += getDimensions(carousel._carouselEl, "width");
3503 Dom.setStyle(clip, "width", containerWidth + "px");
3504 // For multi-row Carousel
3506 containerHeight += getDimensions(carousel._carouselEl,
3508 Dom.setStyle(clip, "height", containerHeight + "px");
3512 carousel._setContainerSize(clip); // adjust the container size too
3516 * Set the container size.
3518 * @method _setContainerSize
3519 * @param clip {HTMLElement} The clip container element.
3520 * @param attr {String} Either set the height or width.
3523 _setContainerSize: function (clip, attr) {
3524 var carousel = this,
3525 config = carousel.CONFIG,
3526 cssClass = carousel.CLASSES,
3532 isVertical = carousel.get("isVertical");
3533 rows = carousel._rows;
3534 cols = carousel._cols;
3535 clip = clip || carousel._clipEl;
3536 attr = attr || (isVertical ? "height" : "width");
3537 size = parseFloat(Dom.getStyle(clip, attr), 10);
3539 size = JS.isNumber(size) ? size : 0;
3542 size += getDimensions(carousel._carouselEl, "height") +
3543 getStyle(carousel._navEl, "height");
3545 size += getDimensions(carousel._carouselEl, "width");
3549 if (size < config.HORZ_MIN_WIDTH) {
3550 size = config.HORZ_MIN_WIDTH;
3551 carousel.addClass(cssClass.MIN_WIDTH);
3554 carousel.setStyle(attr, size + "px");
3556 // Additionally the width of the container should be set for
3557 // the vertical Carousel
3559 size = getCarouselItemSize.call(carousel, "width");
3563 Dom.setStyle(carousel._carouselEl, "width", size + "px");// Bug fix for vertical carousel (goes in conjunction with .yui-carousel-element {... 3200px removed from styles), and allows for multirows in IEs).
3564 if (size < config.VERT_MIN_WIDTH) {
3565 size = config.VERT_MIN_WIDTH;
3566 carousel.addClass(cssClass.MIN_WIDTH);// set a min width on vertical carousel, don't see why this shouldn't always be set...
3568 carousel.setStyle("width", size + "px");
3571 size = getCarouselItemSize.call(carousel, "height");
3573 Dom.setStyle(carousel._carouselEl, "height", size + "px");
3579 * Set the value for the Carousel's first visible item.
3581 * @method _setFirstVisible
3582 * @param val {Number} The new value for firstVisible
3583 * @return {Number} The new value that would be set
3586 _setFirstVisible: function (val) {
3587 var carousel = this;
3589 if (val >= 0 && val < carousel.get("numItems")) {
3590 carousel.scrollTo(val);
3592 val = carousel.get("firstVisible");
3598 * Set the value for the Carousel's navigation.
3600 * @method _setNavigation
3601 * @param cfg {Object} The navigation configuration
3602 * @return {Object} The new value that would be set
3605 _setNavigation: function (cfg) {
3606 var carousel = this;
3609 Event.on(cfg.prev, "click", scrollPageBackward, carousel);
3612 Event.on(cfg.next, "click", scrollPageForward, carousel);
3617 * Clip the container size every time numVisible is set.
3619 * @method _setNumVisible
3620 * @param val {Number} The new value for numVisible
3621 * @return {Number} The new value that would be set
3624 _setNumVisible: function (val) { // TODO: _setNumVisible should just be reserved for setting numVisible.
3625 var carousel = this;
3627 carousel._setClipContainerSize(carousel._clipEl, val);
3631 * Set the value for the number of visible items in the Carousel.
3633 * @method _numVisibleSetter
3634 * @param val {Number} The new value for numVisible
3635 * @return {Number} The new value that would be set
3638 _numVisibleSetter: function (val) {
3639 var carousel = this,
3642 if(JS.isArray(val)) {
3643 carousel._cols = val[0];
3644 carousel._rows = val[1];
3645 numVisible = val[0] * val[1];
3651 * Set the value for selectedItem.
3653 * @method _selectedItemSetter
3654 * @param val {Number} The new value for selectedItem
3655 * @return {Number} The new value that would be set
3658 _selectedItemSetter: function (val) {
3659 var carousel = this;
3660 return (val < carousel.get("numItems")) ? val : 0;
3664 * Set the number of items in the Carousel.
3665 * Warning: Setting this to a lower number than the current removes
3666 * items from the end.
3668 * @method _setNumItems
3669 * @param val {Number} The new value for numItems
3670 * @return {Number} The new value that would be set
3673 _setNumItems: function (val) {
3674 var carousel = this,
3675 num = carousel._itemsTable.numItems;
3677 if (JS.isArray(carousel._itemsTable.items)) {
3678 if (carousel._itemsTable.items.length != num) { // out of sync
3679 num = carousel._itemsTable.items.length;
3680 carousel._itemsTable.numItems = num;
3686 carousel.removeItem(num - 1);
3695 * Set the orientation of the Carousel.
3697 * @method _setOrientation
3698 * @param val {Boolean} The new value for isVertical
3699 * @return {Boolean} The new value that would be set
3702 _setOrientation: function (val) {
3703 var carousel = this,
3704 cssClass = carousel.CLASSES;
3707 carousel.replaceClass(cssClass.HORIZONTAL, cssClass.VERTICAL);
3709 carousel.replaceClass(cssClass.VERTICAL, cssClass.HORIZONTAL);
3711 this._itemAttrCache = {}; // force recomputed next time
3717 * Set the value for the reveal amount percentage in the Carousel.
3719 * @method _setRevealAmount
3720 * @param val {Number} The new value for revealAmount
3721 * @return {Number} The new value that would be set
3724 _setRevealAmount: function (val) {
3725 var carousel = this;
3727 if (val >= 0 && val <= 100) {
3728 val = parseInt(val, 10);
3729 val = JS.isNumber(val) ? val : 0;
3730 carousel._setClipContainerSize();
3732 val = carousel.get("revealAmount");
3738 * Set the value for the selected item.
3740 * @method _setSelectedItem
3741 * @param val {Number} The new value for "selected" item
3744 _setSelectedItem: function (val) {
3745 this._selectedItem = val;
3749 * Get the total number of pages.
3751 * @method _getNumPages
3754 _getNumPages: function () {
3756 parseInt(this.get("numItems"),10) / parseInt(this.get("numVisible"),10)
3761 * Get the index of the last visible item
3763 * @method _getLastVisible
3766 _getLastVisible: function () {
3767 var carousel = this;
3768 return carousel.get("currentPage") + 1 == carousel.get("numPages") ?
3769 carousel.get("numItems") - 1:
3770 carousel.get("firstVisible") + carousel.get("numVisible") - 1;
3774 * Synchronize and redraw the UI after an item is added.
3776 * @method _syncUiForItemAdd
3779 _syncUiForItemAdd: function (obj) {
3782 carouselEl = carousel._carouselEl,
3785 itemsTable = carousel._itemsTable,
3791 pos = JS.isUndefined(obj.pos) ?
3792 obj.newPos || itemsTable.numItems - 1 : obj.pos;
3795 item = itemsTable.items[pos] || {};
3796 el = carousel._createCarouselItem({
3797 className : item.className,
3798 styles : item.styles,
3799 content : item.item,
3803 if (JS.isUndefined(obj.pos)) {
3804 if (!JS.isUndefined(itemsTable.loading[pos])) {
3805 oel = itemsTable.loading[pos];
3806 // if oel is null, it is a problem ...
3810 carouselEl.replaceChild(el, oel);
3811 // ... and remove the item from the data structure
3812 delete itemsTable.loading[pos];
3814 carouselEl.appendChild(el);
3817 if (!JS.isUndefined(itemsTable.items[obj.pos + 1])) {
3818 sibling = Dom.get(itemsTable.items[obj.pos + 1].id);
3821 carouselEl.insertBefore(el, sibling);
3826 if (JS.isUndefined(obj.pos)) {
3827 if (!Dom.isAncestor(carousel._carouselEl, oel)) {
3828 carouselEl.appendChild(oel);
3831 if (!Dom.isAncestor(carouselEl, oel)) {
3832 if (!JS.isUndefined(itemsTable.items[obj.pos + 1])) {
3833 carouselEl.insertBefore(oel,
3834 Dom.get(itemsTable.items[obj.pos + 1].id));
3840 if (!carousel._hasRendered) {
3841 carousel._refreshUi();
3844 if (carousel.get("selectedItem") < 0) {
3845 carousel.set("selectedItem", carousel.get("firstVisible"));
3848 carousel._syncUiItems();
3852 * Synchronize and redraw the UI after an item is replaced.
3854 * @method _syncUiForItemReplace
3857 _syncUiForItemReplace: function (o) {
3858 var carousel = this,
3859 carouselEl = carousel._carouselEl,
3860 itemsTable = carousel._itemsTable,
3866 el = carousel._createCarouselItem({
3867 className : item.className,
3868 styles : item.styles,
3869 content : item.item,
3875 Event.purgeElement(oel, true);
3876 carouselEl.replaceChild(el, Dom.get(oel.id));
3877 if (!JS.isUndefined(itemsTable.loading[pos])) {
3878 itemsTable.numItems++;
3879 delete itemsTable.loading[pos];
3882 // TODO: should we add the item if oel is undefined?
3884 if (!carousel._hasRendered) {
3885 carousel._refreshUi();
3888 carousel._syncUiItems();
3892 * Synchronize and redraw the UI after an item is removed.
3894 * @method _syncUiForItemAdd
3897 _syncUiForItemRemove: function (obj) {
3898 var carousel = this,
3899 carouselEl = carousel._carouselEl,
3902 num = carousel.get("numItems");
3906 if (item && (el = Dom.get(item.id))) {
3907 if (el && Dom.isAncestor(carouselEl, el)) {
3908 Event.purgeElement(el, true);
3909 carouselEl.removeChild(el);
3912 if (carousel.get("selectedItem") == pos) {
3913 pos = pos >= num ? num - 1 : pos;
3918 carousel._syncUiItems();
3922 * Synchronize and redraw the UI for lazy loading.
3924 * @method _syncUiForLazyLoading
3927 _syncUiForLazyLoading: function (obj) {
3928 var carousel = this,
3929 carouselEl = carousel._carouselEl,
3930 itemsTable = carousel._itemsTable,
3931 len = itemsTable.items.length,
3932 sibling = itemsTable.items[obj.last + 1],
3936 // attempt to find the next closest sibling
3937 if(!sibling && obj.last < len){
3940 sibling = itemsTable.items[j];
3942 } while (j<len && !sibling);
3945 for (var i = obj.first; i <= obj.last; i++) {
3946 if(JS.isUndefined(itemsTable.loading[i]) && JS.isUndefined(itemsTable.items[i])){
3947 el = carousel._createCarouselItem({
3948 className : carousel.CLASSES.ITEM_LOADING,
3949 content : carousel.STRINGS.ITEM_LOADING_CONTENT,
3950 id : Dom.generateId(),
3955 sibling = Dom.get(sibling.id);
3957 carouselEl.insertBefore(el, sibling);
3961 carouselEl.appendChild(el);
3964 itemsTable.loading[i] = el;
3968 carousel._syncUiItems();
3972 * Redraw the UI for item positioning.
3974 * @method _syncUiItems
3977 _syncUiItems: function () {
3980 numItems = carousel.get("numItems"),
3982 itemsTable = carousel._itemsTable,
3983 items = itemsTable.items,
3984 loading = itemsTable.loading,
3988 for (i = 0; i < numItems; i++) {
3989 item = items[i] || loading[i];
3991 if (item && item.id) {
3992 styles = getCarouselItemPosition.call(carousel, i);
3993 item.styles = item.styles || {};
3994 for (attr in styles) {
3995 if (styles.hasOwnProperty(attr)) {
3996 item.styles[attr] = styles[attr];
3999 setStyles(Dom.get(item.id), styles);
4005 * Set the correct class for the navigation buttons.
4007 * @method _updateNavButtons
4008 * @param el {Object} The target button
4009 * @param setFocus {Boolean} True to set focus ring, false otherwise.
4012 _updateNavButtons: function (el, setFocus) {
4014 cssClass = this.CLASSES,
4016 parent = el.parentNode;
4021 grandParent = parent.parentNode;
4023 if (el.nodeName.toUpperCase() == "BUTTON" &&
4024 Dom.hasClass(parent, cssClass.BUTTON)) {
4027 children = Dom.getChildren(grandParent);
4029 Dom.removeClass(children, cssClass.FOCUSSED_BUTTON);
4032 Dom.addClass(parent, cssClass.FOCUSSED_BUTTON);
4034 Dom.removeClass(parent, cssClass.FOCUSSED_BUTTON);
4040 * Update the UI for the pager buttons based on the current page and
4041 * the number of pages.
4043 * @method _updatePagerButtons
4046 _updatePagerButtons: function () {
4047 var carousel = this,
4048 css = carousel.CLASSES,
4049 cur = carousel._pages.cur, // current page
4054 n = carousel.get("numVisible"),
4055 num = carousel._pages.num, // total pages
4056 pager = carousel._pages.el; // the pager container element
4058 if (num === 0 || !pager) {
4059 return; // don't do anything if number of pages is 0
4062 // Hide the pager before redrawing it
4063 Dom.setStyle(pager, "visibility", "hidden");
4065 // Remove all nodes from the pager
4066 while (pager.firstChild) {
4067 pager.removeChild(pager.firstChild);
4070 for (i = 0; i < num; i++) {
4072 el = document.createElement("LI");
4075 Dom.addClass(el, css.FIRST_PAGE);
4078 Dom.addClass(el, css.SELECTED_NAV);
4081 html = "<a class=" + css.PAGER_ITEM + " href=\"#" + (i+1) + "\" tabindex=\"0\"><em>" +
4082 carousel.STRINGS.PAGER_PREFIX_TEXT + " " + (i+1) +
4084 el.innerHTML = html;
4086 pager.appendChild(el);
4089 // Show the pager now
4090 Dom.setStyle(pager, "visibility", "visible");
4094 * Update the UI for the pager menu based on the current page and
4095 * the number of pages. If the number of pages is greater than
4096 * MAX_PAGER_BUTTONS, then the selection of pages is provided by a drop
4097 * down menu instead of a set of buttons.
4099 * @method _updatePagerMenu
4102 _updatePagerMenu: function () {
4103 var carousel = this,
4104 css = carousel.CLASSES,
4105 cur = carousel._pages.cur, // current page
4109 n = carousel.get("numVisible"),
4110 num = carousel._pages.num, // total pages
4111 pager = carousel._pages.el, // the pager container element
4115 return;// don't do anything if number of pages is 0
4118 sel = document.createElement("SELECT");
4125 // Hide the pager before redrawing it
4126 Dom.setStyle(pager, "visibility", "hidden");
4128 // Remove all nodes from the pager
4129 while (pager.firstChild) {
4130 pager.removeChild(pager.firstChild);
4133 for (i = 0; i < num; i++) {
4135 el = document.createElement("OPTION");
4137 el.innerHTML = carousel.STRINGS.PAGER_PREFIX_TEXT+" "+(i+1);
4140 el.setAttribute("selected", "selected");
4143 sel.appendChild(el);
4146 el = document.createElement("FORM");
4149 el.appendChild(sel);
4150 pager.appendChild(el);
4153 // Show the pager now
4154 Event.addListener(sel, "change", carousel._pagerChangeHandler, this, true);
4155 Dom.setStyle(pager, "visibility", "visible");
4159 * Set the correct tab index for the Carousel items.
4161 * @method _updateTabIndex
4162 * @param el {Object} The element to be focussed
4165 _updateTabIndex: function (el) {
4166 var carousel = this;
4169 if (carousel._focusableItemEl) {
4170 carousel._focusableItemEl.tabIndex = -1;
4172 carousel._focusableItemEl = el;
4178 * Validate animation parameters.
4180 * @method _validateAnimation
4181 * @param cfg {Object} The animation configuration
4182 * @return {Boolean} The status of the validation
4185 _validateAnimation: function (cfg) {
4188 if (JS.isObject(cfg)) {
4190 rv = rv && JS.isNumber(cfg.speed);
4193 rv = rv && JS.isFunction(cfg.effect);
4194 } else if (!JS.isUndefined(YAHOO.util.Easing)) {
4195 cfg.effect = YAHOO.util.Easing.easeOut;
4205 * Validate the firstVisible value.
4207 * @method _validateFirstVisible
4208 * @param val {Number} The first visible value
4209 * @return {Boolean} The status of the validation
4212 _validateFirstVisible: function (val) {
4213 var carousel = this, numItems = carousel.get("numItems");
4215 if (JS.isNumber(val)) {
4216 if (numItems === 0 && val == numItems) {
4219 return (val >= 0 && val < numItems);
4227 * Validate and navigation parameters.
4229 * @method _validateNavigation
4230 * @param cfg {Object} The navigation configuration
4231 * @return {Boolean} The status of the validation
4234 _validateNavigation : function (cfg) {
4237 if (!JS.isObject(cfg)) {
4242 if (!JS.isArray(cfg.prev)) {
4245 for (i in cfg.prev) {
4246 if (cfg.prev.hasOwnProperty(i)) {
4247 if (!JS.isString(cfg.prev[i].nodeName)) {
4255 if (!JS.isArray(cfg.next)) {
4258 for (i in cfg.next) {
4259 if (cfg.next.hasOwnProperty(i)) {
4260 if (!JS.isString(cfg.next[i].nodeName)) {
4271 * Validate the numItems value.
4273 * @method _validateNumItems
4274 * @param val {Number} The numItems value
4275 * @return {Boolean} The status of the validation
4278 _validateNumItems: function (val) {
4279 return JS.isNumber(val) && (val >= 0);
4283 * Validate the numVisible value.
4285 * @method _validateNumVisible
4286 * @param val {Number} The numVisible value
4287 * @return {Boolean} The status of the validation
4290 _validateNumVisible: function (val) {
4293 if (JS.isNumber(val)) {
4294 rv = val > 0 && val <= this.get("numItems");
4295 } else if (JS.isArray(val)) {
4296 if (JS.isNumber(val[0]) && JS.isNumber(val[1])) {
4297 rv = val[0] * val[1] > 0 && val.length == 2;
4305 * Validate the revealAmount value.
4307 * @method _validateRevealAmount
4308 * @param val {Number} The revealAmount value
4309 * @return {Boolean} The status of the validation
4312 _validateRevealAmount: function (val) {
4315 if (JS.isNumber(val)) {
4316 rv = val >= 0 && val < 100;
4323 * Validate the scrollIncrement value.
4325 * @method _validateScrollIncrement
4326 * @param val {Number} The scrollIncrement value
4327 * @return {Boolean} The status of the validation
4330 _validateScrollIncrement: function (val) {
4333 if (JS.isNumber(val)) {
4334 rv = (val > 0 && val < this.get("numItems"));
4344 ;; Local variables: **
4346 ;; indent-tabs-mode: nil **
4349 YAHOO.register("carousel", YAHOO.widget.Carousel, {version: "2.8.0r4", build: "2449"});