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