]> ToastFreeware Gitweb - philipp/winterrodeln/wradmin.git/blob - wradmin/public/yui/carousel/carousel-debug.js
Start to use flask instead of pylons.
[philipp/winterrodeln/wradmin.git] / wradmin / public / yui / carousel / carousel-debug.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.7.0
6 */
7 /**
8  * The Carousel module provides a widget for browsing among a set of like
9  * objects represented pictorially.
10  *
11  * @module carousel
12  * @requires yahoo, dom, event, element
13  * @optional animation
14  * @namespace YAHOO.widget
15  * @title Carousel Widget
16  * @beta
17  */
18 (function () {
19
20     var WidgetName;             // forward declaration
21
22     /**
23      * The Carousel widget.
24      *
25      * @class Carousel
26      * @extends YAHOO.util.Element
27      * @constructor
28      * @param el {HTMLElement | String} The HTML element that represents the
29      * the container that houses the Carousel.
30      * @param cfg {Object} (optional) The configuration values
31      */
32     YAHOO.widget.Carousel = function (el, cfg) {
33         YAHOO.log("Component creation", WidgetName);
34
35         YAHOO.widget.Carousel.superclass.constructor.call(this, el, cfg);
36     };
37
38     /*
39      * Private variables of the Carousel component
40      */
41
42     /* Some abbreviations to avoid lengthy typing and lookups. */
43     var Carousel    = YAHOO.widget.Carousel,
44         Dom         = YAHOO.util.Dom,
45         Event       = YAHOO.util.Event,
46         JS          = YAHOO.lang;
47
48     /**
49      * The widget name.
50      * @private
51      * @static
52      */
53     WidgetName = "Carousel";
54
55     /**
56      * The internal table of Carousel instances.
57      * @private
58      * @static
59      */
60     var instances = {},
61
62     /*
63      * Custom events of the Carousel component
64      */
65
66     /**
67      * @event afterScroll
68      * @description Fires when the Carousel has scrolled to the previous or
69      * next page.  Passes back the index of the first and last visible items in
70      * the Carousel.  See
71      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
72      * for more information on listening for this event.
73      * @type YAHOO.util.CustomEvent
74      */
75     afterScrollEvent = "afterScroll",
76
77     /**
78      * @event allItemsRemovedEvent
79      * @description Fires when all items have been removed from the Carousel.
80      * See
81      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
82      * for more information on listening for this event.
83      * @type YAHOO.util.CustomEvent
84      */
85     allItemsRemovedEvent = "allItemsRemoved",
86
87     /**
88      * @event beforeHide
89      * @description Fires before the Carousel is hidden.  See
90      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
91      * for more information on listening for this event.
92      * @type YAHOO.util.CustomEvent
93      */
94     beforeHideEvent = "beforeHide",
95
96     /**
97      * @event beforePageChange
98      * @description Fires when the Carousel is about to scroll to the previous
99      * or next page.  Passes back the page number of the current page.  Note
100      * that the first page number is zero.  See
101      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
102      * for more information on listening for this event.
103      * @type YAHOO.util.CustomEvent
104      */
105     beforePageChangeEvent = "beforePageChange",
106
107     /**
108      * @event beforeScroll
109      * @description Fires when the Carousel is about to scroll to the previous
110      * or next page.  Passes back the index of the first and last visible items
111      * in the Carousel and the direction (backward/forward) of the scroll.  See
112      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
113      * for more information on listening for this event.
114      * @type YAHOO.util.CustomEvent
115      */
116     beforeScrollEvent = "beforeScroll",
117
118     /**
119      * @event beforeShow
120      * @description Fires when the Carousel is about to be shown.  See
121      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
122      * for more information on listening for this event.
123      * @type YAHOO.util.CustomEvent
124      */
125     beforeShowEvent = "beforeShow",
126
127     /**
128      * @event blur
129      * @description Fires when the Carousel loses focus.  See
130      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
131      * for more information on listening for this event.
132      * @type YAHOO.util.CustomEvent
133      */
134     blurEvent = "blur",
135
136     /**
137      * @event focus
138      * @description Fires when the Carousel gains focus.  See
139      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
140      * for more information on listening for this event.
141      * @type YAHOO.util.CustomEvent
142      */
143     focusEvent = "focus",
144
145     /**
146      * @event hide
147      * @description Fires when the Carousel is hidden.  See
148      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
149      * for more information on listening for this event.
150      * @type YAHOO.util.CustomEvent
151      */
152     hideEvent = "hide",
153
154     /**
155      * @event itemAdded
156      * @description Fires when an item has been added to the Carousel.  Passes
157      * back the content of the item that would be added, the index at which the
158      * item would be added, and the event itself.  See
159      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
160      * for more information on listening for this event.
161      * @type YAHOO.util.CustomEvent
162      */
163     itemAddedEvent = "itemAdded",
164
165     /**
166      * @event itemRemoved
167      * @description Fires when an item has been removed from the Carousel.
168      * Passes back the content of the item that would be removed, the index
169      * from which the item would be removed, and the event itself.  See
170      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
171      * for more information on listening for this event.
172      * @type YAHOO.util.CustomEvent
173      */
174     itemRemovedEvent = "itemRemoved",
175
176     /**
177      * @event itemSelected
178      * @description Fires when an item has been selected in the Carousel.
179      * Passes back the index of the selected item in the Carousel.  Note, that
180      * the index begins from zero.  See
181      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
182      * for more information on listening for this event.
183      * @type YAHOO.util.CustomEvent
184      */
185     itemSelectedEvent = "itemSelected",
186
187     /**
188      * @event loadItems
189      * @description Fires when the Carousel needs more items to be loaded for
190      * displaying them.  Passes back the first and last visible items in the
191      * Carousel, and the number of items needed to be loaded.  See
192      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
193      * for more information on listening for this event.
194      * @type YAHOO.util.CustomEvent
195      */
196     loadItemsEvent = "loadItems",
197
198     /**
199      * @event navigationStateChange
200      * @description Fires when the state of either one of the navigation
201      * buttons are changed from enabled to disabled or vice versa.  Passes back
202      * the state (true/false) of the previous and next buttons.  The value true
203      * signifies the button is enabled, false signifies disabled.  See
204      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
205      * for more information on listening for this event.
206      * @type YAHOO.util.CustomEvent
207      */
208     navigationStateChangeEvent = "navigationStateChange",
209
210     /**
211      * @event pageChange
212      * @description Fires after the Carousel has scrolled to the previous or
213      * next page.  Passes back the page number of the current page.  Note
214      * that the first page number is zero.  See
215      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
216      * for more information on listening for this event.
217      * @type YAHOO.util.CustomEvent
218      */
219     pageChangeEvent = "pageChange",
220
221     /*
222      * Internal event.
223      * @event render
224      * @description Fires when the Carousel is rendered.  See
225      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
226      * for more information on listening for this event.
227      * @type YAHOO.util.CustomEvent
228      */
229     renderEvent = "render",
230
231     /**
232      * @event show
233      * @description Fires when the Carousel is shown.  See
234      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
235      * for more information on listening for this event.
236      * @type YAHOO.util.CustomEvent
237      */
238     showEvent = "show",
239
240     /**
241      * @event startAutoPlay
242      * @description Fires when the auto play has started in the Carousel.  See
243      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
244      * for more information on listening for this event.
245      * @type YAHOO.util.CustomEvent
246      */
247     startAutoPlayEvent = "startAutoPlay",
248
249     /**
250      * @event stopAutoPlay
251      * @description Fires when the auto play has been stopped in the Carousel.
252      * See
253      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
254      * for more information on listening for this event.
255      * @type YAHOO.util.CustomEvent
256      */
257     stopAutoPlayEvent = "stopAutoPlay",
258
259     /*
260      * Internal event.
261      * @event uiUpdateEvent
262      * @description Fires when the UI has been updated.
263      * See
264      * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
265      * for more information on listening for this event.
266      * @type YAHOO.util.CustomEvent
267      */
268     uiUpdateEvent = "uiUpdate";
269
270     /*
271      * Private helper functions used by the Carousel component
272      */
273
274     /**
275      * Create an element, set its class name and optionally install the element
276      * to its parent.
277      * @method createElement
278      * @param el {String} The element to be created
279      * @param attrs {Object} Configuration of parent, class and id attributes.
280      * If the content is specified, it is inserted after creation of the
281      * element. The content can also be an HTML element in which case it would
282      * be appended as a child node of the created element.
283      * @private
284      */
285     function createElement(el, attrs) {
286         var newEl = document.createElement(el);
287
288         attrs = attrs || {};
289         if (attrs.className) {
290             Dom.addClass(newEl, attrs.className);
291         }
292
293         if (attrs.parent) {
294             attrs.parent.appendChild(newEl);
295         }
296
297         if (attrs.id) {
298             newEl.setAttribute("id", attrs.id);
299         }
300
301         if (attrs.content) {
302             if (attrs.content.nodeName) {
303                 newEl.appendChild(attrs.content);
304             } else {
305                 newEl.innerHTML = attrs.content;
306             }
307         }
308
309         return newEl;
310     }
311
312     /**
313      * Get the computed style of an element.
314      *
315      * @method getStyle
316      * @param el {HTMLElement} The element for which the style needs to be
317      * returned.
318      * @param style {String} The style attribute
319      * @param type {String} "int", "float", etc. (defaults to int)
320      * @private
321      */
322     function getStyle(el, style, type) {
323         var value;
324
325         if (!el) {
326             return 0;
327         }
328
329         function getStyleIntVal(el, style) {
330             var val;
331
332             /*
333              * XXX: Safari calculates incorrect marginRight for an element
334              * which has its parent element style set to overflow: hidden
335              * https://bugs.webkit.org/show_bug.cgi?id=13343
336              * Let us assume marginLeft == marginRight
337              */
338             if (style == "marginRight" && YAHOO.env.ua.webkit) {
339                 val = parseInt(Dom.getStyle(el, "marginLeft"), 10);
340             } else {
341                 val = parseInt(Dom.getStyle(el, style), 10);
342             }
343
344             return JS.isNumber(val) ? val : 0;
345         }
346
347         function getStyleFloatVal(el, style) {
348             var val;
349
350             /*
351              * XXX: Safari calculates incorrect marginRight for an element
352              * which has its parent element style set to overflow: hidden
353              * https://bugs.webkit.org/show_bug.cgi?id=13343
354              * Let us assume marginLeft == marginRight
355              */
356             if (style == "marginRight" && YAHOO.env.ua.webkit) {
357                 val = parseFloat(Dom.getStyle(el, "marginLeft"));
358             } else {
359                 val = parseFloat(Dom.getStyle(el, style));
360             }
361
362             return JS.isNumber(val) ? val : 0;
363         }
364
365         if (typeof type == "undefined") {
366             type = "int";
367         }
368
369         switch (style) {
370         case "height":
371             value = el.offsetHeight;
372             if (value > 0) {
373                 value += getStyleIntVal(el, "marginTop")        +
374                         getStyleIntVal(el, "marginBottom");
375             } else {
376                 value = getStyleFloatVal(el, "height")          +
377                         getStyleIntVal(el, "marginTop")         +
378                         getStyleIntVal(el, "marginBottom")      +
379                         getStyleIntVal(el, "borderTopWidth")    +
380                         getStyleIntVal(el, "borderBottomWidth") +
381                         getStyleIntVal(el, "paddingTop")        +
382                         getStyleIntVal(el, "paddingBottom");
383             }
384             break;
385         case "width":
386             value = el.offsetWidth;
387             if (value > 0) {
388                 value += getStyleIntVal(el, "marginLeft")       +
389                         getStyleIntVal(el, "marginRight");
390             } else {
391                 value = getStyleFloatVal(el, "width")           +
392                         getStyleIntVal(el, "marginLeft")        +
393                         getStyleIntVal(el, "marginRight")       +
394                         getStyleIntVal(el, "borderLeftWidth")   +
395                         getStyleIntVal(el, "borderRightWidth")  +
396                         getStyleIntVal(el, "paddingLeft")       +
397                         getStyleIntVal(el, "paddingRight");
398             }
399             break;
400         default:
401             if (type == "int") {
402                 value = getStyleIntVal(el, style);
403             } else if (type == "float") {
404                 value = getStyleFloatVal(el, style);
405             } else {
406                 value = Dom.getStyle(el, style);
407             }
408             break;
409         }
410
411         return value;
412     }
413
414     /**
415      * Compute and return the height or width of a single Carousel item
416      * depending upon the orientation.
417      *
418      * @method getCarouselItemSize
419      * @param which {String} "height" or "width" to be returned.  If this is
420      * passed explicitly, the calculated size is not cached.
421      * @private
422      */
423     function getCarouselItemSize(which) {
424         var carousel = this,
425             child,
426             size     = 0,
427             vertical = false;
428
429         if (carousel._itemsTable.numItems === 0) {
430             return 0;
431         }
432
433         if (typeof which == "undefined") {
434             if (carousel._itemsTable.size > 0) {
435                 return carousel._itemsTable.size;
436             }
437         }
438
439         if (JS.isUndefined(carousel._itemsTable.items[0])) {
440             return 0;
441         }
442
443         child = Dom.get(carousel._itemsTable.items[0].id);
444
445         if (typeof which == "undefined") {
446             vertical = carousel.get("isVertical");
447         } else {
448             vertical = which == "height";
449         }
450
451         if (vertical) {
452             size = getStyle(child, "height");
453         } else {
454             size = getStyle(child, "width");
455         }
456
457         if (typeof which == "undefined") {
458             carousel._itemsTable.size = size; // save the size for later
459         }
460
461         return size;
462     }
463
464     /**
465      * Return the index of the first item in the view port for displaying item
466      * in "pos".
467      *
468      * @method getFirstVisibleForPosition
469      * @param pos {Number} The position of the item to be displayed
470      * @private
471      */
472     function getFirstVisibleForPosition(pos) {
473         var num = this.get("numVisible");
474
475         return Math.floor(pos / num) * num;
476     }
477
478     /**
479      * Return the scrolling offset size given the number of elements to
480      * scroll.
481      *
482      * @method getScrollOffset
483      * @param delta {Number} The delta number of elements to scroll by.
484      * @private
485      */
486     function getScrollOffset(delta) {
487         var itemSize = 0,
488             size     = 0;
489
490         itemSize = getCarouselItemSize.call(this);
491         size     = itemSize * delta;
492
493         // XXX: really, when the orientation is vertical, the scrolling
494         // is not exactly the number of elements into element size.
495         if (this.get("isVertical")) {
496             size -= delta;
497         }
498
499         return size;
500     }
501
502     /**
503      * Scroll the Carousel by a page backward.
504      *
505      * @method scrollPageBackward
506      * @param {Event} ev The event object
507      * @param {Object} obj The context object
508      * @private
509      */
510     function scrollPageBackward(ev, obj) {
511         obj.scrollPageBackward();
512         Event.preventDefault(ev);
513     }
514
515     /**
516      * Scroll the Carousel by a page forward.
517      *
518      * @method scrollPageForward
519      * @param {Event} ev The event object
520      * @param {Object} obj The context object
521      * @private
522      */
523     function scrollPageForward(ev, obj) {
524         obj.scrollPageForward();
525         Event.preventDefault(ev);
526     }
527
528     /**
529      * Set the selected item.
530      *
531      * @method setItemSelection
532      * @param {Number} newpos The index of the new position
533      * @param {Number} oldpos The index of the previous position
534      * @private
535      */
536      function setItemSelection(newpos, oldpos) {
537         var carousel = this,
538             cssClass   = carousel.CLASSES,
539             el,
540             firstItem  = carousel._firstItem,
541             isCircular = carousel.get("isCircular"),
542             numItems   = carousel.get("numItems"),
543             numVisible = carousel.get("numVisible"),
544             position   = oldpos,
545             sentinel   = firstItem + numVisible - 1;
546
547         if (position >= 0 && position < numItems) {
548             if (!JS.isUndefined(carousel._itemsTable.items[position])) {
549                 el = Dom.get(carousel._itemsTable.items[position].id);
550                 if (el) {
551                     Dom.removeClass(el, cssClass.SELECTED_ITEM);
552                 }
553             }
554         }
555
556         if (JS.isNumber(newpos)) {
557             newpos = parseInt(newpos, 10);
558             newpos = JS.isNumber(newpos) ? newpos : 0;
559         } else {
560             newpos = firstItem;
561         }
562
563         if (JS.isUndefined(carousel._itemsTable.items[newpos])) {
564             newpos = getFirstVisibleForPosition.call(carousel, newpos);
565             carousel.scrollTo(newpos); // still loading the item
566         }
567
568         if (!JS.isUndefined(carousel._itemsTable.items[newpos])) {
569             el = Dom.get(carousel._itemsTable.items[newpos].id);
570             if (el) {
571                 Dom.addClass(el, cssClass.SELECTED_ITEM);
572             }
573         }
574
575         if (newpos < firstItem || newpos > sentinel) { // out of focus
576             newpos = getFirstVisibleForPosition.call(carousel, newpos);
577             carousel.scrollTo(newpos);
578         }
579     }
580
581     /**
582      * Fire custom events for enabling/disabling navigation elements.
583      *
584      * @method syncNavigation
585      * @private
586      */
587     function syncNavigation() {
588         var attach   = false,
589             carousel = this,
590             cssClass = carousel.CLASSES,
591             i,
592             navigation,
593             sentinel;
594
595         // Don't do anything if the Carousel is not rendered
596         if (!carousel._hasRendered) {
597             return;
598         }
599
600         navigation = carousel.get("navigation");
601         sentinel   = carousel._firstItem + carousel.get("numVisible");
602
603         if (navigation.prev) {
604             if (carousel.get("numItems") === 0 || carousel._firstItem === 0) {
605                 if (carousel.get("numItems") === 0 ||
606                    !carousel.get("isCircular")) {
607                     Event.removeListener(navigation.prev, "click",
608                             scrollPageBackward);
609                     Dom.addClass(navigation.prev, cssClass.FIRST_NAV_DISABLED);
610                     for (i = 0; i < carousel._navBtns.prev.length; i++) {
611                         carousel._navBtns.prev[i].setAttribute("disabled",
612                                 "true");
613                     }
614                     carousel._prevEnabled = false;
615                 } else {
616                     attach = !carousel._prevEnabled;
617                 }
618             } else {
619                 attach = !carousel._prevEnabled;
620             }
621
622             if (attach) {
623                 Event.on(navigation.prev, "click", scrollPageBackward,
624                          carousel);
625                 Dom.removeClass(navigation.prev, cssClass.FIRST_NAV_DISABLED);
626                 for (i = 0; i < carousel._navBtns.prev.length; i++) {
627                     carousel._navBtns.prev[i].removeAttribute("disabled");
628                 }
629                 carousel._prevEnabled = true;
630             }
631         }
632
633         attach = false;
634         if (navigation.next) {
635             if (sentinel >= carousel.get("numItems")) {
636                 if (!carousel.get("isCircular")) {
637                     Event.removeListener(navigation.next, "click",
638                             scrollPageForward);
639                     Dom.addClass(navigation.next, cssClass.DISABLED);
640                     for (i = 0; i < carousel._navBtns.next.length; i++) {
641                         carousel._navBtns.next[i].setAttribute("disabled",
642                                 "true");
643                     }
644                     carousel._nextEnabled = false;
645                 } else {
646                     attach = !carousel._nextEnabled;
647                 }
648             } else {
649                 attach = !carousel._nextEnabled;
650             }
651
652             if (attach) {
653                 Event.on(navigation.next, "click", scrollPageForward,
654                          carousel);
655                 Dom.removeClass(navigation.next, cssClass.DISABLED);
656                 for (i = 0; i < carousel._navBtns.next.length; i++) {
657                     carousel._navBtns.next[i].removeAttribute("disabled");
658                 }
659                 carousel._nextEnabled = true;
660             }
661         }
662
663         carousel.fireEvent(navigationStateChangeEvent,
664                 { next: carousel._nextEnabled, prev: carousel._prevEnabled });
665     }
666
667     /**
668      * Synchronize and redraw the Pager UI if necessary.
669      *
670      * @method syncPagerUi
671      * @private
672      */
673     function syncPagerUi(page) {
674         var carousel = this, numPages, numVisible;
675
676         // Don't do anything if the Carousel is not rendered
677         if (!carousel._hasRendered) {
678             return;
679         }
680
681         numVisible = carousel.get("numVisible");
682
683         if (!JS.isNumber(page)) {
684             page = Math.ceil(carousel.get("selectedItem") / numVisible);
685         }
686         numPages = Math.ceil(carousel.get("numItems") / numVisible);
687
688         carousel._pages.num = numPages;
689         carousel._pages.cur = page;
690
691         if (numPages > carousel.CONFIG.MAX_PAGER_BUTTONS) {
692             carousel._updatePagerMenu();
693         } else {
694             carousel._updatePagerButtons();
695         }
696     }
697
698     /**
699      * Handle UI update.
700      * Call the appropriate methods on events fired when an item is added, or
701      * removed for synchronizing the DOM.
702      *
703      * @method syncUi
704      * @param {Object} o The item that needs to be added or removed
705      * @private
706      */
707     function syncUi(o) {
708         var carousel = this;
709
710         if (!JS.isObject(o)) {
711             return;
712         }
713
714         switch (o.ev) {
715         case itemAddedEvent:
716             carousel._syncUiForItemAdd(o);
717             break;
718         case itemRemovedEvent:
719             carousel._syncUiForItemRemove(o);
720             break;
721         case loadItemsEvent:
722             carousel._syncUiForLazyLoading(o);
723             break;
724         }
725
726         carousel.fireEvent(uiUpdateEvent);
727     }
728
729     /**
730      * Update the state variables after scrolling the Carousel view port.
731      *
732      * @method updateStateAfterScroll
733      * @param {Integer} item The index to which the Carousel has scrolled to.
734      * @param {Integer} sentinel The last element in the view port.
735      * @private
736      */
737     function updateStateAfterScroll(item, sentinel) {
738         var carousel   = this,
739             page       = carousel.get("currentPage"),
740             newPage,
741             numPerPage = carousel.get("numVisible");
742
743         newPage = parseInt(carousel._firstItem / numPerPage, 10);
744         if (newPage != page) {
745             carousel.setAttributeConfig("currentPage", { value: newPage });
746             carousel.fireEvent(pageChangeEvent, newPage);
747         }
748
749         if (carousel.get("selectOnScroll")) {
750             if (carousel.get("selectedItem") != carousel._selectedItem) {
751                 carousel.set("selectedItem", carousel._selectedItem);
752             }
753         }
754
755         clearTimeout(carousel._autoPlayTimer);
756         delete carousel._autoPlayTimer;
757         if (carousel.isAutoPlayOn()) {
758             carousel.startAutoPlay();
759         }
760
761         carousel.fireEvent(afterScrollEvent,
762                            { first: carousel._firstItem,
763                              last: sentinel },
764                            carousel);
765     }
766
767     /*
768      * Static members and methods of the Carousel component
769      */
770
771     /**
772      * Return the appropriate Carousel object based on the id associated with
773      * the Carousel element or false if none match.
774      * @method getById
775      * @public
776      * @static
777      */
778     Carousel.getById = function (id) {
779         return instances[id] ? instances[id].object : false;
780     };
781
782     YAHOO.extend(Carousel, YAHOO.util.Element, {
783
784         /*
785          * Internal variables used within the Carousel component
786          */
787
788         /**
789          * The Animation object.
790          *
791          * @property _animObj
792          * @private
793          */
794         _animObj: null,
795
796         /**
797          * The Carousel element.
798          *
799          * @property _carouselEl
800          * @private
801          */
802         _carouselEl: null,
803
804         /**
805          * The Carousel clipping container element.
806          *
807          * @property _clipEl
808          * @private
809          */
810         _clipEl: null,
811
812         /**
813          * The current first index of the Carousel.
814          *
815          * @property _firstItem
816          * @private
817          */
818         _firstItem: 0,
819
820         /**
821          * Does the Carousel element have focus?
822          *
823          * @property _hasFocus
824          * @private
825          */
826         _hasFocus: false,
827
828         /**
829          * Is the Carousel rendered already?
830          *
831          * @property _hasRendered
832          * @private
833          */
834         _hasRendered: false,
835
836         /**
837          * Is the animation still in progress?
838          *
839          * @property _isAnimationInProgress
840          * @private
841          */
842         _isAnimationInProgress: false,
843
844         /**
845          * Is the auto-scrolling of Carousel in progress?
846          *
847          * @property _isAutoPlayInProgress
848          * @private
849          */
850         _isAutoPlayInProgress: false,
851
852         /**
853          * The table of items in the Carousel.
854          * The numItems is the number of items in the Carousel, items being the
855          * array of items in the Carousel.  The size is the size of a single
856          * item in the Carousel.  It is cached here for efficiency (to avoid
857          * computing the size multiple times).
858          *
859          * @property _itemsTable
860          * @private
861          */
862         _itemsTable: null,
863
864         /**
865          * The Carousel navigation buttons.
866          *
867          * @property _navBtns
868          * @private
869          */
870         _navBtns: null,
871
872         /**
873          * The Carousel navigation.
874          *
875          * @property _navEl
876          * @private
877          */
878         _navEl: null,
879
880         /**
881          * Status of the next navigation item.
882          *
883          * @property _nextEnabled
884          * @private
885          */
886         _nextEnabled: true,
887
888         /**
889          * The Carousel pages structure.
890          * This is an object of the total number of pages and the current page.
891          *
892          * @property _pages
893          * @private
894          */
895         _pages: null,
896
897         /**
898          * Status of the previous navigation item.
899          *
900          * @property _prevEnabled
901          * @private
902          */
903         _prevEnabled: true,
904
905         /**
906          * Whether the Carousel size needs to be recomputed or not?
907          *
908          * @property _recomputeSize
909          * @private
910          */
911         _recomputeSize: true,
912
913         /*
914          * CSS classes used by the Carousel component
915          */
916
917         CLASSES: {
918
919             /**
920              * The class name of the Carousel navigation buttons.
921              *
922              * @property BUTTON
923              * @default "yui-carousel-button"
924              */
925             BUTTON: "yui-carousel-button",
926
927             /**
928              * The class name of the Carousel element.
929              *
930              * @property CAROUSEL
931              * @default "yui-carousel"
932              */
933             CAROUSEL: "yui-carousel",
934
935             /**
936              * The class name of the container of the items in the Carousel.
937              *
938              * @property CAROUSEL_EL
939              * @default "yui-carousel-element"
940              */
941             CAROUSEL_EL: "yui-carousel-element",
942
943             /**
944              * The class name of the Carousel's container element.
945              *
946              * @property CONTAINER
947              * @default "yui-carousel-container"
948              */
949             CONTAINER: "yui-carousel-container",
950
951             /**
952              * The class name of the Carousel's container element.
953              *
954              * @property CONTENT
955              * @default "yui-carousel-content"
956              */
957             CONTENT: "yui-carousel-content",
958
959             /**
960              * The class name of a disabled navigation button.
961              *
962              * @property DISABLED
963              * @default "yui-carousel-button-disabled"
964              */
965             DISABLED: "yui-carousel-button-disabled",
966
967             /**
968              * The class name of the first Carousel navigation button.
969              *
970              * @property FIRST_NAV
971              * @default " yui-carousel-first-button"
972              */
973             FIRST_NAV: " yui-carousel-first-button",
974
975             /**
976              * The class name of a first disabled navigation button.
977              *
978              * @property FIRST_NAV_DISABLED
979              * @default "yui-carousel-first-button-disabled"
980              */
981             FIRST_NAV_DISABLED: "yui-carousel-first-button-disabled",
982
983             /**
984              * The class name of a first page element.
985              *
986              * @property FIRST_PAGE
987              * @default "yui-carousel-nav-first-page"
988              */
989             FIRST_PAGE: "yui-carousel-nav-first-page",
990
991             /**
992              * The class name of the Carousel navigation button that has focus.
993              *
994              * @property FOCUSSED_BUTTON
995              * @default "yui-carousel-button-focus"
996              */
997             FOCUSSED_BUTTON: "yui-carousel-button-focus",
998
999             /**
1000              * The class name of a horizontally oriented Carousel.
1001              *
1002              * @property HORIZONTAL
1003              * @default "yui-carousel-horizontal"
1004              */
1005             HORIZONTAL: "yui-carousel-horizontal",
1006
1007             /**
1008              * The element to be used as the progress indicator when the item
1009              * is still being loaded.
1010              *
1011              * @property ITEM_LOADING
1012              * @default The progress indicator (spinner) image CSS class
1013              */
1014             ITEM_LOADING: "yui-carousel-item-loading",
1015
1016             /**
1017              * The class name that will be set if the Carousel adjusts itself
1018              * for a minimum width.
1019              *
1020              * @property MIN_WIDTH
1021              * @default "yui-carousel-min-width"
1022              */
1023             MIN_WIDTH: "yui-carousel-min-width",
1024
1025             /**
1026              * The navigation element container class name.
1027              *
1028              * @property NAVIGATION
1029              * @default "yui-carousel-nav"
1030              */
1031             NAVIGATION: "yui-carousel-nav",
1032
1033             /**
1034              * The class name of the next Carousel navigation button.
1035              *
1036              * @property NEXT_NAV
1037              * @default " yui-carousel-next-button"
1038              */
1039             NEXT_NAV: " yui-carousel-next-button",
1040
1041             /**
1042              * The class name of the next navigation link. This variable is
1043              * not only used for styling, but also for identifying the link
1044              * within the Carousel container.
1045              *
1046              * @property NEXT_PAGE
1047              * @default "yui-carousel-next"
1048              */
1049             NEXT_PAGE: "yui-carousel-next",
1050
1051             /**
1052              * The class name for the navigation container for prev/next.
1053              *
1054              * @property NAV_CONTAINER
1055              * @default "yui-carousel-buttons"
1056              */
1057             NAV_CONTAINER: "yui-carousel-buttons",
1058
1059             /**
1060              * The class name of the focussed page navigation.  This class is
1061              * specifically used for the ugly focus handling in Opera.
1062              *
1063              * @property PAGE_FOCUS
1064              * @default "yui-carousel-nav-page-focus"
1065              */
1066             PAGE_FOCUS: "yui-carousel-nav-page-focus",
1067
1068             /**
1069              * The class name of the previous navigation link. This variable
1070              * is not only used for styling, but also for identifying the link
1071              * within the Carousel container.
1072              *
1073              * @property PREV_PAGE
1074              * @default "yui-carousel-prev"
1075              */
1076             PREV_PAGE: "yui-carousel-prev",
1077
1078             /**
1079              * The class name of the selected item.
1080              *
1081              * @property SELECTED_ITEM
1082              * @default "yui-carousel-item-selected"
1083              */
1084             SELECTED_ITEM: "yui-carousel-item-selected",
1085
1086             /**
1087              * The class name of the selected paging navigation.
1088              *
1089              * @property SELECTED_NAV
1090              * @default "yui-carousel-nav-page-selected"
1091              */
1092             SELECTED_NAV: "yui-carousel-nav-page-selected",
1093
1094             /**
1095              * The class name of a vertically oriented Carousel.
1096              *
1097              * @property VERTICAL
1098              * @default "yui-carousel-vertical"
1099              */
1100             VERTICAL: "yui-carousel-vertical",
1101
1102             /**
1103              * The class name of the (vertical) Carousel's container element.
1104              *
1105              * @property VERTICAL_CONTAINER
1106              * @default "yui-carousel-vertical-container"
1107              */
1108             VERTICAL_CONTAINER: "yui-carousel-vertical-container",
1109
1110             /**
1111              * The class name of a visible Carousel.
1112              *
1113              * @property VISIBLE
1114              * @default "yui-carousel-visible"
1115              */
1116             VISIBLE: "yui-carousel-visible"
1117
1118         },
1119
1120         /*
1121          * Configuration attributes for configuring the Carousel component
1122          */
1123
1124         CONFIG: {
1125
1126             /**
1127              * The offset of the first visible item in the Carousel.
1128              *
1129              * @property FIRST_VISIBLE
1130              * @default 0
1131              */
1132             FIRST_VISIBLE: 0,
1133
1134             /**
1135              * The minimum width of the horizontal Carousel container to support
1136              * the navigation buttons.
1137              *
1138              * @property HORZ_MIN_WIDTH
1139              * @default 180
1140              */
1141             HORZ_MIN_WIDTH: 180,
1142
1143             /**
1144              * The maximum number of pager buttons allowed beyond which the UI
1145              * of the pager would be a drop-down of pages instead of buttons.
1146              *
1147              * @property MAX_PAGER_BUTTONS
1148              * @default 5
1149              */
1150             MAX_PAGER_BUTTONS: 5,
1151
1152             /**
1153              * The minimum width of the vertical Carousel container to support
1154              * the navigation buttons.
1155              *
1156              * @property VERT_MIN_WIDTH
1157              * @default 99
1158              */
1159             VERT_MIN_WIDTH: 99,
1160
1161             /**
1162              * The number of visible items in the Carousel.
1163              *
1164              * @property NUM_VISIBLE
1165              * @default 3
1166              */
1167             NUM_VISIBLE: 3
1168
1169         },
1170
1171         /*
1172          * Internationalizable strings in the Carousel component
1173          */
1174
1175         STRINGS: {
1176
1177             /**
1178              * The content to be used as the progress indicator when the item
1179              * is still being loaded.
1180              *
1181              * @property ITEM_LOADING_CONTENT
1182              * @default "Loading"
1183              */
1184             ITEM_LOADING_CONTENT: "Loading",
1185
1186             /**
1187              * The next navigation button name/text.
1188              *
1189              * @property NEXT_BUTTON_TEXT
1190              * @default "Next Page"
1191              */
1192             NEXT_BUTTON_TEXT: "Next Page",
1193
1194             /**
1195              * The prefix text for the pager in case the UI is a drop-down.
1196              *
1197              * @property PAGER_PREFIX_TEXT
1198              * @default "Go to page "
1199              */
1200             PAGER_PREFIX_TEXT: "Go to page ",
1201
1202             /**
1203              * The previous navigation button name/text.
1204              *
1205              * @property PREVIOUS_BUTTON_TEXT
1206              * @default "Previous Page"
1207              */
1208             PREVIOUS_BUTTON_TEXT: "Previous Page"
1209
1210         },
1211
1212         /*
1213          * Public methods of the Carousel component
1214          */
1215
1216         /**
1217          * Insert or append an item to the Carousel.
1218          *
1219          * @method addItem
1220          * @public
1221          * @param item {String | Object | HTMLElement} The item to be appended
1222          * to the Carousel. If the parameter is a string, it is assumed to be
1223          * the content of the newly created item. If the parameter is an
1224          * object, it is assumed to supply the content and an optional class
1225          * and an optional id of the newly created item.
1226          * @param index {Number} optional The position to where in the list
1227          * (starts from zero).
1228          * @return {Boolean} Return true on success, false otherwise
1229          */
1230         addItem: function (item, index) {
1231             var carousel = this,
1232                 className,
1233                 content,
1234                 elId,
1235                 numItems = carousel.get("numItems");
1236
1237             if (!item) {
1238                 return false;
1239             }
1240
1241             if (JS.isString(item) || item.nodeName) {
1242                 content = item.nodeName ? item.innerHTML : item;
1243             } else if (JS.isObject(item)) {
1244                 content = item.content;
1245             } else {
1246                 YAHOO.log("Invalid argument to addItem", "error", WidgetName);
1247                 return false;
1248             }
1249
1250             className = item.className || "";
1251             elId      = item.id ? item.id : Dom.generateId();
1252
1253             if (JS.isUndefined(index)) {
1254                 carousel._itemsTable.items.push({
1255                         item      : content,
1256                         className : className,
1257                         id        : elId
1258                 });
1259             } else {
1260                 if (index < 0 || index >= numItems) {
1261                     YAHOO.log("Index out of bounds", "error", WidgetName);
1262                     return false;
1263                 }
1264                 carousel._itemsTable.items.splice(index, 0, {
1265                         item      : content,
1266                         className : className,
1267                         id        : elId
1268                 });
1269             }
1270             carousel._itemsTable.numItems++;
1271
1272             if (numItems < carousel._itemsTable.items.length) {
1273                 carousel.set("numItems", carousel._itemsTable.items.length);
1274             }
1275
1276             carousel.fireEvent(itemAddedEvent, { pos: index, ev: itemAddedEvent });
1277
1278             return true;
1279         },
1280
1281         /**
1282          * Insert or append multiple items to the Carousel.
1283          *
1284          * @method addItems
1285          * @public
1286          * @param items {Array} An array of items to be added with each item
1287          * representing an item, index pair [{item, index}, ...]
1288          * @return {Boolean} Return true on success, false otherwise
1289          */
1290         addItems: function (items) {
1291             var i, n, rv = true;
1292
1293             if (!JS.isArray(items)) {
1294                 return false;
1295             }
1296
1297             for (i = 0, n = items.length; i < n; i++) {
1298                 if (this.addItem(items[i][0], items[i][1]) === false) {
1299                     rv = false;
1300                 }
1301             }
1302
1303             return rv;
1304         },
1305
1306         /**
1307          * Remove focus from the Carousel.
1308          *
1309          * @method blur
1310          * @public
1311          */
1312         blur: function () {
1313             this._carouselEl.blur();
1314             this.fireEvent(blurEvent);
1315         },
1316
1317         /**
1318          * Clears the items from Carousel.
1319          *
1320          * @method clearItems
1321          * public
1322          */
1323         clearItems: function () {
1324             var carousel = this, n = carousel.get("numItems");
1325
1326             while (n > 0) {
1327                 if (!carousel.removeItem(0)) {
1328                     YAHOO.log("Item could not be removed - missing?",
1329                               "warn", WidgetName);
1330                 }
1331                 /*
1332                     For dynamic loading, the numItems may be much larger than
1333                     the actual number of items in the table.  So, set the
1334                     numItems to zero, and break out of the loop if the table
1335                     is already empty.
1336                  */
1337                 if (carousel._itemsTable.numItems === 0) {
1338                     carousel.set("numItems", 0);
1339                     break;
1340                 }
1341                 n--;
1342             }
1343
1344             carousel.fireEvent(allItemsRemovedEvent);
1345         },
1346
1347         /**
1348          * Set focus on the Carousel.
1349          *
1350          * @method focus
1351          * @public
1352          */
1353         focus: function () {
1354             var carousel = this,
1355                 first,
1356                 focusEl,
1357                 isSelectionInvisible,
1358                 itemsTable,
1359                 last,
1360                 numVisible,
1361                 selectOnScroll,
1362                 selected,
1363                 selItem;
1364
1365             // Don't do anything if the Carousel is not rendered
1366             if (!carousel._hasRendered) {
1367                 return;
1368             }
1369
1370             if (carousel.isAnimating()) {
1371                 // this messes up real bad!
1372                 return;
1373             }
1374
1375             selItem              = carousel.get("selectedItem");
1376             numVisible           = carousel.get("numVisible");
1377             selectOnScroll       = carousel.get("selectOnScroll");
1378             selected             = (selItem >= 0) ?
1379                                    carousel.getItem(selItem) : null;
1380             first                = carousel.get("firstVisible");
1381             last                 = first + numVisible - 1;
1382             isSelectionInvisible = (selItem < first || selItem > last);
1383             focusEl              = (selected && selected.id) ?
1384                                    Dom.get(selected.id) : null;
1385             itemsTable           = carousel._itemsTable;
1386
1387             if (!selectOnScroll && isSelectionInvisible) {
1388                 focusEl = (itemsTable && itemsTable.items &&
1389                            itemsTable.items[first]) ?
1390                         Dom.get(itemsTable.items[first].id) : null;
1391             }
1392
1393             if (focusEl) {
1394                 try {
1395                     focusEl.focus();
1396                 } catch (ex) {
1397                     // ignore focus errors
1398                 }
1399             }
1400
1401             carousel.fireEvent(focusEvent);
1402         },
1403
1404         /**
1405          * Hide the Carousel.
1406          *
1407          * @method hide
1408          * @public
1409          */
1410         hide: function () {
1411             var carousel = this;
1412
1413             if (carousel.fireEvent(beforeHideEvent) !== false) {
1414                 carousel.removeClass(carousel.CLASSES.VISIBLE);
1415                 carousel.fireEvent(hideEvent);
1416             }
1417         },
1418
1419         /**
1420          * Initialize the Carousel.
1421          *
1422          * @method init
1423          * @public
1424          * @param el {HTMLElement | String} The html element that represents
1425          * the Carousel container.
1426          * @param attrs {Object} The set of configuration attributes for
1427          * creating the Carousel.
1428          */
1429         init: function (el, attrs) {
1430             var carousel = this,
1431                 elId     = el,  // save for a rainy day
1432                 parse    = false;
1433
1434             if (!el) {
1435                 YAHOO.log(el + " is neither an HTML element, nor a string",
1436                         "error", WidgetName);
1437                 return;
1438             }
1439
1440             carousel._hasRendered = false;
1441             carousel._navBtns     = { prev: [], next: [] };
1442             carousel._pages       = { el: null, num: 0, cur: 0 };
1443             carousel._itemsTable  = { loading: {}, numItems: 0,
1444                                       items: [], size: 0 };
1445
1446             YAHOO.log("Component initialization", WidgetName);
1447
1448             if (JS.isString(el)) {
1449                 el = Dom.get(el);
1450             } else if (!el.nodeName) {
1451                 YAHOO.log(el + " is neither an HTML element, nor a string",
1452                         "error", WidgetName);
1453                 return;
1454             }
1455
1456             Carousel.superclass.init.call(carousel, el, attrs);
1457
1458             if (el) {
1459                 if (!el.id) {   // in case the HTML element is passed
1460                     el.setAttribute("id", Dom.generateId());
1461                 }
1462                 parse = carousel._parseCarousel(el);
1463                 if (!parse) {
1464                     carousel._createCarousel(elId);
1465                 }
1466             } else {
1467                 el = carousel._createCarousel(elId);
1468             }
1469             elId = el.id;
1470
1471             carousel.initEvents();
1472
1473             if (parse) {
1474                 carousel._parseCarouselItems();
1475             }
1476
1477             if (!attrs || typeof attrs.isVertical == "undefined") {
1478                 carousel.set("isVertical", false);
1479             }
1480
1481             carousel._parseCarouselNavigation(el);
1482             carousel._navEl = carousel._setupCarouselNavigation();
1483
1484             instances[elId] = { object: carousel };
1485
1486             carousel._loadItems();
1487         },
1488
1489         /**
1490          * Initialize the configuration attributes used to create the Carousel.
1491          *
1492          * @method initAttributes
1493          * @public
1494          * @param attrs {Object} The set of configuration attributes for
1495          * creating the Carousel.
1496          */
1497         initAttributes: function (attrs) {
1498             var carousel = this;
1499
1500             attrs = attrs || {};
1501             Carousel.superclass.initAttributes.call(carousel, attrs);
1502
1503             /**
1504              * @attribute carouselEl
1505              * @description The type of the Carousel element.
1506              * @default OL
1507              * @type Boolean
1508              */
1509             carousel.setAttributeConfig("carouselEl", {
1510                     validator : JS.isString,
1511                     value     : attrs.carouselEl || "OL"
1512             });
1513
1514             /**
1515              * @attribute carouselItemEl
1516              * @description The type of the list of items within the Carousel.
1517              * @default LI
1518              * @type Boolean
1519              */
1520             carousel.setAttributeConfig("carouselItemEl", {
1521                     validator : JS.isString,
1522                     value     : attrs.carouselItemEl || "LI"
1523             });
1524
1525             /**
1526              * @attribute currentPage
1527              * @description The current page number (read-only.)
1528              * @type Number
1529              */
1530             carousel.setAttributeConfig("currentPage", {
1531                     readOnly : true,
1532                     value    : 0
1533             });
1534
1535             /**
1536              * @attribute firstVisible
1537              * @description The index to start the Carousel from (indexes begin
1538              * from zero)
1539              * @default 0
1540              * @type Number
1541              */
1542             carousel.setAttributeConfig("firstVisible", {
1543                     method    : carousel._setFirstVisible,
1544                     validator : carousel._validateFirstVisible,
1545                     value     :
1546                         attrs.firstVisible || carousel.CONFIG.FIRST_VISIBLE
1547             });
1548
1549             /**
1550              * @attribute selectOnScroll
1551              * @description Set this to true to automatically set focus to
1552              * follow scrolling in the Carousel.
1553              * @default true
1554              * @type Boolean
1555              */
1556             carousel.setAttributeConfig("selectOnScroll", {
1557                     validator : JS.isBoolean,
1558                     value     : attrs.selectOnScroll || true
1559             });
1560
1561             /**
1562              * @attribute numVisible
1563              * @description The number of visible items in the Carousel's
1564              * viewport.
1565              * @default 3
1566              * @type Number
1567              */
1568             carousel.setAttributeConfig("numVisible", {
1569                     method    : carousel._setNumVisible,
1570                     validator : carousel._validateNumVisible,
1571                     value     : attrs.numVisible || carousel.CONFIG.NUM_VISIBLE
1572             });
1573
1574             /**
1575              * @attribute numItems
1576              * @description The number of items in the Carousel.
1577              * @type Number
1578              */
1579             carousel.setAttributeConfig("numItems", {
1580                     method    : carousel._setNumItems,
1581                     validator : carousel._validateNumItems,
1582                     value     : carousel._itemsTable.numItems
1583             });
1584
1585             /**
1586              * @attribute scrollIncrement
1587              * @description The number of items to scroll by for arrow keys.
1588              * @default 1
1589              * @type Number
1590              */
1591             carousel.setAttributeConfig("scrollIncrement", {
1592                     validator : carousel._validateScrollIncrement,
1593                     value     : attrs.scrollIncrement || 1
1594             });
1595
1596             /**
1597              * @attribute selectedItem
1598              * @description The index of the selected item.
1599              * @type Number
1600              */
1601             carousel.setAttributeConfig("selectedItem", {
1602                     method    : carousel._setSelectedItem,
1603                     validator : JS.isNumber,
1604                     value     : -1
1605             });
1606
1607             /**
1608              * @attribute revealAmount
1609              * @description The percentage of the item to be revealed on each
1610              * side of the Carousel (before and after the first and last item
1611              * in the Carousel's viewport.)
1612              * @default 0
1613              * @type Number
1614              */
1615             carousel.setAttributeConfig("revealAmount", {
1616                     method    : carousel._setRevealAmount,
1617                     validator : carousel._validateRevealAmount,
1618                     value     : attrs.revealAmount || 0
1619             });
1620
1621             /**
1622              * @attribute isCircular
1623              * @description Set this to true to wrap scrolling of the contents
1624              * in the Carousel.
1625              * @default false
1626              * @type Boolean
1627              */
1628             carousel.setAttributeConfig("isCircular", {
1629                     validator : JS.isBoolean,
1630                     value     : attrs.isCircular || false
1631             });
1632
1633             /**
1634              * @attribute isVertical
1635              * @description True if the orientation of the Carousel is vertical
1636              * @default false
1637              * @type Boolean
1638              */
1639             carousel.setAttributeConfig("isVertical", {
1640                     method    : carousel._setOrientation,
1641                     validator : JS.isBoolean,
1642                     value     : attrs.isVertical || false
1643             });
1644
1645             /**
1646              * @attribute navigation
1647              * @description The set of navigation controls for Carousel
1648              * @default <br>
1649              * { prev: null, // the previous navigation element<br>
1650              *   next: null } // the next navigation element
1651              * @type Object
1652              */
1653             carousel.setAttributeConfig("navigation", {
1654                     method    : carousel._setNavigation,
1655                     validator : carousel._validateNavigation,
1656                     value     :
1657                         attrs.navigation || {prev: null,next: null,page: null}
1658             });
1659
1660             /**
1661              * @attribute animation
1662              * @description The optional animation attributes for the Carousel.
1663              * @default <br>
1664              * { speed: 0, // the animation speed (in seconds)<br>
1665              *   effect: null } // the animation effect (like
1666              *   YAHOO.util.Easing.easeOut)
1667              * @type Object
1668              */
1669             carousel.setAttributeConfig("animation", {
1670                     validator : carousel._validateAnimation,
1671                     value     : attrs.animation || { speed: 0, effect: null }
1672             });
1673
1674             /**
1675              * @attribute autoPlay
1676              * @description Set this to time in milli-seconds to have the
1677              * Carousel automatically scroll the contents.
1678              * @type Number
1679              * @deprecated Use autoPlayInterval instead.
1680              */
1681             carousel.setAttributeConfig("autoPlay", {
1682                     validator : JS.isNumber,
1683                     value     : attrs.autoPlay || 0
1684             });
1685
1686             /**
1687              * @attribute autoPlayInterval
1688              * @description The delay in milli-seconds for scrolling the
1689              * Carousel during auto-play.
1690              * Note: The startAutoPlay() method needs to be invoked to trigger
1691              * automatic scrolling of Carousel.
1692              * @type Number
1693              */
1694             carousel.setAttributeConfig("autoPlayInterval", {
1695                     validator : JS.isNumber,
1696                     value     : attrs.autoPlayInterval || 0
1697             });
1698         },
1699
1700         /**
1701          * Initialize and bind the event handlers.
1702          *
1703          * @method initEvents
1704          * @public
1705          */
1706         initEvents: function () {
1707             var carousel = this,
1708                 cssClass = carousel.CLASSES,
1709                 focussedLi;
1710
1711             carousel.on("keydown", carousel._keyboardEventHandler);
1712
1713             carousel.on(afterScrollEvent, syncNavigation);
1714
1715             carousel.on(itemAddedEvent, syncUi);
1716
1717             carousel.on(itemRemovedEvent, syncUi);
1718
1719             carousel.on(itemSelectedEvent, function () {
1720                 if (carousel._hasFocus) {
1721                     carousel.focus();
1722                 }
1723             });
1724
1725             carousel.on(loadItemsEvent, syncUi);
1726
1727             carousel.on(allItemsRemovedEvent, function (ev) {
1728                 carousel.scrollTo(0);
1729                 syncNavigation.call(carousel);
1730                 syncPagerUi.call(carousel);
1731             });
1732
1733             carousel.on(pageChangeEvent, syncPagerUi, carousel);
1734
1735             carousel.on(renderEvent, function (ev) {
1736                 carousel.set("selectedItem", carousel.get("firstVisible"));
1737                 syncNavigation.call(carousel, ev);
1738                 syncPagerUi.call(carousel, ev);
1739                 carousel._setClipContainerSize();
1740             });
1741
1742             carousel.on("selectedItemChange", function (ev) {
1743                 setItemSelection.call(carousel, ev.newValue, ev.prevValue);
1744                 if (ev.newValue >= 0) {
1745                     carousel._updateTabIndex(
1746                             carousel.getElementForItem(ev.newValue));
1747                 }
1748                 carousel.fireEvent(itemSelectedEvent, ev.newValue);
1749             });
1750
1751             carousel.on(uiUpdateEvent, function (ev) {
1752                 syncNavigation.call(carousel, ev);
1753                 syncPagerUi.call(carousel, ev);
1754             });
1755
1756             carousel.on("firstVisibleChange", function (ev) {
1757                 if (!carousel.get("selectOnScroll")) {
1758                     if (ev.newValue >= 0) {
1759                         carousel._updateTabIndex(
1760                                 carousel.getElementForItem(ev.newValue));
1761                     }
1762                 }
1763             });
1764
1765             // Handle item selection on mouse click
1766             carousel.on("click", function (ev) {
1767                 if (carousel.isAutoPlayOn()) {
1768                     carousel.stopAutoPlay();
1769                 }
1770                 carousel._itemClickHandler(ev);
1771                 carousel._pagerClickHandler(ev);
1772             });
1773
1774             // Restore the focus on the navigation buttons
1775
1776             Event.onFocus(carousel.get("element"), function (ev, obj) {
1777                 var target = Event.getTarget(ev);
1778
1779                 if (target && target.nodeName.toUpperCase() == "A" &&
1780                     Dom.getAncestorByClassName(target, cssClass.NAVIGATION)) {
1781                     if (focussedLi) {
1782                         Dom.removeClass(focussedLi, cssClass.PAGE_FOCUS);
1783                     }
1784                     focussedLi = target.parentNode;
1785                     Dom.addClass(focussedLi, cssClass.PAGE_FOCUS);
1786                 } else {
1787                     if (focussedLi) {
1788                         Dom.removeClass(focussedLi, cssClass.PAGE_FOCUS);
1789                     }
1790                 }
1791
1792                 obj._hasFocus = true;
1793                 obj._updateNavButtons(Event.getTarget(ev), true);
1794             }, carousel);
1795
1796             Event.onBlur(carousel.get("element"), function (ev, obj) {
1797                 obj._hasFocus = false;
1798                 obj._updateNavButtons(Event.getTarget(ev), false);
1799             }, carousel);
1800         },
1801
1802         /**
1803          * Return true if the Carousel is still animating, or false otherwise.
1804          *
1805          * @method isAnimating
1806          * @return {Boolean} Return true if animation is still in progress, or
1807          * false otherwise.
1808          * @public
1809          */
1810         isAnimating: function () {
1811             return this._isAnimationInProgress;
1812         },
1813
1814         /**
1815          * Return true if the auto-scrolling of Carousel is "on", or false
1816          * otherwise.
1817          *
1818          * @method isAutoPlayOn
1819          * @return {Boolean} Return true if autoPlay is "on", or false
1820          * otherwise.
1821          * @public
1822          */
1823         isAutoPlayOn: function () {
1824             return this._isAutoPlayInProgress;
1825         },
1826
1827         /**
1828          * Return the carouselItemEl at index or null if the index is not
1829          * found.
1830          *
1831          * @method getElementForItem
1832          * @param index {Number} The index of the item to be returned
1833          * @return {Element} Return the item at index or null if not found
1834          * @public
1835          */
1836         getElementForItem: function (index) {
1837             var carousel = this;
1838
1839             if (index < 0 || index >= carousel.get("numItems")) {
1840                 YAHOO.log("Index out of bounds", "error", WidgetName);
1841                 return null;
1842             }
1843
1844             // TODO: may be cache the item
1845             if (carousel._itemsTable.numItems > index) {
1846                 if (!JS.isUndefined(carousel._itemsTable.items[index])) {
1847                     return Dom.get(carousel._itemsTable.items[index].id);
1848                 }
1849             }
1850
1851             return null;
1852         },
1853
1854         /**
1855          * Return the carouselItemEl for all items in the Carousel.
1856          *
1857          * @method getElementForItems
1858          * @return {Array} Return all the items
1859          * @public
1860          */
1861         getElementForItems: function () {
1862             var carousel = this, els = [], i;
1863
1864             for (i = 0; i < carousel._itemsTable.numItems; i++) {
1865                 els.push(carousel.getElementForItem(i));
1866             }
1867
1868             return els;
1869         },
1870
1871         /**
1872          * Return the item at index or null if the index is not found.
1873          *
1874          * @method getItem
1875          * @param index {Number} The index of the item to be returned
1876          * @return {Object} Return the item at index or null if not found
1877          * @public
1878          */
1879         getItem: function (index) {
1880             var carousel = this;
1881
1882             if (index < 0 || index >= carousel.get("numItems")) {
1883                 YAHOO.log("Index out of bounds", "error", WidgetName);
1884                 return null;
1885             }
1886
1887             if (carousel._itemsTable.numItems > index) {
1888                 if (!JS.isUndefined(carousel._itemsTable.items[index])) {
1889                     return carousel._itemsTable.items[index];
1890                 }
1891             }
1892
1893             return null;
1894         },
1895
1896         /**
1897          * Return all items as an array.
1898          *
1899          * @method getItems
1900          * @return {Array} Return all items in the Carousel
1901          * @public
1902          */
1903         getItems: function (index) {
1904             return this._itemsTable.items;
1905         },
1906
1907         /**
1908          * Return the position of the Carousel item that has the id "id", or -1
1909          * if the id is not found.
1910          *
1911          * @method getItemPositionById
1912          * @param index {Number} The index of the item to be returned
1913          * @public
1914          */
1915         getItemPositionById: function (id) {
1916             var carousel = this, i = 0, n = carousel._itemsTable.numItems;
1917
1918             while (i < n) {
1919                 if (!JS.isUndefined(carousel._itemsTable.items[i])) {
1920                     if (carousel._itemsTable.items[i].id == id) {
1921                         return i;
1922                     }
1923                 }
1924                 i++;
1925             }
1926
1927             return -1;
1928         },
1929
1930         /**
1931          * Return all visible items as an array.
1932          *
1933          * @method getVisibleItems
1934          * @return {Array} The array of visible items
1935          * @public
1936          */
1937         getVisibleItems: function () {
1938             var carousel = this,
1939                 i        = carousel.get("firstVisible"),
1940                 n        = i + carousel.get("numVisible"),
1941                 r        = [];
1942
1943             while (i < n) {
1944                 r.push(carousel.getElementForItem(i));
1945                 i++;
1946             }
1947
1948             return r;
1949         },
1950
1951         /**
1952          * Remove an item at index from the Carousel.
1953          *
1954          * @method removeItem
1955          * @public
1956          * @param index {Number} The position to where in the list (starts from
1957          * zero).
1958          * @return {Boolean} Return true on success, false otherwise
1959          */
1960         removeItem: function (index) {
1961             var carousel = this,
1962                 item,
1963                 num      = carousel.get("numItems");
1964
1965             if (index < 0 || index >= num) {
1966                 YAHOO.log("Index out of bounds", "error", WidgetName);
1967                 return false;
1968             }
1969
1970             item = carousel._itemsTable.items.splice(index, 1);
1971             if (item && item.length == 1) {
1972                 carousel._itemsTable.numItems--;
1973                 carousel.set("numItems", num - 1);
1974
1975                 carousel.fireEvent(itemRemovedEvent,
1976                         { item: item[0], pos: index, ev: itemRemovedEvent });
1977                 return true;
1978             }
1979
1980             return false;
1981         },
1982
1983         /**
1984          * Render the Carousel.
1985          *
1986          * @method render
1987          * @public
1988          * @param appendTo {HTMLElement | String} The element to which the
1989          * Carousel should be appended prior to rendering.
1990          * @return {Boolean} Status of the operation
1991          */
1992         render: function (appendTo) {
1993             var carousel = this,
1994                 cssClass = carousel.CLASSES;
1995
1996             carousel.addClass(cssClass.CAROUSEL);
1997
1998             if (!carousel._clipEl) {
1999                 carousel._clipEl = carousel._createCarouselClip();
2000                 carousel._clipEl.appendChild(carousel._carouselEl);
2001             }
2002
2003             if (appendTo) {
2004                 carousel.appendChild(carousel._clipEl);
2005                 carousel.appendTo(appendTo);
2006             } else {
2007                 if (!Dom.inDocument(carousel.get("element"))) {
2008                     YAHOO.log("Nothing to render. The container should be " +
2009                             "within the document if appendTo is not "       +
2010                             "specified", "error", WidgetName);
2011                     return false;
2012                 }
2013                 carousel.appendChild(carousel._clipEl);
2014             }
2015
2016             if (carousel.get("isVertical")) {
2017                 carousel.addClass(cssClass.VERTICAL);
2018             } else {
2019                 carousel.addClass(cssClass.HORIZONTAL);
2020             }
2021
2022             if (carousel.get("numItems") < 1) {
2023                 YAHOO.log("No items in the Carousel to render", "warn",
2024                         WidgetName);
2025                 return false;
2026             }
2027
2028             carousel._refreshUi();
2029
2030             return true;
2031         },
2032
2033         /**
2034          * Scroll the Carousel by an item backward.
2035          *
2036          * @method scrollBackward
2037          * @public
2038          */
2039         scrollBackward: function () {
2040             var carousel = this;
2041
2042             carousel.scrollTo(carousel._firstItem -
2043                               carousel.get("scrollIncrement"));
2044         },
2045
2046         /**
2047          * Scroll the Carousel by an item forward.
2048          *
2049          * @method scrollForward
2050          * @public
2051          */
2052         scrollForward: function () {
2053             var carousel = this;
2054
2055             carousel.scrollTo(carousel._firstItem +
2056                               carousel.get("scrollIncrement"));
2057         },
2058
2059         /**
2060          * Scroll the Carousel by a page backward.
2061          *
2062          * @method scrollPageBackward
2063          * @public
2064          */
2065         scrollPageBackward: function () {
2066             var carousel = this,
2067                 item     = carousel._firstItem - carousel.get("numVisible");
2068
2069             if (carousel.get("selectOnScroll")) {
2070                 carousel._selectedItem = carousel._getSelectedItem(item);
2071             } else {
2072                 item = carousel._getValidIndex(item);
2073             }
2074             carousel.scrollTo(item);
2075         },
2076
2077         /**
2078          * Scroll the Carousel by a page forward.
2079          *
2080          * @method scrollPageForward
2081          * @public
2082          */
2083         scrollPageForward: function () {
2084             var carousel = this,
2085                 item     = carousel._firstItem + carousel.get("numVisible");
2086
2087             if (carousel.get("selectOnScroll")) {
2088                 carousel._selectedItem = carousel._getSelectedItem(item);
2089             } else {
2090                 item = carousel._getValidIndex(item);
2091             }
2092             carousel.scrollTo(item);
2093         },
2094
2095         /**
2096          * Scroll the Carousel to make the item the first visible item.
2097          *
2098          * @method scrollTo
2099          * @public
2100          * @param item Number The index of the element to position at.
2101          * @param dontSelect Boolean True if select should be avoided
2102          */
2103         scrollTo: function (item, dontSelect) {
2104             var carousel   = this,
2105                 animate, animCfg, isCircular, delta, direction, firstItem,
2106                 numItems, numPerPage, offset, page, rv, sentinel,
2107                 stopAutoScroll;
2108
2109             if (JS.isUndefined(item) || item == carousel._firstItem ||
2110                 carousel.isAnimating()) {
2111                 return;         // nothing to do!
2112             }
2113
2114             animCfg        = carousel.get("animation");
2115             isCircular     = carousel.get("isCircular");
2116             firstItem      = carousel._firstItem;
2117             numItems       = carousel.get("numItems");
2118             numPerPage     = carousel.get("numVisible");
2119             page           = carousel.get("currentPage");
2120             stopAutoScroll = function () {
2121                 if (carousel.isAutoPlayOn()) {
2122                     carousel.stopAutoPlay();
2123                 }
2124             };
2125
2126             if (item < 0) {
2127                 if (isCircular) {
2128                     item = numItems + item;
2129                 } else {
2130                     stopAutoScroll.call(carousel);
2131                     return;
2132                 }
2133             } else if (numItems > 0 && item > numItems - 1) {
2134                 if (carousel.get("isCircular")) {
2135                     item = numItems - item;
2136                 } else {
2137                     stopAutoScroll.call(carousel);
2138                     return;
2139                 }
2140             }
2141
2142             direction = (carousel._firstItem > item) ? "backward" : "forward";
2143
2144             sentinel  = firstItem + numPerPage;
2145             sentinel  = (sentinel > numItems - 1) ? numItems - 1 : sentinel;
2146             rv = carousel.fireEvent(beforeScrollEvent,
2147                     { dir: direction, first: firstItem, last: sentinel });
2148             if (rv === false) { // scrolling is prevented
2149                 return;
2150             }
2151
2152             carousel.fireEvent(beforePageChangeEvent, { page: page });
2153
2154             delta = firstItem - item; // yes, the delta is reverse
2155             carousel._firstItem = item;
2156             carousel.set("firstVisible", item);
2157
2158             YAHOO.log("Scrolling to " + item + " delta = " + delta,WidgetName);
2159
2160             carousel._loadItems(); // do we have all the items to display?
2161
2162             sentinel  = item + numPerPage;
2163             sentinel  = (sentinel > numItems - 1) ? numItems - 1 : sentinel;
2164
2165             offset    = getScrollOffset.call(carousel, delta);
2166             YAHOO.log("Scroll offset = " + offset, WidgetName);
2167
2168             animate   = animCfg.speed > 0;
2169
2170             if (animate) {
2171                 carousel._animateAndSetCarouselOffset(offset, item, sentinel,
2172                         dontSelect);
2173             } else {
2174                 carousel._setCarouselOffset(offset);
2175                 updateStateAfterScroll.call(carousel, item, sentinel);
2176             }
2177         },
2178
2179         /**
2180          * Select the previous item in the Carousel.
2181          *
2182          * @method selectPreviousItem
2183          * @public
2184          */
2185         selectPreviousItem: function () {
2186             var carousel = this,
2187                 newpos   = 0,
2188                 selected = carousel.get("selectedItem");
2189
2190             if (selected == this._firstItem) {
2191                 newpos = selected - carousel.get("numVisible");
2192                 carousel._selectedItem = carousel._getSelectedItem(selected-1);
2193                 carousel.scrollTo(newpos);
2194             } else {
2195                 newpos = carousel.get("selectedItem") -
2196                          carousel.get("scrollIncrement");
2197                 carousel.set("selectedItem",carousel._getSelectedItem(newpos));
2198             }
2199         },
2200
2201         /**
2202          * Select the next item in the Carousel.
2203          *
2204          * @method selectNextItem
2205          * @public
2206          */
2207         selectNextItem: function () {
2208             var carousel = this, newpos = 0;
2209
2210             newpos = carousel.get("selectedItem") +
2211                      carousel.get("scrollIncrement");
2212             carousel.set("selectedItem", carousel._getSelectedItem(newpos));
2213         },
2214
2215         /**
2216          * Display the Carousel.
2217          *
2218          * @method show
2219          * @public
2220          */
2221         show: function () {
2222             var carousel = this,
2223                 cssClass = carousel.CLASSES;
2224
2225             if (carousel.fireEvent(beforeShowEvent) !== false) {
2226                 carousel.addClass(cssClass.VISIBLE);
2227                 carousel.fireEvent(showEvent);
2228             }
2229         },
2230
2231         /**
2232          * Start auto-playing the Carousel.
2233          *
2234          * @method startAutoPlay
2235          * @public
2236          */
2237         startAutoPlay: function () {
2238             var carousel = this, timer;
2239
2240             if (JS.isUndefined(carousel._autoPlayTimer)) {
2241                 timer = carousel.get("autoPlayInterval");
2242                 if (timer <= 0) {
2243                     return;
2244                 }
2245                 carousel._isAutoPlayInProgress = true;
2246                 carousel.fireEvent(startAutoPlayEvent);
2247                 carousel._autoPlayTimer = setTimeout(function () {
2248                     carousel._autoScroll();
2249                 }, timer);
2250             }
2251         },
2252
2253         /**
2254          * Stop auto-playing the Carousel.
2255          *
2256          * @method stopAutoPlay
2257          * @public
2258          */
2259         stopAutoPlay: function () {
2260             var carousel = this;
2261
2262             if (!JS.isUndefined(carousel._autoPlayTimer)) {
2263                 clearTimeout(carousel._autoPlayTimer);
2264                 delete carousel._autoPlayTimer;
2265                 carousel._isAutoPlayInProgress = false;
2266                 carousel.fireEvent(stopAutoPlayEvent);
2267             }
2268         },
2269
2270         /**
2271          * Return the string representation of the Carousel.
2272          *
2273          * @method toString
2274          * @public
2275          * @return {String}
2276          */
2277         toString: function () {
2278             return WidgetName + (this.get ? " (#" + this.get("id") + ")" : "");
2279         },
2280
2281         /*
2282          * Protected methods of the Carousel component
2283          */
2284
2285         /**
2286          * Set the Carousel offset to the passed offset after animating.
2287          *
2288          * @method _animateAndSetCarouselOffset
2289          * @param {Integer} offset The offset to which the Carousel has to be
2290          * scrolled to.
2291          * @param {Integer} item The index to which the Carousel will scroll.
2292          * @param {Integer} sentinel The last element in the view port.
2293          * @protected
2294          */
2295         _animateAndSetCarouselOffset: function (offset, item, sentinel) {
2296             var carousel = this,
2297                 animCfg  = carousel.get("animation"),
2298                 animObj  = null;
2299
2300             if (carousel.get("isVertical")) {
2301                 animObj = new YAHOO.util.Motion(carousel._carouselEl,
2302                         { points: { by: [0, offset] } },
2303                         animCfg.speed, animCfg.effect);
2304             } else {
2305                 animObj = new YAHOO.util.Motion(carousel._carouselEl,
2306                         { points: { by: [offset, 0] } },
2307                         animCfg.speed, animCfg.effect);
2308             }
2309
2310             carousel._isAnimationInProgress = true;
2311             animObj.onComplete.subscribe(carousel._animationCompleteHandler,
2312                                          { scope: carousel, item: item,
2313                                            last: sentinel });
2314             animObj.animate();
2315         },
2316
2317         /**
2318          * Handle the animation complete event.
2319          *
2320          * @method _animationCompleteHandler
2321          * @param {Event} ev The event.
2322          * @param {Array} p The event parameters.
2323          * @param {Object} o The object that has the state of the Carousel
2324          * @protected
2325          */
2326         _animationCompleteHandler: function (ev, p, o) {
2327             o.scope._isAnimationInProgress = false;
2328             updateStateAfterScroll.call(o.scope, o.item, o.last);
2329         },
2330
2331         /**
2332          * Automatically scroll the contents of the Carousel.
2333          * @method _autoScroll
2334          * @protected
2335          */
2336         _autoScroll: function() {
2337             var carousel  = this,
2338                 currIndex = carousel._firstItem,
2339                 index;
2340
2341             if (currIndex >= carousel.get("numItems") - 1) {
2342                 if (carousel.get("isCircular")) {
2343                     index = 0;
2344                 } else {
2345                     carousel.stopAutoPlay();
2346                 }
2347             } else {
2348                 index = currIndex + carousel.get("numVisible");
2349             }
2350
2351             carousel._selectedItem = carousel._getSelectedItem(index);
2352             carousel.scrollTo.call(carousel, index);
2353         },
2354
2355         /**
2356          * Create the Carousel.
2357          *
2358          * @method createCarousel
2359          * @param elId {String} The id of the element to be created
2360          * @protected
2361          */
2362         _createCarousel: function (elId) {
2363             var carousel = this,
2364                 cssClass = carousel.CLASSES,
2365                 el       = Dom.get(elId);
2366
2367             if (!el) {
2368                 el = createElement("DIV", {
2369                         className : cssClass.CAROUSEL,
2370                         id        : elId
2371                 });
2372             }
2373
2374             if (!carousel._carouselEl) {
2375                 carousel._carouselEl=createElement(carousel.get("carouselEl"),
2376                         { className: cssClass.CAROUSEL_EL });
2377             }
2378
2379             return el;
2380         },
2381
2382         /**
2383          * Create the Carousel clip container.
2384          *
2385          * @method createCarouselClip
2386          * @protected
2387          */
2388         _createCarouselClip: function () {
2389             return createElement("DIV", { className: this.CLASSES.CONTENT });
2390         },
2391
2392         /**
2393          * Create the Carousel item.
2394          *
2395          * @method createCarouselItem
2396          * @param obj {Object} The attributes of the element to be created
2397          * @protected
2398          */
2399         _createCarouselItem: function (obj) {
2400             return createElement(this.get("carouselItemEl"), {
2401                     className : obj.className,
2402                     content   : obj.content,
2403                     id        : obj.id
2404             });
2405         },
2406
2407         /**
2408          * Return a valid item for a possibly out of bounds index considering
2409          * the isCircular property.
2410          *
2411          * @method _getValidIndex
2412          * @param index {Number} The index of the item to be returned
2413          * @return {Object} Return a valid item index
2414          * @protected
2415          */
2416         _getValidIndex: function (index) {
2417             var carousel   = this,
2418                 isCircular = carousel.get("isCircular"),
2419                 numItems   = carousel.get("numItems"),
2420                 sentinel   = numItems - 1;
2421
2422             if (index < 0) {
2423                 index = isCircular ? numItems + index : 0;
2424             } else if (index > sentinel) {
2425                 index = isCircular ? index - numItems : sentinel;
2426             }
2427
2428             return index;
2429         },
2430
2431         /**
2432          * Get the value for the selected item.
2433          *
2434          * @method _getSelectedItem
2435          * @param val {Number} The new value for "selected" item
2436          * @return {Number} The new value that would be set
2437          * @protected
2438          */
2439         _getSelectedItem: function (val) {
2440             var carousel   = this,
2441                 isCircular = carousel.get("isCircular"),
2442                 numItems   = carousel.get("numItems"),
2443                 sentinel   = numItems - 1;
2444
2445             if (val < 0) {
2446                 if (isCircular) {
2447                     val = numItems + val;
2448                 } else {
2449                     val = carousel.get("selectedItem");
2450                 }
2451             } else if (val > sentinel) {
2452                 if (isCircular) {
2453                     val = val - numItems;
2454                 } else {
2455                     val = carousel.get("selectedItem");
2456                 }
2457             }
2458
2459             return val;
2460         },
2461
2462         /**
2463          * The "click" handler for the item.
2464          *
2465          * @method _itemClickHandler
2466          * @param {Event} ev The event object
2467          * @protected
2468          */
2469         _itemClickHandler: function (ev) {
2470             var carousel  = this,
2471                 container = carousel.get("element"),
2472                 el,
2473                 item,
2474                 target    = YAHOO.util.Event.getTarget(ev);
2475
2476             while (target && target != container &&
2477                    target.id != carousel._carouselEl) {
2478                 el = target.nodeName;
2479                 if (el.toUpperCase() == carousel.get("carouselItemEl")) {
2480                     break;
2481                 }
2482                 target = target.parentNode;
2483             }
2484
2485             if ((item = carousel.getItemPositionById(target.id)) >= 0) {
2486                 YAHOO.log("Setting selection to " + item, WidgetName);
2487                 carousel.set("selectedItem", carousel._getSelectedItem(item));
2488                 carousel.focus();
2489             }
2490         },
2491
2492         /**
2493          * The keyboard event handler for Carousel.
2494          *
2495          * @method _keyboardEventHandler
2496          * @param ev {Event} The event that is being handled.
2497          * @protected
2498          */
2499         _keyboardEventHandler: function (ev) {
2500             var carousel = this,
2501                 key      = Event.getCharCode(ev),
2502                 prevent  = false;
2503
2504             if (carousel.isAnimating()) {
2505                 return;         // do not mess while animation is in progress
2506             }
2507
2508             switch (key) {
2509             case 0x25:          // left arrow
2510             case 0x26:          // up arrow
2511                 carousel.selectPreviousItem();
2512                 prevent = true;
2513                 break;
2514             case 0x27:          // right arrow
2515             case 0x28:          // down arrow
2516                 carousel.selectNextItem();
2517                 prevent = true;
2518                 break;
2519             case 0x21:          // page-up
2520                 carousel.scrollPageBackward();
2521                 prevent = true;
2522                 break;
2523             case 0x22:          // page-down
2524                 carousel.scrollPageForward();
2525                 prevent = true;
2526                 break;
2527             }
2528
2529             if (prevent) {
2530                 if (carousel.isAutoPlayOn()) {
2531                     carousel.stopAutoPlay();
2532                 }
2533                 Event.preventDefault(ev);
2534             }
2535         },
2536
2537         /**
2538          * The load the required set of items that are needed for display.
2539          *
2540          * @method _loadItems
2541          * @protected
2542          */
2543         _loadItems: function() {
2544             var carousel   = this,
2545                 first      = carousel.get("firstVisible"),
2546                 last       = 0,
2547                 numItems   = carousel.get("numItems"),
2548                 numVisible = carousel.get("numVisible"),
2549                 reveal     = carousel.get("revealAmount");
2550
2551             last = first + numVisible - 1 + (reveal ? 1 : 0);
2552             last = last > numItems - 1 ? numItems - 1 : last;
2553
2554             if (!carousel.getItem(first) || !carousel.getItem(last)) {
2555                 carousel.fireEvent(loadItemsEvent, {
2556                         ev: loadItemsEvent, first: first, last: last,
2557                         num: last - first
2558                 });
2559             }
2560         },
2561
2562         /**
2563          * The "click" handler for the pager navigation.
2564          *
2565          * @method _pagerClickHandler
2566          * @param {Event} ev The event object
2567          * @protected
2568          */
2569         _pagerClickHandler: function (ev) {
2570             var carousel = this,
2571                 pos,
2572                 target   = Event.getTarget(ev),
2573                 val;
2574
2575             function getPagerNode(el) {
2576                 var itemEl = carousel.get("carouselItemEl");
2577
2578                 if (el.nodeName.toUpperCase() == itemEl.toUpperCase()) {
2579                     el = Dom.getChildrenBy(el, function (node) {
2580                         // either an anchor or select at least
2581                         return node.href || node.value;
2582                     });
2583                     if (el && el[0]) {
2584                         return el[0];
2585                     }
2586                 } else if (el.href || el.value) {
2587                     return el;
2588                 }
2589
2590                 return null;
2591             }
2592
2593             if (target) {
2594                 target = getPagerNode(target);
2595                 if (!target) {
2596                     return;
2597                 }
2598                 val = target.href || target.value;
2599                 if (JS.isString(val) && val) {
2600                     pos = val.lastIndexOf("#");
2601                     if (pos != -1) {
2602                         val = carousel.getItemPositionById(
2603                                 val.substring(pos + 1));
2604                         carousel._selectedItem = val;
2605                         carousel.scrollTo(val);
2606                         if (!target.value) { // not a select element
2607                             carousel.focus();
2608                         }
2609                         Event.preventDefault(ev);
2610                     }
2611                 }
2612             }
2613         },
2614
2615         /**
2616          * Find the Carousel within a container. The Carousel is identified by
2617          * the first element that matches the carousel element tag or the
2618          * element that has the Carousel class.
2619          *
2620          * @method parseCarousel
2621          * @param parent {HTMLElement} The parent element to look under
2622          * @return {Boolean} True if Carousel is found, false otherwise
2623          * @protected
2624          */
2625         _parseCarousel: function (parent) {
2626             var carousel = this, child, cssClass, domEl, found, node;
2627
2628             cssClass  = carousel.CLASSES;
2629             domEl     = carousel.get("carouselEl");
2630             found     = false;
2631
2632             for (child = parent.firstChild; child; child = child.nextSibling) {
2633                 if (child.nodeType == 1) {
2634                     node = child.nodeName;
2635                     if (node.toUpperCase() == domEl) {
2636                         carousel._carouselEl = child;
2637                         Dom.addClass(carousel._carouselEl,
2638                                      carousel.CLASSES.CAROUSEL_EL);
2639                         YAHOO.log("Found Carousel - " + node +
2640                                 (child.id ? " (#" + child.id + ")" : ""),
2641                                 WidgetName);
2642                         found = true;
2643                     }
2644                 }
2645             }
2646
2647             return found;
2648         },
2649
2650         /**
2651          * Find the items within the Carousel and add them to the items table.
2652          * A Carousel item is identified by elements that matches the carousel
2653          * item element tag.
2654          *
2655          * @method parseCarouselItems
2656          * @protected
2657          */
2658         _parseCarouselItems: function () {
2659             var carousel = this,
2660                 child,
2661                 domItemEl,
2662                 elId,
2663                 node,
2664                 parent   = carousel._carouselEl;
2665
2666             domItemEl = carousel.get("carouselItemEl");
2667
2668             for (child = parent.firstChild; child; child = child.nextSibling) {
2669                 if (child.nodeType == 1) {
2670                     node = child.nodeName;
2671                     if (node.toUpperCase() == domItemEl) {
2672                         if (child.id) {
2673                             elId = child.id;
2674                         } else {
2675                             elId = Dom.generateId();
2676                             child.setAttribute("id", elId);
2677                         }
2678                         carousel.addItem(child);
2679                     }
2680                 }
2681             }
2682         },
2683
2684         /**
2685          * Find the Carousel navigation within a container. The navigation
2686          * elements need to match the carousel navigation class names.
2687          *
2688          * @method parseCarouselNavigation
2689          * @param parent {HTMLElement} The parent element to look under
2690          * @return {Boolean} True if at least one is found, false otherwise
2691          * @protected
2692          */
2693         _parseCarouselNavigation: function (parent) {
2694             var carousel = this,
2695                 cfg,
2696                 cssClass = carousel.CLASSES,
2697                 el,
2698                 i,
2699                 j,
2700                 nav,
2701                 rv       = false;
2702
2703             nav = Dom.getElementsByClassName(cssClass.PREV_PAGE, "*", parent);
2704             if (nav.length > 0) {
2705                 for (i in nav) {
2706                     if (nav.hasOwnProperty(i)) {
2707                         el = nav[i];
2708                         YAHOO.log("Found Carousel previous page navigation - " +
2709                                 el + (el.id ? " (#" + el.id + ")" : ""),
2710                                 WidgetName);
2711                         if (el.nodeName == "INPUT" ||
2712                             el.nodeName == "BUTTON") {
2713                             carousel._navBtns.prev.push(el);
2714                         } else {
2715                             j = el.getElementsByTagName("INPUT");
2716                             if (JS.isArray(j) && j.length > 0) {
2717                                 carousel._navBtns.prev.push(j[0]);
2718                             } else {
2719                                 j = el.getElementsByTagName("BUTTON");
2720                                 if (JS.isArray(j) && j.length > 0) {
2721                                     carousel._navBtns.prev.push(j[0]);
2722                                 }
2723                             }
2724                         }
2725                     }
2726                 }
2727                 cfg = { prev: nav };
2728             }
2729
2730             nav = Dom.getElementsByClassName(cssClass.NEXT_PAGE, "*", parent);
2731             if (nav.length > 0) {
2732                 for (i in nav) {
2733                     if (nav.hasOwnProperty(i)) {
2734                         el = nav[i];
2735                         YAHOO.log("Found Carousel next page navigation - " +
2736                                 el + (el.id ? " (#" + el.id + ")" : ""),
2737                                 WidgetName);
2738                         if (el.nodeName == "INPUT" ||
2739                             el.nodeName == "BUTTON") {
2740                             carousel._navBtns.next.push(el);
2741                         } else {
2742                             j = el.getElementsByTagName("INPUT");
2743                             if (JS.isArray(j) && j.length > 0) {
2744                                 carousel._navBtns.next.push(j[0]);
2745                             } else {
2746                                 j = el.getElementsByTagName("BUTTON");
2747                                 if (JS.isArray(j) && j.length > 0) {
2748                                     carousel._navBtns.next.push(j[0]);
2749                                 }
2750                             }
2751                         }
2752                     }
2753                 }
2754                 if (cfg) {
2755                     cfg.next = nav;
2756                 } else {
2757                     cfg = { next: nav };
2758                 }
2759             }
2760
2761             if (cfg) {
2762                 carousel.set("navigation", cfg);
2763                 rv = true;
2764             }
2765
2766             return rv;
2767         },
2768
2769         /**
2770          * Refresh the widget UI if it is not already rendered, on first item
2771          * addition.
2772          *
2773          * @method _refreshUi
2774          * @protected
2775          */
2776         _refreshUi: function () {
2777             var carousel = this;
2778
2779             // Set the rendered state appropriately.
2780             carousel._hasRendered = true;
2781             carousel.fireEvent(renderEvent);
2782         },
2783
2784         /**
2785          * Set the Carousel offset to the passed offset.
2786          *
2787          * @method _setCarouselOffset
2788          * @protected
2789          */
2790         _setCarouselOffset: function (offset) {
2791             var carousel = this, which;
2792
2793             which   = carousel.get("isVertical") ? "top" : "left";
2794             offset += offset !== 0 ? getStyle(carousel._carouselEl, which) : 0;
2795             Dom.setStyle(carousel._carouselEl, which, offset + "px");
2796         },
2797
2798         /**
2799          * Setup/Create the Carousel navigation element (if needed).
2800          *
2801          * @method _setupCarouselNavigation
2802          * @protected
2803          */
2804         _setupCarouselNavigation: function () {
2805             var carousel = this,
2806                 btn, cfg, cssClass, nav, navContainer, nextButton, prevButton;
2807
2808             cssClass = carousel.CLASSES;
2809
2810             // TODO: can the _navBtns be tested against instead?
2811             navContainer = Dom.getElementsByClassName(cssClass.NAVIGATION,
2812                     "DIV", carousel.get("element"));
2813
2814             if (navContainer.length === 0) {
2815                 navContainer = createElement("DIV",
2816                         { className: cssClass.NAVIGATION });
2817                 carousel.insertBefore(navContainer,
2818                         Dom.getFirstChild(carousel.get("element")));
2819             } else {
2820                 navContainer = navContainer[0];
2821             }
2822
2823             carousel._pages.el = createElement("UL");
2824             navContainer.appendChild(carousel._pages.el);
2825
2826             nav = carousel.get("navigation");
2827             if (JS.isString(nav.prev) || JS.isArray(nav.prev)) {
2828                 if (JS.isString(nav.prev)) {
2829                     nav.prev = [nav.prev];
2830                 }
2831                 for (btn in nav.prev) {
2832                     if (nav.prev.hasOwnProperty(btn)) {
2833                         carousel._navBtns.prev.push(Dom.get(nav.prev[btn]));
2834                     }
2835                 }
2836             } else {
2837                 // TODO: separate method for creating a navigation button
2838                 prevButton = createElement("SPAN",
2839                         { className: cssClass.BUTTON + cssClass.FIRST_NAV });
2840                 // XXX: for IE 6.x
2841                 Dom.setStyle(prevButton, "visibility", "visible");
2842                 btn = Dom.generateId();
2843                 prevButton.innerHTML = "<button type=\"button\" "      +
2844                         "id=\"" + btn + "\" name=\""                   +
2845                         carousel.STRINGS.PREVIOUS_BUTTON_TEXT + "\">"  +
2846                         carousel.STRINGS.PREVIOUS_BUTTON_TEXT + "</button>";
2847                 navContainer.appendChild(prevButton);
2848                 btn = Dom.get(btn);
2849                 carousel._navBtns.prev = [btn];
2850                 cfg = { prev: [prevButton] };
2851             }
2852
2853             if (JS.isString(nav.next) || JS.isArray(nav.next)) {
2854                 if (JS.isString(nav.next)) {
2855                     nav.next = [nav.next];
2856                 }
2857                 for (btn in nav.next) {
2858                     if (nav.next.hasOwnProperty(btn)) {
2859                         carousel._navBtns.next.push(Dom.get(nav.next[btn]));
2860                     }
2861                 }
2862             } else {
2863                 // TODO: separate method for creating a navigation button
2864                 nextButton = createElement("SPAN",
2865                         { className: cssClass.BUTTON + cssClass.NEXT_NAV });
2866                 // XXX: for IE 6.x
2867                 Dom.setStyle(nextButton, "visibility", "visible");
2868                 btn = Dom.generateId();
2869                 nextButton.innerHTML = "<button type=\"button\" "      +
2870                         "id=\"" + btn + "\" name=\""                   +
2871                         carousel.STRINGS.NEXT_BUTTON_TEXT + "\">"      +
2872                         carousel.STRINGS.NEXT_BUTTON_TEXT + "</button>";
2873                 navContainer.appendChild(nextButton);
2874                 btn = Dom.get(btn);
2875                 carousel._navBtns.next = [btn];
2876                 if (cfg) {
2877                     cfg.next = [nextButton];
2878                 } else {
2879                     cfg = { next: [nextButton] };
2880                 }
2881             }
2882
2883             if (cfg) {
2884                 carousel.set("navigation", cfg);
2885             }
2886
2887             return navContainer;
2888         },
2889
2890         /**
2891          * Set the clip container size (based on the new numVisible value).
2892          *
2893          * @method _setClipContainerSize
2894          * @param clip {HTMLElement} The clip container element.
2895          * @param num {Number} optional The number of items per page.
2896          * @protected
2897          */
2898         _setClipContainerSize: function (clip, num) {
2899             var carousel = this,
2900                 attr, currVal, isVertical, itemSize, reveal, size, which;
2901
2902             isVertical = carousel.get("isVertical");
2903             reveal     = carousel.get("revealAmount");
2904             which      = isVertical ? "height" : "width";
2905             attr       = isVertical ? "top" : "left";
2906
2907             clip       = clip || carousel._clipEl;
2908             if (!clip) {
2909                 return;
2910             }
2911
2912             num        = num  || carousel.get("numVisible");
2913             itemSize   = getCarouselItemSize.call(carousel, which);
2914             size       = itemSize * num;
2915
2916             // TODO: try to re-use the _hasRendered indicator
2917             carousel._recomputeSize = (size === 0); // bleh!
2918             if (carousel._recomputeSize) {
2919                 carousel._hasRendered = false;
2920                 return;             // no use going further, bail out!
2921             }
2922
2923             if (reveal > 0) {
2924                 reveal = itemSize * (reveal / 100) * 2;
2925                 size += reveal;
2926                 // TODO: set the Carousel's initial offset somwehere
2927                 currVal = parseFloat(Dom.getStyle(carousel._carouselEl, attr));
2928                 currVal = JS.isNumber(currVal) ? currVal : 0;
2929                 Dom.setStyle(carousel._carouselEl,
2930                              attr, currVal + (reveal / 2) + "px");
2931             }
2932
2933             if (isVertical) {
2934                 size += getStyle(carousel._carouselEl, "marginTop")        +
2935                         getStyle(carousel._carouselEl, "marginBottom")     +
2936                         getStyle(carousel._carouselEl, "paddingTop")       +
2937                         getStyle(carousel._carouselEl, "paddingBottom")    +
2938                         getStyle(carousel._carouselEl, "borderTopWidth")   +
2939                         getStyle(carousel._carouselEl, "borderBottomWidth");
2940                 // XXX: for vertical Carousel
2941                 Dom.setStyle(clip, which, (size - (num - 1)) + "px");
2942             } else {
2943                 size += getStyle(carousel._carouselEl, "marginLeft")      +
2944                         getStyle(carousel._carouselEl, "marginRight")     +
2945                         getStyle(carousel._carouselEl, "paddingLeft")     +
2946                         getStyle(carousel._carouselEl, "paddingRight")    +
2947                         getStyle(carousel._carouselEl, "borderLeftWidth") +
2948                         getStyle(carousel._carouselEl, "borderRightWidth");
2949                 Dom.setStyle(clip, which, size + "px");
2950             }
2951
2952             carousel._setContainerSize(clip); // adjust the container size too
2953         },
2954
2955         /**
2956          * Set the container size.
2957          *
2958          * @method _setContainerSize
2959          * @param clip {HTMLElement} The clip container element.
2960          * @param attr {String} Either set the height or width.
2961          * @protected
2962          */
2963         _setContainerSize: function (clip, attr) {
2964             var carousel = this,
2965                 config   = carousel.CONFIG,
2966                 cssClass = carousel.CLASSES,
2967                 isVertical,
2968                 size;
2969
2970             isVertical = carousel.get("isVertical");
2971             clip       = clip || carousel._clipEl;
2972             attr       = attr || (isVertical ? "height" : "width");
2973             size       = parseFloat(Dom.getStyle(clip, attr), 10);
2974
2975             size = JS.isNumber(size) ? size : 0;
2976
2977             if (isVertical) {
2978                 size += getStyle(carousel._carouselEl, "marginTop")         +
2979                         getStyle(carousel._carouselEl, "marginBottom")      +
2980                         getStyle(carousel._carouselEl, "paddingTop")        +
2981                         getStyle(carousel._carouselEl, "paddingBottom")     +
2982                         getStyle(carousel._carouselEl, "borderTopWidth")    +
2983                         getStyle(carousel._carouselEl, "borderBottomWidth") +
2984                         getStyle(carousel._navEl, "height");
2985             } else {
2986                 size += getStyle(clip, "marginLeft")                    +
2987                         getStyle(clip, "marginRight")                   +
2988                         getStyle(clip, "paddingLeft")                   +
2989                         getStyle(clip, "paddingRight")                  +
2990                         getStyle(clip, "borderLeftWidth")               +
2991                         getStyle(clip, "borderRightWidth");
2992             }
2993
2994             if (!isVertical) {
2995                 if (size < config.HORZ_MIN_WIDTH) {
2996                     size = config.HORZ_MIN_WIDTH;
2997                     carousel.addClass(cssClass.MIN_WIDTH);
2998                 }
2999             }
3000             carousel.setStyle(attr,  size + "px");
3001
3002             // Additionally the width of the container should be set for
3003             // the vertical Carousel
3004             if (isVertical) {
3005                 size = getCarouselItemSize.call(carousel, "width");
3006                 if (size < config.VERT_MIN_WIDTH) {
3007                     size = config.VERT_MIN_WIDTH;
3008                     carousel.addClass(cssClass.MIN_WIDTH);
3009                 }
3010                 carousel.setStyle("width",  size + "px");
3011             }
3012         },
3013
3014         /**
3015          * Set the value for the Carousel's first visible item.
3016          *
3017          * @method _setFirstVisible
3018          * @param val {Number} The new value for firstVisible
3019          * @return {Number} The new value that would be set
3020          * @protected
3021          */
3022         _setFirstVisible: function (val) {
3023             var carousel = this;
3024
3025             if (val >= 0 && val < carousel.get("numItems")) {
3026                 carousel.scrollTo(val);
3027             } else {
3028                 val = carousel.get("firstVisible");
3029             }
3030             return val;
3031         },
3032
3033         /**
3034          * Set the value for the Carousel's navigation.
3035          *
3036          * @method _setNavigation
3037          * @param cfg {Object} The navigation configuration
3038          * @return {Object} The new value that would be set
3039          * @protected
3040          */
3041         _setNavigation: function (cfg) {
3042             var carousel = this;
3043
3044             if (cfg.prev) {
3045                 Event.on(cfg.prev, "click", scrollPageBackward, carousel);
3046             }
3047             if (cfg.next) {
3048                 Event.on(cfg.next, "click", scrollPageForward, carousel);
3049             }
3050         },
3051
3052         /**
3053          * Set the value for the number of visible items in the Carousel.
3054          *
3055          * @method _setNumVisible
3056          * @param val {Number} The new value for numVisible
3057          * @return {Number} The new value that would be set
3058          * @protected
3059          */
3060         _setNumVisible: function (val) {
3061             var carousel = this;
3062
3063             carousel._setClipContainerSize(carousel._clipEl, val);
3064         },
3065
3066         /**
3067          * Set the number of items in the Carousel.
3068          * Warning: Setting this to a lower number than the current removes
3069          * items from the end.
3070          *
3071          * @method _setNumItems
3072          * @param val {Number} The new value for numItems
3073          * @return {Number} The new value that would be set
3074          * @protected
3075          */
3076         _setNumItems: function (val) {
3077             var carousel = this,
3078                 num      = carousel._itemsTable.numItems;
3079
3080             if (JS.isArray(carousel._itemsTable.items)) {
3081                 if (carousel._itemsTable.items.length != num) { // out of sync
3082                     num = carousel._itemsTable.items.length;
3083                     carousel._itemsTable.numItems = num;
3084                 }
3085             }
3086
3087             if (val < num) {
3088                 while (num > val) {
3089                     carousel.removeItem(num - 1);
3090                     num--;
3091                 }
3092             }
3093
3094             return val;
3095         },
3096
3097         /**
3098          * Set the orientation of the Carousel.
3099          *
3100          * @method _setOrientation
3101          * @param val {Boolean} The new value for isVertical
3102          * @return {Boolean} The new value that would be set
3103          * @protected
3104          */
3105         _setOrientation: function (val) {
3106             var carousel = this,
3107                 cssClass = carousel.CLASSES;
3108
3109             if (val) {
3110                 carousel.replaceClass(cssClass.HORIZONTAL, cssClass.VERTICAL);
3111             } else {
3112                 carousel.replaceClass(cssClass.VERTICAL, cssClass.HORIZONTAL);
3113             }
3114             carousel._itemsTable.size = 0; // force recalculation next time
3115             return val;
3116         },
3117
3118         /**
3119          * Set the value for the reveal amount percentage in the Carousel.
3120          *
3121          * @method _setRevealAmount
3122          * @param val {Number} The new value for revealAmount
3123          * @return {Number} The new value that would be set
3124          * @protected
3125          */
3126         _setRevealAmount: function (val) {
3127             var carousel = this;
3128
3129             if (val >= 0 && val <= 100) {
3130                 val = parseInt(val, 10);
3131                 val = JS.isNumber(val) ? val : 0;
3132                 carousel._setClipContainerSize();
3133             } else {
3134                 val = carousel.get("revealAmount");
3135             }
3136             return val;
3137         },
3138
3139         /**
3140          * Set the value for the selected item.
3141          *
3142          * @method _setSelectedItem
3143          * @param val {Number} The new value for "selected" item
3144          * @protected
3145          */
3146         _setSelectedItem: function (val) {
3147             this._selectedItem = val;
3148         },
3149
3150         /**
3151          * Synchronize and redraw the UI after an item is added.
3152          *
3153          * @method _syncUiForItemAdd
3154          * @protected
3155          */
3156         _syncUiForItemAdd: function (obj) {
3157             var carousel   = this,
3158                 carouselEl = carousel._carouselEl,
3159                 el,
3160                 item,
3161                 itemsTable = carousel._itemsTable,
3162                 oel,
3163                 pos,
3164                 sibling;
3165
3166             pos  = JS.isUndefined(obj.pos) ? itemsTable.numItems - 1 : obj.pos;
3167             if (!JS.isUndefined(itemsTable.items[pos])) {
3168                 item = itemsTable.items[pos];
3169                 if (item && !JS.isUndefined(item.id)) {
3170                     oel  = Dom.get(item.id);
3171                 }
3172             }
3173             if (!oel) {
3174                 el = carousel._createCarouselItem({
3175                         className : item.className,
3176                         content   : item.item,
3177                         id        : item.id
3178                 });
3179                 if (JS.isUndefined(obj.pos)) {
3180                     if (!JS.isUndefined(itemsTable.loading[pos])) {
3181                         oel = itemsTable.loading[pos];
3182                         // if oel is null, it is a problem ...
3183                     }
3184                     if (oel) {
3185                         // replace the node
3186                         carouselEl.replaceChild(el, oel);
3187                         // ... and remove the item from the data structure
3188                         delete itemsTable.loading[pos];
3189                     } else {
3190                         carouselEl.appendChild(el);
3191                     }
3192                 } else {
3193                     if (!JS.isUndefined(itemsTable.items[obj.pos + 1])) {
3194                         sibling = Dom.get(itemsTable.items[obj.pos + 1].id);
3195                     }
3196                     if (sibling) {
3197                         carouselEl.insertBefore(el, sibling);
3198                     } else {
3199                         YAHOO.log("Unable to find sibling","error",WidgetName);
3200                     }
3201                 }
3202             } else {
3203                 if (JS.isUndefined(obj.pos)) {
3204                     if (!Dom.isAncestor(carousel._carouselEl, oel)) {
3205                         carouselEl.appendChild(oel);
3206                     }
3207                 } else {
3208                     if (!Dom.isAncestor(carouselEl, oel)) {
3209                         if (!JS.isUndefined(itemsTable.items[obj.pos + 1])) {
3210                             carouselEl.insertBefore(oel,
3211                                     Dom.get(itemsTable.items[obj.pos + 1].id));
3212                         }
3213                     }
3214                 }
3215             }
3216
3217             if (!carousel._hasRendered) {
3218                 carousel._refreshUi();
3219             }
3220
3221             if (carousel.get("selectedItem") < 0) {
3222                 carousel.set("selectedItem", carousel.get("firstVisible"));
3223             }
3224         },
3225
3226         /**
3227          * Synchronize and redraw the UI after an item is removed.
3228          *
3229          * @method _syncUiForItemAdd
3230          * @protected
3231          */
3232         _syncUiForItemRemove: function (obj) {
3233             var carousel   = this,
3234                 carouselEl = carousel._carouselEl,
3235                 el, item, num, pos;
3236
3237             num  = carousel.get("numItems");
3238             item = obj.item;
3239             pos  = obj.pos;
3240
3241             if (item && (el = Dom.get(item.id))) {
3242                 if (el && Dom.isAncestor(carouselEl, el)) {
3243                     Event.purgeElement(el, true);
3244                     carouselEl.removeChild(el);
3245                 }
3246
3247                 if (carousel.get("selectedItem") == pos) {
3248                     pos = pos >= num ? num - 1 : pos;
3249                     carousel.set("selectedItem", pos);
3250                 }
3251             } else {
3252                 YAHOO.log("Unable to find item", "warn", WidgetName);
3253             }
3254         },
3255
3256         /**
3257          * Synchronize and redraw the UI for lazy loading.
3258          *
3259          * @method _syncUiForLazyLoading
3260          * @protected
3261          */
3262         _syncUiForLazyLoading: function (obj) {
3263             var carousel   = this,
3264                 carouselEl = carousel._carouselEl,
3265                 el,
3266                 i,
3267                 itemsTable = carousel._itemsTable,
3268                 sibling;
3269
3270             for (i = obj.first; i <= obj.last; i++) {
3271                 el = carousel._createCarouselItem({
3272                         className : carousel.CLASSES.ITEM_LOADING,
3273                         content   : carousel.STRINGS.ITEM_LOADING_CONTENT,
3274                         id        : Dom.generateId()
3275                 });
3276                 if (el) {
3277                     if (!JS.isUndefined(itemsTable.items[obj.last + 1])) {
3278                         sibling = Dom.get(itemsTable.items[obj.last + 1].id);
3279                         if (sibling) {
3280                             carouselEl.insertBefore(el, sibling);
3281                         } else {
3282                             YAHOO.log("Unable to find sibling", "error",
3283                                     WidgetName);
3284                         }
3285                     } else {
3286                         carouselEl.appendChild(el);
3287                     }
3288                 }
3289                 itemsTable.loading[i] = el;
3290             }
3291         },
3292
3293         /**
3294          * Set the correct class for the navigation buttons.
3295          *
3296          * @method _updateNavButtons
3297          * @param el {Object} The target button
3298          * @param setFocus {Boolean} True to set focus ring, false otherwise.
3299          * @protected
3300          */
3301         _updateNavButtons: function (el, setFocus) {
3302             var children,
3303                 cssClass = this.CLASSES,
3304                 grandParent,
3305                 parent   = el.parentNode;
3306
3307             if (!parent) {
3308                 return;
3309             }
3310             grandParent = parent.parentNode;
3311
3312             if (el.nodeName.toUpperCase() == "BUTTON" &&
3313                 Dom.hasClass(parent, cssClass.BUTTON)) {
3314                 if (setFocus) {
3315                     if (grandParent) {
3316                         children = Dom.getChildren(grandParent);
3317                         if (children) {
3318                             Dom.removeClass(children, cssClass.FOCUSSED_BUTTON);
3319                         }
3320                     }
3321                     Dom.addClass(parent, cssClass.FOCUSSED_BUTTON);
3322                 } else {
3323                     Dom.removeClass(parent, cssClass.FOCUSSED_BUTTON);
3324                 }
3325             }
3326         },
3327
3328         /**
3329          * Update the UI for the pager buttons based on the current page and
3330          * the number of pages.
3331          *
3332          * @method _updatePagerButtons
3333          * @protected
3334          */
3335         _updatePagerButtons: function () {
3336             var carousel = this,
3337                 css      = carousel.CLASSES,
3338                 cur      = carousel._pages.cur, // current page
3339                 el,
3340                 html,
3341                 i,
3342                 item,
3343                 n        = carousel.get("numVisible"),
3344                 num      = carousel._pages.num, // total pages
3345                 pager    = carousel._pages.el;  // the pager container element
3346
3347             if (num === 0 || !pager) {
3348                 return;         // don't do anything if number of pages is 0
3349             }
3350
3351             // Hide the pager before redrawing it
3352             Dom.setStyle(pager, "visibility", "hidden");
3353
3354             // Remove all nodes from the pager
3355             while (pager.firstChild) {
3356                 pager.removeChild(pager.firstChild);
3357             }
3358
3359             for (i = 0; i < num; i++) {
3360                 if (JS.isUndefined(carousel._itemsTable.items[i * n])) {
3361                     Dom.setStyle(pager, "visibility", "visible");
3362                     break;
3363                 }
3364                 item = carousel._itemsTable.items[i * n].id;
3365
3366                 el   = document.createElement("LI");
3367                 if (!el) {
3368                     YAHOO.log("Unable to create an LI pager button", "error",
3369                               WidgetName);
3370                     Dom.setStyle(pager, "visibility", "visible");
3371                     break;
3372                 }
3373
3374                 if (i === 0) {
3375                     Dom.addClass(el, css.FIRST_PAGE);
3376                 }
3377                 if (i == cur) {
3378                     Dom.addClass(el, css.SELECTED_NAV);
3379                 }
3380
3381                 // TODO: use a template string for i18N compliance
3382                 html = "<a href=\"#" + item + "\" tabindex=\"0\"><em>"   +
3383                         carousel.STRINGS.PAGER_PREFIX_TEXT + " " + (i+1) +
3384                         "</em></a>";
3385                 el.innerHTML = html;
3386
3387                 pager.appendChild(el);
3388             }
3389
3390             // Show the pager now
3391             Dom.setStyle(pager, "visibility", "visible");
3392         },
3393
3394         /**
3395          * Update the UI for the pager menu based on the current page and
3396          * the number of pages.  If the number of pages is greater than
3397          * MAX_PAGER_BUTTONS, then the selection of pages is provided by a drop
3398          * down menu instead of a set of buttons.
3399          *
3400          * @method _updatePagerMenu
3401          * @protected
3402          */
3403         _updatePagerMenu: function () {
3404             var carousel = this,
3405                 cur      = carousel._pages.cur, // current page
3406                 el,
3407                 i,
3408                 item,
3409                 n        = carousel.get("numVisible"),
3410                 num      = carousel._pages.num, // total pages
3411                 pager    = carousel._pages.el,  // the pager container element
3412                 sel;
3413
3414             if (num === 0) {
3415                 return;         // don't do anything if number of pages is 0
3416             }
3417
3418             sel = document.createElement("SELECT");
3419             if (!sel) {
3420                 YAHOO.log("Unable to create the pager menu", "error",
3421                           WidgetName);
3422                 return;
3423             }
3424
3425             // Hide the pager before redrawing it
3426             Dom.setStyle(pager, "visibility", "hidden");
3427
3428             // Remove all nodes from the pager
3429             while (pager.firstChild) {
3430                 pager.removeChild(pager.firstChild);
3431             }
3432
3433             for (i = 0; i < num; i++) {
3434                 if (JS.isUndefined(carousel._itemsTable.items[i * n])) {
3435                     Dom.setStyle(pager, "visibility", "visible");
3436                     break;
3437                 }
3438                 item = carousel._itemsTable.items[i * n].id;
3439
3440                 el   = document.createElement("OPTION");
3441                 if (!el) {
3442                     YAHOO.log("Unable to create an OPTION pager menu", "error",
3443                               WidgetName);
3444                     Dom.setStyle(pager, "visibility", "visible");
3445                     break;
3446                 }
3447                 el.value     = "#" + item;
3448                 // TODO: use a template string for i18N compliance
3449                 el.innerHTML = carousel.STRINGS.PAGER_PREFIX_TEXT+" "+(i+1);
3450
3451                 if (i == cur) {
3452                     el.setAttribute("selected", "selected");
3453                 }
3454
3455                 sel.appendChild(el);
3456             }
3457
3458             el = document.createElement("FORM");
3459             if (!el) {
3460                 YAHOO.log("Unable to create the pager menu", "error",
3461                           WidgetName);
3462             } else {
3463                 el.appendChild(sel);
3464                 pager.appendChild(el);
3465             }
3466
3467             // Show the pager now
3468             Dom.setStyle(pager, "visibility", "visible");
3469         },
3470
3471         /**
3472          * Set the correct tab index for the Carousel items.
3473          *
3474          * @method _updateTabIndex
3475          * @param el {Object} The element to be focussed
3476          * @protected
3477          */
3478         _updateTabIndex: function (el) {
3479             var carousel = this;
3480
3481             if (el) {
3482                 if (carousel._focusableItemEl) {
3483                     carousel._focusableItemEl.tabIndex = -1;
3484                 }
3485                 carousel._focusableItemEl = el;
3486                 el.tabIndex = 0;
3487             }
3488         },
3489
3490         /**
3491          * Validate animation parameters.
3492          *
3493          * @method _validateAnimation
3494          * @param cfg {Object} The animation configuration
3495          * @return {Boolean} The status of the validation
3496          * @protected
3497          */
3498         _validateAnimation: function (cfg) {
3499             var rv = true;
3500
3501             if (JS.isObject(cfg)) {
3502                 if (cfg.speed) {
3503                     rv = rv && JS.isNumber(cfg.speed);
3504                 }
3505                 if (cfg.effect) {
3506                     rv = rv && JS.isFunction(cfg.effect);
3507                 } else if (!JS.isUndefined(YAHOO.util.Easing)) {
3508                     cfg.effect = YAHOO.util.Easing.easeOut;
3509                 }
3510             } else {
3511                 rv = false;
3512             }
3513
3514             return rv;
3515         },
3516
3517         /**
3518          * Validate the firstVisible value.
3519          *
3520          * @method _validateFirstVisible
3521          * @param val {Number} The first visible value
3522          * @return {Boolean} The status of the validation
3523          * @protected
3524          */
3525         _validateFirstVisible: function (val) {
3526             var carousel = this, numItems = carousel.get("numItems");
3527
3528             if (JS.isNumber(val)) {
3529                 if (numItems === 0 && val == numItems) {
3530                     return true;
3531                 } else {
3532                     return (val >= 0 && val < numItems);
3533                 }
3534             }
3535
3536             return false;
3537         },
3538
3539         /**
3540          * Validate and navigation parameters.
3541          *
3542          * @method _validateNavigation
3543          * @param cfg {Object} The navigation configuration
3544          * @return {Boolean} The status of the validation
3545          * @protected
3546          */
3547         _validateNavigation : function (cfg) {
3548             var i;
3549
3550             if (!JS.isObject(cfg)) {
3551                 return false;
3552             }
3553
3554             if (cfg.prev) {
3555                 if (!JS.isArray(cfg.prev)) {
3556                     return false;
3557                 }
3558                 for (i in cfg.prev) {
3559                     if (cfg.prev.hasOwnProperty(i)) {
3560                         if (!JS.isString(cfg.prev[i].nodeName)) {
3561                             return false;
3562                         }
3563                     }
3564                 }
3565             }
3566
3567             if (cfg.next) {
3568                 if (!JS.isArray(cfg.next)) {
3569                     return false;
3570                 }
3571                 for (i in cfg.next) {
3572                     if (cfg.next.hasOwnProperty(i)) {
3573                         if (!JS.isString(cfg.next[i].nodeName)) {
3574                             return false;
3575                         }
3576                     }
3577                 }
3578             }
3579
3580             return true;
3581         },
3582
3583         /**
3584          * Validate the numItems value.
3585          *
3586          * @method _validateNumItems
3587          * @param val {Number} The numItems value
3588          * @return {Boolean} The status of the validation
3589          * @protected
3590          */
3591         _validateNumItems: function (val) {
3592             return JS.isNumber(val) && (val >= 0);
3593         },
3594
3595         /**
3596          * Validate the numVisible value.
3597          *
3598          * @method _validateNumVisible
3599          * @param val {Number} The numVisible value
3600          * @return {Boolean} The status of the validation
3601          * @protected
3602          */
3603         _validateNumVisible: function (val) {
3604             var rv = false;
3605
3606             if (JS.isNumber(val)) {
3607                 rv = val > 0 && val <= this.get("numItems");
3608             }
3609
3610             return rv;
3611         },
3612
3613         /**
3614          * Validate the revealAmount value.
3615          *
3616          * @method _validateRevealAmount
3617          * @param val {Number} The revealAmount value
3618          * @return {Boolean} The status of the validation
3619          * @protected
3620          */
3621         _validateRevealAmount: function (val) {
3622             var rv = false;
3623
3624             if (JS.isNumber(val)) {
3625                 rv = val >= 0 && val < 100;
3626             }
3627
3628             return rv;
3629         },
3630
3631         /**
3632          * Validate the scrollIncrement value.
3633          *
3634          * @method _validateScrollIncrement
3635          * @param val {Number} The scrollIncrement value
3636          * @return {Boolean} The status of the validation
3637          * @protected
3638          */
3639         _validateScrollIncrement: function (val) {
3640             var rv = false;
3641
3642             if (JS.isNumber(val)) {
3643                 rv = (val > 0 && val < this.get("numItems"));
3644             }
3645
3646             return rv;
3647         }
3648
3649     });
3650
3651 })();
3652 /*
3653 ;;  Local variables: **
3654 ;;  mode: js2 **
3655 ;;  indent-tabs-mode: nil **
3656 ;;  End: **
3657 */
3658 YAHOO.register("carousel", YAHOO.widget.Carousel, {version: "2.7.0", build: "1799"});