1 /**
  2  * Copyright (c) 2006-2007, Bill W. Scott
  3  * All rights reserved.
  4  *
  5  * This work is licensed under the Creative Commons Attribution 2.5 License. To view a copy 
  6  * of this license, visit http://creativecommons.org/licenses/by/2.5/ or send a letter to 
  7  * Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.
  8  *
  9  * This work was created by Bill Scott (billwscott.com, looksgoodworkswell.com).
 10  * 
 11  * The only attribution I require is to keep this notice of copyright & license 
 12  * in this original source file.
 13  *
 14  * Version 0.6.1 - 07.08.2007
 15  *
 16  */
 17 YAHOO.namespace("extension");
 18 
 19 /**
 20 * @class 
 21 * The carousel class manages a content list (a set of LI elements within an UL list)  that can be displayed horizontally or vertically. The content can be scrolled back and forth  with or without animation. The content can reference static HTML content or the list items can  be created dynamically on-the-fly (with or without Ajax). The navigation and event handling  can be externalized from the class.
 22 * @param {object|string} carouselElementID The element ID (id name or id object) of the DIV that will become a carousel
 23 * @param {object} carouselCfg The configuration object literal containing the configuration that should be set for this module. See configuration documentation for more details.
 24 * @constructor
 25 */
 26 YAHOO.extension.Carousel = function(carouselElementID, carouselCfg) {
 27  		this.init(carouselElementID, carouselCfg);
 28 	};
 29 
 30 YAHOO.extension.Carousel.prototype = {
 31 
 32 
 33 	/**
 34 	 * Constant denoting that the carousel size is unbounded (no limits set on scrolling)
 35 	 * @type number
 36 	 */
 37 	UNBOUNDED_SIZE: 1000000,
 38 	
 39 	/**
 40 	 * Initializes the carousel object and all of its local members.
 41      * @param {object|string} carouselElementID The element ID (id name or id object) 
 42      * of the DIV that will become a carousel
 43      * @param {object} carouselCfg The configuration object literal containing the 
 44      * configuration that should be set for this module. See configuration documentation for more details.
 45 	 */
 46 	init: function(carouselElementID, carouselCfg) {
 47 
 48 		var oThis = this;
 49 		
 50 		/**
 51 		 * For deprecation.
 52 		 * getItem is the replacement for getCarouselItem
 53 		 */
 54 		this.getCarouselItem = this.getItem;
 55 		
 56 		// CSS style classes
 57 		var carouselListClass = "carousel-list";
 58 		var carouselClipRegionClass = "carousel-clip-region";
 59 		var carouselNextClass = "carousel-next";
 60 		var carouselPrevClass = "carousel-prev";
 61 
 62  		this._carouselElemID = carouselElementID;
 63  		this.carouselElem = YAHOO.util.Dom.get(carouselElementID);
 64 
 65  		this._prevEnabled = true;
 66  		this._nextEnabled = true;
 67  		
 68  		// Create the config object
 69  		this.cfg = new YAHOO.util.Config(this);
 70 
 71 		/**
 72 		 * scrollBeforeAmount property. 
 73 		 * Normally, set to 0, this is how much you are allowed to
 74 		 * scroll below the first item. Setting it to 2 allows you
 75 		 * to scroll to the -1 position. 
 76 		 * However, the load handlers will not be asked to load anything
 77 		 * below 1.
 78 		 *
 79 		 * A good example is the spotlight example which treats the middle item
 80 		 * as the "selected" item. It sets scrollBeforeAmount to 2 and 
 81 		 * scrollAfterAmount to 2.
 82 		 *
 83 		 * The actual items loaded would be from 1 to 15 (size=15),
 84 		 * but scrolling range would be -1 to 17.
 85 		 */
 86 		this.cfg.addProperty("scrollBeforeAmount", { 
 87 			value:0, 
 88 			handler: function(type, args, carouselElem) {
 89 			},
 90 			validator: oThis.cfg.checkNumber
 91 		} );		
 92 
 93 		/**
 94 		 * scrollAfterAmount property. 
 95 		 * Normally, set to 0, this is how much you are allowed to
 96 		 * scroll past the size. Setting it to 2 allows you
 97 		 * to scroll to the size+scrollAfterAmount position. 
 98 		 * However, the load handlers will not be asked to load anything
 99 		 * beyond size.
100 		 *
101 		 * A good example is the spotlight example which treats the middle item
102 		 * as the "selected" item. It sets scrollBeforeAmount to 2 and 
103 		 * scrollAfterAmount to 2.
104 		 *
105 		 * The actual items loaded would be from 1 to 15 (size=15),
106 		 * but scrolling range would be -1 to 17.
107 		 */
108 		this.cfg.addProperty("scrollAfterAmount", { 
109 			value:0, 
110 			handler: function(type, args, carouselElem) {
111 			},
112 			validator: oThis.cfg.checkNumber
113 		} );		
114 
115 		/**
116 		 * loadOnStart property. 
117 		 * If true, will call loadInitHandler on startup.
118 		 * If false, will not. Useful for delaying the initialization
119 		 * of the carousel for a later time after creation.
120 		 */
121 		this.cfg.addProperty("loadOnStart", { 
122 			value:true, 
123 			handler: function(type, args, carouselElem) {
124 				// no action, only affects startup
125 			},
126 			validator: oThis.cfg.checkBoolean
127 		} );		
128 
129 		/**
130 		 * orientation property. 
131 		 * Either "horizontal" or "vertical". Changes carousel from a 
132 		 * left/right style carousel to a up/down style carousel.
133 		 */
134 		this.cfg.addProperty("orientation", { 
135 			value:"horizontal", 
136 			handler: function(type, args, carouselElem) {
137 				oThis.reload();
138 			},
139 			validator: function(orientation) {
140 			    if(typeof orientation == "string") {
141 			        return ("horizontal,vertical".indexOf(orientation.toLowerCase()) != -1);
142 			    } else {
143 					return false;
144 				}
145 			}
146 		} );		
147 
148 		/**
149 		 * size property. 
150 		 * The upper bound for scrolling in the 'next' set of content. 
151 		 * Set to a large value by default (this means unlimited scrolling.) 
152 		 */
153 		this.cfg.addProperty("size", { 
154 			value:this.UNBOUNDED_SIZE,
155 			handler: function(type, args, carouselElem) {
156 				oThis.reload();
157 			},
158 			validator: oThis.cfg.checkNumber
159 		} );
160 
161 		/**
162 		 * numVisible property. 
163 		 * The number of items that will be visible.
164 		 */
165 		this.cfg.addProperty("numVisible", { 
166 			value:3,
167 			handler: function(type, args, carouselElem) {
168 				oThis.reload();
169 			},
170 			validator: oThis.cfg.checkNumber
171 		} );
172 
173 		/**
174 		 * firstVisible property. 
175 		 * Sets which item should be the first visible item in the carousel. Use to set which item will
176 		 * display as the first element when the carousel is first displayed. After the carousel is created,
177 		 * you can manipulate which item is the first visible by using the moveTo() or scrollTo() convenience
178 		 * methods. Can be < 1 or greater than size if the scrollBeforeAmount or scrollAmountAfter has been set
179 		 * to non-zero values.
180 		 */
181 		this.cfg.addProperty("firstVisible", { 
182 			value:1,
183 			handler: function(type, args, carouselElem) {
184 				oThis.moveTo(args[0]);
185 			},
186 			validator: oThis.cfg.checkNumber
187 		} );
188 
189 		/**
190 		 * scrollInc property. 
191 		 * The number of items to scroll by. Think of this as the page increment.
192 		 */
193 		this.cfg.addProperty("scrollInc", { 
194 			value:3,
195 			handler: function(type, args, carouselElem) {
196 			},
197 			validator: oThis.cfg.checkNumber
198 		} );
199 		
200 		/**
201 		 * animationSpeed property. 
202 		 * The time (in seconds) it takes to complete the scroll animation. 
203 		 * If set to 0, animated transitions are turned off and the new page of content is 
204 		 * moved immdediately into place.
205 		 */
206 		this.cfg.addProperty("animationSpeed", { 
207 			value:0.25,
208 			handler: function(type, args, carouselElem) {
209 				oThis.animationSpeed = args[0];
210 			},
211 			validator: oThis.cfg.checkNumber
212 		} );
213 
214 		/**
215 		 * animationMethod property. 
216 		 * The <a href="http://developer.yahoo.com/yui/docs/animation/YAHOO.util.Easing.html">YAHOO.util.Easing</a> 
217 		 * method.
218 		 */
219 		this.cfg.addProperty("animationMethod", { 
220 			value:  YAHOO.util.Easing.easeOut,
221 			handler: function(type, args, carouselElem) {
222 			}
223 		} );
224 		
225 		/**
226 		 * animationCompleteHandler property. 
227 		 * JavaScript function that is called when the Carousel finishes animation 
228 		 * after a next or previous nagivation. 
229 		 * Only invoked if animationSpeed > 0. 
230 		 * Two parameters are passed: type (set to 'onAnimationComplete') and 
231 		 * args array (args[0] = direction [either: 'next' or 'previous']).
232 		 */
233 		this.cfg.addProperty("animationCompleteHandler", { 
234 			value:null,
235 			handler: function(type, args, carouselElem) {
236 				if(oThis._animationCompleteEvt) {
237 					oThis._animationCompleteEvt.unsubscribe(oThis._currAnimationCompleteHandler, oThis);
238 				}
239 				oThis._currAnimationCompleteHandler = args[0];
240 				if(oThis._currAnimationCompleteHandler) {
241 					if(!oThis._animationCompleteEvt) {
242 						oThis._animationCompleteEvt = new YAHOO.util.CustomEvent("onAnimationComplete", oThis);
243 					}
244 					oThis._animationCompleteEvt.subscribe(oThis._currAnimationCompleteHandler, oThis);
245 				}
246 			}
247 		} );
248 		
249 		/**
250 		 * autoPlay property. 
251 		 * Specifies how many milliseconds to periodically auto scroll the content. 
252 		 * If set to 0 (default) then autoPlay is turned off. 
253 		 * If the user interacts by clicking left or right navigation, autoPlay is turned off. 
254 		 * You can restart autoPlay by calling the <em>startAutoPlay()</em>. 
255 		 * If you externally control navigation (with your own event handlers) 
256 		 * then you may want to turn off the autoPlay by calling<em>stopAutoPlay()</em>
257 		 */
258 		this.cfg.addProperty("autoPlay", { 
259 			value:0,
260 			handler: function(type, args, carouselElem) {
261 				var autoPlay = args[0];
262 				if(autoPlay > 0)
263 					oThis.startAutoPlay();
264 				else
265 					oThis.stopAutoPlay();
266 			}
267 		} );
268 		
269 		/**
270 		 * wrap property. 
271 		 * Specifies whether to wrap when at the end of scrolled content. When the end is reached,
272 		 * the carousel will scroll backwards to the item 1 (the animationSpeed parameter is used to 
273 		 * determine how quickly it should animate back to the start.)
274 		 * Ignored if the <em>size</em> attribute is not explicitly set 
275 		 * (i.e., value equals YAHOO.extension.Carousel.UNBOUNDED_SIZE)
276 		 */
277 		this.cfg.addProperty("wrap", { 
278 			value:false,
279 			handler: function(type, args, carouselElem) {
280 			},
281 			validator: oThis.cfg.checkBoolean
282 		} );
283 		
284 		/**
285 		 * navMargin property. 
286 		 * The margin space for the navigation controls. This is only useful for horizontal carousels 
287 		 * in which you have embedded navigation controls. 
288 		 * The <em>navMargin</em> allocates space between the left and right margins 
289 		 * (each navMargin wide) giving space for the navigation controls.
290 		 */
291 		this.cfg.addProperty("navMargin", { 
292 			value:0,
293 			handler: function(type, args, carouselElem) {
294 				oThis.calculateSize();		
295 			},
296 			validator: oThis.cfg.checkNumber
297 		} );
298 		
299 		/**
300 		 * revealAmount property. 
301 		 * The amount to reveal of what comes before and what comes after the firstVisible and
302 		 * the lastVisible items. Setting this will provide a slight preview that something 
303 		 * exists before and after, providing an additional hint for the user.
304 		 * The <em>revealAmount</em> will reveal the specified number of pixels for any item
305 		 * before the firstVisible and an item after the lastVisible. Additionall, the
306 		 * loadNextHandler and loadPrevHandler methods will be passed a start or end that guarantees
307 		 * the revealed item will be loaded (if set to non-zero).
308 		 */
309 		this.cfg.addProperty("revealAmount", { 
310 			value:0,
311 			handler: function(type, args, carouselElem) {
312 				oThis.reload();
313 			},
314 			validator: oThis.cfg.checkNumber
315 		} );
316 		
317 		// For backward compatibility. Deprecated.
318 		this.cfg.addProperty("prevElementID", { 
319 			value: null,
320 			handler: function(type, args, carouselElem) {
321 				if(oThis._carouselPrev) {
322 					YAHOO.util.Event.removeListener(oThis._carouselPrev, "click", oThis._scrollPrev);
323 				} 
324 				oThis._prevElementID = args[0];
325 				if(oThis._prevElementID == null) {
326 					oThis._carouselPrev = YAHOO.util.Dom.getElementsByClassName(carouselPrevClass, 
327 														"div", oThis.carouselElem)[0];
328 				} else {
329 					oThis._carouselPrev = YAHOO.util.Dom.get(oThis._prevElementID);
330 				}
331 				YAHOO.util.Event.addListener(oThis._carouselPrev, "click", oThis._scrollPrev, oThis);
332 			}
333 		});
334 		
335 		/**
336 		 * prevElement property. 
337 		 * An element or elements that will provide the previous navigation control.
338 		 * prevElement may be a single element or an array of elements. The values may be strings denoting
339 		 * the ID of the element or the object itself.
340 		 * If supplied, then events are wired to this control to fire scroll events to move the carousel to
341 		 * the previous content. 
342 		 * You may want to provide your own interaction for controlling the carousel. If
343 		 * so leave this unset and provide your own event handling mechanism.
344 		 */
345 		this.cfg.addProperty("prevElement", { 
346 			value:null,
347 			handler: function(type, args, carouselElem) {
348 				if(oThis._carouselPrev) {
349 					YAHOO.util.Event.removeListener(oThis._carouselPrev, "click", oThis._scrollPrev);
350 				} 
351 				oThis._prevElementID = args[0];
352 				if(oThis._prevElementID == null) {
353 					oThis._carouselPrev = YAHOO.util.Dom.getElementsByClassName(carouselPrevClass, 
354 														"div", oThis.carouselElem)[0];
355 				} else {
356 					oThis._carouselPrev = YAHOO.util.Dom.get(oThis._prevElementID);
357 				}
358 				YAHOO.util.Event.addListener(oThis._carouselPrev, "click", oThis._scrollPrev, oThis);
359 			}
360 		} );
361 		
362 		// For backward compatibility. Deprecated.
363 		this.cfg.addProperty("nextElementID", { 
364 			value: null,
365 			handler: function(type, args, carouselElem) {
366 				if(oThis._carouselNext) {
367 					YAHOO.util.Event.removeListener(oThis._carouselNext, "click", oThis._scrollNext);
368 				} 
369 				oThis._nextElementID = args[0];
370 				if(oThis._nextElementID == null) {
371 					oThis._carouselNext = YAHOO.util.Dom.getElementsByClassName(carouselNextClass, 
372 														"div", oThis.carouselElem);
373 				} else {
374 					oThis._carouselNext = YAHOO.util.Dom.get(oThis._nextElementID);
375 				}
376 				if(oThis._carouselNext) {
377 					YAHOO.util.Event.addListener(oThis._carouselNext, "click", oThis._scrollNext, oThis);
378 				} 
379 			}
380 		});
381 		
382 		/**
383 		 * nextElement property. 
384 		 * An element or elements that will provide the next navigation control.
385 		 * nextElement may be a single element or an array of elements. The values may be strings denoting
386 		 * the ID of the element or the object itself.
387 		 * If supplied, then events are wired to this control to fire scroll events to move the carousel to
388 		 * the next content. 
389 		 * You may want to provide your own interaction for controlling the carousel. If
390 		 * so leave this unset and provide your own event handling mechanism.
391 		 */
392 		this.cfg.addProperty("nextElement", { 
393 			value:null,
394 			handler: function(type, args, carouselElem) {
395 				if(oThis._carouselNext) {
396 					YAHOO.util.Event.removeListener(oThis._carouselNext, "click", oThis._scrollNext);
397 				} 
398 				oThis._nextElementID = args[0];
399 				if(oThis._nextElementID == null) {
400 					oThis._carouselNext = YAHOO.util.Dom.getElementsByClassName(carouselNextClass, 
401 														"div", oThis.carouselElem);
402 				} else {
403 					oThis._carouselNext = YAHOO.util.Dom.get(oThis._nextElementID);
404 				}
405 				if(oThis._carouselNext) {
406 					YAHOO.util.Event.addListener(oThis._carouselNext, "click", oThis._scrollNext, oThis);
407 				} 
408 			}
409 		} );
410 		
411 		/**
412 		 * loadInitHandler property. 
413 		 * JavaScript function that is called when the Carousel needs to load 
414 		 * the initial set of visible items. Two parameters are passed: 
415 		 * type (set to 'onLoadInit') and an argument array (args[0] = start index, args[1] = last index).
416 		 */
417 		this.cfg.addProperty("loadInitHandler", { 
418 			value:null,
419 			handler: function(type, args, carouselElem) {
420 				if(oThis._loadInitHandlerEvt) {
421 					oThis._loadInitHandlerEvt.unsubscribe(oThis._currLoadInitHandler, oThis);
422 				}
423 				oThis._currLoadInitHandler = args[0];
424 				if(oThis._currLoadInitHandler) {
425 					if(!oThis._loadInitHandlerEvt) {
426 						oThis._loadInitHandlerEvt = new YAHOO.util.CustomEvent("onLoadInit", oThis);
427 					}
428 					oThis._loadInitHandlerEvt.subscribe(oThis._currLoadInitHandler, oThis);
429 				}
430 			}
431 		} );
432 		
433 		/**
434 		 * loadNextHandler property. 
435 		 * JavaScript function that is called when the Carousel needs to load 
436 		 * the next set of items (in response to the user navigating to the next set.) 
437 		 * Two parameters are passed: type (set to 'onLoadNext') and 
438 		 * args array (args[0] = start index, args[1] = last index).
439 		 */
440 		this.cfg.addProperty("loadNextHandler", { 
441 			value:null,
442 			handler: function(type, args, carouselElem) {
443 				if(oThis._loadNextHandlerEvt) {
444 					oThis._loadNextHandlerEvt.unsubscribe(oThis._currLoadNextHandler, oThis);
445 				}
446 				oThis._currLoadNextHandler = args[0];
447 				if(oThis._currLoadNextHandler) {
448 					if(!oThis._loadNextHandlerEvt) {
449 						oThis._loadNextHandlerEvt = new YAHOO.util.CustomEvent("onLoadNext", oThis);
450 					}
451 					oThis._loadNextHandlerEvt.subscribe(oThis._currLoadNextHandler, oThis);
452 				}
453 			}
454 		} );
455 				
456 		/**
457 		 * loadPrevHandler property. 
458 		 * JavaScript function that is called when the Carousel needs to load 
459 		 * the previous set of items (in response to the user navigating to the previous set.) 
460 		 * Two parameters are passed: type (set to 'onLoadPrev') and args array 
461 		 * (args[0] = start index, args[1] = last index).
462 		 */
463 		this.cfg.addProperty("loadPrevHandler", { 
464 			value:null,
465 			handler: function(type, args, carouselElem) {
466 				if(oThis._loadPrevHandlerEvt) {
467 					oThis._loadPrevHandlerEvt.unsubscribe(oThis._currLoadPrevHandler, oThis);
468 				}
469 				oThis._currLoadPrevHandler = args[0];
470 				if(oThis._currLoadPrevHandler) {
471 					if(!oThis._loadPrevHandlerEvt) {
472 						oThis._loadPrevHandlerEvt = new YAHOO.util.CustomEvent("onLoadPrev", oThis);
473 					}
474 					oThis._loadPrevHandlerEvt.subscribe(oThis._currLoadPrevHandler, oThis);
475 				}
476 			}
477 		} );
478 		
479 		/**
480 		 * prevButtonStateHandler property. 
481 		 * JavaScript function that is called when the enabled state of the 
482 		 * 'previous' control is changing. The responsibility of 
483 		 * this method is to enable or disable the 'previous' control. 
484 		 * Two parameters are passed to this method: <em>type</em> 
485 		 * (which is set to "onPrevButtonStateChange") and <em>args</em>, 
486 		 * an array that contains two values. 
487 		 * The parameter args[0] is a flag denoting whether the 'previous' control 
488 		 * is being enabled or disabled. The parameter args[1] is the element object 
489 		 * derived from the <em>prevElement</em> parameter.
490 		 * If you do not supply a prevElement then you will need to track
491 		 * the elements that you would want to enable/disable while handling the state change.
492 		 */
493 		this.cfg.addProperty("prevButtonStateHandler", { 
494 			value:null,
495 			handler: function(type, args, carouselElem) {
496 				if(oThis._currPrevButtonStateHandler) {
497 					oThis._prevButtonStateHandlerEvt.unsubscribe(oThis._currPrevButtonStateHandler, oThis);
498 				}
499 
500 				oThis._currPrevButtonStateHandler = args[0];
501 				
502 				if(oThis._currPrevButtonStateHandler) {
503 					if(!oThis._prevButtonStateHandlerEvt) {
504 						oThis._prevButtonStateHandlerEvt = new YAHOO.util.CustomEvent("onPrevButtonStateChange", oThis);
505 					}
506 					oThis._prevButtonStateHandlerEvt.subscribe(oThis._currPrevButtonStateHandler, oThis);
507 				}
508 			}
509 		} );
510 		
511 		/**
512 		 * nextButtonStateHandler property. 
513 		 * JavaScript function that is called when the enabled state of the 
514 		 * 'next' control is changing. The responsibility of 
515 		 * this method is to enable or disable the 'next' control. 
516 		 * Two parameters are passed to this method: <em>type</em> 
517 		 * (which is set to "onNextButtonStateChange") and <em>args</em>, 
518 		 * an array that contains two values. 
519 		 * The parameter args[0] is a flag denoting whether the 'next' control 
520 		 * is being enabled or disabled. The parameter args[1] is the element object 
521 		 * derived from the <em>nextElement</em> parameter.
522 		 * If you do not supply a nextElement then you will need to track
523 		 * the elements that you would want to enable/disable while handling the state change.
524 		 */
525 		this.cfg.addProperty("nextButtonStateHandler", { 
526 			value:null,
527 			handler: function(type, args, carouselElem) {
528 				if(oThis._currNextButtonStateHandler) {
529 					oThis._nextButtonStateHandlerEvt.unsubscribe(oThis._currNextButtonStateHandler, oThis);
530 				}
531 				oThis._currNextButtonStateHandler = args[0];
532 				
533 				if(oThis._currNextButtonStateHandler) {
534 					if(!oThis._nextButtonStateHandlerEvt) {
535 						oThis._nextButtonStateHandlerEvt = new YAHOO.util.CustomEvent("onNextButtonStateChange", oThis);
536 					}
537 					oThis._nextButtonStateHandlerEvt.subscribe(oThis._currNextButtonStateHandler, oThis);
538 				}
539 			}
540 		} );
541 		
542 		
543  		if(carouselCfg) {
544  			this.cfg.applyConfig(carouselCfg);
545  		}
546  		
547 		this._origFirstVisible = this.cfg.getProperty("firstVisible");
548 		
549 		// keep a copy of curr handler so it can be removed when a new handler is set
550 		this._currLoadInitHandler = this.cfg.getProperty("loadInitHandler");
551 		this._currLoadNextHandler = this.cfg.getProperty("loadNextHandler");
552 		this._currLoadPrevHandler = this.cfg.getProperty("loadPrevHandler");
553 		this._currPrevButtonStateHandler = this.cfg.getProperty("prevButtonStateHandler");
554 		this._currNextButtonStateHandler = this.cfg.getProperty("nextButtonStateHandler");
555 		this._currAnimationCompleteHandler = this.cfg.getProperty("animationCompleteHandler");
556 		
557 		this._nextElementID = this.cfg.getProperty("nextElementID");
558 		if(!this._nextElementID) 
559 			this._nextElementID = this.cfg.getProperty("nextElement");
560 		
561 		this._prevElementID = this.cfg.getProperty("prevElementID");
562 		if(!this._prevElementID) 
563 			this._prevElementID = this.cfg.getProperty("prevElement");
564 
565 		this._autoPlayTimer = null;
566 		this._priorLastVisible = this._priorFirstVisible = this.cfg.getProperty("firstVisible");
567 		this._lastPrebuiltIdx = 0;
568 // this._currSize = 0;
569 		 		
570  		// prefetch elements
571  		this.carouselList = YAHOO.util.Dom.getElementsByClassName(carouselListClass, 
572 												"ul", this.carouselElem)[0];
573 							
574 		if(this._nextElementID == null) {
575 			this._carouselNext = YAHOO.util.Dom.getElementsByClassName(carouselNextClass, 
576 												"div", this.carouselElem)[0];
577 		} else {
578 			this._carouselNext = YAHOO.util.Dom.get(this._nextElementID);
579 		}
580 
581 		if(this._prevElementID == null) {
582  			this._carouselPrev = YAHOO.util.Dom.getElementsByClassName(carouselPrevClass, 
583 												"div", this.carouselElem)[0];
584 		} else {
585 			this._carouselPrev = YAHOO.util.Dom.get(this._prevElementID);
586 		}
587 		
588 		this._clipReg = YAHOO.util.Dom.getElementsByClassName(carouselClipRegionClass, 
589 												"div", this.carouselElem)[0];
590 												
591 		// add a style class dynamically so that the correct styles get applied for a vertical carousel
592 		if(this.isVertical()) {
593 			YAHOO.util.Dom.addClass(this.carouselList, "carousel-vertical");
594 		}
595 		
596 		// initialize the animation objects for next/previous
597  		this._scrollNextAnim = new YAHOO.util.Motion(this.carouselList, this.scrollNextParams, 
598    								this.cfg.getProperty("animationSpeed"), this.cfg.getProperty("animationMethod"));
599  		this._scrollPrevAnim = new YAHOO.util.Motion(this.carouselList, this.scrollPrevParams, 
600    								this.cfg.getProperty("animationSpeed"), this.cfg.getProperty("animationMethod"));
601 		
602 		// If they supplied a nextElementID then wire an event listener for the click
603 		if(this._carouselNext) {
604 			YAHOO.util.Event.addListener(this._carouselNext, "click", this._scrollNext, this);
605 		} 
606 		
607 		// If they supplied a prevElementID then wire an event listener for the click
608 		if(this._carouselPrev) {
609 			YAHOO.util.Event.addListener(this._carouselPrev, "click", this._scrollPrev, this);
610 		}
611 				
612 		// Wire up the various event handlers that they might have supplied
613 		var loadInitHandler = this.cfg.getProperty("loadInitHandler");
614 		if(loadInitHandler) {
615 			this._loadInitHandlerEvt = new YAHOO.util.CustomEvent("onLoadInit", this);
616 			this._loadInitHandlerEvt.subscribe(loadInitHandler, this);
617 		}
618 		var loadNextHandler = this.cfg.getProperty("loadNextHandler");
619 		if(loadNextHandler) {
620 			this._loadNextHandlerEvt = new YAHOO.util.CustomEvent("onLoadNext", this);
621 			this._loadNextHandlerEvt.subscribe(loadNextHandler, this);
622 		}
623 		var loadPrevHandler = this.cfg.getProperty("loadPrevHandler");
624 		if(loadPrevHandler) {
625 			this._loadPrevHandlerEvt = new YAHOO.util.CustomEvent("onLoadPrev", this);
626 			this._loadPrevHandlerEvt.subscribe(loadPrevHandler, this);
627 		}
628 		var animationCompleteHandler = this.cfg.getProperty("animationCompleteHandler");
629 		if(animationCompleteHandler) {
630 			this._animationCompleteEvt = new YAHOO.util.CustomEvent("onAnimationComplete", this);
631 			this._animationCompleteEvt.subscribe(animationCompleteHandler, this);
632 		}
633 		var prevButtonStateHandler = this.cfg.getProperty("prevButtonStateHandler");
634 		if(prevButtonStateHandler) {
635 			this._prevButtonStateHandlerEvt = new YAHOO.util.CustomEvent("onPrevButtonStateChange", 
636 							this);
637 			this._prevButtonStateHandlerEvt.subscribe(prevButtonStateHandler, this);
638 		}
639 		var nextButtonStateHandler = this.cfg.getProperty("nextButtonStateHandler");
640 		if(nextButtonStateHandler) {
641 			this._nextButtonStateHandlerEvt = new YAHOO.util.CustomEvent("onNextButtonStateChange", this);
642 			this._nextButtonStateHandlerEvt.subscribe(nextButtonStateHandler, this);
643 		}
644 		
645 		// Since loading may take some time, wire up a listener to fire when at least the first
646 		// element actually gets loaded
647 		var visibleExtent = this._calculateVisibleExtent();
648   		YAHOO.util.Event.onAvailable(this._carouselElemID + "-item-"+
649 					visibleExtent.start,  this._calculateSize, this);
650   		
651   		// Call the initial loading sequence
652 		if(this.cfg.getProperty("loadOnStart"))
653 			this._loadInitial();	
654 
655 	},
656 	
657 	// /////////////////// Public API //////////////////////////////////////////
658 
659 	/**
660 	 * Clears all items from the list and resets to the carousel to its original initial state.
661 	 */
662 	clear: function() {
663 		// remove all items from the carousel for dynamic content
664 		var loadInitHandler = this.cfg.getProperty("loadInitHandler");
665 		if(loadInitHandler) {
666 			this._removeChildrenFromNode(this.carouselList);
667 			this._lastPrebuiltIdx = 0;
668 		}
669 		// turn off autoplay
670 		this.stopAutoPlay(); // should we only turn this off for dynamic during reload?
671 		
672 		this._priorLastVisible = this._priorFirstVisible = this._origFirstVisible;
673 		
674 		// is this redundant since moveTo will set this?	
675 		this.cfg.setProperty("firstVisible", this._origFirstVisible, true);		
676 		this.moveTo(this._origFirstVisible);
677 	},
678 	
679 	/**
680 	 * Clears all items from the list and calls the loadInitHandler to load new items into the list. 
681 	 * The carousel size is reset to the original size set during creation.
682 	 * @param {number}	numVisible	Optional parameter: numVisible. 
683 	 * If set, the carousel will resize on the reload to show numVisible items.
684 	 */
685 	reload: function(numVisible) {
686 		// this should be deprecated, not needed since can be set via property change
687 	    if(this._isValidObj(numVisible)) {
688 			this.cfg.setProperty("numVisible", numVisible);
689 	    }
690 		this.clear();
691 		
692 		// clear resets back to start
693 		var visibleExtent = this._calculateVisibleExtent();
694 		YAHOO.util.Event.onAvailable(this._carouselElemID+"-item-"+visibleExtent.start,
695 		 								this._calculateSize, this);  		
696 		this._loadInitial();
697 		
698 	},
699 
700 	load: function() {
701 		var visibleExtent = this._calculateVisibleExtent();
702 		
703 		YAHOO.util.Event.onAvailable(this._carouselElemID + "-item-"+visibleExtent.start, 
704 						this._calculateSize, this);  		
705 		this._loadInitial();
706 	},
707 		
708 	/**
709 	 * With patch from Dan Hobbs for handling unordered loading.
710 	 * @param {number}	idx	which item in the list to potentially create. 
711 	 * If item already exists it will not create a new item.
712 	 * @param {string}	innerHTML	The innerHTML string to use to create the contents of an LI element.
713 	 * @param {string}	itemClass	A class optionally supplied to add to the LI item created
714 	 */
715 	addItem: function(idx, innerHTMLOrElem, itemClass) {
716 		
717 		if(idx > this.cfg.getProperty("size")) {
718 			return null;
719 		}
720 		
721         var liElem = this.getItem(idx);
722 
723 		// Need to create the li
724 		if(!this._isValidObj(liElem)) {
725 			liElem = this._createItem(idx, innerHTMLOrElem);
726 			this.carouselList.appendChild(liElem);
727 			
728 		} else if(this._isValidObj(liElem.placeholder)) {		
729 	    	var newLiElem = this._createItem(idx, innerHTMLOrElem);
730 			this.carouselList.replaceChild(newLiElem, liElem);
731 			liElem = newLiElem;
732 		}
733 		
734 		// if they supplied an item class add it to the element
735 		if(this._isValidObj(itemClass)){
736 			YAHOO.util.Dom.addClass(liElem, itemClass);
737 		}
738 		
739 		/**
740 		 * Not real comfortable with this line of code. It exists for vertical
741 		 * carousels for IE6. For some reason LI elements are not displaying
742 		 * unless you after the fact set the display to block. (Even though
743 	     * the CSS sets vertical LIs to display:block)
744 	     */
745 		if(this.isVertical())
746 			setTimeout( function() { liElem.style.display="block"; }, 1 );		
747 				
748 		return liElem;
749 
750 	},
751 
752 	/**
753 	 * Inserts a new LI item before the index specified. Uses the innerHTML to create the contents of the new LI item
754 	 * @param {number}	refIdx	which item in the list to insert this item before. 
755 	 * @param {string}	innerHTML	The innerHTML string to use to create the contents of an LI element.
756 	 */
757 	insertBefore: function(refIdx, innerHTML) {
758 		// don't allow insertion beyond the size
759 		if(refIdx >= this.cfg.getProperty("size")) {
760 			return null;
761 		}
762 		
763 		if(refIdx < 1) {
764 			refIdx = 1;
765 		}
766 		
767 		var insertionIdx = refIdx - 1;
768 		
769 		if(insertionIdx > this._lastPrebuiltIdx) {
770 			this._prebuildItems(this._lastPrebuiltIdx, refIdx); // is this right?
771 		}
772 		
773 		var liElem = this._insertBeforeItem(refIdx, innerHTML);
774 		
775 		this._enableDisableControls();
776 		
777 		return liElem;
778 	},
779 	
780 	/**
781 	 * Inserts a new LI item after the index specified. Uses the innerHTML to create the contents of the new LI item
782 	 * @param {number}	refIdx	which item in the list to insert this item after. 
783 	 * @param {string}	innerHTML	The innerHTML string to use to create the contents of an LI element.
784 	 */
785 	insertAfter: function(refIdx, innerHTML) {
786 	
787 		if(refIdx > this.cfg.getProperty("size")) {
788 			refIdx = this.cfg.getProperty("size");
789 		}
790 		
791 		var insertionIdx = refIdx + 1;			
792 		
793 		// if we are inserting this item past where we have prebuilt items, then
794 		// prebuild up to this point.
795 		if(insertionIdx > this._lastPrebuiltIdx) {
796 			this._prebuildItems(this._lastPrebuiltIdx, insertionIdx+1);
797 		}
798 
799 		var liElem = this._insertAfterItem(refIdx, innerHTML);		
800 
801 		if(insertionIdx > this.cfg.getProperty("size")) {
802 			this.cfg.setProperty("size", insertionIdx, true);
803 		}
804 		
805 		this._enableDisableControls();
806 
807 		return liElem;
808 	},	
809 
810 	/**
811 	 * Simulates a next button event. Causes the carousel to scroll the next set of content into view.
812 	 */
813 	scrollNext: function() {
814 		this._scrollNext(null, this);
815 		
816 		// we know the timer has expired.
817 		//if(this._autoPlayTimer) clearTimeout(this._autoPlayTimer);
818 		this._autoPlayTimer = null;
819 		if(this.cfg.getProperty("autoPlay") !== 0) {
820 			this._autoPlayTimer = this.startAutoPlay();
821 		}
822 	},
823 	
824 	/**
825 	 * Simulates a prev button event. Causes the carousel to scroll the previous set of content into view.
826 	 */
827 	scrollPrev: function() {
828 		this._scrollPrev(null, this);
829 	},
830 	
831 	/**
832 	 * Scrolls the content to place itemNum as the start item in the view 
833 	 * (if size is specified, the last element will not scroll past the end.). 
834 	 * Uses current animation speed & method.
835 	 * @param {number}	newStart	The item to scroll to. 
836 	 */
837 	scrollTo: function(newStart) {
838 		this._position(newStart, true);
839 	},
840 
841 	/**
842 	 * Moves the content to place itemNum as the start item in the view 
843 	 * (if size is specified, the last element will not scroll past the end.) 
844 	 * Ignores animation speed & method; moves directly to the item. 
845 	 * Note that you can also set the <em>firstVisible</em> property upon initialization 
846 	 * to get the carousel to start at a position different than 1.	
847 	 * @param {number}	newStart	The item to move directly to. 
848 	 */
849 	moveTo: function(newStart) {
850 		this._position(newStart, false);
851 	},
852 
853 	/**
854 	 * Starts up autoplay. If autoPlay has been stopped (by calling stopAutoPlay or by user interaction), 
855 	 * you can start it back up by using this method.
856 	 * @param {number}	interval	optional parameter that sets the interval 
857 	 * for auto play the next time that autoplay fires. 
858 	 */
859 	startAutoPlay: function(interval) {
860 		// if interval is passed as arg, then set autoPlay to this interval.
861 		if(this._isValidObj(interval)) {
862 			this.cfg.setProperty("autoPlay", interval, true);
863 		}
864 		
865 		// if we already are playing, then do nothing.
866 		if(this._autoPlayTimer !== null) {
867 			return this._autoPlayTimer;
868 		}
869 				
870 		var oThis = this;  
871 		var autoScroll = function() { oThis.scrollNext(); };
872 		this._autoPlayTimer = setTimeout( autoScroll, this.cfg.getProperty("autoPlay") );
873 		
874 		return this._autoPlayTimer;
875 	},
876 
877 	/**
878 	 * Stops autoplay. Useful for when you want to control what events will stop the autoplay feature. 
879 	 * Call <em>startAutoPlay()</em> to restart autoplay.
880 	 */
881 	stopAutoPlay: function() {
882 		if (this._autoPlayTimer !== null) {
883 			clearTimeout(this._autoPlayTimer);
884 			this._autoPlayTimer = null;
885 		}
886 	},
887 	
888 	/**
889 	 * Returns whether the carousel's orientation is set to vertical.
890 	 */
891 	isVertical: function() {
892 		return (this.cfg.getProperty("orientation") != "horizontal");
893 	},
894 	
895 	
896 	/**
897 	 * Check to see if an element (by index) has been loaded or not. If the item is simply pre-built, but not
898 	 * loaded this will return false. If the item has not been pre-built it will also return false.
899 	 * @param {number}	idx	Index of the element to check load status for. 
900 	 */
901 	isItemLoaded: function(idx) {
902 		var liElem = this.getItem(idx);
903 		
904 		// if item exists and is not a placeholder, then it is already loaded.
905 		if(this._isValidObj(liElem) && !this._isValidObj(liElem.placeholder)) {
906 			return true;
907 		}
908 		
909 		return false;
910 	},
911 	
912 	/**
913 	 * Lookup the element object for a carousel list item by index.
914 	 * @param {number}	idx	Index of the element to lookup. 
915 	 */
916 	getItem: function(idx) {
917 		var elemName = this._carouselElemID + "-item-" + idx;
918  		var liElem = YAHOO.util.Dom.get(elemName);
919 		return liElem;	
920 	},
921 	
922 	show: function() {
923 		YAHOO.util.Dom.setStyle(this.carouselElem, "display", "block");
924 		this.calculateSize();
925 	},
926 	
927 	hide: function() {
928 		YAHOO.util.Dom.setStyle(this.carouselElem, "display", "none");
929 	},
930 
931 	calculateSize: function() {
932  		var ulKids = this.carouselList.childNodes;
933  		var li = null;
934 		for(var i=0; i<ulKids.length; i++) {
935 		
936 			li = ulKids[i];
937 			if(li.tagName == "LI" || li.tagName == "li") {
938 				break;
939 			}
940 		}
941 
942 		var navMargin = this.cfg.getProperty("navMargin");
943 		var numVisible = this.cfg.getProperty("numVisible");
944 		var firstVisible = this.cfg.getProperty("firstVisible");
945 		var pl = this._getStyleVal(li, "paddingLeft");
946 		var pr = this._getStyleVal(li, "paddingRight");
947 		var ml = this._getStyleVal(li, "marginLeft");
948 		var mr = this._getStyleVal(li, "marginRight");
949 		var pt = this._getStyleVal(li, "paddingTop");
950 		var pb = this._getStyleVal(li, "paddingBottom");
951 		var mt = this._getStyleVal(li, "marginTop");
952 		var mb = this._getStyleVal(li, "marginBottom");
953 
954 		YAHOO.util.Dom.removeClass(this.carouselList, "carousel-vertical");
955 		YAHOO.util.Dom.removeClass(this.carouselList, "carousel-horizontal");
956 		if(this.isVertical()) {
957 			var liPaddingMarginWidth = pl + pr + ml + mr;
958 			YAHOO.util.Dom.addClass(this.carouselList, "carousel-vertical");
959 			var liPaddingMarginHeight = pt + pb + mt + mb;
960 			
961 			var upt = this._getStyleVal(this.carouselList, "paddingTop");
962 			var upb = this._getStyleVal(this.carouselList, "paddingBottom");
963 			var umt = this._getStyleVal(this.carouselList, "marginTop")
964 			var umb = this._getStyleVal(this.carouselList, "marginBottom")
965 			var ulPaddingHeight = upt + upb + umt + umb;
966 
967 			// try to reveal the amount taking into consideration the margin & padding.
968 			// This guarantees that this.revealAmount of pixels will be shown on both sides
969 			var revealAmt = (this._isExtraRevealed()) ?
970 			 			(this.cfg.getProperty("revealAmount")+(liPaddingMarginHeight)/2) : 0;
971 
972 			// get the height from the height computed style not the offset height
973 			// The reason is that on IE the offsetHeight when some part of the margin is
974 			// explicitly set to 'auto' can cause accessing that value to crash AND
975 			// on FF, in certain cases the actual value used for the LI's height is fractional
976 			// For example, while li.offsetHeight might return 93, YAHOO.util.Dom.getStyle(li, "height") 
977 			// would return "93.2px". This fractional value will affect the scrolling, so it must be
978 			// factored in for FF.
979 			// The caveat is that for IE, you will need to set the LI's height explicitly
980 			// REPLACED: this.scrollAmountPerInc = (li.offsetHeight + liPaddingMarginHeight);
981 			// WITH:
982 			var liHeight = this._getStyleVal(li, "height", true);
983 			this.scrollAmountPerInc = (liHeight + liPaddingMarginHeight);
984 			
985 			var liWidth = this._getStyleVal(li, "width");
986 			this.carouselElem.style.width = (liWidth + liPaddingMarginWidth) + "px";			
987 			this._clipReg.style.height = 
988 					(this.scrollAmountPerInc * numVisible + revealAmt*2 + 
989 					ulPaddingHeight) + "px";
990 //alert(this._clipReg.style.height);
991 			this.carouselElem.style.height = 
992 				(this.scrollAmountPerInc * numVisible + revealAmt*2 + navMargin*2 +
993 					ulPaddingHeight) + "px";
994 
995 			// possible that the umt+upt is needed... need to test this.
996 			var revealTop = (this._isExtraRevealed()) ? 
997 					(revealAmt - (Math.abs(mt-mb)+Math.abs(pt-pb))/2
998 					) : 
999 					0;
1000 			YAHOO.util.Dom.setStyle(this.carouselList, "position", "relative");
1001 			YAHOO.util.Dom.setStyle(this.carouselList, "top", "" + revealTop + "px");
1002 
1003 			// if we set the initial start > 1 then this will adjust the scrolled location
1004 			var currY = YAHOO.util.Dom.getY(this.carouselList);	
1005 			YAHOO.util.Dom.setY(this.carouselList, currY - this.scrollAmountPerInc*(firstVisible-1));
1006 
1007 		// --- HORIZONTAL
1008 		} else {
1009 			YAHOO.util.Dom.addClass(this.carouselList, "carousel-horizontal");
1010 
1011 			var upl = this._getStyleVal(this.carouselList, "paddingLeft");
1012 			var upr = this._getStyleVal(this.carouselList, "paddingRight");
1013 			var uml = this._getStyleVal(this.carouselList, "marginLeft")
1014 			var umr = this._getStyleVal(this.carouselList, "marginRight")
1015 			var ulPaddingWidth = upl + upr + uml + umr;
1016 
1017 			var liMarginWidth = ml + mr;
1018 			var liPaddingMarginWidth = liMarginWidth + pr + pl;
1019 			
1020 			// try to reveal the amount taking into consideration the margin & padding.
1021 			// This guarantees that this.revealAmount of pixels will be shown on both sides
1022 			var revealAmt = (this._isExtraRevealed()) ?
1023 			 					(this.cfg.getProperty("revealAmount")+(liPaddingMarginWidth)/2) : 0;
1024 			
1025 			var liWidth = li.offsetWidth; 
1026 			this.scrollAmountPerInc = liWidth + liMarginWidth;
1027 			
1028 			this._clipReg.style.width = 
1029 					(this.scrollAmountPerInc*numVisible + revealAmt*2) + "px";
1030 			this.carouselElem.style.width =
1031 			 		(this.scrollAmountPerInc*numVisible + navMargin*2 + revealAmt*2 + 
1032 					ulPaddingWidth) + "px";
1033 			
1034 			var revealLeft = (this._isExtraRevealed()) ? 
1035 					(revealAmt - (Math.abs(mr-ml)+Math.abs(pr-pl))/2 - (uml+upl)
1036 					) : 
1037 					0;
1038 			YAHOO.util.Dom.setStyle(this.carouselList, "position", "relative");
1039 			YAHOO.util.Dom.setStyle(this.carouselList, "left", "" + revealLeft + "px");
1040 
1041 			// if we set the initial start > 1 then this will adjust the scrolled location
1042 			var currX = YAHOO.util.Dom.getX(this.carouselList);
1043 			YAHOO.util.Dom.setX(this.carouselList, currX - this.scrollAmountPerInc*(firstVisible-1));
1044 		}
1045 	},
1046 	
1047 	// Hides the cfg object
1048 	setProperty: function(property, value, silent) {
1049 		this.cfg.setProperty(property, value, silent);
1050 	},
1051 	
1052 	getProperty: function(property) {
1053 		return this.cfg.getProperty(property);
1054 	},
1055 	
1056 	getFirstItemRevealed: function() {
1057 		return this._firstItemRevealed;
1058 	},
1059 	getLastItemRevealed: function() {
1060 		return this._lastItemRevealed;
1061 	},
1062 	
1063 	// Just for convenience and to be symmetrical with getFirstVisible
1064 	getFirstVisible: function() {
1065 		return this.cfg.getProperty("firstVisible");
1066 	},
1067 	
1068 	getLastVisible: function() {
1069 		var firstVisible = this.cfg.getProperty("firstVisible");
1070 		var numVisible = this.cfg.getProperty("numVisible");
1071 		
1072 		return firstVisible + numVisible - 1;
1073 	},
1074 	
1075 	// /////////////////// PRIVATE API //////////////////////////////////////////
1076 	_getStyleVal : function(li, style, returnFloat) {
1077 		var styleValStr = YAHOO.util.Dom.getStyle(li, style);
1078 		
1079 		var styleVal = returnFloat ? parseFloat(styleValStr) : parseInt(styleValStr, 10);
1080 		if(style=="height" && isNaN(styleVal)) {
1081 			styleVal = li.offsetHeight;
1082 		} else if(isNaN(styleVal)) {
1083 			styleVal = 0;
1084 		}
1085 		return styleVal;
1086 	},
1087 	
1088 	_calculateSize: function(me) {
1089 		me.calculateSize();
1090 		me.show();
1091 		//YAHOO.util.Dom.setStyle(me.carouselElem, "visibility", "visible");
1092 	},
1093 
1094 	// From Mike Chambers: http://weblogs.macromedia.com/mesh/archives/2006/01/removing_html_e.html
1095 	_removeChildrenFromNode: function(node)
1096 	{
1097 		if(!this._isValidObj(node))
1098 		{
1099       		return;
1100 		}
1101    
1102 		var len = node.childNodes.length;
1103    
1104 		while (node.hasChildNodes())
1105 		{
1106 			node.removeChild(node.firstChild);
1107 		}
1108 	},
1109 	
1110 	_prebuildLiElem: function(idx) {
1111 		if(idx < 1) return;
1112 		
1113 		
1114 		var liElem = document.createElement("li");
1115 		liElem.id = this._carouselElemID + "-item-" + idx;
1116 		// this is default flag to know that we're not really loaded yet.
1117 		liElem.placeholder = true;   
1118 		this.carouselList.appendChild(liElem);
1119 		
1120 		this._lastPrebuiltIdx = (idx > this._lastPrebuiltIdx) ? idx : this._lastPrebuiltIdx;
1121 	},
1122 	
1123 	_createItem: function(idx, innerHTMLOrElem) {
1124 		if(idx < 1) return;
1125 		
1126 		
1127 		var liElem = document.createElement("li");
1128 		liElem.id = this._carouselElemID + "-item-" + idx;
1129 
1130 		// if String then assume innerHTML, else an elem object
1131 		if(typeof(innerHTMLOrElem) === "string") {
1132 			liElem.innerHTML = innerHTMLOrElem;
1133 		} else {
1134 			liElem.appendChild(innerHTMLOrElem);
1135 		}
1136 		
1137 		return liElem;
1138 	},
1139 	
1140 	// idx is the location to insert after
1141 	_insertAfterItem: function(refIdx, innerHTMLOrElem) {
1142 		return this._insertBeforeItem(refIdx+1, innerHTMLOrElem);
1143 	},
1144 	
1145 	
1146 	_insertBeforeItem: function(refIdx, innerHTMLOrElem) {
1147 
1148 		var refItem = this.getItem(refIdx);
1149 		var size = this.cfg.getProperty("size");
1150 		if(size != this.UNBOUNDED_SIZE) {
1151 			this.cfg.setProperty("size", size + 1, true);
1152 		}
1153 				
1154 		for(var i=this._lastPrebuiltIdx; i>=refIdx; i--) {
1155 			var anItem = this.getItem(i);
1156 			if(this._isValidObj(anItem)) {
1157 				anItem.id = this._carouselElemID + "-item-" + (i+1);
1158 			}
1159 		}
1160 
1161 		var liElem = this._createItem(refIdx, innerHTMLOrElem);
1162 		
1163 		var insertedItem = this.carouselList.insertBefore(liElem, refItem);
1164 		this._lastPrebuiltIdx += 1;
1165 		
1166 		return liElem;
1167 	},
1168 	
1169 	// TEST THIS... think it has to do with prebuild
1170 	insertAfterEnd: function(innerHTMLOrElem) {
1171 		return this.insertAfter(this.cfg.getProperty("size"), innerHTMLOrElem);
1172 	},
1173 		
1174 	_position: function(newStart, showAnimation) {
1175 		// do we bypass the isAnimated check?
1176 		var currStart = this._priorFirstVisible;
1177 		if(newStart > currStart) {
1178 			var inc = newStart - currStart;
1179 			this._scrollNextInc(inc, showAnimation);
1180 		} else {
1181 			var dec = currStart - newStart;
1182 			this._scrollPrevInc(dec, showAnimation);
1183 		}
1184 	},
1185 
1186 	_scrollPrev: function(e, carousel) {
1187 		if(e !== null) { // event fired this so disable autoplay
1188 			carousel.stopAutoPlay();
1189 		}
1190 		if(carousel._scrollPrevAnim.isAnimated()) {
1191 			return false;
1192 		}
1193 		carousel._scrollPrevInc(carousel.cfg.getProperty("scrollInc"), 
1194 							(carousel.cfg.getProperty("animationSpeed") !== 0));
1195 	},
1196 	
1197 	// event handler
1198 	_scrollNext: function(e, carousel) {		
1199 		if(e !== null) { // event fired this so disable autoplay
1200 			carousel.stopAutoPlay();
1201 		}
1202 		if(carousel._scrollNextAnim.isAnimated()) {
1203 			return false; // might be better to set ourself waiting for animation completion and
1204 			// then just do this function. that will allow faster scroll responses.
1205 		}
1206 
1207 		carousel._scrollNextInc(carousel.cfg.getProperty("scrollInc"), 
1208 								(carousel.cfg.getProperty("animationSpeed") !== 0));
1209 	},
1210 	
1211 	
1212 	_handleAnimationComplete: function(type, args, argList) {
1213 		var carousel = argList[0];
1214 		var direction = argList[1];
1215 		
1216 		carousel._animationCompleteEvt.fire(direction);
1217 
1218 		
1219 	},
1220 	
1221 	// If EVERY item is already loaded in the range then return true
1222 	// Also prebuild whatever is not already created.
1223 	_areAllItemsLoaded: function(first, last) {
1224 		var itemsLoaded = true;
1225 		for(var i=first; i<=last; i++) {
1226 			var liElem = this.getItem(i);
1227 			
1228 			// If the li elem does not exist, then prebuild it in the correct order
1229 			// but still flag as not loaded (just prebuilt the li item.
1230 			if(!this._isValidObj(liElem)) {
1231 				this._prebuildLiElem(i);
1232 				itemsLoaded = false;
1233 			// but if the item exists and is a placeholder, then
1234 			// note that this item is not loaded (only a placeholder)
1235 			} else if(this._isValidObj(liElem.placeholder)) {
1236 				itemsLoaded = false;
1237 			}
1238 		}
1239 		return itemsLoaded;
1240 	}, 
1241 	
1242 	_prebuildItems: function(first, last) {
1243 		for(var i=first; i<=last; i++) {
1244 			var liElem = this.getItem(i);
1245 			
1246 			// If the li elem does not exist, then prebuild it in the correct order
1247 			// but still flag as not loaded (just prebuilt the li item.
1248 			if(!this._isValidObj(liElem)) {
1249 				this._prebuildLiElem(i);
1250 			}
1251 		}
1252 	}, 
1253 	
1254 	_isExtraRevealed: function() {
1255 		return (this.cfg.getProperty("revealAmount") > 0);
1256 	},
1257 
1258 	// probably no longer need carousel passed in, this should be correct now.
1259 	_scrollNextInc: function(inc, showAnimation) {
1260 		var numVisible = this.cfg.getProperty("numVisible");
1261 		var currStart = this._priorFirstVisible;
1262 		var currEnd = this._priorLastVisible;
1263 		var size = this.cfg.getProperty("size");
1264 
1265 		var scrollExtent = this._calculateAllowableScrollExtent();
1266 		
1267 		if(this.cfg.getProperty("wrap") && currEnd == scrollExtent.end) {
1268 			this.scrollTo(scrollExtent.start); // might need to check animation is on or not
1269 			return;
1270 		}
1271 
1272 		// increment start by inc
1273 		var newStart = currStart + inc;		
1274 		var newEnd = newStart + numVisible - 1;
1275 
1276 		// If we are past the end, adjust or wrap
1277 		if(newEnd > scrollExtent.end) {
1278 			newEnd = scrollExtent.end;
1279 			newStart = newEnd - numVisible + 1;
1280 		}
1281 
1282 		inc = newStart - currStart;
1283 
1284 		// at this point the following variables are set
1285 		// inc... amount to increment by
1286 		// newStart... the firstVisible item after the scroll
1287 		// newEnd... the last item visible after the scroll
1288 
1289 		this.cfg.setProperty("firstVisible", newStart, true);
1290 
1291 
1292 		if(inc > 0) {
1293 			if(this._isValidObj(this.cfg.getProperty("loadNextHandler"))) {
1294 				var visibleExtent = this._calculateVisibleExtent(newStart, newEnd);
1295 				var cacheStart = (currEnd+1) < visibleExtent.start ? (currEnd+1) : visibleExtent.start;						
1296 				var alreadyCached = this._areAllItemsLoaded(cacheStart, visibleExtent.end);
1297 				this._loadNextHandlerEvt.fire(visibleExtent.start, visibleExtent.end, alreadyCached);
1298 			}
1299 
1300 			if(showAnimation) {
1301 	 			var nextParams = { points: { by: [-this.scrollAmountPerInc*inc, 0] } };
1302 	 			if(this.isVertical()) {
1303 	 				nextParams = { points: { by: [0, -this.scrollAmountPerInc*inc] } };
1304 	 			}
1305 
1306 	 			this._scrollNextAnim = new YAHOO.util.Motion(this.carouselList, 
1307 	 							nextParams, 
1308    								this.cfg.getProperty("animationSpeed"), 
1309 								this.cfg.getProperty("animationMethod"));
1310 
1311 // is this getting added multiple times?
1312 				if(this.cfg.getProperty("animationCompleteHandler")) {
1313 					this._scrollNextAnim.onComplete.subscribe(this._handleAnimationComplete, [this, "next"]);
1314 				}
1315 				this._scrollNextAnim.animate();
1316 			} else {
1317 				if(this.isVertical()) {
1318 					var currY = YAHOO.util.Dom.getY(this.carouselList);
1319 
1320 					YAHOO.util.Dom.setY(this.carouselList, 
1321 								currY - this.scrollAmountPerInc*inc);
1322 				} else {
1323 					var currX = YAHOO.util.Dom.getX(this.carouselList);
1324 					YAHOO.util.Dom.setX(this.carouselList, 
1325 								currX - this.scrollAmountPerInc*inc);
1326 				}
1327 			}
1328 
1329 		}
1330 		this._priorFirstVisible = newStart;
1331 		this._priorLastVisible = newEnd;	
1332 
1333 		this._enableDisableControls();
1334 		return false;
1335 	},
1336 
1337 	// firstVisible is already set
1338 	_scrollPrevInc: function(dec, showAnimation) {
1339 		var numVisible = this.cfg.getProperty("numVisible");
1340 		var currStart = this._priorFirstVisible;
1341 		var currEnd = this._priorLastVisible;
1342 		var size = this.cfg.getProperty("size");
1343 
1344 		// decrement start by dec
1345 		var newStart = currStart - dec;	
1346 
1347 		var scrollExtent = this._calculateAllowableScrollExtent();
1348 	
1349 		// How to decide whether to stop at 1 or not
1350 		newStart = (newStart < scrollExtent.start) ? scrollExtent.start : newStart;
1351 		
1352 		// if we are going to extend past the end, then we need to correct the start
1353 		var newEnd = newStart + numVisible - 1;
1354 		if(newEnd > scrollExtent.end) {
1355 			newEnd = scrollExtent.end;
1356 			newStart = newEnd - numVisible + 1;
1357 		}
1358 				
1359 		dec = currStart - newStart;
1360 
1361 		// at this point the following variables are set
1362 		// dec... amount to decrement by
1363 		// newStart... the firstVisible item after the scroll
1364 		// newEnd... the last item visible after the scroll
1365 		this.cfg.setProperty("firstVisible", newStart, true);
1366 				
1367 		// if we are decrementing
1368 		if(dec > 0) {			
1369 			if(this._isValidObj(this.cfg.getProperty("loadPrevHandler"))) {	
1370 				var visibleExtent = this._calculateVisibleExtent(newStart, newEnd);
1371 				var cacheEnd = (currStart-1) > visibleExtent.end ? (currStart-1) : visibleExtent.end;						
1372 				var alreadyCached = this._areAllItemsLoaded(visibleExtent.start, cacheEnd);
1373 				
1374 				this._loadPrevHandlerEvt.fire(visibleExtent.start, visibleExtent.end, alreadyCached);
1375 			}
1376 
1377 			if(showAnimation) {
1378 	 			var prevParams = { points: { by: [this.scrollAmountPerInc*dec, 0] } };
1379 	 			if(this.isVertical()) {
1380 	 				prevParams = { points: { by: [0, this.scrollAmountPerInc*dec] } };
1381 	 			}
1382  		
1383 	 			this._scrollPrevAnim = new YAHOO.util.Motion(this.carouselList,
1384 	 							prevParams, 
1385    								this.cfg.getProperty("animationSpeed"), this.cfg.getProperty("animationMethod"));
1386 				if(this.cfg.getProperty("animationCompleteHandler")) {
1387 					this._scrollPrevAnim.onComplete.subscribe(this._handleAnimationComplete, [this, "prev"]);
1388 				}
1389 				this._scrollPrevAnim.animate();
1390 			} else {
1391 				if(this.isVertical()) {
1392 					var currY = YAHOO.util.Dom.getY(this.carouselList);
1393 					YAHOO.util.Dom.setY(this.carouselList, currY + 
1394 							this.scrollAmountPerInc*dec);				
1395 				} else {
1396 					var currX = YAHOO.util.Dom.getX(this.carouselList);
1397 					YAHOO.util.Dom.setX(this.carouselList, currX + 
1398 							this.scrollAmountPerInc*dec);
1399 				}
1400 			}
1401 		}
1402 		this._priorFirstVisible = newStart;
1403 		this._priorLastVisible = newEnd;	
1404 		
1405 		this._enableDisableControls();
1406 
1407 		return false;
1408 	},
1409 	
1410 	// Check for all cases and enable/disable controls as needed by current state
1411 	_enableDisableControls: function() {
1412 	
1413 		var firstVisible = this.cfg.getProperty("firstVisible");
1414 		var lastVisible = this.getLastVisible();
1415 		var scrollExtent = this._calculateAllowableScrollExtent();
1416 				
1417 		// previous arrow is turned on. Check to see if we need to turn it off
1418 		if(this._prevEnabled) {
1419 			if(firstVisible === scrollExtent.start) {
1420 				this._disablePrev();
1421 			}
1422 		}
1423 
1424 		// previous arrow is turned off. Check to see if we need to turn it on
1425 		if(this._prevEnabled === false) {
1426 			if(firstVisible > scrollExtent.start) {
1427 				this._enablePrev();
1428 			}
1429 		}
1430 	
1431 		// next arrow is turned on. Check to see if we need to turn it off
1432 		if(this._nextEnabled) {
1433 			if(lastVisible === scrollExtent.end) {
1434 				this._disableNext();
1435 			}
1436 		}
1437 
1438 		// next arrow is turned off. Check to see if we need to turn it on
1439 		if(this._nextEnabled === false) {
1440 			if(lastVisible < scrollExtent.end) {
1441 				this._enableNext();
1442 			}
1443 		}	
1444 	},
1445 	
1446 	/**
1447 	 * _loadInitial looks at firstItemVisible for the start (not necessarily 1)
1448 	 */
1449 	_loadInitial: function() {
1450 		var firstVisible = this.cfg.getProperty("firstVisible");
1451 		this._priorLastVisible = this.getLastVisible();
1452 		// Load from 1 to the last visible
1453 		// The _calculateSize method will adjust the scroll position
1454 		// for starts > 1
1455 		if(this._loadInitHandlerEvt) {
1456 			var visibleExtent = this._calculateVisibleExtent(firstVisible, this._priorLastVisible);
1457 			// still treat the first real item as starting at 1 
1458 			var alreadyCached = this._areAllItemsLoaded(1, visibleExtent.end);
1459 			
1460 			this._loadInitHandlerEvt.fire(visibleExtent.start, visibleExtent.end, alreadyCached); 
1461 		}
1462 		
1463 		if(this.cfg.getProperty("autoPlay") !== 0) {
1464 			this._autoPlayTimer = this.startAutoPlay();
1465 		}	
1466 		
1467 		this._enableDisableControls();	
1468     },
1469 	
1470 	_calculateAllowableScrollExtent: function() {
1471 		var scrollBeforeAmount = this.cfg.getProperty("scrollBeforeAmount");
1472 		var scrollAfterAmount = this.cfg.getProperty("scrollAfterAmount");
1473 		var size = this.cfg.getProperty("size");
1474 		
1475 		var extent = {start: 1-scrollBeforeAmount, end: size+scrollAfterAmount};
1476 		return extent;
1477 		
1478 	},
1479 	
1480 	_calculateVisibleExtent: function(start, end) {
1481 		if(!start) {
1482 			start = this.cfg.getProperty("firstVisible");
1483 			end = this.getLastVisible();
1484 		}
1485 		
1486 		var size = this.cfg.getProperty("size");
1487 		
1488 		// we ignore the firstItem property... this method is used
1489 		// for prebuilding the cache and signaling the developer
1490 		// what to render on a given scroll.
1491 		start = start<1?1:start;
1492 		end = end>size?size:end;
1493 		
1494 		var extent = {start: start, end: end};
1495 		
1496 		// set up the indices for revealed items. If there is no item revealed, then set
1497 		// the index to -1
1498 		this._firstItemRevealed = -1;
1499 		this._lastItemRevealed = -1;
1500 		if(this._isExtraRevealed()) {
1501 			if(start > 1) {
1502 				this._firstItemRevealed = start - 1;
1503 				extent.start = this._firstItemRevealed;
1504 			}
1505 			if(end < size) {
1506 				this._lastItemRevealed = end + 1;
1507 				extent.end = this._lastItemRevealed;
1508 			}
1509 		}
1510 
1511 		return extent;
1512 	},
1513 	
1514 	_disablePrev: function() {
1515 		this._prevEnabled = false;
1516 		if(this._prevButtonStateHandlerEvt) {
1517 			this._prevButtonStateHandlerEvt.fire(false, this._carouselPrev);
1518 		}
1519 		if(this._isValidObj(this._carouselPrev)) {
1520 			YAHOO.util.Event.removeListener(this._carouselPrev, "click", this._scrollPrev);
1521 		}
1522 	},
1523 	
1524 	_enablePrev: function() {
1525 		this._prevEnabled = true;
1526 		if(this._prevButtonStateHandlerEvt) {
1527 			this._prevButtonStateHandlerEvt.fire(true, this._carouselPrev);
1528 		}
1529 		if(this._isValidObj(this._carouselPrev)) {
1530 			YAHOO.util.Event.addListener(this._carouselPrev, "click", this._scrollPrev, this);
1531 		}
1532 	},
1533 		
1534 	_disableNext: function() {
1535 		if(this.cfg.getProperty("wrap")) {
1536 			return;
1537 		}
1538 		this._nextEnabled = false;
1539 		if(this._isValidObj(this._nextButtonStateHandlerEvt)) {
1540 			this._nextButtonStateHandlerEvt.fire(false, this._carouselNext);
1541 		}
1542 		if(this._isValidObj(this._carouselNext)) {
1543 			YAHOO.util.Event.removeListener(this._carouselNext, "click", this._scrollNext);
1544 		}
1545 	},
1546 	
1547 	_enableNext: function() {
1548 		this._nextEnabled = true;
1549 		if(this._isValidObj(this._nextButtonStateHandlerEvt)) {
1550 			this._nextButtonStateHandlerEvt.fire(true, this._carouselNext);
1551 		}
1552 		if(this._isValidObj(this._carouselNext)) {
1553 			YAHOO.util.Event.addListener(this._carouselNext, "click", this._scrollNext, this);
1554 		}
1555 	},
1556 		
1557 	_isValidObj: function(obj) {
1558 
1559 		if (null == obj) {
1560 			return false;
1561 		}
1562 		if ("undefined" == typeof(obj) ) {
1563 			return false;
1564 		}
1565 		return true;
1566 	}
1567 };
1568