X-Git-Url: https://git.toastfreeware.priv.at/philipp/winterrodeln/wradmin.git/blobdiff_plain/c2d2b07d134a64271aede4886b8c1d0f6960cb4d..582150b643140e3e670d66f244812e314c7aa0c1:/wradmin/static/yui/carousel/carousel.js?ds=sidebyside diff --git a/wradmin/static/yui/carousel/carousel.js b/wradmin/static/yui/carousel/carousel.js new file mode 100644 index 0000000..f957c1f --- /dev/null +++ b/wradmin/static/yui/carousel/carousel.js @@ -0,0 +1,3616 @@ +/* +Copyright (c) 2009, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 2.7.0 +*/ +/** + * The Carousel module provides a widget for browsing among a set of like + * objects represented pictorially. + * + * @module carousel + * @requires yahoo, dom, event, element + * @optional animation + * @namespace YAHOO.widget + * @title Carousel Widget + * @beta + */ +(function () { + + var WidgetName; // forward declaration + + /** + * The Carousel widget. + * + * @class Carousel + * @extends YAHOO.util.Element + * @constructor + * @param el {HTMLElement | String} The HTML element that represents the + * the container that houses the Carousel. + * @param cfg {Object} (optional) The configuration values + */ + YAHOO.widget.Carousel = function (el, cfg) { + + YAHOO.widget.Carousel.superclass.constructor.call(this, el, cfg); + }; + + /* + * Private variables of the Carousel component + */ + + /* Some abbreviations to avoid lengthy typing and lookups. */ + var Carousel = YAHOO.widget.Carousel, + Dom = YAHOO.util.Dom, + Event = YAHOO.util.Event, + JS = YAHOO.lang; + + /** + * The widget name. + * @private + * @static + */ + WidgetName = "Carousel"; + + /** + * The internal table of Carousel instances. + * @private + * @static + */ + var instances = {}, + + /* + * Custom events of the Carousel component + */ + + /** + * @event afterScroll + * @description Fires when the Carousel has scrolled to the previous or + * next page. Passes back the index of the first and last visible items in + * the Carousel. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + afterScrollEvent = "afterScroll", + + /** + * @event allItemsRemovedEvent + * @description Fires when all items have been removed from the Carousel. + * See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + allItemsRemovedEvent = "allItemsRemoved", + + /** + * @event beforeHide + * @description Fires before the Carousel is hidden. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + beforeHideEvent = "beforeHide", + + /** + * @event beforePageChange + * @description Fires when the Carousel is about to scroll to the previous + * or next page. Passes back the page number of the current page. Note + * that the first page number is zero. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + beforePageChangeEvent = "beforePageChange", + + /** + * @event beforeScroll + * @description Fires when the Carousel is about to scroll to the previous + * or next page. Passes back the index of the first and last visible items + * in the Carousel and the direction (backward/forward) of the scroll. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + beforeScrollEvent = "beforeScroll", + + /** + * @event beforeShow + * @description Fires when the Carousel is about to be shown. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + beforeShowEvent = "beforeShow", + + /** + * @event blur + * @description Fires when the Carousel loses focus. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + blurEvent = "blur", + + /** + * @event focus + * @description Fires when the Carousel gains focus. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + focusEvent = "focus", + + /** + * @event hide + * @description Fires when the Carousel is hidden. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + hideEvent = "hide", + + /** + * @event itemAdded + * @description Fires when an item has been added to the Carousel. Passes + * back the content of the item that would be added, the index at which the + * item would be added, and the event itself. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + itemAddedEvent = "itemAdded", + + /** + * @event itemRemoved + * @description Fires when an item has been removed from the Carousel. + * Passes back the content of the item that would be removed, the index + * from which the item would be removed, and the event itself. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + itemRemovedEvent = "itemRemoved", + + /** + * @event itemSelected + * @description Fires when an item has been selected in the Carousel. + * Passes back the index of the selected item in the Carousel. Note, that + * the index begins from zero. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + itemSelectedEvent = "itemSelected", + + /** + * @event loadItems + * @description Fires when the Carousel needs more items to be loaded for + * displaying them. Passes back the first and last visible items in the + * Carousel, and the number of items needed to be loaded. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + loadItemsEvent = "loadItems", + + /** + * @event navigationStateChange + * @description Fires when the state of either one of the navigation + * buttons are changed from enabled to disabled or vice versa. Passes back + * the state (true/false) of the previous and next buttons. The value true + * signifies the button is enabled, false signifies disabled. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + navigationStateChangeEvent = "navigationStateChange", + + /** + * @event pageChange + * @description Fires after the Carousel has scrolled to the previous or + * next page. Passes back the page number of the current page. Note + * that the first page number is zero. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + pageChangeEvent = "pageChange", + + /* + * Internal event. + * @event render + * @description Fires when the Carousel is rendered. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + renderEvent = "render", + + /** + * @event show + * @description Fires when the Carousel is shown. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + showEvent = "show", + + /** + * @event startAutoPlay + * @description Fires when the auto play has started in the Carousel. See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + startAutoPlayEvent = "startAutoPlay", + + /** + * @event stopAutoPlay + * @description Fires when the auto play has been stopped in the Carousel. + * See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + stopAutoPlayEvent = "stopAutoPlay", + + /* + * Internal event. + * @event uiUpdateEvent + * @description Fires when the UI has been updated. + * See + * Element.addListener + * for more information on listening for this event. + * @type YAHOO.util.CustomEvent + */ + uiUpdateEvent = "uiUpdate"; + + /* + * Private helper functions used by the Carousel component + */ + + /** + * Create an element, set its class name and optionally install the element + * to its parent. + * @method createElement + * @param el {String} The element to be created + * @param attrs {Object} Configuration of parent, class and id attributes. + * If the content is specified, it is inserted after creation of the + * element. The content can also be an HTML element in which case it would + * be appended as a child node of the created element. + * @private + */ + function createElement(el, attrs) { + var newEl = document.createElement(el); + + attrs = attrs || {}; + if (attrs.className) { + Dom.addClass(newEl, attrs.className); + } + + if (attrs.parent) { + attrs.parent.appendChild(newEl); + } + + if (attrs.id) { + newEl.setAttribute("id", attrs.id); + } + + if (attrs.content) { + if (attrs.content.nodeName) { + newEl.appendChild(attrs.content); + } else { + newEl.innerHTML = attrs.content; + } + } + + return newEl; + } + + /** + * Get the computed style of an element. + * + * @method getStyle + * @param el {HTMLElement} The element for which the style needs to be + * returned. + * @param style {String} The style attribute + * @param type {String} "int", "float", etc. (defaults to int) + * @private + */ + function getStyle(el, style, type) { + var value; + + if (!el) { + return 0; + } + + function getStyleIntVal(el, style) { + var val; + + /* + * XXX: Safari calculates incorrect marginRight for an element + * which has its parent element style set to overflow: hidden + * https://bugs.webkit.org/show_bug.cgi?id=13343 + * Let us assume marginLeft == marginRight + */ + if (style == "marginRight" && YAHOO.env.ua.webkit) { + val = parseInt(Dom.getStyle(el, "marginLeft"), 10); + } else { + val = parseInt(Dom.getStyle(el, style), 10); + } + + return JS.isNumber(val) ? val : 0; + } + + function getStyleFloatVal(el, style) { + var val; + + /* + * XXX: Safari calculates incorrect marginRight for an element + * which has its parent element style set to overflow: hidden + * https://bugs.webkit.org/show_bug.cgi?id=13343 + * Let us assume marginLeft == marginRight + */ + if (style == "marginRight" && YAHOO.env.ua.webkit) { + val = parseFloat(Dom.getStyle(el, "marginLeft")); + } else { + val = parseFloat(Dom.getStyle(el, style)); + } + + return JS.isNumber(val) ? val : 0; + } + + if (typeof type == "undefined") { + type = "int"; + } + + switch (style) { + case "height": + value = el.offsetHeight; + if (value > 0) { + value += getStyleIntVal(el, "marginTop") + + getStyleIntVal(el, "marginBottom"); + } else { + value = getStyleFloatVal(el, "height") + + getStyleIntVal(el, "marginTop") + + getStyleIntVal(el, "marginBottom") + + getStyleIntVal(el, "borderTopWidth") + + getStyleIntVal(el, "borderBottomWidth") + + getStyleIntVal(el, "paddingTop") + + getStyleIntVal(el, "paddingBottom"); + } + break; + case "width": + value = el.offsetWidth; + if (value > 0) { + value += getStyleIntVal(el, "marginLeft") + + getStyleIntVal(el, "marginRight"); + } else { + value = getStyleFloatVal(el, "width") + + getStyleIntVal(el, "marginLeft") + + getStyleIntVal(el, "marginRight") + + getStyleIntVal(el, "borderLeftWidth") + + getStyleIntVal(el, "borderRightWidth") + + getStyleIntVal(el, "paddingLeft") + + getStyleIntVal(el, "paddingRight"); + } + break; + default: + if (type == "int") { + value = getStyleIntVal(el, style); + } else if (type == "float") { + value = getStyleFloatVal(el, style); + } else { + value = Dom.getStyle(el, style); + } + break; + } + + return value; + } + + /** + * Compute and return the height or width of a single Carousel item + * depending upon the orientation. + * + * @method getCarouselItemSize + * @param which {String} "height" or "width" to be returned. If this is + * passed explicitly, the calculated size is not cached. + * @private + */ + function getCarouselItemSize(which) { + var carousel = this, + child, + size = 0, + vertical = false; + + if (carousel._itemsTable.numItems === 0) { + return 0; + } + + if (typeof which == "undefined") { + if (carousel._itemsTable.size > 0) { + return carousel._itemsTable.size; + } + } + + if (JS.isUndefined(carousel._itemsTable.items[0])) { + return 0; + } + + child = Dom.get(carousel._itemsTable.items[0].id); + + if (typeof which == "undefined") { + vertical = carousel.get("isVertical"); + } else { + vertical = which == "height"; + } + + if (vertical) { + size = getStyle(child, "height"); + } else { + size = getStyle(child, "width"); + } + + if (typeof which == "undefined") { + carousel._itemsTable.size = size; // save the size for later + } + + return size; + } + + /** + * Return the index of the first item in the view port for displaying item + * in "pos". + * + * @method getFirstVisibleForPosition + * @param pos {Number} The position of the item to be displayed + * @private + */ + function getFirstVisibleForPosition(pos) { + var num = this.get("numVisible"); + + return Math.floor(pos / num) * num; + } + + /** + * Return the scrolling offset size given the number of elements to + * scroll. + * + * @method getScrollOffset + * @param delta {Number} The delta number of elements to scroll by. + * @private + */ + function getScrollOffset(delta) { + var itemSize = 0, + size = 0; + + itemSize = getCarouselItemSize.call(this); + size = itemSize * delta; + + // XXX: really, when the orientation is vertical, the scrolling + // is not exactly the number of elements into element size. + if (this.get("isVertical")) { + size -= delta; + } + + return size; + } + + /** + * Scroll the Carousel by a page backward. + * + * @method scrollPageBackward + * @param {Event} ev The event object + * @param {Object} obj The context object + * @private + */ + function scrollPageBackward(ev, obj) { + obj.scrollPageBackward(); + Event.preventDefault(ev); + } + + /** + * Scroll the Carousel by a page forward. + * + * @method scrollPageForward + * @param {Event} ev The event object + * @param {Object} obj The context object + * @private + */ + function scrollPageForward(ev, obj) { + obj.scrollPageForward(); + Event.preventDefault(ev); + } + + /** + * Set the selected item. + * + * @method setItemSelection + * @param {Number} newpos The index of the new position + * @param {Number} oldpos The index of the previous position + * @private + */ + function setItemSelection(newpos, oldpos) { + var carousel = this, + cssClass = carousel.CLASSES, + el, + firstItem = carousel._firstItem, + isCircular = carousel.get("isCircular"), + numItems = carousel.get("numItems"), + numVisible = carousel.get("numVisible"), + position = oldpos, + sentinel = firstItem + numVisible - 1; + + if (position >= 0 && position < numItems) { + if (!JS.isUndefined(carousel._itemsTable.items[position])) { + el = Dom.get(carousel._itemsTable.items[position].id); + if (el) { + Dom.removeClass(el, cssClass.SELECTED_ITEM); + } + } + } + + if (JS.isNumber(newpos)) { + newpos = parseInt(newpos, 10); + newpos = JS.isNumber(newpos) ? newpos : 0; + } else { + newpos = firstItem; + } + + if (JS.isUndefined(carousel._itemsTable.items[newpos])) { + newpos = getFirstVisibleForPosition.call(carousel, newpos); + carousel.scrollTo(newpos); // still loading the item + } + + if (!JS.isUndefined(carousel._itemsTable.items[newpos])) { + el = Dom.get(carousel._itemsTable.items[newpos].id); + if (el) { + Dom.addClass(el, cssClass.SELECTED_ITEM); + } + } + + if (newpos < firstItem || newpos > sentinel) { // out of focus + newpos = getFirstVisibleForPosition.call(carousel, newpos); + carousel.scrollTo(newpos); + } + } + + /** + * Fire custom events for enabling/disabling navigation elements. + * + * @method syncNavigation + * @private + */ + function syncNavigation() { + var attach = false, + carousel = this, + cssClass = carousel.CLASSES, + i, + navigation, + sentinel; + + // Don't do anything if the Carousel is not rendered + if (!carousel._hasRendered) { + return; + } + + navigation = carousel.get("navigation"); + sentinel = carousel._firstItem + carousel.get("numVisible"); + + if (navigation.prev) { + if (carousel.get("numItems") === 0 || carousel._firstItem === 0) { + if (carousel.get("numItems") === 0 || + !carousel.get("isCircular")) { + Event.removeListener(navigation.prev, "click", + scrollPageBackward); + Dom.addClass(navigation.prev, cssClass.FIRST_NAV_DISABLED); + for (i = 0; i < carousel._navBtns.prev.length; i++) { + carousel._navBtns.prev[i].setAttribute("disabled", + "true"); + } + carousel._prevEnabled = false; + } else { + attach = !carousel._prevEnabled; + } + } else { + attach = !carousel._prevEnabled; + } + + if (attach) { + Event.on(navigation.prev, "click", scrollPageBackward, + carousel); + Dom.removeClass(navigation.prev, cssClass.FIRST_NAV_DISABLED); + for (i = 0; i < carousel._navBtns.prev.length; i++) { + carousel._navBtns.prev[i].removeAttribute("disabled"); + } + carousel._prevEnabled = true; + } + } + + attach = false; + if (navigation.next) { + if (sentinel >= carousel.get("numItems")) { + if (!carousel.get("isCircular")) { + Event.removeListener(navigation.next, "click", + scrollPageForward); + Dom.addClass(navigation.next, cssClass.DISABLED); + for (i = 0; i < carousel._navBtns.next.length; i++) { + carousel._navBtns.next[i].setAttribute("disabled", + "true"); + } + carousel._nextEnabled = false; + } else { + attach = !carousel._nextEnabled; + } + } else { + attach = !carousel._nextEnabled; + } + + if (attach) { + Event.on(navigation.next, "click", scrollPageForward, + carousel); + Dom.removeClass(navigation.next, cssClass.DISABLED); + for (i = 0; i < carousel._navBtns.next.length; i++) { + carousel._navBtns.next[i].removeAttribute("disabled"); + } + carousel._nextEnabled = true; + } + } + + carousel.fireEvent(navigationStateChangeEvent, + { next: carousel._nextEnabled, prev: carousel._prevEnabled }); + } + + /** + * Synchronize and redraw the Pager UI if necessary. + * + * @method syncPagerUi + * @private + */ + function syncPagerUi(page) { + var carousel = this, numPages, numVisible; + + // Don't do anything if the Carousel is not rendered + if (!carousel._hasRendered) { + return; + } + + numVisible = carousel.get("numVisible"); + + if (!JS.isNumber(page)) { + page = Math.ceil(carousel.get("selectedItem") / numVisible); + } + numPages = Math.ceil(carousel.get("numItems") / numVisible); + + carousel._pages.num = numPages; + carousel._pages.cur = page; + + if (numPages > carousel.CONFIG.MAX_PAGER_BUTTONS) { + carousel._updatePagerMenu(); + } else { + carousel._updatePagerButtons(); + } + } + + /** + * Handle UI update. + * Call the appropriate methods on events fired when an item is added, or + * removed for synchronizing the DOM. + * + * @method syncUi + * @param {Object} o The item that needs to be added or removed + * @private + */ + function syncUi(o) { + var carousel = this; + + if (!JS.isObject(o)) { + return; + } + + switch (o.ev) { + case itemAddedEvent: + carousel._syncUiForItemAdd(o); + break; + case itemRemovedEvent: + carousel._syncUiForItemRemove(o); + break; + case loadItemsEvent: + carousel._syncUiForLazyLoading(o); + break; + } + + carousel.fireEvent(uiUpdateEvent); + } + + /** + * Update the state variables after scrolling the Carousel view port. + * + * @method updateStateAfterScroll + * @param {Integer} item The index to which the Carousel has scrolled to. + * @param {Integer} sentinel The last element in the view port. + * @private + */ + function updateStateAfterScroll(item, sentinel) { + var carousel = this, + page = carousel.get("currentPage"), + newPage, + numPerPage = carousel.get("numVisible"); + + newPage = parseInt(carousel._firstItem / numPerPage, 10); + if (newPage != page) { + carousel.setAttributeConfig("currentPage", { value: newPage }); + carousel.fireEvent(pageChangeEvent, newPage); + } + + if (carousel.get("selectOnScroll")) { + if (carousel.get("selectedItem") != carousel._selectedItem) { + carousel.set("selectedItem", carousel._selectedItem); + } + } + + clearTimeout(carousel._autoPlayTimer); + delete carousel._autoPlayTimer; + if (carousel.isAutoPlayOn()) { + carousel.startAutoPlay(); + } + + carousel.fireEvent(afterScrollEvent, + { first: carousel._firstItem, + last: sentinel }, + carousel); + } + + /* + * Static members and methods of the Carousel component + */ + + /** + * Return the appropriate Carousel object based on the id associated with + * the Carousel element or false if none match. + * @method getById + * @public + * @static + */ + Carousel.getById = function (id) { + return instances[id] ? instances[id].object : false; + }; + + YAHOO.extend(Carousel, YAHOO.util.Element, { + + /* + * Internal variables used within the Carousel component + */ + + /** + * The Animation object. + * + * @property _animObj + * @private + */ + _animObj: null, + + /** + * The Carousel element. + * + * @property _carouselEl + * @private + */ + _carouselEl: null, + + /** + * The Carousel clipping container element. + * + * @property _clipEl + * @private + */ + _clipEl: null, + + /** + * The current first index of the Carousel. + * + * @property _firstItem + * @private + */ + _firstItem: 0, + + /** + * Does the Carousel element have focus? + * + * @property _hasFocus + * @private + */ + _hasFocus: false, + + /** + * Is the Carousel rendered already? + * + * @property _hasRendered + * @private + */ + _hasRendered: false, + + /** + * Is the animation still in progress? + * + * @property _isAnimationInProgress + * @private + */ + _isAnimationInProgress: false, + + /** + * Is the auto-scrolling of Carousel in progress? + * + * @property _isAutoPlayInProgress + * @private + */ + _isAutoPlayInProgress: false, + + /** + * The table of items in the Carousel. + * The numItems is the number of items in the Carousel, items being the + * array of items in the Carousel. The size is the size of a single + * item in the Carousel. It is cached here for efficiency (to avoid + * computing the size multiple times). + * + * @property _itemsTable + * @private + */ + _itemsTable: null, + + /** + * The Carousel navigation buttons. + * + * @property _navBtns + * @private + */ + _navBtns: null, + + /** + * The Carousel navigation. + * + * @property _navEl + * @private + */ + _navEl: null, + + /** + * Status of the next navigation item. + * + * @property _nextEnabled + * @private + */ + _nextEnabled: true, + + /** + * The Carousel pages structure. + * This is an object of the total number of pages and the current page. + * + * @property _pages + * @private + */ + _pages: null, + + /** + * Status of the previous navigation item. + * + * @property _prevEnabled + * @private + */ + _prevEnabled: true, + + /** + * Whether the Carousel size needs to be recomputed or not? + * + * @property _recomputeSize + * @private + */ + _recomputeSize: true, + + /* + * CSS classes used by the Carousel component + */ + + CLASSES: { + + /** + * The class name of the Carousel navigation buttons. + * + * @property BUTTON + * @default "yui-carousel-button" + */ + BUTTON: "yui-carousel-button", + + /** + * The class name of the Carousel element. + * + * @property CAROUSEL + * @default "yui-carousel" + */ + CAROUSEL: "yui-carousel", + + /** + * The class name of the container of the items in the Carousel. + * + * @property CAROUSEL_EL + * @default "yui-carousel-element" + */ + CAROUSEL_EL: "yui-carousel-element", + + /** + * The class name of the Carousel's container element. + * + * @property CONTAINER + * @default "yui-carousel-container" + */ + CONTAINER: "yui-carousel-container", + + /** + * The class name of the Carousel's container element. + * + * @property CONTENT + * @default "yui-carousel-content" + */ + CONTENT: "yui-carousel-content", + + /** + * The class name of a disabled navigation button. + * + * @property DISABLED + * @default "yui-carousel-button-disabled" + */ + DISABLED: "yui-carousel-button-disabled", + + /** + * The class name of the first Carousel navigation button. + * + * @property FIRST_NAV + * @default " yui-carousel-first-button" + */ + FIRST_NAV: " yui-carousel-first-button", + + /** + * The class name of a first disabled navigation button. + * + * @property FIRST_NAV_DISABLED + * @default "yui-carousel-first-button-disabled" + */ + FIRST_NAV_DISABLED: "yui-carousel-first-button-disabled", + + /** + * The class name of a first page element. + * + * @property FIRST_PAGE + * @default "yui-carousel-nav-first-page" + */ + FIRST_PAGE: "yui-carousel-nav-first-page", + + /** + * The class name of the Carousel navigation button that has focus. + * + * @property FOCUSSED_BUTTON + * @default "yui-carousel-button-focus" + */ + FOCUSSED_BUTTON: "yui-carousel-button-focus", + + /** + * The class name of a horizontally oriented Carousel. + * + * @property HORIZONTAL + * @default "yui-carousel-horizontal" + */ + HORIZONTAL: "yui-carousel-horizontal", + + /** + * The element to be used as the progress indicator when the item + * is still being loaded. + * + * @property ITEM_LOADING + * @default The progress indicator (spinner) image CSS class + */ + ITEM_LOADING: "yui-carousel-item-loading", + + /** + * The class name that will be set if the Carousel adjusts itself + * for a minimum width. + * + * @property MIN_WIDTH + * @default "yui-carousel-min-width" + */ + MIN_WIDTH: "yui-carousel-min-width", + + /** + * The navigation element container class name. + * + * @property NAVIGATION + * @default "yui-carousel-nav" + */ + NAVIGATION: "yui-carousel-nav", + + /** + * The class name of the next Carousel navigation button. + * + * @property NEXT_NAV + * @default " yui-carousel-next-button" + */ + NEXT_NAV: " yui-carousel-next-button", + + /** + * The class name of the next navigation link. This variable is + * not only used for styling, but also for identifying the link + * within the Carousel container. + * + * @property NEXT_PAGE + * @default "yui-carousel-next" + */ + NEXT_PAGE: "yui-carousel-next", + + /** + * The class name for the navigation container for prev/next. + * + * @property NAV_CONTAINER + * @default "yui-carousel-buttons" + */ + NAV_CONTAINER: "yui-carousel-buttons", + + /** + * The class name of the focussed page navigation. This class is + * specifically used for the ugly focus handling in Opera. + * + * @property PAGE_FOCUS + * @default "yui-carousel-nav-page-focus" + */ + PAGE_FOCUS: "yui-carousel-nav-page-focus", + + /** + * The class name of the previous navigation link. This variable + * is not only used for styling, but also for identifying the link + * within the Carousel container. + * + * @property PREV_PAGE + * @default "yui-carousel-prev" + */ + PREV_PAGE: "yui-carousel-prev", + + /** + * The class name of the selected item. + * + * @property SELECTED_ITEM + * @default "yui-carousel-item-selected" + */ + SELECTED_ITEM: "yui-carousel-item-selected", + + /** + * The class name of the selected paging navigation. + * + * @property SELECTED_NAV + * @default "yui-carousel-nav-page-selected" + */ + SELECTED_NAV: "yui-carousel-nav-page-selected", + + /** + * The class name of a vertically oriented Carousel. + * + * @property VERTICAL + * @default "yui-carousel-vertical" + */ + VERTICAL: "yui-carousel-vertical", + + /** + * The class name of the (vertical) Carousel's container element. + * + * @property VERTICAL_CONTAINER + * @default "yui-carousel-vertical-container" + */ + VERTICAL_CONTAINER: "yui-carousel-vertical-container", + + /** + * The class name of a visible Carousel. + * + * @property VISIBLE + * @default "yui-carousel-visible" + */ + VISIBLE: "yui-carousel-visible" + + }, + + /* + * Configuration attributes for configuring the Carousel component + */ + + CONFIG: { + + /** + * The offset of the first visible item in the Carousel. + * + * @property FIRST_VISIBLE + * @default 0 + */ + FIRST_VISIBLE: 0, + + /** + * The minimum width of the horizontal Carousel container to support + * the navigation buttons. + * + * @property HORZ_MIN_WIDTH + * @default 180 + */ + HORZ_MIN_WIDTH: 180, + + /** + * The maximum number of pager buttons allowed beyond which the UI + * of the pager would be a drop-down of pages instead of buttons. + * + * @property MAX_PAGER_BUTTONS + * @default 5 + */ + MAX_PAGER_BUTTONS: 5, + + /** + * The minimum width of the vertical Carousel container to support + * the navigation buttons. + * + * @property VERT_MIN_WIDTH + * @default 99 + */ + VERT_MIN_WIDTH: 99, + + /** + * The number of visible items in the Carousel. + * + * @property NUM_VISIBLE + * @default 3 + */ + NUM_VISIBLE: 3 + + }, + + /* + * Internationalizable strings in the Carousel component + */ + + STRINGS: { + + /** + * The content to be used as the progress indicator when the item + * is still being loaded. + * + * @property ITEM_LOADING_CONTENT + * @default "Loading" + */ + ITEM_LOADING_CONTENT: "Loading", + + /** + * The next navigation button name/text. + * + * @property NEXT_BUTTON_TEXT + * @default "Next Page" + */ + NEXT_BUTTON_TEXT: "Next Page", + + /** + * The prefix text for the pager in case the UI is a drop-down. + * + * @property PAGER_PREFIX_TEXT + * @default "Go to page " + */ + PAGER_PREFIX_TEXT: "Go to page ", + + /** + * The previous navigation button name/text. + * + * @property PREVIOUS_BUTTON_TEXT + * @default "Previous Page" + */ + PREVIOUS_BUTTON_TEXT: "Previous Page" + + }, + + /* + * Public methods of the Carousel component + */ + + /** + * Insert or append an item to the Carousel. + * + * @method addItem + * @public + * @param item {String | Object | HTMLElement} The item to be appended + * to the Carousel. If the parameter is a string, it is assumed to be + * the content of the newly created item. If the parameter is an + * object, it is assumed to supply the content and an optional class + * and an optional id of the newly created item. + * @param index {Number} optional The position to where in the list + * (starts from zero). + * @return {Boolean} Return true on success, false otherwise + */ + addItem: function (item, index) { + var carousel = this, + className, + content, + elId, + numItems = carousel.get("numItems"); + + if (!item) { + return false; + } + + if (JS.isString(item) || item.nodeName) { + content = item.nodeName ? item.innerHTML : item; + } else if (JS.isObject(item)) { + content = item.content; + } else { + return false; + } + + className = item.className || ""; + elId = item.id ? item.id : Dom.generateId(); + + if (JS.isUndefined(index)) { + carousel._itemsTable.items.push({ + item : content, + className : className, + id : elId + }); + } else { + if (index < 0 || index >= numItems) { + return false; + } + carousel._itemsTable.items.splice(index, 0, { + item : content, + className : className, + id : elId + }); + } + carousel._itemsTable.numItems++; + + if (numItems < carousel._itemsTable.items.length) { + carousel.set("numItems", carousel._itemsTable.items.length); + } + + carousel.fireEvent(itemAddedEvent, { pos: index, ev: itemAddedEvent }); + + return true; + }, + + /** + * Insert or append multiple items to the Carousel. + * + * @method addItems + * @public + * @param items {Array} An array of items to be added with each item + * representing an item, index pair [{item, index}, ...] + * @return {Boolean} Return true on success, false otherwise + */ + addItems: function (items) { + var i, n, rv = true; + + if (!JS.isArray(items)) { + return false; + } + + for (i = 0, n = items.length; i < n; i++) { + if (this.addItem(items[i][0], items[i][1]) === false) { + rv = false; + } + } + + return rv; + }, + + /** + * Remove focus from the Carousel. + * + * @method blur + * @public + */ + blur: function () { + this._carouselEl.blur(); + this.fireEvent(blurEvent); + }, + + /** + * Clears the items from Carousel. + * + * @method clearItems + * public + */ + clearItems: function () { + var carousel = this, n = carousel.get("numItems"); + + while (n > 0) { + if (!carousel.removeItem(0)) { + } + /* + For dynamic loading, the numItems may be much larger than + the actual number of items in the table. So, set the + numItems to zero, and break out of the loop if the table + is already empty. + */ + if (carousel._itemsTable.numItems === 0) { + carousel.set("numItems", 0); + break; + } + n--; + } + + carousel.fireEvent(allItemsRemovedEvent); + }, + + /** + * Set focus on the Carousel. + * + * @method focus + * @public + */ + focus: function () { + var carousel = this, + first, + focusEl, + isSelectionInvisible, + itemsTable, + last, + numVisible, + selectOnScroll, + selected, + selItem; + + // Don't do anything if the Carousel is not rendered + if (!carousel._hasRendered) { + return; + } + + if (carousel.isAnimating()) { + // this messes up real bad! + return; + } + + selItem = carousel.get("selectedItem"); + numVisible = carousel.get("numVisible"); + selectOnScroll = carousel.get("selectOnScroll"); + selected = (selItem >= 0) ? + carousel.getItem(selItem) : null; + first = carousel.get("firstVisible"); + last = first + numVisible - 1; + isSelectionInvisible = (selItem < first || selItem > last); + focusEl = (selected && selected.id) ? + Dom.get(selected.id) : null; + itemsTable = carousel._itemsTable; + + if (!selectOnScroll && isSelectionInvisible) { + focusEl = (itemsTable && itemsTable.items && + itemsTable.items[first]) ? + Dom.get(itemsTable.items[first].id) : null; + } + + if (focusEl) { + try { + focusEl.focus(); + } catch (ex) { + // ignore focus errors + } + } + + carousel.fireEvent(focusEvent); + }, + + /** + * Hide the Carousel. + * + * @method hide + * @public + */ + hide: function () { + var carousel = this; + + if (carousel.fireEvent(beforeHideEvent) !== false) { + carousel.removeClass(carousel.CLASSES.VISIBLE); + carousel.fireEvent(hideEvent); + } + }, + + /** + * Initialize the Carousel. + * + * @method init + * @public + * @param el {HTMLElement | String} The html element that represents + * the Carousel container. + * @param attrs {Object} The set of configuration attributes for + * creating the Carousel. + */ + init: function (el, attrs) { + var carousel = this, + elId = el, // save for a rainy day + parse = false; + + if (!el) { + return; + } + + carousel._hasRendered = false; + carousel._navBtns = { prev: [], next: [] }; + carousel._pages = { el: null, num: 0, cur: 0 }; + carousel._itemsTable = { loading: {}, numItems: 0, + items: [], size: 0 }; + + + if (JS.isString(el)) { + el = Dom.get(el); + } else if (!el.nodeName) { + return; + } + + Carousel.superclass.init.call(carousel, el, attrs); + + if (el) { + if (!el.id) { // in case the HTML element is passed + el.setAttribute("id", Dom.generateId()); + } + parse = carousel._parseCarousel(el); + if (!parse) { + carousel._createCarousel(elId); + } + } else { + el = carousel._createCarousel(elId); + } + elId = el.id; + + carousel.initEvents(); + + if (parse) { + carousel._parseCarouselItems(); + } + + if (!attrs || typeof attrs.isVertical == "undefined") { + carousel.set("isVertical", false); + } + + carousel._parseCarouselNavigation(el); + carousel._navEl = carousel._setupCarouselNavigation(); + + instances[elId] = { object: carousel }; + + carousel._loadItems(); + }, + + /** + * Initialize the configuration attributes used to create the Carousel. + * + * @method initAttributes + * @public + * @param attrs {Object} The set of configuration attributes for + * creating the Carousel. + */ + initAttributes: function (attrs) { + var carousel = this; + + attrs = attrs || {}; + Carousel.superclass.initAttributes.call(carousel, attrs); + + /** + * @attribute carouselEl + * @description The type of the Carousel element. + * @default OL + * @type Boolean + */ + carousel.setAttributeConfig("carouselEl", { + validator : JS.isString, + value : attrs.carouselEl || "OL" + }); + + /** + * @attribute carouselItemEl + * @description The type of the list of items within the Carousel. + * @default LI + * @type Boolean + */ + carousel.setAttributeConfig("carouselItemEl", { + validator : JS.isString, + value : attrs.carouselItemEl || "LI" + }); + + /** + * @attribute currentPage + * @description The current page number (read-only.) + * @type Number + */ + carousel.setAttributeConfig("currentPage", { + readOnly : true, + value : 0 + }); + + /** + * @attribute firstVisible + * @description The index to start the Carousel from (indexes begin + * from zero) + * @default 0 + * @type Number + */ + carousel.setAttributeConfig("firstVisible", { + method : carousel._setFirstVisible, + validator : carousel._validateFirstVisible, + value : + attrs.firstVisible || carousel.CONFIG.FIRST_VISIBLE + }); + + /** + * @attribute selectOnScroll + * @description Set this to true to automatically set focus to + * follow scrolling in the Carousel. + * @default true + * @type Boolean + */ + carousel.setAttributeConfig("selectOnScroll", { + validator : JS.isBoolean, + value : attrs.selectOnScroll || true + }); + + /** + * @attribute numVisible + * @description The number of visible items in the Carousel's + * viewport. + * @default 3 + * @type Number + */ + carousel.setAttributeConfig("numVisible", { + method : carousel._setNumVisible, + validator : carousel._validateNumVisible, + value : attrs.numVisible || carousel.CONFIG.NUM_VISIBLE + }); + + /** + * @attribute numItems + * @description The number of items in the Carousel. + * @type Number + */ + carousel.setAttributeConfig("numItems", { + method : carousel._setNumItems, + validator : carousel._validateNumItems, + value : carousel._itemsTable.numItems + }); + + /** + * @attribute scrollIncrement + * @description The number of items to scroll by for arrow keys. + * @default 1 + * @type Number + */ + carousel.setAttributeConfig("scrollIncrement", { + validator : carousel._validateScrollIncrement, + value : attrs.scrollIncrement || 1 + }); + + /** + * @attribute selectedItem + * @description The index of the selected item. + * @type Number + */ + carousel.setAttributeConfig("selectedItem", { + method : carousel._setSelectedItem, + validator : JS.isNumber, + value : -1 + }); + + /** + * @attribute revealAmount + * @description The percentage of the item to be revealed on each + * side of the Carousel (before and after the first and last item + * in the Carousel's viewport.) + * @default 0 + * @type Number + */ + carousel.setAttributeConfig("revealAmount", { + method : carousel._setRevealAmount, + validator : carousel._validateRevealAmount, + value : attrs.revealAmount || 0 + }); + + /** + * @attribute isCircular + * @description Set this to true to wrap scrolling of the contents + * in the Carousel. + * @default false + * @type Boolean + */ + carousel.setAttributeConfig("isCircular", { + validator : JS.isBoolean, + value : attrs.isCircular || false + }); + + /** + * @attribute isVertical + * @description True if the orientation of the Carousel is vertical + * @default false + * @type Boolean + */ + carousel.setAttributeConfig("isVertical", { + method : carousel._setOrientation, + validator : JS.isBoolean, + value : attrs.isVertical || false + }); + + /** + * @attribute navigation + * @description The set of navigation controls for Carousel + * @default
+ * { prev: null, // the previous navigation element
+ * next: null } // the next navigation element + * @type Object + */ + carousel.setAttributeConfig("navigation", { + method : carousel._setNavigation, + validator : carousel._validateNavigation, + value : + attrs.navigation || {prev: null,next: null,page: null} + }); + + /** + * @attribute animation + * @description The optional animation attributes for the Carousel. + * @default
+ * { speed: 0, // the animation speed (in seconds)
+ * effect: null } // the animation effect (like + * YAHOO.util.Easing.easeOut) + * @type Object + */ + carousel.setAttributeConfig("animation", { + validator : carousel._validateAnimation, + value : attrs.animation || { speed: 0, effect: null } + }); + + /** + * @attribute autoPlay + * @description Set this to time in milli-seconds to have the + * Carousel automatically scroll the contents. + * @type Number + * @deprecated Use autoPlayInterval instead. + */ + carousel.setAttributeConfig("autoPlay", { + validator : JS.isNumber, + value : attrs.autoPlay || 0 + }); + + /** + * @attribute autoPlayInterval + * @description The delay in milli-seconds for scrolling the + * Carousel during auto-play. + * Note: The startAutoPlay() method needs to be invoked to trigger + * automatic scrolling of Carousel. + * @type Number + */ + carousel.setAttributeConfig("autoPlayInterval", { + validator : JS.isNumber, + value : attrs.autoPlayInterval || 0 + }); + }, + + /** + * Initialize and bind the event handlers. + * + * @method initEvents + * @public + */ + initEvents: function () { + var carousel = this, + cssClass = carousel.CLASSES, + focussedLi; + + carousel.on("keydown", carousel._keyboardEventHandler); + + carousel.on(afterScrollEvent, syncNavigation); + + carousel.on(itemAddedEvent, syncUi); + + carousel.on(itemRemovedEvent, syncUi); + + carousel.on(itemSelectedEvent, function () { + if (carousel._hasFocus) { + carousel.focus(); + } + }); + + carousel.on(loadItemsEvent, syncUi); + + carousel.on(allItemsRemovedEvent, function (ev) { + carousel.scrollTo(0); + syncNavigation.call(carousel); + syncPagerUi.call(carousel); + }); + + carousel.on(pageChangeEvent, syncPagerUi, carousel); + + carousel.on(renderEvent, function (ev) { + carousel.set("selectedItem", carousel.get("firstVisible")); + syncNavigation.call(carousel, ev); + syncPagerUi.call(carousel, ev); + carousel._setClipContainerSize(); + }); + + carousel.on("selectedItemChange", function (ev) { + setItemSelection.call(carousel, ev.newValue, ev.prevValue); + if (ev.newValue >= 0) { + carousel._updateTabIndex( + carousel.getElementForItem(ev.newValue)); + } + carousel.fireEvent(itemSelectedEvent, ev.newValue); + }); + + carousel.on(uiUpdateEvent, function (ev) { + syncNavigation.call(carousel, ev); + syncPagerUi.call(carousel, ev); + }); + + carousel.on("firstVisibleChange", function (ev) { + if (!carousel.get("selectOnScroll")) { + if (ev.newValue >= 0) { + carousel._updateTabIndex( + carousel.getElementForItem(ev.newValue)); + } + } + }); + + // Handle item selection on mouse click + carousel.on("click", function (ev) { + if (carousel.isAutoPlayOn()) { + carousel.stopAutoPlay(); + } + carousel._itemClickHandler(ev); + carousel._pagerClickHandler(ev); + }); + + // Restore the focus on the navigation buttons + + Event.onFocus(carousel.get("element"), function (ev, obj) { + var target = Event.getTarget(ev); + + if (target && target.nodeName.toUpperCase() == "A" && + Dom.getAncestorByClassName(target, cssClass.NAVIGATION)) { + if (focussedLi) { + Dom.removeClass(focussedLi, cssClass.PAGE_FOCUS); + } + focussedLi = target.parentNode; + Dom.addClass(focussedLi, cssClass.PAGE_FOCUS); + } else { + if (focussedLi) { + Dom.removeClass(focussedLi, cssClass.PAGE_FOCUS); + } + } + + obj._hasFocus = true; + obj._updateNavButtons(Event.getTarget(ev), true); + }, carousel); + + Event.onBlur(carousel.get("element"), function (ev, obj) { + obj._hasFocus = false; + obj._updateNavButtons(Event.getTarget(ev), false); + }, carousel); + }, + + /** + * Return true if the Carousel is still animating, or false otherwise. + * + * @method isAnimating + * @return {Boolean} Return true if animation is still in progress, or + * false otherwise. + * @public + */ + isAnimating: function () { + return this._isAnimationInProgress; + }, + + /** + * Return true if the auto-scrolling of Carousel is "on", or false + * otherwise. + * + * @method isAutoPlayOn + * @return {Boolean} Return true if autoPlay is "on", or false + * otherwise. + * @public + */ + isAutoPlayOn: function () { + return this._isAutoPlayInProgress; + }, + + /** + * Return the carouselItemEl at index or null if the index is not + * found. + * + * @method getElementForItem + * @param index {Number} The index of the item to be returned + * @return {Element} Return the item at index or null if not found + * @public + */ + getElementForItem: function (index) { + var carousel = this; + + if (index < 0 || index >= carousel.get("numItems")) { + return null; + } + + // TODO: may be cache the item + if (carousel._itemsTable.numItems > index) { + if (!JS.isUndefined(carousel._itemsTable.items[index])) { + return Dom.get(carousel._itemsTable.items[index].id); + } + } + + return null; + }, + + /** + * Return the carouselItemEl for all items in the Carousel. + * + * @method getElementForItems + * @return {Array} Return all the items + * @public + */ + getElementForItems: function () { + var carousel = this, els = [], i; + + for (i = 0; i < carousel._itemsTable.numItems; i++) { + els.push(carousel.getElementForItem(i)); + } + + return els; + }, + + /** + * Return the item at index or null if the index is not found. + * + * @method getItem + * @param index {Number} The index of the item to be returned + * @return {Object} Return the item at index or null if not found + * @public + */ + getItem: function (index) { + var carousel = this; + + if (index < 0 || index >= carousel.get("numItems")) { + return null; + } + + if (carousel._itemsTable.numItems > index) { + if (!JS.isUndefined(carousel._itemsTable.items[index])) { + return carousel._itemsTable.items[index]; + } + } + + return null; + }, + + /** + * Return all items as an array. + * + * @method getItems + * @return {Array} Return all items in the Carousel + * @public + */ + getItems: function (index) { + return this._itemsTable.items; + }, + + /** + * Return the position of the Carousel item that has the id "id", or -1 + * if the id is not found. + * + * @method getItemPositionById + * @param index {Number} The index of the item to be returned + * @public + */ + getItemPositionById: function (id) { + var carousel = this, i = 0, n = carousel._itemsTable.numItems; + + while (i < n) { + if (!JS.isUndefined(carousel._itemsTable.items[i])) { + if (carousel._itemsTable.items[i].id == id) { + return i; + } + } + i++; + } + + return -1; + }, + + /** + * Return all visible items as an array. + * + * @method getVisibleItems + * @return {Array} The array of visible items + * @public + */ + getVisibleItems: function () { + var carousel = this, + i = carousel.get("firstVisible"), + n = i + carousel.get("numVisible"), + r = []; + + while (i < n) { + r.push(carousel.getElementForItem(i)); + i++; + } + + return r; + }, + + /** + * Remove an item at index from the Carousel. + * + * @method removeItem + * @public + * @param index {Number} The position to where in the list (starts from + * zero). + * @return {Boolean} Return true on success, false otherwise + */ + removeItem: function (index) { + var carousel = this, + item, + num = carousel.get("numItems"); + + if (index < 0 || index >= num) { + return false; + } + + item = carousel._itemsTable.items.splice(index, 1); + if (item && item.length == 1) { + carousel._itemsTable.numItems--; + carousel.set("numItems", num - 1); + + carousel.fireEvent(itemRemovedEvent, + { item: item[0], pos: index, ev: itemRemovedEvent }); + return true; + } + + return false; + }, + + /** + * Render the Carousel. + * + * @method render + * @public + * @param appendTo {HTMLElement | String} The element to which the + * Carousel should be appended prior to rendering. + * @return {Boolean} Status of the operation + */ + render: function (appendTo) { + var carousel = this, + cssClass = carousel.CLASSES; + + carousel.addClass(cssClass.CAROUSEL); + + if (!carousel._clipEl) { + carousel._clipEl = carousel._createCarouselClip(); + carousel._clipEl.appendChild(carousel._carouselEl); + } + + if (appendTo) { + carousel.appendChild(carousel._clipEl); + carousel.appendTo(appendTo); + } else { + if (!Dom.inDocument(carousel.get("element"))) { + return false; + } + carousel.appendChild(carousel._clipEl); + } + + if (carousel.get("isVertical")) { + carousel.addClass(cssClass.VERTICAL); + } else { + carousel.addClass(cssClass.HORIZONTAL); + } + + if (carousel.get("numItems") < 1) { + return false; + } + + carousel._refreshUi(); + + return true; + }, + + /** + * Scroll the Carousel by an item backward. + * + * @method scrollBackward + * @public + */ + scrollBackward: function () { + var carousel = this; + + carousel.scrollTo(carousel._firstItem - + carousel.get("scrollIncrement")); + }, + + /** + * Scroll the Carousel by an item forward. + * + * @method scrollForward + * @public + */ + scrollForward: function () { + var carousel = this; + + carousel.scrollTo(carousel._firstItem + + carousel.get("scrollIncrement")); + }, + + /** + * Scroll the Carousel by a page backward. + * + * @method scrollPageBackward + * @public + */ + scrollPageBackward: function () { + var carousel = this, + item = carousel._firstItem - carousel.get("numVisible"); + + if (carousel.get("selectOnScroll")) { + carousel._selectedItem = carousel._getSelectedItem(item); + } else { + item = carousel._getValidIndex(item); + } + carousel.scrollTo(item); + }, + + /** + * Scroll the Carousel by a page forward. + * + * @method scrollPageForward + * @public + */ + scrollPageForward: function () { + var carousel = this, + item = carousel._firstItem + carousel.get("numVisible"); + + if (carousel.get("selectOnScroll")) { + carousel._selectedItem = carousel._getSelectedItem(item); + } else { + item = carousel._getValidIndex(item); + } + carousel.scrollTo(item); + }, + + /** + * Scroll the Carousel to make the item the first visible item. + * + * @method scrollTo + * @public + * @param item Number The index of the element to position at. + * @param dontSelect Boolean True if select should be avoided + */ + scrollTo: function (item, dontSelect) { + var carousel = this, + animate, animCfg, isCircular, delta, direction, firstItem, + numItems, numPerPage, offset, page, rv, sentinel, + stopAutoScroll; + + if (JS.isUndefined(item) || item == carousel._firstItem || + carousel.isAnimating()) { + return; // nothing to do! + } + + animCfg = carousel.get("animation"); + isCircular = carousel.get("isCircular"); + firstItem = carousel._firstItem; + numItems = carousel.get("numItems"); + numPerPage = carousel.get("numVisible"); + page = carousel.get("currentPage"); + stopAutoScroll = function () { + if (carousel.isAutoPlayOn()) { + carousel.stopAutoPlay(); + } + }; + + if (item < 0) { + if (isCircular) { + item = numItems + item; + } else { + stopAutoScroll.call(carousel); + return; + } + } else if (numItems > 0 && item > numItems - 1) { + if (carousel.get("isCircular")) { + item = numItems - item; + } else { + stopAutoScroll.call(carousel); + return; + } + } + + direction = (carousel._firstItem > item) ? "backward" : "forward"; + + sentinel = firstItem + numPerPage; + sentinel = (sentinel > numItems - 1) ? numItems - 1 : sentinel; + rv = carousel.fireEvent(beforeScrollEvent, + { dir: direction, first: firstItem, last: sentinel }); + if (rv === false) { // scrolling is prevented + return; + } + + carousel.fireEvent(beforePageChangeEvent, { page: page }); + + delta = firstItem - item; // yes, the delta is reverse + carousel._firstItem = item; + carousel.set("firstVisible", item); + + + carousel._loadItems(); // do we have all the items to display? + + sentinel = item + numPerPage; + sentinel = (sentinel > numItems - 1) ? numItems - 1 : sentinel; + + offset = getScrollOffset.call(carousel, delta); + + animate = animCfg.speed > 0; + + if (animate) { + carousel._animateAndSetCarouselOffset(offset, item, sentinel, + dontSelect); + } else { + carousel._setCarouselOffset(offset); + updateStateAfterScroll.call(carousel, item, sentinel); + } + }, + + /** + * Select the previous item in the Carousel. + * + * @method selectPreviousItem + * @public + */ + selectPreviousItem: function () { + var carousel = this, + newpos = 0, + selected = carousel.get("selectedItem"); + + if (selected == this._firstItem) { + newpos = selected - carousel.get("numVisible"); + carousel._selectedItem = carousel._getSelectedItem(selected-1); + carousel.scrollTo(newpos); + } else { + newpos = carousel.get("selectedItem") - + carousel.get("scrollIncrement"); + carousel.set("selectedItem",carousel._getSelectedItem(newpos)); + } + }, + + /** + * Select the next item in the Carousel. + * + * @method selectNextItem + * @public + */ + selectNextItem: function () { + var carousel = this, newpos = 0; + + newpos = carousel.get("selectedItem") + + carousel.get("scrollIncrement"); + carousel.set("selectedItem", carousel._getSelectedItem(newpos)); + }, + + /** + * Display the Carousel. + * + * @method show + * @public + */ + show: function () { + var carousel = this, + cssClass = carousel.CLASSES; + + if (carousel.fireEvent(beforeShowEvent) !== false) { + carousel.addClass(cssClass.VISIBLE); + carousel.fireEvent(showEvent); + } + }, + + /** + * Start auto-playing the Carousel. + * + * @method startAutoPlay + * @public + */ + startAutoPlay: function () { + var carousel = this, timer; + + if (JS.isUndefined(carousel._autoPlayTimer)) { + timer = carousel.get("autoPlayInterval"); + if (timer <= 0) { + return; + } + carousel._isAutoPlayInProgress = true; + carousel.fireEvent(startAutoPlayEvent); + carousel._autoPlayTimer = setTimeout(function () { + carousel._autoScroll(); + }, timer); + } + }, + + /** + * Stop auto-playing the Carousel. + * + * @method stopAutoPlay + * @public + */ + stopAutoPlay: function () { + var carousel = this; + + if (!JS.isUndefined(carousel._autoPlayTimer)) { + clearTimeout(carousel._autoPlayTimer); + delete carousel._autoPlayTimer; + carousel._isAutoPlayInProgress = false; + carousel.fireEvent(stopAutoPlayEvent); + } + }, + + /** + * Return the string representation of the Carousel. + * + * @method toString + * @public + * @return {String} + */ + toString: function () { + return WidgetName + (this.get ? " (#" + this.get("id") + ")" : ""); + }, + + /* + * Protected methods of the Carousel component + */ + + /** + * Set the Carousel offset to the passed offset after animating. + * + * @method _animateAndSetCarouselOffset + * @param {Integer} offset The offset to which the Carousel has to be + * scrolled to. + * @param {Integer} item The index to which the Carousel will scroll. + * @param {Integer} sentinel The last element in the view port. + * @protected + */ + _animateAndSetCarouselOffset: function (offset, item, sentinel) { + var carousel = this, + animCfg = carousel.get("animation"), + animObj = null; + + if (carousel.get("isVertical")) { + animObj = new YAHOO.util.Motion(carousel._carouselEl, + { points: { by: [0, offset] } }, + animCfg.speed, animCfg.effect); + } else { + animObj = new YAHOO.util.Motion(carousel._carouselEl, + { points: { by: [offset, 0] } }, + animCfg.speed, animCfg.effect); + } + + carousel._isAnimationInProgress = true; + animObj.onComplete.subscribe(carousel._animationCompleteHandler, + { scope: carousel, item: item, + last: sentinel }); + animObj.animate(); + }, + + /** + * Handle the animation complete event. + * + * @method _animationCompleteHandler + * @param {Event} ev The event. + * @param {Array} p The event parameters. + * @param {Object} o The object that has the state of the Carousel + * @protected + */ + _animationCompleteHandler: function (ev, p, o) { + o.scope._isAnimationInProgress = false; + updateStateAfterScroll.call(o.scope, o.item, o.last); + }, + + /** + * Automatically scroll the contents of the Carousel. + * @method _autoScroll + * @protected + */ + _autoScroll: function() { + var carousel = this, + currIndex = carousel._firstItem, + index; + + if (currIndex >= carousel.get("numItems") - 1) { + if (carousel.get("isCircular")) { + index = 0; + } else { + carousel.stopAutoPlay(); + } + } else { + index = currIndex + carousel.get("numVisible"); + } + + carousel._selectedItem = carousel._getSelectedItem(index); + carousel.scrollTo.call(carousel, index); + }, + + /** + * Create the Carousel. + * + * @method createCarousel + * @param elId {String} The id of the element to be created + * @protected + */ + _createCarousel: function (elId) { + var carousel = this, + cssClass = carousel.CLASSES, + el = Dom.get(elId); + + if (!el) { + el = createElement("DIV", { + className : cssClass.CAROUSEL, + id : elId + }); + } + + if (!carousel._carouselEl) { + carousel._carouselEl=createElement(carousel.get("carouselEl"), + { className: cssClass.CAROUSEL_EL }); + } + + return el; + }, + + /** + * Create the Carousel clip container. + * + * @method createCarouselClip + * @protected + */ + _createCarouselClip: function () { + return createElement("DIV", { className: this.CLASSES.CONTENT }); + }, + + /** + * Create the Carousel item. + * + * @method createCarouselItem + * @param obj {Object} The attributes of the element to be created + * @protected + */ + _createCarouselItem: function (obj) { + return createElement(this.get("carouselItemEl"), { + className : obj.className, + content : obj.content, + id : obj.id + }); + }, + + /** + * Return a valid item for a possibly out of bounds index considering + * the isCircular property. + * + * @method _getValidIndex + * @param index {Number} The index of the item to be returned + * @return {Object} Return a valid item index + * @protected + */ + _getValidIndex: function (index) { + var carousel = this, + isCircular = carousel.get("isCircular"), + numItems = carousel.get("numItems"), + sentinel = numItems - 1; + + if (index < 0) { + index = isCircular ? numItems + index : 0; + } else if (index > sentinel) { + index = isCircular ? index - numItems : sentinel; + } + + return index; + }, + + /** + * Get the value for the selected item. + * + * @method _getSelectedItem + * @param val {Number} The new value for "selected" item + * @return {Number} The new value that would be set + * @protected + */ + _getSelectedItem: function (val) { + var carousel = this, + isCircular = carousel.get("isCircular"), + numItems = carousel.get("numItems"), + sentinel = numItems - 1; + + if (val < 0) { + if (isCircular) { + val = numItems + val; + } else { + val = carousel.get("selectedItem"); + } + } else if (val > sentinel) { + if (isCircular) { + val = val - numItems; + } else { + val = carousel.get("selectedItem"); + } + } + + return val; + }, + + /** + * The "click" handler for the item. + * + * @method _itemClickHandler + * @param {Event} ev The event object + * @protected + */ + _itemClickHandler: function (ev) { + var carousel = this, + container = carousel.get("element"), + el, + item, + target = YAHOO.util.Event.getTarget(ev); + + while (target && target != container && + target.id != carousel._carouselEl) { + el = target.nodeName; + if (el.toUpperCase() == carousel.get("carouselItemEl")) { + break; + } + target = target.parentNode; + } + + if ((item = carousel.getItemPositionById(target.id)) >= 0) { + carousel.set("selectedItem", carousel._getSelectedItem(item)); + carousel.focus(); + } + }, + + /** + * The keyboard event handler for Carousel. + * + * @method _keyboardEventHandler + * @param ev {Event} The event that is being handled. + * @protected + */ + _keyboardEventHandler: function (ev) { + var carousel = this, + key = Event.getCharCode(ev), + prevent = false; + + if (carousel.isAnimating()) { + return; // do not mess while animation is in progress + } + + switch (key) { + case 0x25: // left arrow + case 0x26: // up arrow + carousel.selectPreviousItem(); + prevent = true; + break; + case 0x27: // right arrow + case 0x28: // down arrow + carousel.selectNextItem(); + prevent = true; + break; + case 0x21: // page-up + carousel.scrollPageBackward(); + prevent = true; + break; + case 0x22: // page-down + carousel.scrollPageForward(); + prevent = true; + break; + } + + if (prevent) { + if (carousel.isAutoPlayOn()) { + carousel.stopAutoPlay(); + } + Event.preventDefault(ev); + } + }, + + /** + * The load the required set of items that are needed for display. + * + * @method _loadItems + * @protected + */ + _loadItems: function() { + var carousel = this, + first = carousel.get("firstVisible"), + last = 0, + numItems = carousel.get("numItems"), + numVisible = carousel.get("numVisible"), + reveal = carousel.get("revealAmount"); + + last = first + numVisible - 1 + (reveal ? 1 : 0); + last = last > numItems - 1 ? numItems - 1 : last; + + if (!carousel.getItem(first) || !carousel.getItem(last)) { + carousel.fireEvent(loadItemsEvent, { + ev: loadItemsEvent, first: first, last: last, + num: last - first + }); + } + }, + + /** + * The "click" handler for the pager navigation. + * + * @method _pagerClickHandler + * @param {Event} ev The event object + * @protected + */ + _pagerClickHandler: function (ev) { + var carousel = this, + pos, + target = Event.getTarget(ev), + val; + + function getPagerNode(el) { + var itemEl = carousel.get("carouselItemEl"); + + if (el.nodeName.toUpperCase() == itemEl.toUpperCase()) { + el = Dom.getChildrenBy(el, function (node) { + // either an anchor or select at least + return node.href || node.value; + }); + if (el && el[0]) { + return el[0]; + } + } else if (el.href || el.value) { + return el; + } + + return null; + } + + if (target) { + target = getPagerNode(target); + if (!target) { + return; + } + val = target.href || target.value; + if (JS.isString(val) && val) { + pos = val.lastIndexOf("#"); + if (pos != -1) { + val = carousel.getItemPositionById( + val.substring(pos + 1)); + carousel._selectedItem = val; + carousel.scrollTo(val); + if (!target.value) { // not a select element + carousel.focus(); + } + Event.preventDefault(ev); + } + } + } + }, + + /** + * Find the Carousel within a container. The Carousel is identified by + * the first element that matches the carousel element tag or the + * element that has the Carousel class. + * + * @method parseCarousel + * @param parent {HTMLElement} The parent element to look under + * @return {Boolean} True if Carousel is found, false otherwise + * @protected + */ + _parseCarousel: function (parent) { + var carousel = this, child, cssClass, domEl, found, node; + + cssClass = carousel.CLASSES; + domEl = carousel.get("carouselEl"); + found = false; + + for (child = parent.firstChild; child; child = child.nextSibling) { + if (child.nodeType == 1) { + node = child.nodeName; + if (node.toUpperCase() == domEl) { + carousel._carouselEl = child; + Dom.addClass(carousel._carouselEl, + carousel.CLASSES.CAROUSEL_EL); + found = true; + } + } + } + + return found; + }, + + /** + * Find the items within the Carousel and add them to the items table. + * A Carousel item is identified by elements that matches the carousel + * item element tag. + * + * @method parseCarouselItems + * @protected + */ + _parseCarouselItems: function () { + var carousel = this, + child, + domItemEl, + elId, + node, + parent = carousel._carouselEl; + + domItemEl = carousel.get("carouselItemEl"); + + for (child = parent.firstChild; child; child = child.nextSibling) { + if (child.nodeType == 1) { + node = child.nodeName; + if (node.toUpperCase() == domItemEl) { + if (child.id) { + elId = child.id; + } else { + elId = Dom.generateId(); + child.setAttribute("id", elId); + } + carousel.addItem(child); + } + } + } + }, + + /** + * Find the Carousel navigation within a container. The navigation + * elements need to match the carousel navigation class names. + * + * @method parseCarouselNavigation + * @param parent {HTMLElement} The parent element to look under + * @return {Boolean} True if at least one is found, false otherwise + * @protected + */ + _parseCarouselNavigation: function (parent) { + var carousel = this, + cfg, + cssClass = carousel.CLASSES, + el, + i, + j, + nav, + rv = false; + + nav = Dom.getElementsByClassName(cssClass.PREV_PAGE, "*", parent); + if (nav.length > 0) { + for (i in nav) { + if (nav.hasOwnProperty(i)) { + el = nav[i]; + if (el.nodeName == "INPUT" || + el.nodeName == "BUTTON") { + carousel._navBtns.prev.push(el); + } else { + j = el.getElementsByTagName("INPUT"); + if (JS.isArray(j) && j.length > 0) { + carousel._navBtns.prev.push(j[0]); + } else { + j = el.getElementsByTagName("BUTTON"); + if (JS.isArray(j) && j.length > 0) { + carousel._navBtns.prev.push(j[0]); + } + } + } + } + } + cfg = { prev: nav }; + } + + nav = Dom.getElementsByClassName(cssClass.NEXT_PAGE, "*", parent); + if (nav.length > 0) { + for (i in nav) { + if (nav.hasOwnProperty(i)) { + el = nav[i]; + if (el.nodeName == "INPUT" || + el.nodeName == "BUTTON") { + carousel._navBtns.next.push(el); + } else { + j = el.getElementsByTagName("INPUT"); + if (JS.isArray(j) && j.length > 0) { + carousel._navBtns.next.push(j[0]); + } else { + j = el.getElementsByTagName("BUTTON"); + if (JS.isArray(j) && j.length > 0) { + carousel._navBtns.next.push(j[0]); + } + } + } + } + } + if (cfg) { + cfg.next = nav; + } else { + cfg = { next: nav }; + } + } + + if (cfg) { + carousel.set("navigation", cfg); + rv = true; + } + + return rv; + }, + + /** + * Refresh the widget UI if it is not already rendered, on first item + * addition. + * + * @method _refreshUi + * @protected + */ + _refreshUi: function () { + var carousel = this; + + // Set the rendered state appropriately. + carousel._hasRendered = true; + carousel.fireEvent(renderEvent); + }, + + /** + * Set the Carousel offset to the passed offset. + * + * @method _setCarouselOffset + * @protected + */ + _setCarouselOffset: function (offset) { + var carousel = this, which; + + which = carousel.get("isVertical") ? "top" : "left"; + offset += offset !== 0 ? getStyle(carousel._carouselEl, which) : 0; + Dom.setStyle(carousel._carouselEl, which, offset + "px"); + }, + + /** + * Setup/Create the Carousel navigation element (if needed). + * + * @method _setupCarouselNavigation + * @protected + */ + _setupCarouselNavigation: function () { + var carousel = this, + btn, cfg, cssClass, nav, navContainer, nextButton, prevButton; + + cssClass = carousel.CLASSES; + + // TODO: can the _navBtns be tested against instead? + navContainer = Dom.getElementsByClassName(cssClass.NAVIGATION, + "DIV", carousel.get("element")); + + if (navContainer.length === 0) { + navContainer = createElement("DIV", + { className: cssClass.NAVIGATION }); + carousel.insertBefore(navContainer, + Dom.getFirstChild(carousel.get("element"))); + } else { + navContainer = navContainer[0]; + } + + carousel._pages.el = createElement("UL"); + navContainer.appendChild(carousel._pages.el); + + nav = carousel.get("navigation"); + if (JS.isString(nav.prev) || JS.isArray(nav.prev)) { + if (JS.isString(nav.prev)) { + nav.prev = [nav.prev]; + } + for (btn in nav.prev) { + if (nav.prev.hasOwnProperty(btn)) { + carousel._navBtns.prev.push(Dom.get(nav.prev[btn])); + } + } + } else { + // TODO: separate method for creating a navigation button + prevButton = createElement("SPAN", + { className: cssClass.BUTTON + cssClass.FIRST_NAV }); + // XXX: for IE 6.x + Dom.setStyle(prevButton, "visibility", "visible"); + btn = Dom.generateId(); + prevButton.innerHTML = ""; + navContainer.appendChild(prevButton); + btn = Dom.get(btn); + carousel._navBtns.prev = [btn]; + cfg = { prev: [prevButton] }; + } + + if (JS.isString(nav.next) || JS.isArray(nav.next)) { + if (JS.isString(nav.next)) { + nav.next = [nav.next]; + } + for (btn in nav.next) { + if (nav.next.hasOwnProperty(btn)) { + carousel._navBtns.next.push(Dom.get(nav.next[btn])); + } + } + } else { + // TODO: separate method for creating a navigation button + nextButton = createElement("SPAN", + { className: cssClass.BUTTON + cssClass.NEXT_NAV }); + // XXX: for IE 6.x + Dom.setStyle(nextButton, "visibility", "visible"); + btn = Dom.generateId(); + nextButton.innerHTML = ""; + navContainer.appendChild(nextButton); + btn = Dom.get(btn); + carousel._navBtns.next = [btn]; + if (cfg) { + cfg.next = [nextButton]; + } else { + cfg = { next: [nextButton] }; + } + } + + if (cfg) { + carousel.set("navigation", cfg); + } + + return navContainer; + }, + + /** + * Set the clip container size (based on the new numVisible value). + * + * @method _setClipContainerSize + * @param clip {HTMLElement} The clip container element. + * @param num {Number} optional The number of items per page. + * @protected + */ + _setClipContainerSize: function (clip, num) { + var carousel = this, + attr, currVal, isVertical, itemSize, reveal, size, which; + + isVertical = carousel.get("isVertical"); + reveal = carousel.get("revealAmount"); + which = isVertical ? "height" : "width"; + attr = isVertical ? "top" : "left"; + + clip = clip || carousel._clipEl; + if (!clip) { + return; + } + + num = num || carousel.get("numVisible"); + itemSize = getCarouselItemSize.call(carousel, which); + size = itemSize * num; + + // TODO: try to re-use the _hasRendered indicator + carousel._recomputeSize = (size === 0); // bleh! + if (carousel._recomputeSize) { + carousel._hasRendered = false; + return; // no use going further, bail out! + } + + if (reveal > 0) { + reveal = itemSize * (reveal / 100) * 2; + size += reveal; + // TODO: set the Carousel's initial offset somwehere + currVal = parseFloat(Dom.getStyle(carousel._carouselEl, attr)); + currVal = JS.isNumber(currVal) ? currVal : 0; + Dom.setStyle(carousel._carouselEl, + attr, currVal + (reveal / 2) + "px"); + } + + if (isVertical) { + size += getStyle(carousel._carouselEl, "marginTop") + + getStyle(carousel._carouselEl, "marginBottom") + + getStyle(carousel._carouselEl, "paddingTop") + + getStyle(carousel._carouselEl, "paddingBottom") + + getStyle(carousel._carouselEl, "borderTopWidth") + + getStyle(carousel._carouselEl, "borderBottomWidth"); + // XXX: for vertical Carousel + Dom.setStyle(clip, which, (size - (num - 1)) + "px"); + } else { + size += getStyle(carousel._carouselEl, "marginLeft") + + getStyle(carousel._carouselEl, "marginRight") + + getStyle(carousel._carouselEl, "paddingLeft") + + getStyle(carousel._carouselEl, "paddingRight") + + getStyle(carousel._carouselEl, "borderLeftWidth") + + getStyle(carousel._carouselEl, "borderRightWidth"); + Dom.setStyle(clip, which, size + "px"); + } + + carousel._setContainerSize(clip); // adjust the container size too + }, + + /** + * Set the container size. + * + * @method _setContainerSize + * @param clip {HTMLElement} The clip container element. + * @param attr {String} Either set the height or width. + * @protected + */ + _setContainerSize: function (clip, attr) { + var carousel = this, + config = carousel.CONFIG, + cssClass = carousel.CLASSES, + isVertical, + size; + + isVertical = carousel.get("isVertical"); + clip = clip || carousel._clipEl; + attr = attr || (isVertical ? "height" : "width"); + size = parseFloat(Dom.getStyle(clip, attr), 10); + + size = JS.isNumber(size) ? size : 0; + + if (isVertical) { + size += getStyle(carousel._carouselEl, "marginTop") + + getStyle(carousel._carouselEl, "marginBottom") + + getStyle(carousel._carouselEl, "paddingTop") + + getStyle(carousel._carouselEl, "paddingBottom") + + getStyle(carousel._carouselEl, "borderTopWidth") + + getStyle(carousel._carouselEl, "borderBottomWidth") + + getStyle(carousel._navEl, "height"); + } else { + size += getStyle(clip, "marginLeft") + + getStyle(clip, "marginRight") + + getStyle(clip, "paddingLeft") + + getStyle(clip, "paddingRight") + + getStyle(clip, "borderLeftWidth") + + getStyle(clip, "borderRightWidth"); + } + + if (!isVertical) { + if (size < config.HORZ_MIN_WIDTH) { + size = config.HORZ_MIN_WIDTH; + carousel.addClass(cssClass.MIN_WIDTH); + } + } + carousel.setStyle(attr, size + "px"); + + // Additionally the width of the container should be set for + // the vertical Carousel + if (isVertical) { + size = getCarouselItemSize.call(carousel, "width"); + if (size < config.VERT_MIN_WIDTH) { + size = config.VERT_MIN_WIDTH; + carousel.addClass(cssClass.MIN_WIDTH); + } + carousel.setStyle("width", size + "px"); + } + }, + + /** + * Set the value for the Carousel's first visible item. + * + * @method _setFirstVisible + * @param val {Number} The new value for firstVisible + * @return {Number} The new value that would be set + * @protected + */ + _setFirstVisible: function (val) { + var carousel = this; + + if (val >= 0 && val < carousel.get("numItems")) { + carousel.scrollTo(val); + } else { + val = carousel.get("firstVisible"); + } + return val; + }, + + /** + * Set the value for the Carousel's navigation. + * + * @method _setNavigation + * @param cfg {Object} The navigation configuration + * @return {Object} The new value that would be set + * @protected + */ + _setNavigation: function (cfg) { + var carousel = this; + + if (cfg.prev) { + Event.on(cfg.prev, "click", scrollPageBackward, carousel); + } + if (cfg.next) { + Event.on(cfg.next, "click", scrollPageForward, carousel); + } + }, + + /** + * Set the value for the number of visible items in the Carousel. + * + * @method _setNumVisible + * @param val {Number} The new value for numVisible + * @return {Number} The new value that would be set + * @protected + */ + _setNumVisible: function (val) { + var carousel = this; + + carousel._setClipContainerSize(carousel._clipEl, val); + }, + + /** + * Set the number of items in the Carousel. + * Warning: Setting this to a lower number than the current removes + * items from the end. + * + * @method _setNumItems + * @param val {Number} The new value for numItems + * @return {Number} The new value that would be set + * @protected + */ + _setNumItems: function (val) { + var carousel = this, + num = carousel._itemsTable.numItems; + + if (JS.isArray(carousel._itemsTable.items)) { + if (carousel._itemsTable.items.length != num) { // out of sync + num = carousel._itemsTable.items.length; + carousel._itemsTable.numItems = num; + } + } + + if (val < num) { + while (num > val) { + carousel.removeItem(num - 1); + num--; + } + } + + return val; + }, + + /** + * Set the orientation of the Carousel. + * + * @method _setOrientation + * @param val {Boolean} The new value for isVertical + * @return {Boolean} The new value that would be set + * @protected + */ + _setOrientation: function (val) { + var carousel = this, + cssClass = carousel.CLASSES; + + if (val) { + carousel.replaceClass(cssClass.HORIZONTAL, cssClass.VERTICAL); + } else { + carousel.replaceClass(cssClass.VERTICAL, cssClass.HORIZONTAL); + } + carousel._itemsTable.size = 0; // force recalculation next time + return val; + }, + + /** + * Set the value for the reveal amount percentage in the Carousel. + * + * @method _setRevealAmount + * @param val {Number} The new value for revealAmount + * @return {Number} The new value that would be set + * @protected + */ + _setRevealAmount: function (val) { + var carousel = this; + + if (val >= 0 && val <= 100) { + val = parseInt(val, 10); + val = JS.isNumber(val) ? val : 0; + carousel._setClipContainerSize(); + } else { + val = carousel.get("revealAmount"); + } + return val; + }, + + /** + * Set the value for the selected item. + * + * @method _setSelectedItem + * @param val {Number} The new value for "selected" item + * @protected + */ + _setSelectedItem: function (val) { + this._selectedItem = val; + }, + + /** + * Synchronize and redraw the UI after an item is added. + * + * @method _syncUiForItemAdd + * @protected + */ + _syncUiForItemAdd: function (obj) { + var carousel = this, + carouselEl = carousel._carouselEl, + el, + item, + itemsTable = carousel._itemsTable, + oel, + pos, + sibling; + + pos = JS.isUndefined(obj.pos) ? itemsTable.numItems - 1 : obj.pos; + if (!JS.isUndefined(itemsTable.items[pos])) { + item = itemsTable.items[pos]; + if (item && !JS.isUndefined(item.id)) { + oel = Dom.get(item.id); + } + } + if (!oel) { + el = carousel._createCarouselItem({ + className : item.className, + content : item.item, + id : item.id + }); + if (JS.isUndefined(obj.pos)) { + if (!JS.isUndefined(itemsTable.loading[pos])) { + oel = itemsTable.loading[pos]; + // if oel is null, it is a problem ... + } + if (oel) { + // replace the node + carouselEl.replaceChild(el, oel); + // ... and remove the item from the data structure + delete itemsTable.loading[pos]; + } else { + carouselEl.appendChild(el); + } + } else { + if (!JS.isUndefined(itemsTable.items[obj.pos + 1])) { + sibling = Dom.get(itemsTable.items[obj.pos + 1].id); + } + if (sibling) { + carouselEl.insertBefore(el, sibling); + } else { + } + } + } else { + if (JS.isUndefined(obj.pos)) { + if (!Dom.isAncestor(carousel._carouselEl, oel)) { + carouselEl.appendChild(oel); + } + } else { + if (!Dom.isAncestor(carouselEl, oel)) { + if (!JS.isUndefined(itemsTable.items[obj.pos + 1])) { + carouselEl.insertBefore(oel, + Dom.get(itemsTable.items[obj.pos + 1].id)); + } + } + } + } + + if (!carousel._hasRendered) { + carousel._refreshUi(); + } + + if (carousel.get("selectedItem") < 0) { + carousel.set("selectedItem", carousel.get("firstVisible")); + } + }, + + /** + * Synchronize and redraw the UI after an item is removed. + * + * @method _syncUiForItemAdd + * @protected + */ + _syncUiForItemRemove: function (obj) { + var carousel = this, + carouselEl = carousel._carouselEl, + el, item, num, pos; + + num = carousel.get("numItems"); + item = obj.item; + pos = obj.pos; + + if (item && (el = Dom.get(item.id))) { + if (el && Dom.isAncestor(carouselEl, el)) { + Event.purgeElement(el, true); + carouselEl.removeChild(el); + } + + if (carousel.get("selectedItem") == pos) { + pos = pos >= num ? num - 1 : pos; + carousel.set("selectedItem", pos); + } + } else { + } + }, + + /** + * Synchronize and redraw the UI for lazy loading. + * + * @method _syncUiForLazyLoading + * @protected + */ + _syncUiForLazyLoading: function (obj) { + var carousel = this, + carouselEl = carousel._carouselEl, + el, + i, + itemsTable = carousel._itemsTable, + sibling; + + for (i = obj.first; i <= obj.last; i++) { + el = carousel._createCarouselItem({ + className : carousel.CLASSES.ITEM_LOADING, + content : carousel.STRINGS.ITEM_LOADING_CONTENT, + id : Dom.generateId() + }); + if (el) { + if (!JS.isUndefined(itemsTable.items[obj.last + 1])) { + sibling = Dom.get(itemsTable.items[obj.last + 1].id); + if (sibling) { + carouselEl.insertBefore(el, sibling); + } else { + } + } else { + carouselEl.appendChild(el); + } + } + itemsTable.loading[i] = el; + } + }, + + /** + * Set the correct class for the navigation buttons. + * + * @method _updateNavButtons + * @param el {Object} The target button + * @param setFocus {Boolean} True to set focus ring, false otherwise. + * @protected + */ + _updateNavButtons: function (el, setFocus) { + var children, + cssClass = this.CLASSES, + grandParent, + parent = el.parentNode; + + if (!parent) { + return; + } + grandParent = parent.parentNode; + + if (el.nodeName.toUpperCase() == "BUTTON" && + Dom.hasClass(parent, cssClass.BUTTON)) { + if (setFocus) { + if (grandParent) { + children = Dom.getChildren(grandParent); + if (children) { + Dom.removeClass(children, cssClass.FOCUSSED_BUTTON); + } + } + Dom.addClass(parent, cssClass.FOCUSSED_BUTTON); + } else { + Dom.removeClass(parent, cssClass.FOCUSSED_BUTTON); + } + } + }, + + /** + * Update the UI for the pager buttons based on the current page and + * the number of pages. + * + * @method _updatePagerButtons + * @protected + */ + _updatePagerButtons: function () { + var carousel = this, + css = carousel.CLASSES, + cur = carousel._pages.cur, // current page + el, + html, + i, + item, + n = carousel.get("numVisible"), + num = carousel._pages.num, // total pages + pager = carousel._pages.el; // the pager container element + + if (num === 0 || !pager) { + return; // don't do anything if number of pages is 0 + } + + // Hide the pager before redrawing it + Dom.setStyle(pager, "visibility", "hidden"); + + // Remove all nodes from the pager + while (pager.firstChild) { + pager.removeChild(pager.firstChild); + } + + for (i = 0; i < num; i++) { + if (JS.isUndefined(carousel._itemsTable.items[i * n])) { + Dom.setStyle(pager, "visibility", "visible"); + break; + } + item = carousel._itemsTable.items[i * n].id; + + el = document.createElement("LI"); + if (!el) { + Dom.setStyle(pager, "visibility", "visible"); + break; + } + + if (i === 0) { + Dom.addClass(el, css.FIRST_PAGE); + } + if (i == cur) { + Dom.addClass(el, css.SELECTED_NAV); + } + + // TODO: use a template string for i18N compliance + html = "" + + carousel.STRINGS.PAGER_PREFIX_TEXT + " " + (i+1) + + ""; + el.innerHTML = html; + + pager.appendChild(el); + } + + // Show the pager now + Dom.setStyle(pager, "visibility", "visible"); + }, + + /** + * Update the UI for the pager menu based on the current page and + * the number of pages. If the number of pages is greater than + * MAX_PAGER_BUTTONS, then the selection of pages is provided by a drop + * down menu instead of a set of buttons. + * + * @method _updatePagerMenu + * @protected + */ + _updatePagerMenu: function () { + var carousel = this, + cur = carousel._pages.cur, // current page + el, + i, + item, + n = carousel.get("numVisible"), + num = carousel._pages.num, // total pages + pager = carousel._pages.el, // the pager container element + sel; + + if (num === 0) { + return; // don't do anything if number of pages is 0 + } + + sel = document.createElement("SELECT"); + if (!sel) { + return; + } + + // Hide the pager before redrawing it + Dom.setStyle(pager, "visibility", "hidden"); + + // Remove all nodes from the pager + while (pager.firstChild) { + pager.removeChild(pager.firstChild); + } + + for (i = 0; i < num; i++) { + if (JS.isUndefined(carousel._itemsTable.items[i * n])) { + Dom.setStyle(pager, "visibility", "visible"); + break; + } + item = carousel._itemsTable.items[i * n].id; + + el = document.createElement("OPTION"); + if (!el) { + Dom.setStyle(pager, "visibility", "visible"); + break; + } + el.value = "#" + item; + // TODO: use a template string for i18N compliance + el.innerHTML = carousel.STRINGS.PAGER_PREFIX_TEXT+" "+(i+1); + + if (i == cur) { + el.setAttribute("selected", "selected"); + } + + sel.appendChild(el); + } + + el = document.createElement("FORM"); + if (!el) { + } else { + el.appendChild(sel); + pager.appendChild(el); + } + + // Show the pager now + Dom.setStyle(pager, "visibility", "visible"); + }, + + /** + * Set the correct tab index for the Carousel items. + * + * @method _updateTabIndex + * @param el {Object} The element to be focussed + * @protected + */ + _updateTabIndex: function (el) { + var carousel = this; + + if (el) { + if (carousel._focusableItemEl) { + carousel._focusableItemEl.tabIndex = -1; + } + carousel._focusableItemEl = el; + el.tabIndex = 0; + } + }, + + /** + * Validate animation parameters. + * + * @method _validateAnimation + * @param cfg {Object} The animation configuration + * @return {Boolean} The status of the validation + * @protected + */ + _validateAnimation: function (cfg) { + var rv = true; + + if (JS.isObject(cfg)) { + if (cfg.speed) { + rv = rv && JS.isNumber(cfg.speed); + } + if (cfg.effect) { + rv = rv && JS.isFunction(cfg.effect); + } else if (!JS.isUndefined(YAHOO.util.Easing)) { + cfg.effect = YAHOO.util.Easing.easeOut; + } + } else { + rv = false; + } + + return rv; + }, + + /** + * Validate the firstVisible value. + * + * @method _validateFirstVisible + * @param val {Number} The first visible value + * @return {Boolean} The status of the validation + * @protected + */ + _validateFirstVisible: function (val) { + var carousel = this, numItems = carousel.get("numItems"); + + if (JS.isNumber(val)) { + if (numItems === 0 && val == numItems) { + return true; + } else { + return (val >= 0 && val < numItems); + } + } + + return false; + }, + + /** + * Validate and navigation parameters. + * + * @method _validateNavigation + * @param cfg {Object} The navigation configuration + * @return {Boolean} The status of the validation + * @protected + */ + _validateNavigation : function (cfg) { + var i; + + if (!JS.isObject(cfg)) { + return false; + } + + if (cfg.prev) { + if (!JS.isArray(cfg.prev)) { + return false; + } + for (i in cfg.prev) { + if (cfg.prev.hasOwnProperty(i)) { + if (!JS.isString(cfg.prev[i].nodeName)) { + return false; + } + } + } + } + + if (cfg.next) { + if (!JS.isArray(cfg.next)) { + return false; + } + for (i in cfg.next) { + if (cfg.next.hasOwnProperty(i)) { + if (!JS.isString(cfg.next[i].nodeName)) { + return false; + } + } + } + } + + return true; + }, + + /** + * Validate the numItems value. + * + * @method _validateNumItems + * @param val {Number} The numItems value + * @return {Boolean} The status of the validation + * @protected + */ + _validateNumItems: function (val) { + return JS.isNumber(val) && (val >= 0); + }, + + /** + * Validate the numVisible value. + * + * @method _validateNumVisible + * @param val {Number} The numVisible value + * @return {Boolean} The status of the validation + * @protected + */ + _validateNumVisible: function (val) { + var rv = false; + + if (JS.isNumber(val)) { + rv = val > 0 && val <= this.get("numItems"); + } + + return rv; + }, + + /** + * Validate the revealAmount value. + * + * @method _validateRevealAmount + * @param val {Number} The revealAmount value + * @return {Boolean} The status of the validation + * @protected + */ + _validateRevealAmount: function (val) { + var rv = false; + + if (JS.isNumber(val)) { + rv = val >= 0 && val < 100; + } + + return rv; + }, + + /** + * Validate the scrollIncrement value. + * + * @method _validateScrollIncrement + * @param val {Number} The scrollIncrement value + * @return {Boolean} The status of the validation + * @protected + */ + _validateScrollIncrement: function (val) { + var rv = false; + + if (JS.isNumber(val)) { + rv = (val > 0 && val < this.get("numItems")); + } + + return rv; + } + + }); + +})(); +/* +;; Local variables: ** +;; mode: js2 ** +;; indent-tabs-mode: nil ** +;; End: ** +*/ +YAHOO.register("carousel", YAHOO.widget.Carousel, {version: "2.7.0", build: "1799"});