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