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