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