3659 lines
119 KiB
JavaScript
3659 lines
119 KiB
JavaScript
/*
|
|
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.log("Component creation", WidgetName);
|
|
|
|
YAHOO.widget.Carousel.superclass.constructor.call(this, el, cfg);
|
|
};
|
|
|
|
/*
|
|
* Private variables of the Carousel component
|
|
*/
|
|
|
|
/* Some abbreviations to avoid lengthy typing and lookups. */
|
|
var Carousel = YAHOO.widget.Carousel,
|
|
Dom = YAHOO.util.Dom,
|
|
Event = YAHOO.util.Event,
|
|
JS = YAHOO.lang;
|
|
|
|
/**
|
|
* The widget name.
|
|
* @private
|
|
* @static
|
|
*/
|
|
WidgetName = "Carousel";
|
|
|
|
/**
|
|
* The internal table of Carousel instances.
|
|
* @private
|
|
* @static
|
|
*/
|
|
var instances = {},
|
|
|
|
/*
|
|
* Custom events of the Carousel component
|
|
*/
|
|
|
|
/**
|
|
* @event afterScroll
|
|
* @description Fires when the Carousel has scrolled to the previous or
|
|
* next page. Passes back the index of the first and last visible items in
|
|
* the Carousel. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
afterScrollEvent = "afterScroll",
|
|
|
|
/**
|
|
* @event allItemsRemovedEvent
|
|
* @description Fires when all items have been removed from the Carousel.
|
|
* See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
allItemsRemovedEvent = "allItemsRemoved",
|
|
|
|
/**
|
|
* @event beforeHide
|
|
* @description Fires before the Carousel is hidden. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
beforeHideEvent = "beforeHide",
|
|
|
|
/**
|
|
* @event beforePageChange
|
|
* @description Fires when the Carousel is about to scroll to the previous
|
|
* or next page. Passes back the page number of the current page. Note
|
|
* that the first page number is zero. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
beforePageChangeEvent = "beforePageChange",
|
|
|
|
/**
|
|
* @event beforeScroll
|
|
* @description Fires when the Carousel is about to scroll to the previous
|
|
* or next page. Passes back the index of the first and last visible items
|
|
* in the Carousel and the direction (backward/forward) of the scroll. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
beforeScrollEvent = "beforeScroll",
|
|
|
|
/**
|
|
* @event beforeShow
|
|
* @description Fires when the Carousel is about to be shown. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
beforeShowEvent = "beforeShow",
|
|
|
|
/**
|
|
* @event blur
|
|
* @description Fires when the Carousel loses focus. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
blurEvent = "blur",
|
|
|
|
/**
|
|
* @event focus
|
|
* @description Fires when the Carousel gains focus. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
focusEvent = "focus",
|
|
|
|
/**
|
|
* @event hide
|
|
* @description Fires when the Carousel is hidden. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
hideEvent = "hide",
|
|
|
|
/**
|
|
* @event itemAdded
|
|
* @description Fires when an item has been added to the Carousel. Passes
|
|
* back the content of the item that would be added, the index at which the
|
|
* item would be added, and the event itself. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
itemAddedEvent = "itemAdded",
|
|
|
|
/**
|
|
* @event itemRemoved
|
|
* @description Fires when an item has been removed from the Carousel.
|
|
* Passes back the content of the item that would be removed, the index
|
|
* from which the item would be removed, and the event itself. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
itemRemovedEvent = "itemRemoved",
|
|
|
|
/**
|
|
* @event itemSelected
|
|
* @description Fires when an item has been selected in the Carousel.
|
|
* Passes back the index of the selected item in the Carousel. Note, that
|
|
* the index begins from zero. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
itemSelectedEvent = "itemSelected",
|
|
|
|
/**
|
|
* @event loadItems
|
|
* @description Fires when the Carousel needs more items to be loaded for
|
|
* displaying them. Passes back the first and last visible items in the
|
|
* Carousel, and the number of items needed to be loaded. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
loadItemsEvent = "loadItems",
|
|
|
|
/**
|
|
* @event navigationStateChange
|
|
* @description Fires when the state of either one of the navigation
|
|
* buttons are changed from enabled to disabled or vice versa. Passes back
|
|
* the state (true/false) of the previous and next buttons. The value true
|
|
* signifies the button is enabled, false signifies disabled. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
navigationStateChangeEvent = "navigationStateChange",
|
|
|
|
/**
|
|
* @event pageChange
|
|
* @description Fires after the Carousel has scrolled to the previous or
|
|
* next page. Passes back the page number of the current page. Note
|
|
* that the first page number is zero. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
pageChangeEvent = "pageChange",
|
|
|
|
/*
|
|
* Internal event.
|
|
* @event render
|
|
* @description Fires when the Carousel is rendered. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
renderEvent = "render",
|
|
|
|
/**
|
|
* @event show
|
|
* @description Fires when the Carousel is shown. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
showEvent = "show",
|
|
|
|
/**
|
|
* @event startAutoPlay
|
|
* @description Fires when the auto play has started in the Carousel. See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
startAutoPlayEvent = "startAutoPlay",
|
|
|
|
/**
|
|
* @event stopAutoPlay
|
|
* @description Fires when the auto play has been stopped in the Carousel.
|
|
* See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* for more information on listening for this event.
|
|
* @type YAHOO.util.CustomEvent
|
|
*/
|
|
stopAutoPlayEvent = "stopAutoPlay",
|
|
|
|
/*
|
|
* Internal event.
|
|
* @event uiUpdateEvent
|
|
* @description Fires when the UI has been updated.
|
|
* See
|
|
* <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
|
|
* 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 {
|
|
YAHOO.log("Invalid argument to addItem", "error", WidgetName);
|
|
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) {
|
|
YAHOO.log("Index out of bounds", "error", WidgetName);
|
|
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)) {
|
|
YAHOO.log("Item could not be removed - missing?",
|
|
"warn", WidgetName);
|
|
}
|
|
/*
|
|
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) {
|
|
YAHOO.log(el + " is neither an HTML element, nor a string",
|
|
"error", WidgetName);
|
|
return;
|
|
}
|
|
|
|
carousel._hasRendered = false;
|
|
carousel._navBtns = { prev: [], next: [] };
|
|
carousel._pages = { el: null, num: 0, cur: 0 };
|
|
carousel._itemsTable = { loading: {}, numItems: 0,
|
|
items: [], size: 0 };
|
|
|
|
YAHOO.log("Component initialization", WidgetName);
|
|
|
|
if (JS.isString(el)) {
|
|
el = Dom.get(el);
|
|
} else if (!el.nodeName) {
|
|
YAHOO.log(el + " is neither an HTML element, nor a string",
|
|
"error", WidgetName);
|
|
return;
|
|
}
|
|
|
|
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 <br>
|
|
* { prev: null, // the previous navigation element<br>
|
|
* 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 <br>
|
|
* { speed: 0, // the animation speed (in seconds)<br>
|
|
* 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")) {
|
|
YAHOO.log("Index out of bounds", "error", WidgetName);
|
|
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")) {
|
|
YAHOO.log("Index out of bounds", "error", WidgetName);
|
|
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) {
|
|
YAHOO.log("Index out of bounds", "error", WidgetName);
|
|
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"))) {
|
|
YAHOO.log("Nothing to render. The container should be " +
|
|
"within the document if appendTo is not " +
|
|
"specified", "error", WidgetName);
|
|
return false;
|
|
}
|
|
carousel.appendChild(carousel._clipEl);
|
|
}
|
|
|
|
if (carousel.get("isVertical")) {
|
|
carousel.addClass(cssClass.VERTICAL);
|
|
} else {
|
|
carousel.addClass(cssClass.HORIZONTAL);
|
|
}
|
|
|
|
if (carousel.get("numItems") < 1) {
|
|
YAHOO.log("No items in the Carousel to render", "warn",
|
|
WidgetName);
|
|
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);
|
|
|
|
YAHOO.log("Scrolling to " + item + " delta = " + delta,WidgetName);
|
|
|
|
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);
|
|
YAHOO.log("Scroll offset = " + offset, WidgetName);
|
|
|
|
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) {
|
|
YAHOO.log("Setting selection to " + item, WidgetName);
|
|
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);
|
|
YAHOO.log("Found Carousel - " + node +
|
|
(child.id ? " (#" + child.id + ")" : ""),
|
|
WidgetName);
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return found;
|
|
},
|
|
|
|
/**
|
|
* Find the items within the Carousel and add them to the items table.
|
|
* A Carousel item is identified by elements that matches the carousel
|
|
* item element tag.
|
|
*
|
|
* @method parseCarouselItems
|
|
* @protected
|
|
*/
|
|
_parseCarouselItems: function () {
|
|
var 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];
|
|
YAHOO.log("Found Carousel previous page navigation - " +
|
|
el + (el.id ? " (#" + el.id + ")" : ""),
|
|
WidgetName);
|
|
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];
|
|
YAHOO.log("Found Carousel next page navigation - " +
|
|
el + (el.id ? " (#" + el.id + ")" : ""),
|
|
WidgetName);
|
|
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 = "<button type=\"button\" " +
|
|
"id=\"" + btn + "\" name=\"" +
|
|
carousel.STRINGS.PREVIOUS_BUTTON_TEXT + "\">" +
|
|
carousel.STRINGS.PREVIOUS_BUTTON_TEXT + "</button>";
|
|
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 = "<button type=\"button\" " +
|
|
"id=\"" + btn + "\" name=\"" +
|
|
carousel.STRINGS.NEXT_BUTTON_TEXT + "\">" +
|
|
carousel.STRINGS.NEXT_BUTTON_TEXT + "</button>";
|
|
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 {
|
|
YAHOO.log("Unable to find sibling","error",WidgetName);
|
|
}
|
|
}
|
|
} 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 {
|
|
YAHOO.log("Unable to find item", "warn", WidgetName);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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 {
|
|
YAHOO.log("Unable to find sibling", "error",
|
|
WidgetName);
|
|
}
|
|
} 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) {
|
|
YAHOO.log("Unable to create an LI pager button", "error",
|
|
WidgetName);
|
|
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 = "<a href=\"#" + item + "\" tabindex=\"0\"><em>" +
|
|
carousel.STRINGS.PAGER_PREFIX_TEXT + " " + (i+1) +
|
|
"</em></a>";
|
|
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) {
|
|
YAHOO.log("Unable to create the pager menu", "error",
|
|
WidgetName);
|
|
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) {
|
|
YAHOO.log("Unable to create an OPTION pager menu", "error",
|
|
WidgetName);
|
|
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) {
|
|
YAHOO.log("Unable to create the pager menu", "error",
|
|
WidgetName);
|
|
} 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"});
|