1 /*!
  2 	Copyright 2010 British Broadcasting Corporation
  3 
  4 	Licensed under the Apache License, Version 2.0 (the "License");
  5 	you may not use this file except in compliance with the License.
  6 	You may obtain a copy of the License at
  7 
  8 	   http://www.apache.org/licenses/LICENSE-2.0
  9 
 10 	Unless required by applicable law or agreed to in writing, software
 11 	distributed under the License is distributed on an "AS IS" BASIS,
 12 	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13 	See the License for the specific language governing permissions and
 14 	limitations under the License.
 15 */
 16 Glow.provide(function(glow) {
 17 	var NodeList = glow.NodeList,
 18 		NodeListProto = NodeList.prototype,
 19 		undefined;
 20 	
 21 	/**
 22 		@name glow.NodeList.focusable
 23 		@function
 24 		@extends glow.ui.Behaviour
 25 		@description Manage a focusable element, or group of elements
 26 			This method is a shortcut to {@link glow.ui.Focusable} and requires
 27 			the 'ui' package to be loaded.
 28 			
 29 			The first item in the NodeList is treated as the focusable's container.
 30 			An error is thrown if the first item in the NodeList is not an element.
 31 		
 32 			This can be used to create a single focus point for a set
 33 			of focusable elements. Eg, a menu can have a single tab stop,
 34 			and the arrow keys can be used to cycle through menu items.
 35 			
 36 			This means the user doesn't have to tab through every item in the
 37 			menu to get to the next set of focusable items.
 38 			
 39 			The FocusManager can also be used to make a element 'modal', ensuring
 40 			focus doesn't go to elements outside it.
 41 		
 42 		@param {object} [opts] Options
 43 			The same options as the {@link glow.ui.Focusable} constructor
 44 			
 45 		@returns {glow.ui.Focusable}
 46 	*/
 47 	NodeListProto.focusable = function(opts) {
 48 		/*!debug*/
 49 			if (arguments.length > 1) {
 50 				glow.debug.warn('[wrong count] glow.NodeList#focusable expects 0 or 1 argument, not ' + arguments.length + '.');
 51 			}
 52 			if (opts !== undefined && typeof opts !== 'object') {
 53 				glow.debug.warn('[wrong type] glow.NodeList#focusable expects object as "opts" argument, not ' + typeof opts + '.');
 54 			}
 55 		/*gubed!*/
 56 		return new glow.ui.Focusable(this, opts);
 57 	};
 58 });
 59 // start-source: ui.js
 60 
 61 /**
 62 	@name glow.ui
 63 	@namespace
 64  */
 65 		 
 66 Glow.provide(function(glow) {
 67 	glow.ui = glow.ui || {};
 68 });
 69 
 70 // end-source: ui.js
 71 Glow.provide(function(glow) {
 72 	/**
 73 		@name glow.ui.Behaviour
 74 		@class
 75 		@extends glow.events.Target
 76 		@description Abstract behaviour class.
 77 		@param {string} name The name of this widget.
 78 		This is added to class names in the generated DOM nodes that wrap the widget interface.
 79 		
 80 	*/
 81 	function Behaviour() {}
 82 	glow.util.extend(Behaviour, glow.events.Target);
 83 	
 84 	/*!debug*/
 85 		/**
 86 			@name glow.ui.Behaviour#enabled
 87 			@function
 88 			@description Get/set the enabled state
 89 				
 90 			@param {boolean} [state=true] 
 91 		*/
 92 		Behaviour.prototype.enabled = function() {
 93 			throw new Error('#enabled not implemented on behaviour');
 94 		}
 95 		
 96 		/**
 97 			@name glow.ui.Behaviour#destroy
 98 			@function
 99 			@description Removes the behaviour & event listeners
100 		*/
101 		Behaviour.prototype.destroy = function() {
102 			throw new Error('#destroy not implemented on behaviour');
103 		}
104 		
105 	/*gubed!*/
106 	
107 	// EXPORT
108 	glow.ui.Behaviour = Behaviour;
109 });
110 Glow.provide(function(glow) {
111 	var undefined, FocusableProto,
112 		// array of focusable instances
113 		focusables = [],
114 		// the focused element
115 		focused,
116 		// we use this to track the modal focusable, also to ensure there's only one
117 		modalFocusable,
118 		documentNodeList = glow(document),
119 		ignoreFocus = false;
120 	
121 	// keep track of what element has focus
122 	documentNodeList.on('blur', function(event) {
123 		focused = undefined;
124 		if (focusables.length) {
125 			// activate focusables on a timeout so we pick up a possible subsequent
126 			// focus event
127 			setTimeout(deactivateAllIfBlurred, 0);
128 		}
129 	}).on('focus', function(event) {
130 		if ( modalFocusable && !modalFocusable.container.contains(event.source) ) {
131 			// refocus either the active child or container
132 			( modalFocusable.activeChild[0] || modalFocusable.container[0] ).focus();
133 			return false;
134 		}
135 		
136 		focused = event.source;
137 		
138 		if (ignoreFocus) {
139 			return;
140 		}
141 		
142 		ignoreFocus = true;
143 		
144 		activateFocusables();
145 		
146 		setTimeout(stopIgnoringFocus, 0);
147 	});
148 	
149 	/**
150 		@private
151 		@function
152 		@description Wot it sez on da tin.
153 			(used to cater for browsers that fire multiple focuses per click)
154 	*/
155 	function stopIgnoringFocus() {
156 		ignoreFocus = false;
157 	}
158 	
159 	/**
160 		@private
161 		@function
162 		@description Deactivate all our focusables if nothing has focus
163 	*/
164 	function deactivateAllIfBlurred() {
165 		// if nothing has focus, deactivate our focusables
166 		!focused &&	activateFocusables();
167 	}
168 	
169 	/**
170 		@private
171 		@function
172 		@description React to a change in focus
173 	*/
174 	function activateFocusables() {
175 		// taking a copy of the array in case any destroy
176 		var instances = focusables.slice(0),
177 			i = instances.length;
178 		
179 		while (i--) {
180 			// activate / deactivate the focusable depending on where focus is.
181 			// This calls active(), passing in either the element focused (within the Focusable container) or false.
182 			// The 2 mentions of 'focused' is deliberate.
183 			instances[i].active( (focused && instances[i].container.contains(focused) && focused) || false );
184 		}
185 	}
186 	
187 	/**
188 		@private
189 		@function
190 		@description Update the children property for a focusable
191 	*/
192 	function updateChildren(focusable) {
193 		focusable.children = focusable.container.get( focusable._opts.children );
194 		
195 		// remove focusable items from the tab flow, we're going to conrol this with tab keys
196 		glow(focusable.children).push(focusable.container).prop('tabIndex', -1);
197 	}
198 	
199 	/**
200 		@private
201 		@function
202 		@description Create the default key handler functions
203 	*/
204 	function createKeyHandler(useLeftRight, useUpDown) {
205 		return function(event) {
206 			// listen for keypresses, react, and return false if the key was used
207 			switch (event.key) {
208 				case 'up':
209 					return !( useUpDown    && this.prev() );
210 				case 'left':
211 					return !( useLeftRight && this.prev() );
212 				case 'down':
213 					return !( useUpDown    && this.next() );
214 				case 'right':
215 					return !( useLeftRight && this.next() );
216 			}
217 		}
218 	}
219 	
220 	/**
221 		@private
222 		@description The default key handler functions
223 	*/
224 	var keyHandlers = {
225 		'arrows'  : createKeyHandler(1, 1),
226 		'arrows-x': createKeyHandler(1, 0),
227 		'arrows-y': createKeyHandler(0, 1)
228 	}
229 	
230 	/**
231 		@private
232 		@function
233 		@description Hover listener
234 			Used to focus items on hover.
235 			'this' is the Focusable.
236 	*/
237 	function hoverListener(event) {
238 		// set the _activeMethod so this can be passed onto the event
239 		this._activeMethod = 'hover';
240 		this._activeEvent = event;
241 		this.active(event.source);
242 		this._activeEvent = this._activeMethod = undefined;
243 	}
244 	
245 	/**
246 		@private
247 		@function
248 		@description Set _activeMethod to a value and call another function.
249 			This allows the _activeMethod to be passed to the event.
250 	*/
251 	function activeMethodWrap(focusable, methodName, func) {
252 		return function(event) {
253 			var returnVal;
254 			
255 			focusable._activeMethod = methodName;
256 			focusable._activeEvent = event;
257 			returnVal = func.apply(this, arguments);
258 			focusable._activeEvent = focusable._activeMethod = undefined;
259 			return returnVal;
260 		}
261 	}
262 	
263 	/**
264 		@name glow.ui.Focusable
265 		@class
266 		@extends glow.ui.Behaviour
267 		@description Manage a focusable element, or group of elements
268 			This can be used to create a single focus point for a set
269 			of focusable elements. Eg, a menu can have a single tab stop,
270 			and the arrow keys can be used to cycle through menu items.
271 			
272 			This means the user doesn't have to tab through every item in the
273 			menu to get to the next set of focusable items.
274 			
275 			The FocusManager can also be used to make a element 'modal', ensuring
276 			focus doesn't go to elements outside it.
277 			
278 			The aim of this behaviour is to make it easier to conform to
279 			<a href="http://www.w3.org/TR/2009/WD-wai-aria-practices-20091215/#keyboard">
280 				ARIA best practices for keyboard navigation
281 			</a>
282 			
283 		@param {glow.NodeList|string} container Parent focusable element of the group
284 			If tabindex isn't set on this element, it will be given tabindex="0",
285 			allowing the element to be focused using the tab key.
286 		@param {object} [opts] Options
287 			@param {string} [opts.children] Selector for child items that can be active
288 				These can be cycled through using the arrow keys when the Focusable
289 				or one of its children is active (usually when it has focus).
290 			@param {function|string} [opts.keyboardNav='arrows'] Alter the default keyboard behaviour.
291 				If 'arrows-x', the left & right arrow keys trigger {@link glow.ui.Focusable#next Focusable#next}
292 				and {@link glow.ui.Focusable#prev Focusable#prev} respectively. If 'arrows-y', the up & down
293 				arrow keys trigger {@link glow.ui.Focusable#next Focusable#next}
294 				and {@link glow.ui.Focusable#prev Focusable#prev} respectively. 'arrows' is
295 				a combination of the two.
296 				
297 				If a function is provided, it will be passed a {@link glow.events.KeyboardEvent} object.
298 				Use {@link glow.ui.Focusable#next Focusable#next},
299 				{@link glow.ui.Focusable#prev Focusable#prev} or
300 				{@link glow.ui.Focusable#activate Focusable#activate} to react to the
301 				key event.
302 				
303 				'this' inside this function refers to the Focusable.
304 			@param {boolean} [opts.setFocus=true] Sets whether focus is given to the active element.
305 				You need to set this to false if you want focus to remain in another
306 				element.
307 			@param {string} [opts.activeChildClass='active'] Class name to give the active child element.
308 			@param {boolean} [opts.activateOnHover=false] Activate items on hover?
309 			@param {boolean} [opts.loop=false] Loop from the last child item to the first (and vice-versa)?
310 				When this is true, calling {@link glow.ui.Focusable#next Focusable#next} when
311 				the last focusable item is active will activate the first.
312 				
313 		@example
314 			// A collection of buttons
315 			glow('#toolbar').focusable({
316 				children: '> li.button'
317 			});
318 			
319 			// The #toolbar now appears in tab order.
320 			// Once focused, the left & right arrow keys navigate between
321 			// buttons.
322 			
323 		@example
324 			// A modal dialog
325 			var dialog = glow('#dialog').hide();
326 			var focusable = dialog.focusable();
327 			
328 			glow('#openDialog').on('click', function() {
329 				dialog.show();
330 				focusable.modal(true);
331 			});
332 			
333 			glow('#closeDialog').on('click', function() {
334 				dialog.hide();
335 				focusable.modal(false);
336 			});
337 	*/
338 	function Focusable(container, opts) {
339 		/*!debug*/
340 			if (arguments.length > 2) {
341 				glow.debug.warn('[wrong count] glow.ui.Focusable expects 1 or 2 arguments, not ' + arguments.length + '.');
342 			}
343 			if (opts !== undefined && typeof opts !== 'object') {
344 				glow.debug.warn('[wrong type] glow.ui.Focusable expects object for "opts" argument, not ' + typeof opts + '.');
345 			}
346 		/*gubed!*/
347 		
348 		var keyboardNav;
349 		
350 		opts = this._opts = glow.util.apply({
351 			children: '',
352 			keyboardNav: 'arrows',
353 			setFocus: true,
354 			activeChildClass: 'active'
355 			// commented as undefined is falsey enough
356 			//activateOnHover: false,
357 			//loop: false
358 		}, opts || {});
359 		
360 		this.container = glow(container);
361 		keyboardNav = opts.keyboardNav;
362 		
363 		// build the keyhander, using presets or provided function
364 		this._keyHandler = activeMethodWrap(this, 'key',
365 			(typeof keyboardNav === 'string' ? keyHandlers[keyboardNav] : keyboardNav)
366 		);
367 		
368 		/*!debug*/
369 			if ( !this.container[0] ) {
370 				glow.debug.warn('[wrong value] glow.ui.Focusable - No container found');
371 			}
372 			if (typeof this._keyHandler != 'function') {
373 				glow.debug.warn('[wrong value] glow.ui.Focusable - unexpected value for opts.keyboardNav');
374 			}
375 			if (typeof opts.children != 'string') {
376 				glow.debug.warn('[wrong type] glow.ui.Focusable expects CSS string for "opts.children" argument, not ' + typeof opts.children + '.');
377 			}
378 		/*gubed!*/
379 		
380 		// populate #children
381 		updateChildren(this);
382 		
383 		// create initial focal point
384 		this.container[0].tabIndex = 0;
385 		
386 		// Add listener for activateOnHover
387 		if (opts.activateOnHover) {
388 			this.container.on('mouseover', hoverListener, this);
389 		}
390 		
391 		// listen for clicks
392 		this.container.on('click', clickSelectListener, this);
393 		
394 		// add to our array of focusables
395 		focusables.push(this);
396 	};
397 	glow.util.extend(Focusable, glow.ui.Behaviour);
398 	FocusableProto = Focusable.prototype;
399 	
400 	/**
401 		@name glow.ui.Focusable#_opts
402 		@type boolean
403 		@description Option object used in construction
404 	*/
405 	/**
406 		@name glow.ui.Focusable#_active
407 		@type boolean
408 		@description True/false to indicate if the Focusable is active
409 	*/
410 	FocusableProto._active = false;
411 	/**
412 		@name glow.ui.Focusable#_modal
413 		@type boolean
414 		@description True/false to indicate if the Focusable is modal
415 	*/
416 	FocusableProto._modal = false;
417 	/**
418 		@name glow.ui.Focusable#_disabled
419 		@type boolean
420 		@description True/false to indicate if the Focusable is enabled
421 	*/
422 	FocusableProto._disabled = false;
423 	/**
424 		@name glow.ui.Focusable#_lastActiveChild
425 		@type HTMLElement
426 		@description Stores the last value of #activeChild while the focusable is inactive
427 	*/
428 	/**
429 		@name glow.ui.Focusable#_keyHandler
430 		@type function
431 		@description Key handler function
432 	*/
433 	/**
434 		@name glow.ui.Focusable#_activeMethod
435 		@type string
436 		@description The last method used to activate a child element
437 	*/
438 	/**
439 		@name glow.ui.Focusable#_activeEvent
440 		@type string
441 		@description The event object accociated with _activeMethod
442 	*/
443 	/**
444 		@name glow.ui.Focusable#activeChild
445 		@type glow.NodeList
446 		@description The active child item.
447 			This will be an empty NodeList if no child is active
448 	*/
449 	FocusableProto.activeChild = glow();
450 	/**
451 		@name glow.ui.Focusable#activeIndex
452 		@type number
453 		@description The index of the active child item in {@link glow.ui.Focusable#children}.
454 			This will be undefined if no child is active.
455 	*/
456 	/**
457 		@name glow.ui.Focusable#container
458 		@type glow.NodeList
459 		@description Focusable container
460 	*/
461 	/**
462 		@name glow.ui.Focusable#children
463 		@type glow.NodeList
464 		@description NodeList of child items that are managed by this Focusable.
465 			This will be an empty nodelist if the focusable has no children
466 	*/
467 	FocusableProto.children = glow();
468 	
469 	/**
470 		@name glow.ui.Focusable#modal
471 		@function
472 		@description Get/set modality
473 			When a Focusable is modal it cannot be deactivated, focus cannot
474 			be given to elements outside of it until modal set to false.
475 			
476 		@param {boolean} setModal New modal value
477 		
478 		@returns this when setting, true/false when getting
479 	*/
480 	FocusableProto.modal = function(setModal) {
481 		/*!debug*/
482 			if (arguments.length > 1) {
483 				glow.debug.warn('[wrong count] glow.ui.Focusable#modal expects 0 or 1 argument, not ' + arguments.length + '.');
484 			}
485 		/*gubed!*/
486 		
487 		if (setModal === undefined) {
488 			return this._modal;
489 		}
490 		
491 		if (!this._disabled) {
492 			// Activate the modal if it isn't modal already
493 			if (setModal && !this._modal) {
494 				// Ensure we're not going to get a deadlock with another focusable
495 				if (modalFocusable) {
496 					modalFocusable.modal(false);
497 				}
498 				modalFocusable = this;
499 				this.active(true);
500 			}
501 			// switch modal off, if this focusable is modal
502 			else if (!setModal && this._modal) {
503 				modalFocusable = undefined;
504 			}
505 			
506 			this._modal = !!setModal;
507 		}
508 		return this;
509 	};
510 	
511 	/**
512 		@private
513 		@function
514 		@description Update activeChild and activeIndex according to an index.
515 	*/
516 	function activateChildIndex(focusable, index) {
517 		var prevActiveChild = focusable.activeChild[0],
518 			activeChildClass = focusable._opts.activeChildClass,
519 			activeChild = focusable.activeChild = glow( focusable.children[index] ),
520 			eventData = {
521 				item: activeChild,
522 				itemIndex: index,
523 				method: focusable._activeMethod || 'api',
524 				methodEvent: focusable._activeEvent
525 			};
526 		
527 		focusable.activeIndex = index;
528 		
529 		// have we changed child focus?
530 		if ( prevActiveChild === activeChild || focusable.fire('childActivate', eventData).defaultPrevented() ) {
531 			return;
532 		}
533 		
534 		// take the current active item out of the tab order
535 		if (prevActiveChild) {
536 			prevActiveChild.tabIndex = -1;
537 			glow(prevActiveChild).removeClass(activeChildClass);
538 		}
539 		
540 		// put the current active item into the tab order
541 		focusable.activeChild[0].tabIndex = 0;
542 		focusable.activeChild.addClass(activeChildClass);
543 		
544 		// give physical focus to the new item
545 		focusable._opts.setFocus && focusable.activeChild[0].focus();
546 	}
547 	
548 	/**
549 		@private
550 		@function
551 		@description Get the focusable child index of an element.
552 			The element may also be an element within the focusable's child items.
553 		@param {glow.ui.Focusable} focusable
554 		@param {glow.NodeList} child Element to get the index from.
555 		
556 		@returns {number} Index or -1 if element is not (and is not within) any of the focusable's child items.
557 	*/
558 	function getIndexFromElement(focusable, child) {
559 		var i,
560 			children = focusable.children,
561 			firstChild = children[0];
562 		
563 		// just exit if there are no child items
564 		if ( !firstChild ) {
565 			return -1;
566 		}
567 		
568 		child = glow(child).item(0);
569 		
570 		// do we have an active child to re-enable?
571 		if ( child[0] ) {
572 			i = children.length;
573 			
574 			// see if it's in the current child set
575 			while (i--) {
576 				if ( glow( children[i] ).contains(child) ) {
577 					return i;
578 				}
579 			}
580 		}
581 		return -1;
582 	}
583 	
584 	/**
585 		@private
586 		@function
587 		@description Ensure an index is within the range of indexes for this focusable.
588 		@param {glow.ui.Focusable} focusable
589 		@param {number} index Index to keep within range
590 		
591 		@returns {number} The index within range.
592 			If the focusable can loop, the index will be looped. Otherwise
593 			the index will be limited to its maximum & minimum
594 	*/
595 	function assertIndexRange(focusable, index) {
596 		var childrenLen = focusable.children.length;
597 		
598 		// ensure the index is within children range
599 		if (focusable._opts.loop) {
600 			index = index % childrenLen;
601 			if (index < 0) {
602 				index = childrenLen + index;
603 			}
604 		}
605 		else {
606 			index = Math.max( Math.min(index, childrenLen - 1), 0);
607 		}
608 		
609 		return index;
610 	}
611 	
612 	/**
613 		@private
614 		@function
615 		@description Deactivate the focusable
616 	*/
617 	function deactivate(focusable) {
618 		if ( focusable.fire('deactivate').defaultPrevented() ) {
619 			return;
620 		}
621 		
622 		// remove active class
623 		focusable.activeChild.removeClass(focusable._opts.activeChildClass);
624 		
625 		// store focusable so we can reactivate it later
626 		focusable._lastActiveChild = focusable.activeChild[0];
627 		
628 		// blur the active element
629 		( focusable.activeChild[0] || focusable.container[0] ).blur();
630 		
631 		focusable.activeIndex = undefined;
632 		
633 		// reset to empty nodelist
634 		focusable.activeChild = FocusableProto.activeChild;
635 		focusable._active = false;
636 		
637 		// remove listeners
638 		documentNodeList.detach('keypress', focusable._keyHandler).detach('keydown', keySelectListener);
639 		
640 		// allow the container to receive focus in case the child elements change
641 		focusable.container.prop('tabIndex', 0);
642 	}
643 	
644 	/**
645 		@private
646 		@function
647 		@description Activate the focusable
648 	*/
649 	function activate(focusable, toActivate) {
650 		var _active = focusable._active,
651 			focusContainerIfChildNotFound,
652 			indexToActivate = -1;
653 		
654 		// if currently inactive...
655 		if (!_active) {
656 			if ( focusable.fire('activate').defaultPrevented() ) {
657 				return;
658 			}
659 			
660 			updateChildren(focusable);
661 			focusable._active = true;
662 			// start listening to the keyboard
663 			documentNodeList.on('keypress', focusable._keyHandler, focusable).on('keydown', keySelectListener, focusable);
664 			// give focus to the container - a child element may steal focus in activateChildIndex
665 			focusContainerIfChildNotFound = true;
666 		}
667 		
668 		// Work out what child item to focus.
669 		// We avoid doing this if we were 
670 		if ( focusable.children[0] ) {
671 			// activating by index
672 			if (typeof toActivate === 'number') {
673 				indexToActivate = assertIndexRange(focusable, toActivate);
674 			}
675 			// activating by element
676 			else if (typeof toActivate !== 'boolean') {
677 				indexToActivate = getIndexFromElement(focusable, toActivate);
678 			}
679 			
680 			// still no index to activate? If we were previously inactive, try the last active item
681 			if (indexToActivate === -1 && !_active) {
682 				indexToActivate = getIndexFromElement(focusable, focusable._lastActiveChild);
683 				indexToActivate = indexToActivate !== -1 ? indexToActivate : 0;
684 			}
685 		}
686 		
687 		// If we have an item to activate, let's go for it
688 		if (indexToActivate !== -1 && indexToActivate !== focusable.activeIndex) {
689 			activateChildIndex(focusable, indexToActivate);
690 		}
691 		else if (focusContainerIfChildNotFound) {
692 			focusable._opts.setFocus && focusable.container[0].focus();
693 		}
694 	}
695 	
696 	/**
697 		@name glow.ui.Focusable#active
698 		@function
699 		@description Get/set the active state of the Focusable
700 			Call without arguments to get the active state. Call with
701 			arguments to set the active element.
702 			
703 			A Focusable will be activated automatically when it receieves focus.
704 		
705 		@param {number|glow.NodeList|boolean} [toActivate] Item to activate.
706 			Numbers will be treated as an index of {@link glow.ui.FocusManager#children children}.
707 			
708 			'true' will activate the container, but none of the children.
709 			
710 			'false' will deactivate the container and any active child
711 		
712 		@returns {glow.ui.Focusable|boolean}
713 			Returns boolean when getting, Focusable when setting
714 	*/
715 	FocusableProto.active = function(toActivate) {
716 		/*!debug*/
717 			if (arguments.length > 1) {
718 				glow.debug.warn('[wrong count] glow.ui.Focusable#active expects 0 or 1 argument, not ' + arguments.length + '.');
719 			}
720 		/*gubed!*/
721 		
722 		var _active = this._active;
723 		
724 		// getting
725 		if (toActivate === undefined) {
726 			return _active;
727 		}
728 		
729 		// setting
730 		if (!this._disabled) {
731 			// deactivating
732 			if (toActivate === false) {
733 				if (!this._modal && _active) {
734 					deactivate(this);
735 				}
736 			}
737 			// activating
738 			else {
739 				activate(this, toActivate)
740 			}
741 		}
742 		return this;
743 	};
744 	
745 	/**
746 		@private
747 		@function
748 		@description Generates #next and #prev
749 	*/
750 	function nextPrev(amount) {
751 		return function() {
752 			/*!debug*/
753 				if (arguments.length > 1) {
754 					glow.debug.warn('[wrong count] glow.ui.Focusable#' + (amount > 0 ? 'next' : 'prev') + ' expects 0 arguments, not ' + arguments.length + '.');
755 				}
756 			/*gubed!*/
757 			
758 			if (this._active) {
759 				this.active( this.activeIndex + amount );
760 			}
761 			return this;
762 		}
763 	}
764 	
765 	/**
766 		@name glow.ui.Focusable#next
767 		@function
768 		@description Activate next child item.
769 			Has no effect on an inactive Focusable.
770 		@returns this
771 	*/
772 	FocusableProto.next = nextPrev(1);
773 	
774 	/**
775 		@name glow.ui.Focusable#prev
776 		@function
777 		@description Activate previous child item
778 			Has no effect on an inactive Focusable.
779 		@returns this
780 	*/
781 	FocusableProto.prev = nextPrev(-1);
782 	
783 	/**
784 		@name glow.ui.Focusable#disabled
785 		@function
786 		@description Enable/disable the Focusable, or get the disabled state
787 			When the Focusable is disabled, it (and its child items) cannot
788 			be activated or receive focus.
789 			
790 		@param {boolean} [newState] Disable the focusable?
791 			'false' will enable a disabled focusable.
792 		
793 		@returns {glow.ui.Focusable|boolean}
794 			Returns boolean when getting, Focusable when setting
795 	*/
796 	FocusableProto.disabled = function(newState) {
797 		/*!debug*/
798 			if (arguments.length > 1) {
799 				glow.debug.warn('[wrong count] glow.ui.Focusable#disabled expects 0 or 1 argument, not ' + arguments.length + '.');
800 			}
801 		/*gubed!*/
802 		
803 		// geting
804 		if (newState === undefined) {
805 			return this._disabled;
806 		}
807 		
808 		// setting
809 		if (newState) {
810 			this.active(false);
811 			this._disabled = !!newState;
812 		}
813 		else {
814 			this._disabled = !!newState;
815 			
816 			// reactivate it if it were modal
817 			if (this._modal) {
818 				this.active(true);
819 			}
820 		}
821 		return this;
822 	}
823 	
824 	/**
825 		@name glow.ui.Focusable#destroy
826 		@function
827 		@description Destroy the Focusable
828 			This removes all focusable behaviour from the continer
829 			and child items.
830 			
831 			The elements themselves will not be destroyed.
832 		@returns this
833 	*/
834 	FocusableProto.destroy = function() {
835 		var i = focusables.length;
836 		
837 		glow.events.removeAllListeners( [this] );
838 		
839 		this.modal(false).active(false).container
840 			// remove listeners
841 			.detach('mouseover', hoverListener)
842 			.detach('click', clickSelectListener)
843 			// remove from tab order
844 			.prop('tabIndex', -1);
845 			
846 		this.container = undefined;
847 		
848 		// remove this focusable from the static array
849 		while (i--) {
850 			if (focusables[i] === this) {
851 				focusables.splice(i, 1);
852 				break;
853 			}
854 		}
855 	}
856 	
857 	/**
858 		@name glow.ui.Focusable#event:select
859 		@event
860 		@description Fires when a child of the Focusable is selected.
861 			Items are selected by clicking, or pressing enter when a child is active.
862 		
863 			Cancelling this event prevents the default click/key action.
864 		
865 		@param {glow.events.Event} event Event Object
866 		@param {glow.NodeList} event.item Item selected.
867 		@param {number} event.itemIndex The index of the selected item in {@link glow.ui.Focusable#children}.
868 	*/
869 	
870 	/**
871 		@private
872 		@function
873 		@description
874 			Listens for click selections on the Focusable
875 			'this' is the Focusable.
876 	*/
877 	function clickSelectListener() {
878 		if ( this.activeChild[0] ) {
879 			return !this.fire('select', {
880 				item: this.activeChild,
881 				itemIndex: this.activeIndex
882 			}).defaultPrevented();
883 		}
884 	}
885 	
886 	/**
887 		@private
888 		@function
889 		@description
890 			Same as above, but for keys
891 			'this' is the Focusable.
892 	*/
893 	function keySelectListener(event) {
894 		if (event.key === 'return') {
895 			return clickSelectListener.call(this);
896 		}
897 	}
898 	
899 	/**
900 		@name glow.ui.Focusable#event:activate
901 		@event
902 		@description Fires when the Focusable becomes active
903 			Cancelling this event prevents the Focusable being actived
904 		
905 		@param {glow.events.Event} event Event Object
906 	*/
907 	
908 	/**
909 		@name glow.ui.Focusable#event:childActivate
910 		@event
911 		@description Fires when a child item of the Focusable becomes active
912 			Cancelling this event prevents the child item being actived
913 		
914 		@param {glow.events.Event} event Event Object
915 		@param {glow.NodeList} event.item Item activated.
916 		@param {number} event.itemIndex The index of the activated item in {@link glow.ui.Focusable#children}.
917 		@param {string} event.method Either 'key', 'hover' or 'api' depending on how the item was activated.
918 			This allows you to react to certain kinds of activation.
919 		@param {glow.events.DomEvent} [event.methodEvent] An event object for the 'key' or 'hover' event.
920 			For 'key' methods this will be a more specific {@link glow.events.KeyboardEvent}.
921 			
922 			If the method was neither 'key' or 'hover', methodEvent will be undefined.
923 	*/
924 	
925 	/**
926 		@name glow.ui.Focusable#event:deactivate
927 		@event
928 		@description Fires when the Focusable becomes deactive
929 			Cancelling this event prevents the Focusable being deactived
930 		
931 		@param {glow.events.Event} event Event Object
932 	*/
933 	
934 	// EXPORT
935 	glow.ui.Focusable = Focusable;
936 });
937 Glow.provide(function(glow) {
938 	var undefined,
939 		WidgetProto;
940 
941 	/**
942 		@name glow.ui.Widget
943 		@constructor
944 		@extends glow.events.Target
945 		
946 		@description An abstract Widget class
947 			The Widget class serves as a base class that provides a shared framework on which other,
948 			more specific, widgets can be implemented. While it is possible to create an instance
949 			of this generic widget, it is more likely that your widget class will extend this class.
950 			
951 			Your widget constructor should call the base constructor, and should end in a call to _init.
952 			
953 		@param {string} name The name of this widget.
954 			This is added to class names in the generated DOM nodes that wrap the widget interface.
955 			
956 		@param {object} [opts]
957 			@param {string} [opts.className] Class name to add to the container.
958 			@param {string} [opts.id]  Id to add to the container.
959 			
960 		@example
961 			function MyWidget(opts) {
962 				// set up your widget...
963 				// call the base constructor, passing in the name and the options
964 				glow.ui.Widget.call(this, 'MyWidget', opts);
965 				
966 				// start init
967 				this._init();
968 			}
969 			glow.util.extend(MyWidget, glow.ui.Widget);
970 	*/
971 	
972 	function Widget(name, opts) {
973 		/*!debug*/
974 			if (arguments.length < 1 || arguments.length > 2) {
975 				glow.debug.warn('[wrong count] glow.ui.Widget expects 1 or 2 arguments, not '+arguments.length+'.');
976 			}
977 			if (typeof arguments[0] !== 'string') {
978 				glow.debug.warn('[wrong type] glow.ui.Widget expects argument 1 to be of type string, not '+typeof arguments[0]+'.');
979 			}
980 		/*gubed!*/
981 		
982 		this._name = name;
983 		this._locale = 'en'; // todo: default should come from i18n module
984 		this.phase = 'constructed';
985 		this._observers = [];
986 		this._opts = opts || {};
987 	}
988 	glow.util.extend(Widget, glow.events.Target); // Widget is a Target
989 	WidgetProto = Widget.prototype;
990 	/**
991 		@name glow.ui.Widget#_locale
992 		@protected
993 		@type string
994 		@description The locale of the widget
995 	*/
996 	/**
997 		@name glow.ui.Widget#_name
998 		@protected
999 		@type string
1000 		@description The name of the widget.
1001 			This is the first argument passed into the constructor.
1002 	*/
1003 	/**
1004 		@name glow.ui.Widget#_stateElm
1005 		@protected
1006 		@type glow.NodeList
1007 		@description The wrapper element that contains the state class
1008 	*/
1009 	/**
1010 		@name glow.ui.Widget#_themeElm
1011 		@protected
1012 		@type glow.NodeList
1013 		@description The wrapper element that contains the theme class
1014 	*/
1015 	/**
1016 		@name glow.ui.Widget#_opts
1017 		@protected
1018 		@type Object
1019 		@description The option object passed into the constructor
1020 	*/
1021 	/**
1022 		@name glow.ui.Widget#_disabled
1023 		@protected
1024 		@type boolean
1025 		@description Is the widget disabled?
1026 			This is read-only
1027 	*/
1028 	WidgetProto._disabled = false;
1029 	/**
1030 		@name glow.ui.Widget#_observers
1031 		@protected
1032 		@type object[]
1033 		@description Objects (usually widgets & dehaviours) observing this Widget
1034 	*/
1035 	
1036 	/**
1037 		@name glow.ui.Widget#phase
1038 		@type string
1039 		@description The phase within the lifecycle of the widget.
1040 			Will be one of the following:
1041 			
1042 			<dl>
1043 				<dt>constructed</dt>
1044 				<dd>The widget has been constructed but not yet initialised</dd>
1045 				<dt>initialised</dt>
1046 				<dd>The widget has been initialised but not yet build</dd>
1047 				<dt>built</dt>
1048 				<dd>The widget has been built but not yet bound</dd>
1049 				<dt>ready</dt>
1050 				<dd>The widget is in a fully usable state</dd>
1051 				<dt>destroyed</dt>
1052 				<dd>The widget has been destroyed</dd>
1053 			</dl>
1054 			
1055 			Usually, init, build & bind are done in the constructor, so
1056 			you may only interact with a widget that is either 'ready' or 'destroyed'.
1057 	*/
1058 	/**
1059 		@name glow.ui.Widget#container
1060 		@type glow.NodeList
1061 		@description The outermost wrapper element of the widget.
1062 	*/
1063 	/**
1064 		@name glow.ui.Widget#content
1065 		@type glow.NodeList
1066 		@description The content element of the widget
1067 			This is inside various wrapper elements used to track the state of
1068 			the widget.
1069 	*/
1070 	
1071 	function syncListener(e) {
1072 		// handle notifications about changes to the disabled state
1073 		if (e.disabled !== undefined) {
1074 			this.disabled(e.disabled);
1075 		}
1076 		else if (e.destroy) {
1077 			this.destroy();
1078 		}
1079 	}
1080 	
1081 	/**
1082 		@name glow.ui.Widget#_tie
1083 		@protected
1084 		@function
1085 		@description Specify a group of widgets that should stay in _sync with this one.
1086 			These synced widgets can listen for a '_sync' event on themselves, defining their own handler for the provided event.
1087 			The disabled and destroy methods automatically synchronize with their synced child widgets.
1088 		
1089 		@param {glow.ui.Widget} ... Child widgets to synchronize with.
1090 		
1091 		@example
1092 			function MyWidget() {
1093 				this.value = 0; // initially
1094 				
1095 				// this widget handles notifications of new values
1096 				// from other widgets it is syced to
1097 				var that = this;
1098 				this.on('_sync', function(e) {
1099 					if (typeof e.newValue !== undefined) {
1100 						that.value = e.newValue;
1101 					}
1102 				});	
1103 			}
1104 			glow.util.extend(MyWidget, glow.ui.Widget); // MyWidget extends the Base Widget
1105 			
1106 			MyWidget.prototype.setValue = function(n) {
1107 				this.value = n;
1108 				
1109 				// this widget notifies about changes to its value
1110 				this._sync({newValue: this.value});
1111 			}
1112 			
1113 			// widgets b and c will all be notified when a's value changes
1114 			var a = new MyWidget('A');
1115 			var b = new MyWidget('B');
1116 			var c = new MyWidget('C');
1117 			
1118 			a._tie(b, c);
1119 			
1120 			a.setValue(1); // a, b, and c all have a value of 1 now
1121 	 */
1122 	WidgetProto._tie = function(/*...*/) {
1123 		/*!debug*/
1124 			if (arguments.length === 0) {
1125 				glow.debug.warn('[wrong count] glow.ui.Widget#_tie expects at least 1 argument, not '+arguments.length+'.');
1126 			}
1127 		/*gubed!*/
1128 		
1129 		var newObservers = Array.prototype.slice.call(arguments),
1130 			i = newObservers.length;
1131 		
1132 		// add a default _sync listener to react to disabled and destroy
1133 		while (i--) {
1134 			newObservers[i].on('_sync', syncListener);
1135 		}
1136 		
1137 		this._observers = this._observers.concat(newObservers);
1138 		
1139 		return this;
1140 	}
1141 
1142 	/**
1143 		@developer
1144 		@name glow.ui.Widget#_sync
1145 		@method
1146 		@param {object} [changes] Key/value collection of new information. Will be added to the listeners' event.
1147 		@description Tell all widgets synced with this widget about any changes.
1148 	 */
1149 	WidgetProto._sync = function(changes) { // public shortcut to fire _notify
1150 		/*!debug*/
1151 			if (arguments.length > 1) {
1152 				glow.debug.warn('[wrong count] glow.ui.Widget#_sync expects 1 or fewer arguments, not '+arguments.length+'.');
1153 			}
1154 			
1155 			if (arguments.length && typeof changes !== 'object') {
1156 				glow.debug.warn('[wrong type] glow.ui.Widget#_sync expects argument 1 to be of type object, not '+(typeof changes)+'.');
1157 			}
1158 		/*gubed!*/
1159 		
1160 		glow.events.fire( this._observers, '_sync', changes || {} );
1161 		
1162 		return this;
1163 	}
1164 
1165 	/**
1166 		@name glow.ui.Widget#disabled
1167 		@function
1168 		@description Enable/disable the Widget, or get the disabled state
1169 			If other widgets are synced with this one, they will become disabled too.
1170 			
1171 		@param {boolean} [newState] Disable the focusable?
1172 			'false' will enable a disabled focusable.
1173 		
1174 		@example
1175 			var a = new MyWidget();
1176 			var b = new MyWidget();
1177 			var c = new MyWidget();
1178 			
1179 			c._tie(a, b);
1180 			
1181 			c.disabled(true); // a, b, and c are now disabled
1182 	 */
1183 	WidgetProto.disabled = function(newState) {
1184 		/*!debug*/
1185 			if (arguments.length > 1) {
1186 				glow.debug.warn('[wrong count] glow.ui.Widget#disabled expects 0 or 1 argument, not ' + arguments.length + '.');
1187 			}
1188 		/*gubed!*/
1189 		
1190 		// geting
1191 		if (newState === undefined) {
1192 			return this._disabled;
1193 		}
1194 		
1195 		// setting
1196 		newState = !!newState;
1197 		if ( newState !== this._disabled && !this.fire('disabled', {disabled:newState}).defaultPrevented() ) {
1198 			this._sync({
1199 				disabled: newState
1200 			});
1201 			this._stateElm[newState ? 'addClass' : 'removeClass']('disabled');
1202 			this._disabled = !!newState;
1203 		}
1204 		return this;
1205 	}
1206 
1207 	/**
1208 		@name glow.ui.Widget#_init
1209 		@protected
1210 		@function
1211 		@description Initialise the widget.
1212 			This is similar to the constructor, but for code that you may need to run
1213 			again.
1214 			
1215 			You init function should call the base _init, and end in a call to _build on your widget.
1216 		
1217 		@example
1218 			MyWidget.prototype._init = function() {
1219 				// set properties here
1220 				
1221 				// call base _init
1222 				glow.ui.Widget.prototype._init.call(this);
1223 				// call _build
1224 				this._build();
1225 			}
1226 		
1227 	 */
1228 	WidgetProto._init = function() {
1229 		this.phase = 'initialised';
1230 	}
1231 
1232 	/**
1233 		@name glow.ui.Widget#_build
1234 		@protected
1235 		@function
1236 		@description Build the html structure for this widget.
1237 			All actions relating to wrapping, creating & moving elements should be
1238 			done in this method. The base method creates the container, theme & state elements.
1239 			
1240 			Adding behaviour to these elements should be handed in {@link glow.ui.Widget#_bind}.
1241 			
1242 			You Widget's _build method should call the base build method and end in a call to _bind.
1243 		
1244 		@param {selector|HTMLElement|NodeList} [content] Content element for the widget.
1245 			This will be wrapped in container, theme & state elements. By default this is
1246 			an empty div.
1247 			
1248 		@example
1249 			MyWidget.prototype._build = function() {
1250 				// create some content
1251 				var content = glow('<p>Hello!</p>');
1252 				// call the base build
1253 				glow.ui.Widget.prototype._build.call(this, content);
1254 				// call _bind
1255 				this._bind();
1256 			}
1257 	 */
1258 	WidgetProto._build = function(content) {
1259 		/*!debug*/
1260 			if (arguments.length > 1) {
1261 				glow.debug.warn('[wrong count] glow.ui.Widget#_build expects 0-1 argument, not '+arguments.length+'.');
1262 			}
1263 		/*gubed!*/
1264 		
1265 		var container,
1266 			name = this._name,
1267 			opts = this._opts;
1268 		
1269 		content = this.content = glow(content || '<div></div>');
1270 		
1271 		/*!debug*/
1272 			if (content.length < 1) {
1273 				glow.debug.warn('[error] glow.ui.Widget#_build expects a content node to attach to. The given "content" argument was empty or not found.');
1274 			}
1275 		/*gubed!*/
1276 		
1277 		container = this.container = glow('' +
1278 			'<div class="glow200b1-' + name + '">' +
1279 				'<div class="' + name + '-theme">' +
1280 					'<div class="' + name + '-state"></div>' +
1281 				'</div>' +
1282 			'</div>' +
1283 		'');
1284 		
1285 		content.addClass(name + '-content').wrap(container);
1286 		this._stateElm = content.parent();
1287 		this._themeElm = this._stateElm.parent();
1288 		
1289 		if (opts.className) {
1290 			container.addClass(opts.className);
1291 		}
1292 		if (opts.id) {
1293 			container[0].id = opts.id;
1294 		}
1295 		
1296 		this.phase = 'built';
1297 	}
1298 	
1299 	/**
1300 		@developer
1301 		@name glow.ui.Widget#_bind
1302 		@function
1303 		@description Add behaviour to elements created in {@link glow.ui.Widget#_build _build}.
1304 			Your _bind method should call the base _bind and may end in a call
1305 			to _updateUi for initial positioning etc.
1306 			
1307 		@example
1308 			MyWidget.prototype._bind = function() {
1309 				// add some behaviour
1310 				this.content.on('click', function() {
1311 					alert('Hello!');
1312 				});
1313 				// call base _bind
1314 				glow.ui.Widget.prototype._bind.call(this);
1315 			}
1316 	 */
1317 	WidgetProto._bind = function() {
1318 		this.phase = 'ready';
1319 	}
1320 
1321 	/**
1322 		@name glow.ui.Widget#_updateUi
1323 		@function
1324 		@description Cause any functionality that deals with visual layout or UI display to update.
1325 			This function should be overwritten by Widgets that need to update or redraw. For example,
1326 			you may use this method to reposition or reorder elements.
1327 			
1328 			This is a convention only, the base method does nothing.
1329 		
1330 		@example
1331 			MyWidget.prototype.updateUi = function() {
1332 				// update the UI
1333 			}
1334 			
1335 	 */
1336 	WidgetProto._updateUi = function() {}
1337 
1338 	/**
1339 		@developer
1340 		@name glow.ui.Widget#destroy
1341 		@function
1342 		@description Cause any functionality that deals with removing and deleting this widget to run.
1343 			By default the container and all it's contents are removed.
1344 		@fires glow.ui.Widget#event:destroy
1345 	 */
1346 	WidgetProto.destroy = function() {
1347 		/*!debug*/
1348 			if (arguments.length !== 0) {
1349 				glow.debug.warn('[wrong count] glow.ui.Widget#destroy expects 0 arguments, not '+arguments.length+'.');
1350 			}
1351 		/*gubed!*/
1352 		if ( !this.fire('destroy').defaultPrevented() ) {
1353 			this._sync({
1354 				destroy: 1
1355 			});
1356 			glow.events.removeAllListeners( [this] );
1357 			this.container.destroy();
1358 			this.phase = 'destroyed';
1359 		}
1360 		return this;
1361 	}
1362 
1363 	/**
1364 		@developer
1365 		@name glow.ui.Widget#event:disable
1366 		@event
1367 		@description Fired after the disabled property is changed via the {@link glow.ui.Widget#disable} or {@link glow.ui.Widget#enable} method.
1368 		This includes widgets that are changed as a result of being synced to this one.
1369 	 */
1370 	
1371 	/**
1372 		@developer
1373 		@name glow.ui.Widget#event:destroy
1374 		@event
1375 		@description Fired when destroy is called on this widget.
1376 		@see glow.ui.Widget#destroy
1377 	 */
1378 
1379 	// export
1380 	glow.ui.Widget = Widget;
1381 });
1382 Glow.provide(function(glow) {
1383 	var OverlayProto,
1384 		WidgetProto = glow.ui.Widget.prototype,
1385 		idCounter = 0,
1386 		undefined,
1387 		instances = {}; // like {uid: overlayInstance}
1388 	
1389 	
1390 	var vis = {
1391 		SHOWING: 2,
1392 		SHOWN: 1,
1393 		HIDING: -1,
1394 		HIDDEN: -2
1395 	};
1396 	
1397 	
1398 	/**
1399 		@name glow.ui.Overlay
1400 		@class
1401 		@augments glow.ui
1402 		@description A container element displayed on top of the other page content
1403 		@param {selector|NodeList|String|boolean} content
1404 			the element that contains the contents of the overlay. If not in the document, you must append it to the document before calling show().
1405 
1406 		@param {object} [opts]
1407 			@param {function|selector|NodeList|boolean} [opts.hideWhenShown] Select which things to hide whenevr the overlay is in a shown state.
1408 			By default all `object` and `embed` elements will be hidden, in browsers that cannot properly layer those elements, whenever any overlay is shown.
1409 			Set this option to a false value to cause the overlay to never hide any elements, or set it to a bespoke selector, NodeList
1410 			or a function that returns a NodeList which will be used instead.
1411 		@example
1412 			var myOverlay = new glow.ui.Overlay(
1413 				glow(
1414 					'<div>' +
1415 					'  <p>Your Story has been saved.</p>' +
1416 					'</div>'
1417 				).appendTo(document.body)
1418 			);
1419 			
1420 			glow('#save-story-button').on('click', function() {
1421 				myOverlay.show();
1422 			});
1423 	 */
1424 	
1425 	function Overlay(content, opts) {
1426 		/*!debug*/
1427 			if (arguments.length < 1 || content === undefined) {
1428 				glow.debug.warn('[wrong type] glow.ui.Overlay expects "content" argument to be defined, not ' + typeof content + '.');
1429 			}
1430 			if (opts !== undefined && typeof opts !== 'object') {
1431 				glow.debug.warn('[wrong type] glow.ui.Overlay expects object as "opts" argument, not ' + typeof opts + '.');
1432 			}
1433 		/*gubed!*/
1434 		var that = this,
1435 			ua;
1436 		
1437 		opts = glow.util.apply({ }, opts);
1438 		
1439 		//call the base class's constructor
1440 		Overlay.base.call(this, 'overlay', opts);
1441 		
1442 		this.uid = 'overlayId_' + glow.UID + '_' + (++idCounter);
1443 		instances[this.uid] = this; // useful for modal overlays?
1444 		
1445 		this._init(opts);
1446 		this._build(content);
1447 		this._bind();
1448 	}
1449 	glow.util.extend(Overlay, glow.ui.Widget);
1450 	
1451 	OverlayProto = Overlay.prototype;
1452 	
1453 	OverlayProto._init = function() {
1454 		WidgetProto._init.call(this);
1455 		
1456 		/**
1457 			@name glow.ui.Overlay#shown
1458 			@description True if the overlay is shown.
1459 				This is a read-only property to check the state of the overlay.
1460 			@type boolean
1461 		*/
1462 		this.shown = false;
1463 		
1464 		return this;
1465 	}
1466 	
1467 	OverlayProto.destroy = function() {
1468 		WidgetProto.destroy.call(this);
1469 		
1470 		delete instances[this.uid];
1471 	}
1472 	
1473 	OverlayProto._build = function(content) {
1474 		var that = this;
1475 		
1476 		WidgetProto._build.call(this, content);
1477 		
1478 		/*!debug*/
1479 			if (this.content.length < 1) {
1480 				glow.debug.warn('[ivalid argument] glow.ui.Overlay expects "content" argument to refer to an element that exists, no elements found for the content argument provided.');
1481 			}
1482 		/*gubed!*/
1483 		
1484 		// some browsers need to hide Flash when the overlay is shown (like non-mac opera and gecko 1.9 or less)
1485 		if (this._opts.hideWhenShown === undefined) { // need to make our own flash handler
1486 			ua = navigator.userAgent; // like "... rv:1.9.0.5) gecko ..."
1487 			/**
1488 				A function that returns a NodeList containing all elements that need to be hidden.
1489 				@name glow.ui.Overlay#_whatToHide
1490 				@private
1491 				@returns {glow.NodeList} Elements that need to be hidden when the overlay is shown.
1492 			 */
1493 			this._whatToHide = (
1494 				glow.env.opera && !/macintosh/i.test(ua)
1495 				|| /rv:1\.9\.0.*\bgecko\//i.test(ua)
1496 				|| glow.env.webkit && !/macintosh/i.test(ua)
1497 			)?
1498 				function() {
1499 					return glow('object, embed')/*.filter(function() {
1500 						return !that.container.contains(this); // don't hide elements that are inside the overlay
1501 					});*/
1502 				}
1503 				: function() { return glow(); }
1504 		}
1505 		else { // user provides their own info about what to hide
1506 			if (!this._opts.hideWhenShown) { // a value that is false
1507 				this._whatToHide = function() { return glow(); }
1508 			}
1509 			else if (typeof this._opts.hideWhenShown === 'function') { // a function
1510 				this._whatToHide = this._opts.hideWhenShown;
1511 			}
1512 			else if (this._opts.hideWhenShown.length !== undefined) { // nodelist or string?
1513 				this._whatToHide = function() { return glow('*').filter(this._opts.hideWhenShown); }
1514 			}
1515 		}
1516 		
1517 		//add IE iframe hack if needed, wrap content in an iFrame to prevent certain elements below from showing through
1518 		if (glow.env.ie) {
1519 			this._iframe = glow('<iframe src="javascript:\'\'" style="display:block;width:100%;height:1000px;margin:0;padding:0;border:none;position:absolute;top:0;left:0;filter:alpha(opacity=0);"></iframe>')
1520 			this._iframe.css('z-index', 0);
1521 			
1522 			this._iframe.insertBefore(this.content);
1523 		}
1524 		
1525 		this.content
1526 			.css('position', 'relative')
1527 			.css('z-index', 1)
1528 			.css('top', 0)
1529 			.css('left', 0);
1530 
1531 		return this;
1532 	}
1533 	
1534 	/**
1535 		@name glow.ui.Overlay#hideFlash
1536 		@method
1537 		@description Hides all Flash elements on the page, outside of the overlay.
1538 		This should only be neccessary on older browsers that cannot properly display
1539 		overlay content on top of Flash elements. On those browsers Glow will automatically
1540 		call this method for you in the onshow event, and will automatically call
1541 		showFlash for you in the afterhide event.
1542 	 */
1543 	OverlayProto.hideFlash = function() { /*debug*///console.log('hideFlash');
1544 		var toHide,
1545 			that = this,
1546 			hidBy = '';
1547 			
1548 		toHide = this._whatToHide();
1549 		
1550 		// multiple overlays may hide the same element
1551 		// flash elements keep track of which overlays have hidden them
1552 		// trying to hide a flash element more than once does nothing
1553 		for (var i = 0, leni = toHide.length; i < leni; i++) {
1554 			hidBy = (toHide.item(i).data('overlayHidBy') || '');
1555 			
1556 			if (hidBy === '') {
1557 				toHide.item(i).data('overlayOrigVisibility', toHide.item(i).css('visibility'));
1558 				toHide.item(i).css('visibility', 'hidden');
1559 			}
1560 			if (hidBy.indexOf('['+this.uid+']') === -1) {
1561 				toHide.item(i).data('overlayHidBy', hidBy + '['+this.uid+']');
1562 			}
1563 		}
1564 		
1565 		// things were hidden, make sure they get shown again
1566 		if (toHide.length && !that._doShowFlash) { // do this only once
1567 			that._doShowFlash = true;
1568 			that.on('afterHide', function() { /*debug*///console.log('callback');
1569 				that.showFlash();
1570 			});
1571 		}
1572 		
1573 		this._hiddenElements = toHide;
1574 	}
1575 	
1576 	/**
1577 		@name glow.ui.Overlay#showFlash
1578 		@method
1579 		@description Hides all Flash elements on the page, outside of the overlay.
1580 		If a Flash element has been hidden by more than one overlay, you must call
1581 		showFlash once for each time it was hidden before the Flash will finally appear.
1582 	 */
1583 	OverlayProto.showFlash = function() { /*debug*///console.log('showFlash');
1584 		var hidBy = '';
1585 		
1586 		if (!this._hiddenElements || this._hiddenElements.length === 0) { // this overlay has not hidden anything?
1587 			return;
1588 		}
1589 		
1590 		var toShow = this._hiddenElements;
1591 		
1592 		for (var i = 0, leni = toShow.length; i < leni; i++) {
1593 			hidBy = (toShow.item(i).data('overlayHidBy') || '');
1594 			
1595 			if (hidBy.indexOf('['+this.uid+']') > -1) { // I hid this
1596 				hidBy = hidBy.replace('['+this.uid+']', ''); // remove me from the list of hiders
1597 				toShow.item(i).data('overlayHidBy', hidBy);
1598 			}
1599 			
1600 			if (hidBy == '') { // no hiders lefts
1601 				toShow.item(i).css( 'visibility', toShow.item(i).data('overlayOrigVisibility') );
1602 			}
1603 		}
1604 	}
1605 	
1606 	/**
1607 		@name glow.ui.Overlay#event:show
1608 		@event
1609 		@description Fired when the overlay is about to appear on the screen, before any animation.
1610 
1611 			At this	point you can access the content of the overlay and make changes 
1612 			before it is shown to the user. If you prevent the default action of this
1613 			event (by returning false or calling event.preventDefault) the overlay 
1614 			will not show.
1615 			
1616 		@param {glow.events.Event} event Event Object
1617 	*/
1618 	
1619 	/**
1620 		@name glow.ui.Overlay#event:afterShow
1621 		@event
1622 		@description Fired when the overlay is showing to the user and any delay or 'show' animation is complete.
1623 
1624 			This event is ideal to assign focus to a particular part of	the overlay.
1625 			If you want to change content of the overlay before it appears, see the 
1626 			'show' event.
1627 			
1628 		@param {glow.events.Event} event Event Object
1629 	*/
1630 	
1631 	/**
1632 		@name glow.ui.Overlay#event:hide
1633 		@event
1634 		@description Fired when the overlay is about to hide.
1635 
1636 			If you prevent the default action of this event (by returning false or 
1637 			calling event.preventDefault) the overlay will not hide.
1638 			
1639 		@param {glow.events.Event} event Event Object
1640 	*/
1641 	
1642 	/**
1643 		@name glow.ui.Overlay#event:afterHide
1644 		@event
1645 		@description Fired when the overlay has fully hidden, after any delay or hiding animation has completed.
1646 		@param {glow.events.Event} event Event Object
1647 	*/
1648 	
1649 	// animations that can be referred to in setAnim by string.
1650 	// Each is an array of 2 item, one function to put the Overlay in an initial state
1651 	// for this animation, and one for the animation itself
1652 	var anims = {
1653 		slide: [
1654 			function(overlay) {
1655 				overlay.container.height(0);
1656 			},
1657 			function(isShow, callback) {
1658 				var anim,
1659 					container = this.container;
1660 					
1661 				if (isShow) {
1662 					anim = container.slideOpen(0.5).data('glow_slideOpen');
1663 				}
1664 				else {
1665 					anim = container.slideShut(0.5).data('glow_slideShut');
1666 				}
1667 				
1668 				anim.on('complete', callback);
1669 			}
1670 		],
1671 		fade: [
1672 			function(overlay) {
1673 				overlay.container.css('opacity', 0);
1674 			},
1675 			function(isShow, callback) {
1676 				var anim,
1677 					container = this.container;
1678 					
1679 				if (isShow) {
1680 					anim = container.fadeIn(0.5).data('glow_fadeIn');
1681 				}
1682 				else {
1683 					anim = container.fadeOut(0.5).data('glow_fadeOut');
1684 				}
1685 				
1686 				anim.on('complete', callback);
1687 			}
1688 		]
1689 	}
1690 	
1691 	/**
1692 		@name glow.ui.Overlay#setAnim
1693 		@function
1694 		@description Set the animation to use when showing and hiding this overlay.
1695 		@param {string|Array|Function|null} anim Anim to use.
1696 			At its simplest, this can be the string 'slide' or 'fade', to give
1697 			the overlay a fading/sliding animation.
1698 		
1699 			If this value is an animation definition, in the form of an array of
1700 			arguments to pass to the {@link glow.Anim} constructor, those values
1701 			will be used to create the show animation. The hide animation will
1702 			then be the reverse of the show. This is the easiest option if you
1703 			intend your show and hide animations to simply reverse one another.
1704 			
1705 			Alternatively, if you need more control over your show and hide
1706 			animations, you can provide a function.	This function will be called
1707 			whenever the overlay has its show or hide method invoked, and will
1708 			be provided a boolean (true meaning it's being shown, false meaning
1709 			it's being hidden), and a callback. You can then manage the animations
1710 			yourself within that function, and then invoke the callback when
1711 			either animation is complete. In your function, 'this' refers to the
1712 			overlay.
1713 			
1714 			Passing null will delete any previously set animation.
1715 		
1716 		@returns this
1717 	*/
1718 	OverlayProto.setAnim = function(anim) {
1719 		if (anim === null) {
1720 			delete this._animDef;
1721 			delete this._animator;
1722 		}
1723 		else if (typeof anim === 'string') {
1724 			anims[anim][0](this);
1725 			this._animator = anims[anim][1];
1726 		}
1727 		else if (typeof anim === 'function') {
1728 			this._animator = anim;
1729 		}
1730 		else {
1731 			this._animDef = anim;
1732 			this._animDef[2] = this._animDef[2] || {};
1733 			this._animDef[2].destroyOnComplete = false;
1734 		}
1735 		
1736 		return this;
1737 	}
1738 	
1739 	/**
1740 		@name glow.ui.Overlay#show
1741 		@function
1742 		@param {number} [delay=0] The delay before the overlay is shown.
1743 			By default, the overlay will show immediately. Specify a number of seconds to delay showing.
1744 			The event "afterShow" will be called after any delay and animation.
1745 		@description Displays the overlay after an optional delay period and animation.
1746 
1747 		@returns this
1748 	*/
1749 	OverlayProto.show = function(delay) {
1750 		//if (this.shown) { /*debug*///console.log('show ignored');
1751 		//	return this;
1752 		//}
1753 		var that = this;
1754 		
1755 		if ( !this.fire('show').defaultPrevented() ) {
1756 			if (this._timer) {
1757 				clearTimeout(this._timer);
1758 			}
1759 			
1760 			if (delay) {
1761 				this._timer = setTimeout(function() {
1762 					show.call(that);
1763 				}, delay * 1000);
1764 			}
1765 			else {
1766 				show.call(that);
1767 			}
1768 		}
1769 		
1770 		return this;
1771 	}
1772 	
1773 	function show() { /*debug*///console.log('show() curently '+this.state);
1774 		var that = this;
1775 		
1776 		// already being shown?
1777 		if (this.state === vis.SHOWING || this.state === vis.SHOWN) {
1778 			return;
1779 		}
1780 		
1781 		setShown(that, true);
1782 		
1783 		if (this._whatToHide) { this.hideFlash(); }
1784 		
1785 		if (this._animator) {
1786 			that.state = vis.SHOWING;
1787 			this._animator.call(this, true, function() { afterShow.call(that); });
1788 		}
1789 		else if (this._animDef) {
1790 			if (this._anim) { // is hiding?
1791 				this.state = vis.SHOWING;
1792 				this._anim.reverse();
1793 			}
1794 			else { // is hidden?
1795 				this.state = vis.SHOWING;
1796 				
1797 				// this same anim is reused (by reversing it) for showing and hiding
1798 				this._anim = this.container.anim(this._animDef[0], this._animDef[1], this._animDef[2]);
1799 				this._anim.on('complete', function() {
1800 					
1801 					if (that.state === vis.SHOWING) {
1802 						setShown(that, true);
1803 						afterShow.call(that);
1804 					} 
1805 					else if (that.state === vis.HIDING) {
1806 						setShown(that, false);
1807 						afterHide.call(that);
1808 					}
1809 				});
1810 			}
1811 			
1812 			this._anim.start();
1813 		}
1814 		else {
1815 			afterShow.call(this);
1816 		}
1817 	}
1818 	
1819 	function afterShow() { /*debug*///console.log('after show');
1820 		this.state = vis.SHOWN;
1821 		this.fire('afterShow');
1822 	}
1823 	
1824 	/**
1825 		@private
1826 		@function
1827 		@description Set the shown state & add/remove a class from the state element
1828 	*/
1829 	function setShown(overlay, shownState) {
1830 		var stateElm = overlay._stateElm;
1831 		
1832 		overlay.shown = shownState;
1833 		
1834 		if (shownState) {
1835 			stateElm.removeClass('hidden');
1836 			stateElm.addClass('shown');
1837 		}
1838 		else {
1839 			stateElm.removeClass('shown');
1840 			stateElm.addClass('hidden');
1841 		}
1842 	}
1843 	
1844 	function hide() { /*debug*///console.log('hide() curently '+this.state);
1845 		var that = this;
1846 		
1847 		if (this.state === vis.HIDING || this.state === vis.HIDDEN) {
1848 			return;
1849 		}
1850 		
1851 		if (this._animator) { // provided by user
1852 			this._animator.call(this, false, function() {
1853 				setShown(that, false);
1854 				afterHide.call(that);
1855 			});
1856 		}
1857 		else if (this._anim) { // generated by overlay
1858 			this.state = vis.HIDING;
1859 			this._anim.reverse();
1860 			this._anim.start();
1861 		}
1862 		else { // no animation
1863 			setShown(that, false);
1864 			afterHide.call(this);
1865 		}
1866 	}
1867 	
1868 	function afterHide() { /*debug*///console.log('after hide');
1869 		this.state = vis.HIDDEN;
1870 		this.fire('afterHide');
1871 	}
1872 	
1873 	/**
1874 		@name glow.ui.Overlay#hide
1875 		@function
1876 		@param {number} [delay=0] The delay before the overlay is shown.
1877 			By default, the overlay will show immediately. Specify a number of seconds to delay showing.
1878 			The event "afterShow" will be called after any delay and animation.
1879 		@description Hides the overlay after an optional delay period and animation
1880 
1881 		@returns this
1882 	*/
1883 	OverlayProto.hide = function(delay) {
1884 		//if (!this.shown) { /*debug*///console.log('hide ignored');
1885 		//	return this;
1886 		//}
1887 		
1888 		var that = this;
1889 		
1890 		if ( !this.fire('hide').defaultPrevented() ) {
1891 			if (this._timer) {
1892 				clearTimeout(this._timer);
1893 			}
1894 			
1895 			if (delay) {
1896 				this._timer = setTimeout(function() {
1897 					hide.call(that);
1898 				}, delay * 1000);
1899 			}
1900 			else {
1901 				hide.call(that);
1902 			}
1903 		}
1904 		
1905 		return this;
1906 	}
1907 
1908 	// export
1909 	glow.ui = glow.ui || {};
1910 	glow.ui.Overlay = Overlay;
1911 });
1912 Glow.provide(function(glow) {
1913 	var undefined, AutoSuggestProto,
1914 		Widget = glow.ui.Widget,
1915 		WidgetProto = Widget.prototype,
1916 		// this is used for HTML escaping in _format
1917 		tmpDiv = glow('<div></div>');
1918 	
1919 	/**
1920 		@name glow.ui.AutoSuggest
1921 		@extends glow.ui.Widget
1922 		@constructor
1923 		@description Create a menu that displays results filtered by a search term.
1924 			This widget can be easily linked to a text input via {@link glow.ui.AutoSuggest#linkToInput}
1925 			so results will be filtered by text entered by the user. This appears as a list of selectable
1926 			items below the input element (optional) which dynamically updates based on what
1927 			has been typed so far.
1928 			
1929 			By default, items where the search term matches the start of the item
1930 			(or its 'name' property) will be returned. You can change the
1931 			filtering behaviour via {@link glow.ui.AutoSuggest#setFilter setFilter}.
1932 			
1933 			The matched item (or its 'name' property) will be displayed with the matching
1934 			portion underlined. You can change the output via {@link glow.ui.AutoSuggest#setFormat setFormat}
1935 		
1936 		@param {Object} [opts] Options				
1937 			@param {number} [opts.width] Apply a width to the results list.
1938 				By default, the AutoSuggest is the full width of its containing element,
1939 				or the width of the input it's linked to if autoPositioning.
1940 			@param {number} [opts.maxResults] Limit the number of results to display.
1941 			@param {number} [opts.minLength=3] Minimum number of chars before search is executed
1942 				This prevents searching being performed until a specified amount of chars
1943 				have been entered.
1944 			@param {boolean} [opts.caseSensitive=false] Whether case is important when matching suggestions.
1945 				If false, the value passed to the filter will be made lowercase, a custom filter
1946 				must also lowercase the property it checks.
1947 			@param {boolean} [opts.activateFirst=true] Activate the first item when results appear?
1948 				If false, results with be shown with no active item.
1949 			@param {function|string} [opts.keyboardNav='arrow-y'] Alter the default keyboard behaviour.
1950 				This is the same as keyboardNav in {@link glow.ui.Focusable}.
1951 		
1952 		@example
1953 			// Make an input auto-complete from an array of tags for a recipe database
1954 			glow.ui.AutoSuggest()
1955 				.data(['Vegetarian', 'Soup', 'Sandwich', 'Wheat-free', 'Organic', 'etc etc'])
1956 				.linkToInput('#recipeTags');
1957 			
1958 		@example
1959 			// An AutoSuggest embedded in the page, rather than in an overlay
1960 			var myAutoSuggest = glow.ui.AutoSuggest()
1961 				.data('recipe.php?ingredients={val}')
1962 				.linkToInput('#username', {
1963 					// don't use an overlay, we'll add the autosuggest to the document outselves
1964 					useOverlay: false
1965 				});
1966 			
1967 			// add the results into the document
1968 			myAutoSuggest.container.appendTo('#results');
1969 			
1970 		@example
1971 			// Make an input suggest from an array of program names, where the
1972 			// whole string is searched rather than just the start
1973 			// When the item is clicked, we go to a url
1974 			new glow.ui.AutoSuggest().setFilter(function(item, val) {
1975 				return item.name.indexOf(val) !== -1;
1976 			}).data([
1977 				{name: 'Doctor Who', url: '...'},
1978 				{name: 'Eastenders', url: '...'},
1979 				{name: 'The Thick Of It', url: '...'},
1980 				// ...
1981 			]).linkToInput('#programSearch').on('select', function(event) {
1982 				location.href = event.selected.url;
1983 			});
1984 	*/
1985 	function AutoSuggest(opts) {
1986 		/*!debug*/
1987 			if (opts !== undefined && typeof opts !== 'object') {
1988 				glow.debug.warn('[wrong type] glow.ui.AutoSuggest expects object as "opts" argument, not ' + typeof opts + '.');
1989 			}
1990 		/*gubed!*/
1991 		opts = glow.util.apply({
1992 			minLength: 3,
1993 			keyboardNav: 'arrows-y',
1994 			activateFirst: true
1995 		}, opts);
1996 
1997 		Widget.call(this, 'AutoSuggest', opts);
1998 		this._init();
1999 	};
2000 	
2001 	glow.util.extend(AutoSuggest, Widget);
2002 	AutoSuggestProto = AutoSuggest.prototype;
2003 	
2004 	/**
2005 		@name glow.ui.AutoSuggest#_loading
2006 		@type boolean
2007 		@description True if the autosuggest is waiting for data.
2008 			This happens when getting data is async, has been requested but not returned.
2009 	*/
2010 	
2011 	/**
2012 		@name glow.ui.AutoSuggest#_pendingFind
2013 		@type string
2014 		@description Pending search string.
2015 			This is populated if find is called while the autoSuggest is _loading.
2016 	*/
2017 	
2018 	/**
2019 		@name glow.ui.AutoSuggest#_data
2020 		@type Object[]
2021 		@description Array of objects, the current datasource for this AutoSuggest.
2022 	*/
2023 	AutoSuggestProto._data = [];
2024 	
2025 	/**
2026 		@name glow.ui.AutoSuggest#_dataFunc
2027 		@type function
2028 		@description Function used for fetching data (potentially) async.
2029 	*/
2030 	
2031 	/**
2032 		@name glow.ui.AutoSuggest#_filter
2033 		@type function
2034 		@description The current filter function.
2035 	*/
2036 	AutoSuggestProto._filter = function(val, caseSensitive) {
2037 		var nameStart = this.name.slice(0, val.length);
2038 		nameStart = caseSensitive ? nameStart : nameStart.toLowerCase();
2039 		
2040 		return nameStart === val;
2041 	};
2042 	
2043 	/**
2044 		@name glow.ui.AutoSuggest#_format
2045 		@type function
2046 		@description The current format function.
2047 	*/
2048 	AutoSuggestProto._format = function(result, val) {
2049 		var text = tmpDiv.text(result.name).html(),
2050 			valStart = text.toLowerCase().indexOf( val.toLowerCase() ),
2051 			valEnd = valStart + val.length;
2052 		
2053 		// wrap the selected portion in <strong>
2054 		// This would be so much easier if it weren't for case sensitivity
2055 		if (valStart !== -1) {
2056 			text = text.slice(0, valStart) + '<em class="AutoSuggest-match">' + text.slice(valStart, valEnd) + '</em>' + text.slice(valEnd)
2057 		}
2058 		
2059 		return text;
2060 	};
2061 	
2062 	/**
2063 		@name glow.ui.AutoSuggest#focusable
2064 		@type glow.ui.Focusable
2065 		@description The focusable linked to this autosuggest.
2066 	*/
2067 	
2068 	// Widget lifecycle phases
2069 	AutoSuggestProto._init = function() {
2070 		WidgetProto._init.call(this);
2071 		// call _build
2072 		this._build();
2073 	}
2074 	
2075 	AutoSuggestProto._build = function() {
2076 		WidgetProto._build.call(this, '<ol></ol>');
2077 		
2078 		var opts = this._opts,
2079 			width = opts.width
2080 			content = this.content;
2081 		
2082 		this.focusable = content.focusable({
2083 			children: '> li',
2084 			keyboardNav: this._opts.keyboardNav,
2085 			setFocus: false,
2086 			activateOnHover: true
2087 		});
2088 		
2089 		width && this.container.width(width);
2090 		
2091 		// call _build
2092 		this._bind();
2093 	}
2094 	
2095 	/**
2096 		@private
2097 		@function
2098 		@description Select listener for the focusable.
2099 			'this' is the AutoSuggest
2100 	*/
2101 	function focusableSelectListener(e) {
2102 		return !this.fire('select', {
2103 			li: e.item,
2104 			item: e.item.data('as_data')
2105 		}).defaultPrevented();
2106 	}
2107 	
2108 	/**
2109 		@private
2110 		@function
2111 		@description Listens for focus moving in the focusable.
2112 			'this' is the autoSuggest
2113 	*/
2114 	function focusableChildActivate(e) {
2115 		var item = e.item,
2116 			focusable = this.focusable;
2117 	}
2118 	
2119 	function returnFalse() { return false; }
2120 	
2121 	AutoSuggestProto._bind = function() {
2122 		var focusable = this.focusable.on('select', focusableSelectListener, this)
2123 			.on('childActivate', focusableChildActivate, this);
2124 		
2125 		this._tie(focusable);
2126 		
2127 		// prevent focus moving on mouse down
2128 		this.container.on('mousedown', returnFalse);
2129 		
2130 		WidgetProto._bind.call(this);
2131 	}
2132 	
2133 	/**
2134 		@name glow.ui.AutoSuggest#setFilter
2135 		@function
2136 		@description Set the function used to filter the dataset for results.
2137 			Overwrite this to change the filtering behaviour.
2138 		
2139 		@param {function} filter Filter function.
2140 			Your function will be passed 2 arguments, the term entered by the user,
2141 			and if the search should be case sensitive. Return true to confirm a match.
2142 			
2143 			'this' will be the item in the dataset to check.
2144 			
2145 			If the search is case-insensitive, the term entered by the user is automatically
2146 			lowercased.
2147 		  
2148 			The default filter will return items where the search term matches the start of their 'name'
2149 			property. If the dataset is simply an array of strings, that string will be used instead of the 'name' property.
2150 		
2151 		@example
2152 			// Search the name property for strings that contain val
2153 			myAutoSuggest.setFilter(function(val, caseSensitive) {
2154 				var name = caseSensitive ? this.name : this.name.toLowerCase();
2155 				return name.indexOf(val) !== -1;
2156 			});
2157 			
2158 		@example
2159 			// Search the tags property for strings that contain val surrounded by pipe chars
2160 			// this.tags is like: |hello|world|foo|bar|
2161 			myAutoSuggest.setFilter(function(val, caseSensitive) {
2162 				var tags = caseSensitive ? this.tags : this.tags.toLowerCase();
2163 				return tags.indexOf('|' + val + '|') !== -1;
2164 			});
2165 			
2166 		@return this
2167 	*/
2168 	AutoSuggestProto.setFilter = function(filter) {
2169 		/*!debug*/
2170 			if (arguments.length !== 1) {
2171 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#setFilter expects 1 argument, not ' + arguments.length + '.');
2172 			}
2173 		/*gubed!*/
2174 		this._filter = filter;
2175 		return this;
2176 	};
2177 	
2178 	/**
2179 		@name glow.ui.AutoSuggest#setFormat
2180 		@function
2181 		@description Control how matches are output.
2182 			
2183 		@param {function} formatter Function to generate output.
2184 			The first param to your function will be the matched item from your data list.
2185 			The second param is the search value.
2186 			
2187 			Return an HTML string or glow.NodeList to display this item in the results
2188 			list. Ensure you escape any content you don't want treated as HTML.
2189 			
2190 		@returns this
2191 		
2192 		@example
2193 			// A username auto-complete
2194 			
2195 			// The data url returns a JSON object like [{name='JaffaTheCake', fullName:'Jake Archibald', photo:'JaffaTheCake.jpg'}, ...]
2196 			glow.ui.AutoSuggest().setFormat(function() {
2197 				// Format the results like <img src="JaffaTheCake.jpg" alt=""> Jake Archibald (JaffaTheCake)
2198 				return '<img src="' + data.photo + '" alt=""> ' + data.fullName + ' (' + data.name + ')';
2199 			}).data('userSearch.php?usernamePartial={val}').linkToInput('#username');
2200 	*/
2201 	AutoSuggestProto.setFormat = function(formatter) {
2202 		/*!debug*/
2203 			if (arguments.length !== 1) {
2204 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#setFormat expects 1 argument, not ' + arguments.length + '.');
2205 			}
2206 		/*gubed!*/
2207 		this._format = formatter;
2208 		return this;
2209 	};
2210 	
2211 	/**
2212 		@private
2213 		@function
2214 		@description Process the data into an acceptable format for #_data.
2215 		@param {glow.ui.AutoSuggest} autoSuggest
2216 		@param {Object[]|string[]|glow.net.XhrResponse} data
2217 			Array of strings will be converted into an array of objects like {name: val}
2218 			
2219 			glow.net.XhrResponse will be converted into Object[]|string[] via .json
2220 	*/
2221 	function populateData(autoSuggest, data) {
2222 		var i,
2223 			tmpData,
2224 			event = autoSuggest.fire('data', {data:data});
2225 
2226 		if ( !event.defaultPrevented() ) {
2227 			// a listener may have altered the data
2228 			data = event.data;
2229 
2230 			// if it's an XHR response, convert it to json
2231 			if (data instanceof glow.net.XhrResponse) {
2232 				data = data.json();
2233 			}
2234 			
2235 			if (typeof data[0] === 'string') {
2236 				tmpData = [];
2237 				i = data.length;
2238 				while (i--) {
2239 					tmpData[i] = { name: data[i] };
2240 				}
2241 				data = tmpData;
2242 			}
2243 			
2244 			/*!debug*/
2245 				if ( !data.push ) {
2246 					glow.debug.warn('[wrong type] glow.ui.Autosuggest data expected to be array, not ' + typeof data + '.');
2247 				}
2248 				else if (data.length && typeof data[0] !== 'object') {
2249 					glow.debug.warn('[wrong type] glow.ui.Autosuggest data expected to be array of objects, not array of ' + typeof data[0] + '.');
2250 				}
2251 			/*gubed!*/
2252 			
2253 			autoSuggest._data = data;
2254 		}
2255 	}
2256 	
2257 	
2258 	
2259 	/**
2260 		@private
2261 		@function
2262 		@description Create _dataFunc based on a custom function.
2263 		@param {glow.ui.AutoSuggest} autoSuggest Instance
2264 		@param {function} func Data fetching function provided by the user via #data
2265 	*/
2266 	function setDataFunction(autoSuggest, func) {
2267 		// create a new function for fetching data
2268 		autoSuggest._dataFunc = function(val) {
2269 			var input = autoSuggest.input,
2270 				bindOpts = autoSuggest._bindOpts,
2271 				loadingClass = (bindOpts && bindOpts.loadingClass) || '';
2272 			
2273 			// put us in the loading state and call the user's function
2274 			autoSuggest._loading = true;
2275 			input.addClass(loadingClass);
2276 			
2277 			// call the user's function, providing a callback
2278 			func.call(this, val, function(data) {
2279 				var pendingFind = autoSuggest._pendingFind;
2280 				autoSuggest._loading = false;
2281 				input.removeClass(loadingClass);
2282 				// populate data if we've been given some
2283 				data && populateData(autoSuggest, data);
2284 				if (pendingFind) {
2285 					performFind(autoSuggest, pendingFind);
2286 					autoSuggest._pendingFind = undefined;
2287 				}
2288 			});
2289 		}
2290 	}
2291 	
2292 	/**
2293 		@private
2294 		@function
2295 		@description Creates a data function to load a single url once.
2296 		@param url With no {val} placeholder.
2297 	*/
2298 	function singleLoadUrl(url) {
2299 		var dataFetched,
2300 			currentRequest;
2301 		
2302 		return function(val, callback) {
2303 			// if we've already fetched the data, just call back & return
2304 			if (dataFetched) {
2305 				return callback();
2306 			}
2307 			
2308 			// if we've already sent a request off, just let that one continue
2309 			if ( !currentRequest ) {				
2310 				currentRequest = glow.net.get(url).on('load', function(response) {
2311 					// set data for quick retrieval later
2312 					dataFetched = 1;
2313 					callback(response);
2314 				});
2315 			}
2316 		}
2317 	}
2318 	
2319 	/**
2320 		@private
2321 		@function
2322 		@description Creates a data function to load from a url each time a search is made.
2323 		@param url With {val} placeholder.
2324 	*/
2325 	function multiLoadUrl(url) {
2326 		var currentRequest;
2327 		
2328 		return function(val, callback) {
2329 			var processedUrl = glow.util.interpolate(url, {val:val});
2330 			
2331 			// abort any current request
2332 			currentRequest && currentRequest.abort();
2333 			currentRequest = glow.net.get(processedUrl).on('load', function(response) {
2334 				callback(response);
2335 			});
2336 		}
2337 	}
2338 	
2339 	/**
2340 		@name glow.ui.AutoSuggest#data
2341 		@function
2342 		@description Set the data or datasource to search.
2343 			This gives the AutoSuggest the data to search, or the means to fetch
2344 			the data to search.
2345 			
2346 		@param {string|string[]|Object[]|glow.net.Response|function} data Data or datasource.
2347 		
2348 			<p><strong>String URL</strong></p>
2349 			
2350 			A URL on the same domain can be provided, eg 'results.json?search={val}', where {val} is replaced
2351 			with the search term. If {val} is used, the URL if fetched on each search, otherwise it is only fetched
2352 			once on the first search.
2353 			
2354 			The result is a {@link glow.net.XhrResponse}, by default this is decoded as json. Use
2355 			the 'data' event to convert your incoming data from other types (such as XML).
2356 			
2357 			<p><strong>glow.net.XhrResponse</strong></p>
2358 			
2359 			This will be treated as a json response and decoded to string[] or Object[], see below.
2360 			
2361 			<p><strong>string[] or Object[] dataset</strong></p>
2362 			
2363 			An Array of strings can be provided. Each string will be converted to {name: theString}, leaving
2364 			you with an array of objects.
2365 			
2366 			An Array of objects can be provided, each object is an object that can be matched. By default
2367 			the 'name' property of these objects is searched to determine a match, but {@link glow.ui.AutoSuggest#filter filter} can
2368 			be used to change this.
2369 			
2370 			<p><strong>function</strong></p>
2371 			
2372 			Providing a function means you have total freedom over fetching the data
2373 			for your autoSuggest, sync or async.
2374 			
2375 			Your function will be called by the AutoSuggest whenever a {@link glow.ui.AutoSuggest#find find}
2376 			is performed, and will be passed 2 arguments: the search
2377 			string and a callback.
2378 			
2379 			You can fetch the data however you wish. Once you have the data, pass it
2380 			to the callback to complete the {@link glow.ui.AutoSuggest#find find}.
2381 			Until the callback is called, the AutoSuggest remains in a 'loading' state.
2382 			
2383 			`this` inside the function refers to the AutoSuggest instance.
2384 			
2385 			Your function will be called multiple times, ensure you cancel any existing
2386 			requests before starting a new one.
2387 			
2388 		@example
2389 			// providing a URL
2390 			myAutoSuggest.data('/search?text={val}');
2391 			
2392 		@example
2393 			// providing an array of program names
2394 			myAutoSuggest.data( ['Doctor Who', 'Eastenders', 'The Thick of it', 'etc etc'] );
2395 			
2396 		@example
2397 			// providing an object of user data
2398 			myAutoSuggest.data([
2399 				{name='JaffaTheCake', fullName:'Jake Archibald', photo:'JaffaTheCake.jpg'},
2400 				{name='Bobby', fullName:'Robert Cackpeas', photo:'Bobby.jpg'}
2401 				...
2402 			]);
2403 			
2404 		@example
2405 			// Getting the data via jsonp
2406 			var request;
2407 			
2408 			myAutoSuggest.data(function(val, callback) {
2409 				// abort previous request
2410 				request && request.abort();
2411 				
2412 				request = glow.net.getJsonp('http://blah.com/data?callback={callback}&val=' + val)
2413 					.on('load', function(data) {
2414 						callback(data);
2415 					})
2416 			});
2417 			
2418 		@returns this
2419 	*/
2420 	AutoSuggestProto.data = function(data) {
2421 		/*!debug*/
2422 			if (arguments.length !== 1) {
2423 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#data expects 1 argument, not ' + arguments.length + '.');
2424 			}
2425 		/*gubed!*/
2426 		if (typeof data === 'string') {
2427 			// look for urls without {val}, they get their data once & once only
2428 			if (data.indexOf('{val}') === -1) {
2429 				// replace data with function
2430 				data = singleLoadUrl(data);
2431 			}
2432 			// look for urls with {val}, they get their data on each search
2433 			else {
2434 				// replace data with function
2435 				data = multiLoadUrl(data);
2436 			}
2437 		}
2438 		
2439 		if (typeof data === 'function') {
2440 			setDataFunction(this, data);
2441 		}
2442 		else if (data.push) {
2443 			// clear any data functions set
2444 			this._dataFunc = undefined;
2445 			populateData(this, data);
2446 		}
2447 		
2448 		return this;
2449 	};
2450 	
2451 	/**
2452 		@private
2453 		@function
2454 		@description Generate the output of a find
2455 			
2456 		@param {glow.ui.AutoSuggest} autoSuggest
2457 		@param {Object[]} results Array of filtered results
2458 		@param {string} val The search string
2459 	*/
2460 	function generateOutput(autoSuggest, results, val) {
2461 		var content = autoSuggest.content,
2462 			resultsLen = results.length,
2463 			i = resultsLen,
2464 			listItem,
2465 			itemContent,
2466 			opts = autoSuggest._opts,
2467 			focusable = autoSuggest.focusable;
2468 		
2469 		focusable.active(false);
2470 		
2471 		// if we've got an overlay, we don't bother clearing the list,
2472 		// just hide the overlay to let it animate away nicely
2473 		if ( !resultsLen && autoSuggest.overlay ) {
2474 			autoSuggest._hideOverlay();
2475 			return;
2476 		}
2477 		
2478 		// remove any current results
2479 		content.children().destroy();
2480 		
2481 		while (i--) {
2482 			itemContent = autoSuggest._format( results[i], val );
2483 			listItem = glow('<li class="AutoSuggest-item"></li>')
2484 				.data( 'as_data', results[i] )
2485 				.prependTo(content);
2486 			
2487 			// append HTML or nodes
2488 			(typeof itemContent === 'string') ?
2489 				listItem.html(itemContent) :
2490 				listItem.append(itemContent);
2491 		}
2492 		
2493 		// Activate the focusable if we have results
2494 		if (resultsLen) {
2495 			opts.activateFirst && focusable.active(true);
2496 			// show & position our overlay
2497 			autoSuggest._showOverlay();
2498 		}
2499 		else {
2500 			autoSuggest._hideOverlay();
2501 		}
2502 	}
2503 	
2504 	/**
2505 		@private
2506 		@function
2507 		@description Performs the find operation without calling _dataFunc.
2508 			Or checking _loading or string length. These are done in #find.
2509 			
2510 		@param {glow.ui.AutoSuggest} autoSuggest
2511 		@param {string} str The search string
2512 	*/
2513 	function performFind(autoSuggest, str) {
2514 		var filteredResults = [],
2515 			filteredResultsLen = 0,
2516 			data = autoSuggest._data,
2517 			findEvent = autoSuggest.fire('find', {val: str}),
2518 			resultsEvent,
2519 			caseSensitive = autoSuggest._opts.caseSensitive;
2520 		
2521 		if ( !findEvent.defaultPrevented() ) {
2522 			// pick up any changes a listener has made to the find string
2523 			str = findEvent.val;
2524 			
2525 			str = caseSensitive ? str : str.toLowerCase();
2526 			
2527 			// start filtering the data
2528 			for (var i = 0, len = data.length; i < len; i++) {
2529 				if ( autoSuggest._filter.call(data[i], str, caseSensitive) ) {
2530 					filteredResults[ filteredResultsLen++ ] = data[i];
2531 					
2532 					// break if we have enough results now
2533 					if (filteredResultsLen === autoSuggest._opts.maxResults) {
2534 						break;
2535 					}
2536 				}
2537 			}
2538 			
2539 			// fire result event
2540 			resultsEvent = autoSuggest.fire('results', {results: filteredResults});
2541 			
2542 			if ( resultsEvent.defaultPrevented() ) {
2543 				filteredResults = [];
2544 			}
2545 			else {
2546 				// pick up any changes a listener has made to the results
2547 				filteredResults = resultsEvent.results
2548 			}
2549 			
2550 			// output results
2551 			generateOutput(autoSuggest, filteredResults, findEvent.val);
2552 		}
2553 	}
2554 	
2555 	/**
2556 		@name glow.ui.AutoSuggest#find
2557 		@function
2558 		@description Search the datasource for a given string
2559 			This fetches results from the datasource and displays them. This
2560 			may be an asyncrounous action if data needs to be fetched from
2561 			the server.
2562 		
2563 		@param {string} str String to search for
2564 			{@link glow.ui.AutoSuggest#filter AutoSuggest#filter} is used
2565 			to determine whether results match or not.
2566 			
2567 		@returns this
2568 	*/
2569 	AutoSuggestProto.find = function(str) {
2570 		/*!debug*/
2571 			if (arguments.length !== 1) {
2572 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#find expects 1 argument, not ' + arguments.length + '.');
2573 			}
2574 		/*gubed!*/
2575 		if (str.length >= this._opts.minLength) {
2576 			// refresh/load data if there's a function
2577 			this._dataFunc && this._dataFunc(str);
2578 			
2579 			// can't find if we're loading...
2580 			if (this._loading) {
2581 				// leave it here, _dataFunc will pick it up and call performFind later
2582 				this._pendingFind = str;
2583 			}
2584 			else {
2585 				performFind(this, str);
2586 			}
2587 		}
2588 		else {
2589 			this.hide();
2590 		}
2591 		return this;
2592 	};
2593 	
2594 	/**
2595 		@name glow.ui.AutoSuggest#hide
2596 		@function
2597 		@description Clear the results so the AutoSuggest is no longer visible
2598 			
2599 		@returns this
2600 	*/
2601 	AutoSuggestProto.hide = function() {
2602 		/*!debug*/
2603 			if (arguments.length !== 0) {
2604 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#hide expects 0 arguments, not ' + arguments.length + '.');
2605 			}
2606 		/*gubed!*/
2607 		clearTimeout(this._inputTimeout);
2608 		// generating empty output does the trick
2609 		generateOutput(this, [], '');
2610 		return this;
2611 	};
2612 	
2613 	/**
2614 		@name glow.ui.AutoSuggest#destroy
2615 		@function
2616 		@description Destroy the AutoSuggest.
2617 			Removes all events that cause the AutoSuggest to run. The input
2618 			element will remain on the page.
2619 	*/
2620 	AutoSuggestProto.destroy = function() {
2621 		/*!debug*/
2622 			if (arguments.length !== 0) {
2623 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#destroy expects 0 arguments, not ' + arguments.length + '.');
2624 			}
2625 		/*gubed!*/
2626 		this._data = undefined;
2627 		
2628 		// remove events from the input
2629 		this.input.detach('keypress', this._inputPress)
2630 			.detach('blur', this._inputBlur)
2631 			.detach('onbeforedeactivate', this._inputDeact);
2632 		
2633 		WidgetProto.destroy.call(this);
2634 	};
2635 	
2636 	/**
2637 		@name glow.ui.AutoSuggest#disabled
2638 		@function
2639 		@description Enable/disable the AutoSuggest, or get the disabled state
2640 			When the AutoSuggest is disabled it is not shown.
2641 			
2642 		@param {boolean} [newState] Disable the AutoSuggest?
2643 			'false' will enable a disabled AutoSuggest.
2644 		
2645 		@returns {glow.ui.AutoSuggest|boolean}
2646 			Returns boolean when getting, AutoSuggest when setting
2647 	*/
2648 	
2649 	/**
2650 		@name glow.ui.AutoSuggest#event:data
2651 		@event
2652 		@description Fired when the dataset changes
2653 			This can be the result of calling {@link glow.ui.AutoSuggest#data data} or
2654 			new data has been fetched from the server.
2655 			
2656 			You can use this event to intercept and transform data into the
2657 			correct JSON format.
2658 			
2659 			Cancel this event to ignore the new dataset, and continue
2660 			with the current one.
2661 		@param {glow.events.Event} event Event Object
2662 		@param {*} event.data The new dataset
2663 			You can modify / overwrite this property to alter the dataset.
2664 			
2665 			The type of this object depends on the data source and other listeners
2666 			which may have overwritten / changed the original data.
2667 			
2668 		@example
2669 			myAutoSuggest.data('data.xml?search={val}').on('data', function(event) {
2670 				// When providing a url to .data(), event.data is a glow.net.XhrResponse object 
2671 				// Note: xmlToJson is not a function defined by Glow
2672 				event.data = xmlToJson( event.data.xml() );
2673 			});
2674 	*/
2675 	
2676 	/**
2677 		@name glow.ui.AutoSuggest#event:results
2678 		@event
2679 		@description Fired when the dataset has been filtered but before HTML is output
2680 			You can use this event to sort the dataset and/or add additional items
2681 			
2682 			Cancelling this event is equivalent to setting event.results to an
2683 			empty array.
2684 		@param {glow.events.Event} event Event Object
2685 		@param {string[]|Object[]} event.results The filtered dataset
2686 			You can modify / overwrite this property to alter the results
2687 			
2688 		@example
2689 			myAutoSuggest.on('results', function(event) {
2690 				// sort results by an 'author' property
2691 				event.results = event.results.sort(function(a, b) {
2692 					return a.author > b.author ? 1 : -1;
2693 				});
2694 				
2695 				// Add a 'More...' item to the data set
2696 				event.results.push( {name:'More...'} );
2697 				
2698 				// Behaviour will be added into the 'select' listener to handle what
2699 				// happens when 'More...' is selected
2700 			});
2701 	*/
2702 	
2703 	/**
2704 		@name glow.ui.AutoSuggest#event:select
2705 		@event
2706 		@description Fired when an item in the AutoSuggest is selected.
2707 			You can use this event to react to the user interacting with
2708 			the AutoSuggest
2709 			
2710 			Cancel this event to prevent the default click action.
2711 		@param {glow.events.Event} event Event Object
2712 		@param {string|Object} event.item The item in the dataset that was selected
2713 		@param {glow.NodeList} event.li The list item in the AutoSuggest that was selected
2714 			
2715 		@example
2716 			myAutoSuggest.on('select', function(event) {
2717 				// this assumes our data objects have a 'url' property
2718 				loaction.href = event.item.url;
2719 			});
2720 	*/
2721 	
2722 	/**
2723 		@name glow.ui.AutoSuggest#event:find
2724 		@event
2725 		@description Fired when a search starts.
2726 			Cancel this event to prevent the search.
2727 		
2728 		@param {glow.events.Event} event Event Object.
2729 		@param {string} event.val The search string.
2730 			You can set this to another value if you wish.
2731 	*/
2732 	
2733 	// EXPORT
2734 	glow.ui.AutoSuggest = AutoSuggest;
2735 });
2736 Glow.provide(function(glow) {
2737 	var undefined,
2738 		AutoSuggestProto = glow.ui.AutoSuggest.prototype;
2739 	
2740 	/**
2741 		@name glow.ui.AutoSuggest#bindOpts
2742 		@type Object
2743 		@description The options object passed into #bindInput, with defaults added.
2744 	*/
2745 	
2746 	/**
2747 		@name glow.ui.AutoSuggest#input
2748 		@type glow.NodeList
2749 		@description Refers to the input element to which this is linked to, or an empty NodeList.
2750 			Link an input to an AutoSuggest using {@link glow.ui.AutoSuggest#bindInput bindInput}.
2751 	*/
2752 	AutoSuggestProto.input = glow();
2753 	
2754 	/**
2755 		@name glow.ui.AutoSuggest#overlay
2756 		@type glow.ui.Overlay
2757 		@description The overlay linked to this autosuggest.
2758 			The Overlay is created when {@link glow.ui.AutoSuggest#bindInput bindInput} is
2759 			called.
2760 	*/
2761 	
2762 	/**
2763 		@name glow.ui.AutoSuggest#_inputPress
2764 		@private
2765 		@function
2766 		@description Listener for input's keypress event.
2767 			'this' is the AutoSuggest.
2768 			
2769 			Needed to make this pseudo-private so we could remove the listener later
2770 	*/
2771 	function inputPress(e) {
2772 		var autoSuggest = this,
2773 			focusable = autoSuggest.focusable,
2774 			focusableActive,
2775 			focusableIndex = focusable.activeIndex,
2776 			childrenLength;
2777 			
2778 		// we only care about printable chars and keys that modify input
2779 		if ( e.keyChar || e.key === 'delete' || e.key === 'backspace' ) {
2780 			// look out for printable chars going into the input
2781 			clearTimeout(autoSuggest._inputTimeout);
2782 			
2783 			autoSuggest._inputTimeout = setTimeout(function() {
2784 				autoSuggest.find( getFindValue(autoSuggest) );
2785 			}, autoSuggest._bindOpts.delay * 1000);
2786 		}
2787 		else {
2788 			focusableActive = focusable.active();
2789 			switch (e.key) {
2790 				case 'escape':
2791 					autoSuggest.hide();
2792 					deleteSelectedText(autoSuggest);
2793 					return false;
2794 				case 'up':
2795 					// Is up being pressed on the first item?
2796 					if (focusableActive && !focusableIndex) {
2797 						// deactivate the focusable
2798 						focusable.active(false);
2799 						deleteSelectedText(autoSuggest);
2800 						return false;
2801 					}
2802 					// I'm deliberately not breaking here, want to capture both up & down keys in the next case
2803 				case 'down':
2804 					if ( !focusableActive && (childrenLength = autoSuggest.content.children().length) ) {
2805 						// if the focusable isn't active, activate the first/last item
2806 						focusable.active(e.key == 'up' ? childrenLength - 1 : 0);
2807 						e.stopPropagation();
2808 						return false;
2809 					}
2810 			}
2811 		}
2812 	}
2813 	AutoSuggestProto._inputPress = inputPress;
2814 	
2815 	/**
2816 		@private
2817 		@function
2818 		@description Gets the value to find from the input.
2819 		@returns The value to find.
2820 			This is the same as the input value unless delimiters are used
2821 	*/
2822 	function getFindValue(autoSuggest) {
2823 		var input = autoSuggest.input,
2824 			delim = autoSuggest._bindOpts.delim,
2825 			val = input.val(),
2826 			lastDelimPos,
2827 			caretPos;
2828 		
2829 		// deal with delims
2830 		if (delim) {
2831 			caretPos = getCaretPosition(autoSuggest);
2832 			// get the text before the caret
2833 			val = val.slice(0, caretPos);
2834 			// is there a delimiter before the caret?
2835 			lastDelimPos = val.lastIndexOf(delim);
2836 			// if so, ignore the bits before the caret
2837 			if (lastDelimPos !== -1) {
2838 				val = val.slice( val.lastIndexOf(delim) + delim.length );
2839 			}
2840 		}
2841 		
2842 		return glow.util.trim(val);
2843 	}
2844 	
2845 	/**
2846 		@name glow.ui.AutoSuggest#_inputBlur
2847 		@private
2848 		@function
2849 		@description Listener for input's blur event.
2850 			'this' is the AutoSuggest.
2851 			
2852 			Needed to make this pseudo-private so we could remove the listener later
2853 	*/
2854 	function inputBlur() {
2855 		this.hide();
2856 	}
2857 	AutoSuggestProto._inputBlur = inputBlur;
2858 	
2859 	/**
2860 		@name glow.ui.AutoSuggest#_inputDeact
2861 		@private
2862 		@function
2863 		@description Listener for input's beforedeactivate event.
2864 			'this' is the AutoSuggest.
2865 			
2866 			Prevents IE from bluring the input element when the autosuggest is clicked.
2867 			
2868 			Needed to make this pseudo-private so we could remove the listener later
2869 	*/
2870 	function inputDeact(e) {
2871 		if ( this.container.contains( e.related ) ) {
2872 			return false;
2873 		}
2874 	}
2875 	AutoSuggestProto._inputDeact = inputDeact;
2876 	
2877 	/**
2878 		@private
2879 		@function
2880 		@description Listener for AutoSuggest's select event if opts.autoComplete is true
2881 			This creates the autoComplete behaviour.
2882 			'this' is the AutoSuggest.
2883 	*/
2884 	function completeSelectListener(event) {
2885 		completeInput(this.hide(), event.item.name);
2886 		makeSelection(this, this.input.val().length);
2887 	}
2888 	
2889 	/**
2890 		@private
2891 		@function
2892 		@description Listener for focusable's childActivate event if opts.autoComplete is true.
2893 			This updates the text as the user cycles through items.
2894 		
2895 			'this' is the AutoSuggest
2896 	*/
2897 	function focusablechildActivate(event) {
2898 		if (event.method !== 'hover') {
2899 			completeInput(this, event.item.data('as_data').name, true);
2900 		}
2901 	}
2902 	
2903 	/**
2904 		@private
2905 		@function
2906 		@description Autocomplete value in the input.
2907 		@param {glow.ui.AutoSuggest} autoSuggest
2908 		@param {string} newVal Value to complete to
2909 		@param {boolean} [select=false] Highlight the completed portion?
2910 			This is used while cycling through values
2911 	*/
2912 	function completeInput(autoSuggest, newVal, select) {
2913 		deleteSelectedText(autoSuggest);
2914 
2915 		var input = autoSuggest.input,
2916 			oldVal = input.val(),
2917 			caretPos = getCaretPosition(autoSuggest),
2918 			rangeStart = caretPos,
2919 			rangeEnd = newVal.length,
2920 			delim = autoSuggest._bindOpts.delim,
2921 			lastDelimPos,
2922 			firstValPart = '';
2923 		
2924 		// we don't want to overwrite the whole thing if we're using delimiters
2925 		if (delim) {
2926 			lastDelimPos = oldVal.slice(0, caretPos).lastIndexOf(delim);
2927 			if (lastDelimPos !== -1) {
2928 				firstValPart = oldVal.slice(0, lastDelimPos) + delim + ' ';
2929 			}
2930 			newVal = firstValPart + newVal + delim + ' ';
2931 			rangeEnd = newVal.length;
2932 			newVal += oldVal.slice(caretPos);
2933 		}
2934 		input.val(newVal);
2935 		select && makeSelection(autoSuggest, rangeStart, rangeEnd);
2936 	}
2937 	
2938 	
2939 	/**
2940 		@private
2941 		@function
2942 		@description Make a selection in the bound input
2943 		
2944 		@param {glow.ui.AutoSuggest} autoSuggest
2945 		@param {number} start Start point of the selection
2946 		@param {number} [end=start] End point of the selection
2947 	*/
2948 	function makeSelection(autoSuggest, start, end) {
2949 		end = (end === undefined) ? start : end;
2950 		
2951 		var inputElm = autoSuggest.input[0],
2952 			character = 'character',
2953 			range;
2954 
2955 		if (!window.opera && inputElm.createTextRange) { // IE
2956 			range = inputElm.createTextRange();
2957 			range.moveStart(character, start);
2958 			range.moveEnd(character, end - inputElm.value.length);
2959 			range.select();
2960 		}
2961 		else { // moz, saf, opera
2962 			inputElm.select();
2963 			inputElm.selectionStart = start;
2964 			inputElm.selectionEnd = end;
2965 		}
2966 	}
2967 	
2968 	/**
2969 		@private
2970 		@function
2971 		@description Get the caret position within the input
2972 	*/
2973 	function getCaretPosition(autoSuggest) {
2974 		var inputElm = autoSuggest.input[0],
2975 			r;
2976 		
2977 		if (glow.env.ie) { // IE
2978 			range = document.selection.createRange();
2979 			range.collapse();
2980 			range.setEndPoint( 'StartToStart', inputElm.createTextRange() );
2981 			r = range.text.length;
2982 		}
2983 		else { // moz, saf, opera
2984 			r = inputElm.selectionStart;
2985 		}
2986 		
2987 		return r;
2988 	}
2989 	
2990 	/**
2991 		@private
2992 		@function
2993 		@description Delete the currently selected text in the input.
2994 			This is used when esc is pressed and in focusablechildActivate
2995 	*/
2996 	function deleteSelectedText(autoSuggest) {
2997 		var inputElm = autoSuggest.input[0],
2998 			val = inputElm.value,
2999 			selectionStart;
3000 		
3001 		if (glow.env.ie) { // IE
3002 			document.selection.createRange().text = '';
3003 		}
3004 		else { // others
3005 			selectionStart = inputElm.selectionStart;
3006 			inputElm.value = val.slice(0, selectionStart) + val.slice(inputElm.selectionEnd);
3007 			inputElm.selectionStart = selectionStart;
3008 		}
3009 	}
3010 	
3011 	/**
3012 		@name glow.ui.AutoSuggest#_showOverlay
3013 		@private
3014 		@function
3015 		@description Shows the overlay, if one is attached.
3016 			Also positions the overlay according to options set.
3017 	*/
3018 	AutoSuggestProto._showOverlay = function() {
3019 		var overlay = this.overlay,
3020 			autoSuggestOpts = this._opts,
3021 			bindOpts = this._bindOpts,
3022 			input = this.input,
3023 			inputOffset;
3024 		
3025 		if (!overlay) { return; }
3026 		
3027 		if (!autoSuggestOpts.width) {
3028 			this.container.width( input[0].offsetWidth );
3029 		}
3030 		
3031 		if (bindOpts.autoPosition) {
3032 			inputOffset = input.offset();
3033 			overlay.container.css({
3034 				top: inputOffset.top + input[0].offsetHeight,
3035 				left: inputOffset.left
3036 			})
3037 		}
3038 		
3039 		overlay.show();
3040 	}
3041 	
3042 	/**
3043 		@name glow.ui.AutoSuggest#_hideOverlay
3044 		@private
3045 		@function
3046 		@description Hide the overlay, if one is attached.
3047 	*/
3048 	AutoSuggestProto._hideOverlay = function() {
3049 		var overlay = this.overlay;
3050 		overlay && overlay.hide();
3051 	}
3052 	
3053 	/**
3054 		@name glow.ui.AutoSuggest#bindInput
3055 		@function
3056 		@description Link this autosuggest to a text input.
3057 			This triggers {@link glow.ui.AutoSuggest#find} when the value in
3058 			the input changes.
3059 			
3060 			The AutoSuggest is placed in an Overlay beneath the input and displayed
3061 			when results are found.
3062 			
3063 			If the input loses focus, or esc is pressed,
3064 			the Overlay will be hidden and results cleared.
3065 			
3066 		@param {selector|glow.NodeList|HTMLElement} input Test input element
3067 		
3068 		@param {Object} [opts] Options
3069 		@param {selector|glow.NodeList} [opts.appendTo] Add the AutoSuggest somewhere in the document rather than an {@link glow.ui.Overlay Overlay}
3070 			By default, the AutoSuggest will be wrapped in an {@link glow.ui.Overlay Overlay} and
3071 			appended to the document's body.
3072 		@param {boolean} [opts.autoPosition=true] Place the overlay beneath the input
3073 			If false, you need to position the overlay's container manually. It's
3074 			recommended to do this as part of the Overlay's show event, so the
3075 			position is updated each time it appears.
3076 		@param {boolean} [opts.autoComplete=true] Update the input when an item is highlighted & selected.
3077 			This will complete the typed text with the result matched.
3078 			
3079 			You can create custom actions by listening for the
3080 			{@link glow.ui.AutoSuggest#event:select 'select' event}
3081 		@param {string} [opts.delim] Delimiting char(s) for selections.
3082 			When defined, the input text will be treated as multiple values,
3083 			separated by this string (with surrounding spaces ignored).
3084 		@param {number} [opts.delay=0.5] How many seconds to delay before searching.
3085 			This prevents searches being made on each key press, instead it
3086 			waits for the input to be idle for a given number of seconds.
3087 		@param {string} [opts.anim] Animate the Overlay when it shows/hides.
3088 			This can be any parameter accepted by {@link glow.ui.Overlay#setAnim Overlay#setAnim}.
3089 		@param {string} [opts.loadingClass] Class name added to the input while data is being loaded.
3090 			This can be used to change the display of the input element while data is being
3091 			fetched from the server. By default, a spinner is displayed in the input.
3092 		
3093 		
3094 		@returns this
3095 	*/
3096 	AutoSuggestProto.bindInput = function(input, opts) {
3097 		/*!debug*/
3098 			if (arguments.length < 1 || arguments.length > 2) {
3099 				glow.debug.warn('[wrong count] glow.ui.AutoSuggest#bindInput expects 1 or 2 arguments, not ' + arguments.length + '.');
3100 			}
3101 			if (opts !== undefined && typeof opts !== 'object') {
3102 				glow.debug.warn('[wrong type] glow.ui.AutoSuggest#bindInput expects object as "opts" argument, not ' + typeof opts + '.');
3103 			}
3104 		/*gubed!*/
3105 		var bindOpts = this._bindOpts = glow.util.apply({
3106 				autoPosition: true,
3107 				autoComplete: true,
3108 				delay: 0.5,
3109 				loadingClass: 'glow200b1-AutoSuggest-loading'
3110 			}, opts),
3111 			appendTo = bindOpts.appendTo,
3112 			container = this.container,
3113 			overlay,
3114 			autoSuggestOpts = this._opts;
3115 			
3116 		// if autocomplete isn't turned off, the browser doesn't let
3117 		// us hear about up & down arrow presses
3118 		this.input = glow(input).attr('autocomplete', 'off')
3119 			.on('keypress', inputPress, this)
3120 			.on('blur', inputBlur, this)
3121 			.on('beforedeactivate', inputDeact, this);
3122 		
3123 		if (bindOpts.autoComplete) {
3124 			this.on('select', completeSelectListener, this)
3125 				.focusable.on('childActivate', focusablechildActivate, this);
3126 		}
3127 		
3128 		// add to document, or...
3129 		if (appendTo) {
3130 			glow(appendTo).append(container);
3131 		}
3132 		// ...make overlay
3133 		else {
3134 			this.overlay = overlay = new glow.ui.Overlay(container)
3135 				.on('hide', overlayHide, this)
3136 				.on('afterShow', overlayAfterShow, this)
3137 				.hide();
3138 			
3139 			// the overlay will reactivate the focusable when needed
3140 			this.focusable.disabled(true);
3141 			
3142 			overlay.container.appendTo(document.body);
3143 			
3144 			// use alternate slide anim
3145 			if (bindOpts.anim === 'slide') {
3146 				bindOpts.anim = altSlideAnim;
3147 			}
3148 			
3149 			bindOpts.anim && overlay.setAnim(bindOpts.anim);
3150 			
3151 			this._tie(overlay);
3152 		}
3153 		
3154 		return this;
3155 	};
3156 	
3157 	/**
3158 		@private
3159 		@function
3160 		@description Alternative slide animation.
3161 			The AutoSuggest uses a different style of slide animation to the
3162 			usual Overlay, this creates it.
3163 	*/
3164 	function altSlideAnim(isShow, callback) {
3165 		var anim,
3166 			container = this.container,
3167 			animOpts = {
3168 				lockToBottom: true
3169 			};
3170 		
3171 		if (isShow) {
3172 			container.height(0);
3173 			anim = container.slideOpen(0.5, animOpts).data('glow_slideOpen')
3174 		}
3175 		else {
3176 			anim = container.slideShut(0.5, animOpts).data('glow_slideShut');
3177 		}
3178 		
3179 		anim.on('complete', callback);
3180 	}
3181 	
3182 	/**
3183 		@private
3184 		@function
3185 		@description Listener for overlay hide.
3186 			'this' is the autoSuggest.
3187 			
3188 			This stops the focusable being interactive during its hide & show animation.
3189 	*/
3190 	function overlayHide() {
3191 		this.focusable.disabled(true);
3192 	}
3193 	
3194 	/**
3195 		@private
3196 		@function
3197 		@description Listener for overlay show.
3198 			'this' is the autoSuggest
3199 	*/
3200 	function overlayAfterShow() {
3201 		var focusable = this.focusable;
3202 		
3203 		focusable.disabled(false);
3204 		
3205 		if (this._opts.activateFirst) {
3206 			focusable.active(true);
3207 		}
3208 	}
3209 });
3210 Glow.provide(function(glow) {
3211 	var undefined,
3212 		CarouselPaneProto,
3213 		WidgetProto = glow.ui.Widget.prototype;
3214 	
3215 	/**
3216 		@name glow.ui.CarouselPane
3217 		@class
3218 		@extends glow.ui.Widget
3219 		@description Create a pane of elements that scroll from one to another.
3220 			This is a component of Carousel.
3221 			
3222 		@param {glow.NodeList|selector|HTMLElement} container Container of the carousel items.
3223 			The direct children of this item will be treated as carousel items. They will
3224 			be positioned next to each other horizontally.
3225 			
3226 			Each item takes up the same horizontal space, equal to the width of the largest
3227 			item (including padding and border) + the largest of its horizontal margins (as set in CSS).
3228 			
3229 			The height of the container will be equal to the height of the largest item (including
3230 			padding and border) + the total of its vertical margins.
3231 			
3232 		@param {object} [opts] Options
3233 			@param {number} [opts.duration=0.2] Duration of scrolling animations in seconds.
3234 			@param {string|function} [opts.tween='easeBoth'] Tween to use for animations.
3235 				This can be a property name of {@link glow.tweens} or a tweening function.
3236 			
3237 			@param {boolean | number} [opts.step=1] Number of items to move at a time.
3238 				If true, the step will be the same size as the spotlight.
3239 			@param {boolean} [opts.loop=false] Loop the carousel from the last item to the first.
3240 			@param {boolean} [opts.page=false] Keep pages in sync by adding space to the end of the carousel.
3241 				Spaces don't exist as physical HTML elements, but simply a gap from the last item
3242 				to the end.
3243 			
3244 			@param {number} [opts.spotlight] The number of items to treat as main spotlighted items.
3245 				A carousel may be wide enough to display 2 whole items, but setting
3246 				this to 1 will result in the spotlight item sitting in the middle, with
3247 				half of the previous item appearing before, and half the next item
3248 				appearing after.
3249 				
3250 				By default, this is the largest number of whole items that can exist in
3251 				the width of the container. Any remaining width will be used to partially
3252 				show the previous/next item.
3253 				
3254 		@example
3255 			new glow.ui.CarouselPane('#carouselItems', {
3256 				duration: 0.4,
3257 				step: 2,
3258 				loop: true
3259 			});
3260 	*/
3261 	function CarouselPane(container, opts) {
3262 		/*!debug*/
3263 			if (!container) {
3264 				glow.debug.warn('[wrong count] glow.ui.CarouselPane - argument "container" is required.');
3265 				return;
3266 			}
3267 			
3268 			if (!container || glow(container).length === 0) {
3269 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - "'+container+'" is not a valid element specifier for the container.');
3270 			}
3271 			
3272 			if (opts && opts.spotlight && opts.step && opts.spotlight < opts.step && opts.step !== true) {
3273 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + opts.step +') cannot be greater than opts.spotlight ('+ opts.spotlight + ').');
3274 			}
3275 			
3276 			if (opts && opts.spotlight && opts.step && opts.page && opts.spotlight !== opts.step && opts.step !== true) {
3277 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + opts.step +') cannot be different than opts.spotlight ('+ opts.spotlight + ') if opts.page is true.');
3278 			}
3279 		/*gubed!*/
3280 		
3281 		var that = this;
3282 		
3283 		opts = glow.util.apply({
3284 			duration: 0.2,
3285 			tween: 'easeBoth',
3286 			step: 1,
3287 			loop: false,
3288 			page: false, // add a gap?
3289 			axis: 'x'    // either 'x' or 'y'
3290 		}, opts || {});
3291 
3292 		glow.ui.Widget.call(this, 'CarouselPane', opts);
3293 		
3294 		
3295 		if (glow(container).length > 0) { this._init(container, opts); }
3296 	};
3297 	
3298 	glow.util.extend(CarouselPane, glow.ui.Widget); // CarouselPane is a Widget
3299 	CarouselPaneProto = CarouselPane.prototype;     // shortcut
3300 	
3301 	
3302 	
3303 	/**
3304 		Tracks the order and location of all items, including cloned items.
3305 		@private
3306 		@constructor
3307 		@param {glow.NodeList} nodeList The real items to track.
3308 	 */
3309 	function ItemList(nodeList) {
3310 		var thisMeta;
3311 		
3312 		this.range = {min: 0, max: 0};
3313 		this.items = {};
3314 		this.meta = {};
3315 		
3316 		for (var i = 0, leni = nodeList.length; i < leni; i++) {
3317 			this.addItem(i, nodeList.item(i));
3318 		}
3319 	}
3320 	
3321 	ItemList.prototype.addItem = function(index, item, meta) {/*debug*///console.log('ItemList.prototype.addItem('+index+')');
3322 		this.range.min = Math.min(this.range.min, index);
3323 		this.range.max = Math.max(this.range.max, index);
3324 		
3325 		this.items[index] = item;
3326 		this.meta[index] = meta || {};
3327 	}
3328 	
3329 	ItemList.prototype.addMeta = function(index, meta) {/*debug*///console.log('ItemList.prototype.addMeta('+index+', '+meta.offset+')');
3330 		if (this.meta[index]) {
3331 			this.meta[index] = glow.util.apply(this.meta[index], meta);
3332 		}
3333 	}
3334 	
3335 	ItemList.prototype.place = function(top, left) {
3336 		// TODO styleName = this._geom[1]
3337 		for (var p in this.items) {
3338 			if (top !== undefined ) this.items[p].css('top', top);
3339 			this.items[p].css('left', (left === undefined)? this.meta[p].offset : left);
3340 		}
3341 	}
3342 	
3343 	ItemList.prototype.dump = function(c) {
3344 		if (typeof console !== 'undefined') {
3345 			for (var i = c._itemList.range.min, maxi = c._itemList.range.max; i <= maxi; i++) {
3346 				if (c._itemList.meta[i]) {
3347 					console.log('>> '+ i + ': ' + (c._itemList.meta[i].isClone? 'clone':'real') + ' at ' + c._itemList.meta[i].offset + ' ' + c._itemList.items[i][0].children[0].alt);
3348 				}
3349 				else {
3350 					console.log('>> '+ i + ': ' + c._itemList.meta[i]);
3351 				}
3352 			}
3353 		}
3354 	}
3355 	
3356 	ItemList.prototype.swap = function(index1, index2) { /*debug*///console.log('ItemList.prototype.swap('+index1+', '+index2+')');
3357 		this.items[index1].css('left', this.meta[index2].offset);
3358 		this.items[index2].css('left', this.meta[index1].offset);
3359 	}
3360 	
3361 	CarouselPaneProto._init = function(container) { /*debug*///console.log('CarouselPaneProto._init');
3362 		WidgetProto._init.call(this);
3363 		
3364 		// used value vs configured value (they may not be the same). Might be set to spotlight capacity, in _build.
3365 		this._step = this._opts.step;
3366 		
3367 		this._geom = (this._opts.axis === 'y')? ['height', 'top'] : ['width', 'left'];
3368 		
3369 		/**
3370 			@name glow.ui.CarouselPane#stage
3371 			@type glow.NodeList
3372 			@description The container passed in to the constructor for glow.ui.CarouselPane.
3373 		*/
3374 		this.stage = glow(container).item(0);
3375 
3376 		this._focusable = this.stage.focusable( {children: '> *', loop: true, setFocus: true} );
3377 		
3378 		
3379 		// what would have been the "content" of this widget, is named "viewport"
3380 		this._viewport = glow('<div class="CarouselPane-viewport"></div>');
3381 		glow(this.stage).wrap(this._viewport);
3382 		
3383 		/**
3384 			@name glow.ui.CarouselPane#items
3385 			@type glow.NodeList
3386 			@description Carousel items.
3387 				This is the same as `myCarouselPane.stage.children()`
3388 		*/
3389 		this.items = this.stage.children();
3390 		this._itemList = new ItemList(this.items);
3391 		
3392 		if (this._opts.spotlight > this.items.length) {
3393 			/*!debug*/
3394 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.spotlight (' + this._opts.spotlight +') cannot be greater than the number of items ('+ this.items.length + ').');
3395 			/*gubed!*/
3396 			this._opts.spotlight = this.items.length;
3397 		}
3398 		
3399 		// track what the offset of the current leftmost spotlighted item is
3400 		this._index = 0;
3401 		
3402 		this._build();
3403 	}
3404 	
3405 	CarouselPaneProto._build = function() { /*debug*///console.log('CarouselPaneProto._build');
3406 		WidgetProto._build.call(this, this._viewport);
3407 		
3408 		this.stage.css({
3409 			margin: 0,
3410 			listStyleType: 'none' // useful when content is a list
3411 		});
3412 
3413 		this.items.css( {position:'absolute', 'z-index':2} );
3414 		this._itemDimensions = getDimensions(this.items); // get this *after* setting position to absolute
3415 		this.items.css({
3416 			margin: 0,
3417 			width: this._itemDimensions.innerWidth,
3418 			height: this._itemDimensions.innerHeight
3419 		});
3420 
3421 		this._wingSize = Math.ceil(this.items.length * this._itemDimensions[this._geom[0]] * 1.5);
3422 
3423 		this._viewport.css({
3424 			overflow: 'scroll',
3425 			overflowX: 'hidden', // hide scroll bars
3426 			overflowY: 'hidden',
3427 			position: 'relative',
3428 			padding: 0,
3429 			margin: 0,
3430 			width: this._opts.axis === 'x'? '100%' : this._itemDimensions.width,
3431 			height: this._opts.axis === 'y'? '100%' : this._itemDimensions.height
3432 		});
3433 		
3434 		/**
3435 			@private
3436 			@name glow.ui.CarouselPane#_spot
3437 			@type Object
3438 			@description Information about the spotlight area.
3439 		*/
3440 		this._spot = CarouselPane._getSpot(this._viewport.width(), this.items, this._itemDimensions, this._opts);
3441 		
3442 		/**
3443 			@private
3444 			@name glow.ui.CarouselPane#_step
3445 			@type number
3446 			@description How far to move when going next or prev.
3447 		*/
3448 		if (this._opts.step === true) {
3449 			this._step = this._spot.capacity;
3450 		}
3451 		else if (this._opts.step > this._spot.capacity) {
3452 			/*!debug*/
3453 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + this._opts.step +') cannot be greater than the calculated spotlight ('+ this._spot.capacity + ').');
3454 			/*gubed!*/
3455 			
3456 			this._step = this._spot.capacity;
3457 		}
3458 
3459 		if (this._opts.page && this._step !== this._spot.capacity) {
3460 			/*!debug*/
3461 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + this._opts.step +') cannot be different than the spotlight ('+ this._spot.capacity + ') when opts.page is true.');
3462 			/*gubed!*/
3463 			
3464 			this._step = this._spot.capacity;
3465 		}
3466 		
3467 		/**
3468 			@private
3469 			@name glow.ui.CarouselPane#_gap
3470 			@type Object
3471 			@description Information about the gap at the end of the items.
3472 			@property size
3473 			@property count
3474 		*/
3475 		this._gap = getGap(this);
3476 		
3477 		// must set height to anything other than 0, else FF won't *ever* render the stage
3478 		this.stage.css({width: this.stage.width() + this._wingSize * 2, height: '100%'}); // [wing][stage[spot]stage][wing]
3479 		
3480 		layout.call(this);
3481 		
3482 		this._bind();
3483 		
3484 		calculateIndex.call(this);
3485 	}
3486 	
3487 	/**
3488 		@private
3489 		@name getGap
3490 		@description Calculate the size of the empty space at the end of the items
3491 			which may be required to enforce paging.
3492 		@param {glow.ui.CarouselPane} carouselPane
3493 	 */
3494 	function getGap(carouselPane) { /*debug*///console.log('getGap()');
3495 		var gap = { size: 0, count: 0 },
3496 			danglingItemCount = carouselPane.items.length % carouselPane._step;
3497 	
3498 		if (carouselPane._opts.page && carouselPane._step > 1) {
3499 			gap.count = danglingItemCount? carouselPane._spot.capacity - danglingItemCount : 0;
3500 			gap.size = gap.count * carouselPane._itemDimensions[carouselPane._geom[0]];
3501 		}
3502 	
3503 		return gap;
3504 	}
3505 	
3506 	CarouselPaneProto._bind = function() { /*debug*///console.log('CarouselPaneProto._bind');
3507 		var that = this;
3508 		
3509 		WidgetProto._bind.call(that);
3510 
3511 		attachEvent(that, that._focusable, 'childActivate', function(e) {
3512 			var itemNumber = e.itemIndex,
3513 				indexes = that.spotlightIndexes(true),
3514 				isVisible = (' '+indexes.join(' ')+' ').indexOf(' '+itemNumber+' ') > -1;
3515 
3516 			if (itemNumber !== undefined && !isVisible) {
3517 				that.moveTo(itemNumber, {tween: ''});
3518 				that._index = itemNumber;
3519 			}
3520 		});
3521 		
3522 		this._focusable.on('select', function(e) {
3523 			e.itemIndex = e.item.data('itemIndex');
3524 			that.fire('select', e);
3525 		});
3526 	}
3527 	
3528 	/**
3529 		@private
3530 		@name attachEvent
3531 		@function
3532 		@decription Add an event listener and handler to a node related to this carouselpane.
3533 		Stores a reference to that transaction so each handler can easily be detached later.
3534 		@see glow.ui.CarouselPane-detachEvents
3535 		@param {glow.ui.CarouselPane} carouselPane
3536 		@param {glow.EventTarget} target
3537 		@param {string} name The name of the event to listen for.
3538 		@param {Function} handler
3539 	*/
3540 	function attachEvent(carouselPane, target, name, handler) {
3541 		target.on(name, handler);
3542 		carouselPane._addedEvents = carouselPane._addedEvents || [];
3543 		carouselPane._addedEvents.push( {target:target, name:name, handler:handler} );
3544 	}
3545 	
3546 	/**
3547 		@private
3548 		@name detachEvents
3549 		@function
3550 		@decription Remove all events add via the {@link glow.ui.CarouselPane-attachEvent}.
3551 		@see glow.ui.CarouselPane-removeEvents
3552 		@param {glow.ui.CarouselPane} carouselPane
3553 	*/
3554 	function detachEvents(carouselPane) {
3555 		var i = carouselPane._addedEvents? carouselPane._addedEvents.length : 0,
3556 			e;
3557 		while (i--) {
3558 			e = carouselPane._addedEvents[i];
3559 			e.target.detach(e.name, e.handler);
3560 		}
3561 	}
3562 	
3563 	CarouselPaneProto.updateUi = function() { /*debug*///console.log('updateUi');
3564 		WidgetProto._updateUi.call(this);
3565 		
3566 		// must set height to anything other than 0, else FF won't *ever* render the stage
3567 		this.stage.css({width: this.stage.width() + this._wingSize * 2, height: '100%'}); // [wing][stage[spot]stage][wing]
3568 		
3569 		this._spot = CarouselPane._getSpot(this._viewport.width(), this.items, this._itemDimensions, this._opts);
3570 		
3571 		if (this._opts.step === true) {
3572 			this._step = this._spot.capacity;
3573 		}
3574 		
3575 		layout.call(this);
3576 		
3577 		this._index = 0;
3578 		this.fire('updateUi', {});
3579 	}
3580 	
3581 	/**
3582 		@name glow.ui.CarouselPane#moveStop
3583 		@function
3584 		@description Stop moving the carousel.
3585 			The current animation will end, leaving the carousel
3586 			in step. Note that this is asynchronous: expect this method
3587 			to return before the carousel actually stops.
3588 			
3589 		@returns this
3590 	*/
3591 	CarouselPaneProto.moveStop = function() { /*debug*///console.log('moveStop()');
3592 		// set temporary flag to signal the next animation in the timeline to stop
3593 		this._gliderBrake = true;
3594 	}
3595 	
3596 	/**
3597 		@name glow.ui.CarouselPane#moveStart
3598 		@function
3599 		@description Start moving the carousel in a particular direction.
3600 		
3601 		@param {boolean} [backwards=false] True to move backwards, otherwise move forwards.
3602 		
3603 		@returns this
3604 		@see glow.ui.CarouselPane#moveStop
3605 		
3606 		@example
3607 			nextBtn.on('mousedown', function() {
3608 				myCarouselPane.moveStart();
3609 			}).on('mouseup', function() {
3610 				myCarouselPane.moveStop();
3611 			});
3612 	*/
3613 	CarouselPaneProto.moveStart = function(backwards) { /*debug*///console.log('moveStart('+backwards+')');
3614 		/*!debug*/
3615 			if (arguments.length > 1) {
3616 				glow.debug.warn('[wrong count] glow.ui.moveStart - too many arguments, must be 1 or 0, not '+arguments.length+'.');
3617 			}
3618 		/*gubed!*/
3619 		
3620 		var step = (backwards? -1 : 1) * this._step,
3621 			carouselPane = this;
3622 		
3623 		if (!carouselPane._inMotion) {
3624 			carouselPane._gliderBrake = false;
3625 			
3626 			carouselPane.moveTo(
3627 				carouselPane._index + step,
3628 				{
3629 					callback: function() {
3630 						if (!carouselPane._gliderBrake) {
3631 							if ( // if looping or if there's room to go in the given direction 
3632 								carouselPane._opts.loop ||
3633 								( (backwards && carouselPane._index > 0) || (!backwards && carouselPane._index + carouselPane._spot.capacity < carouselPane.items.length) )
3634 							) {
3635 								if (carouselPane._step === 1) {
3636 									glide.call(carouselPane, backwards);
3637 								}
3638 								else {
3639 									carouselPane.moveStart(backwards); // recursive
3640 								}
3641 							}
3642 						}
3643 					}
3644 				}
3645 			);
3646 		}
3647 		
3648 		return carouselPane;
3649 	}
3650 	
3651 	/**
3652 		@name glow.ui.CarouselPane#moveToggle
3653 		@function
3654 		@description If this CarouselPane is currently moving via moveStart, will call moveStop,
3655 		otherwise will call moveStart.
3656 		@param {boolean} [backwards=false] When calling moveStart, move backwards?
3657 		@returns this
3658 	 */
3659 	CarouselPaneProto.moveToggle = function(backwards) { /*debug*///console.log('moveToggle()');
3660 		/*!debug*/
3661 			if (arguments.length > 1) {
3662 				glow.debug.warn('[wrong count] glow.ui.moveToggle - too many arguments, must be 1 or 0, not '+arguments.length+'.');
3663 			}
3664 		/*gubed!*/
3665 		
3666 		if (this._inMotion && !this._gliderBrake) {
3667 			this.moveStop();
3668 		}
3669 		else {
3670 			this.moveStart(backwards);
3671 		}
3672 		
3673 		return this;
3674 	}
3675 	
3676 	/**
3677 		@private
3678 		@name glide
3679 		@function
3680 		@description Move this using an animation that is continuous, with a linear tween.
3681 		@param {boolean} backwards Glide in a previous-direction?
3682 	 */
3683 	var glide = function(backwards) { /*debug*///console.log('glide('+backwards+')');
3684 		var dir = (backwards? -1 : 1),
3685 			moves = [],
3686 			offset = this.content[0].scrollLeft, // from where is the move starting?
3687 			amount = this._itemDimensions[this._geom[0]], // how many pixels are we moving by?
3688 			from,
3689 			to,
3690 			that = this,
3691 			moveAnim,
3692 			// when to loop back to where we started?
3693 			wrapAt = offset + (backwards? -this._index * amount : (this.items.length - this._index) * amount);
3694 		
3695 		swap.call(this, 'back');
3696 
3697 		for (var i = 0, leni = this.items.length; i < leni; i += this._step) {
3698 			// calculate the start and end points of the next move
3699 			from = offset + dir * i * amount;
3700 			to   = offset + dir * (i + this._step) * amount;
3701 
3702 			if ( (backwards && from === wrapAt) || (!backwards && to === wrapAt) ) {
3703 				offset -= dir * this.items.length * amount; // wrap
3704 			}
3705 
3706 			moveAnim = this.content.anim(
3707 				this._opts.duration,
3708 				{scrollLeft: [from, to]},
3709 				{tween: 'linear', startNow: false}
3710 			)
3711 			.on('start', function() {
3712 				indexMoveTo.call(that);
3713 					
3714 				if ( that.fire('move', { moveBy: dir, currentIndex: that._index }).defaultPrevented() ) {
3715 					glideStop.call(that);
3716 				}
3717 			})
3718 			.on('complete', function() {
3719 				that._index += dir; // assumes move amount will be +/- 1
3720 
3721  				if (
3722  					that._gliderBrake
3723  					||
3724  					( !that._opts.loop && (that._index + that._spot.capacity === that.items.length || that._index === 0) )
3725  				) {
3726  					glideStop.call(that);
3727  					that.fire( 'afterMove', {currentIndex: that._index} );
3728  				}
3729 			});
3730 			
3731 			moves.push(moveAnim);
3732 		}
3733 		
3734 		this._glider = new glow.anim.Timeline({loop: true});
3735 		glow.anim.Timeline.prototype.track.apply(this._glider, moves);
3736 		
3737 		this._inMotion = true;
3738 		this._gliderBrake = false;
3739 		this._glider.start();
3740 	}
3741 	
3742 	/**
3743 		@private
3744 		@name indexMoveTo
3745 		@function
3746 		@description Calculate what the new index would be and set this._index to that.
3747 		@param {number} index The destination index.
3748 		@returns this._index
3749 		@example
3750 			// items.length is 3
3751 			var newIndex = indexMoveTo(10);
3752 			// newIndex is 1
3753 	 */
3754 	function indexMoveTo(index) {
3755 		if (index !== undefined) { this._index = index; }
3756 		
3757 		// force index to be a number from 0 to items.length
3758 		this._index = this._index % this.items.length;
3759 		while (this._index < 0) { this._index += this.items.length; }
3760 		
3761 		return this._index;
3762 	}
3763 	
3764 	/**
3765 		@private
3766 		@name indexMoveBy
3767 		@function
3768 		@description Calculate what the new index would be and set this._index to that.
3769 		@param {number} delta The amount to change the index by, can be positive or negative.
3770 		@returns this._index
3771 		@example
3772 			// items.length is 3
3773 			// currentIndex is 1
3774 			var newIndex = indexMoveBy(100);
3775 			// newIndex is 2
3776 	 */
3777 	function indexMoveBy(delta) {
3778 		return indexMoveTo.call(this, this._index += delta);
3779 	}
3780 	
3781 	/**
3782 		@private
3783 		@name glideStop
3784 		@description Reset this CarouselPane after a glide is finished.
3785 	 */
3786 	function glideStop() { /*debug*///console.log('glideStop()');
3787 		this._glider.stop();
3788 		this._glider.destroy();
3789 		
3790 		this._inMotion = false;
3791 		this._index = calculateIndex.call(this); // where did we end up?
3792 		
3793 		// in case our clones are showing
3794 		jump.call(this);
3795 		swap.call(this);
3796 	}
3797 	
3798 	/**
3799 		@name glow.ui.CarouselPane#spotlightIndexes
3800 		@function
3801 		@description Gets an array of spotlighted indexes.
3802 			These are the indexes of the nodes within {@link glow.ui.CarouselPane#items}.
3803 			Only item indexes currently visible in the spotlight will be included.
3804 		@private-param {boolean} _real Return only indexes of real items, regardless of what clones are visible.
3805 		@returns {number[]}
3806 	*/
3807 	CarouselPaneProto.spotlightIndexes = function(_real) { /*debug*///console.log('CarouselPaneProto.spotlightIndexes()');
3808 		var indexes = [],
3809 			findex = calculateIndex.call(this),
3810 			index,
3811 			maxi = (this._opts.loop)? this._spot.capacity : Math.min(this._spot.capacity, this.items.length);
3812 		
3813 		// takes into account gaps and wraps
3814 		for (var i = 0; i < maxi; i++) {
3815 			index = _real? (findex + i) : (findex + i)%(this.items.length + this._gap.count);
3816 			// skip gaps
3817 			if (index >= this.items.length || index < 0) {
3818 				continue; // or maybe keep gaps? index = NaN;
3819 			}
3820 			indexes.push(index);
3821 		}		
3822 		return indexes;
3823 	}
3824 	
3825 	/**
3826 		@name glow.ui.CarouselPane#spotlightItems
3827 		@function
3828 		@description Get the currently spotlighted items.
3829 		Only items currently visible in the spotlight will be included.
3830 		@returns {glow.NodeList}
3831 	*/
3832 	CarouselPaneProto.spotlightItems = function() { /*debug*///console.log('CarouselPaneProto.spotlightItems()');
3833 		var items = glow(),
3834 			indexes = this.spotlightIndexes();
3835 		
3836 		// takes into account gaps and wraps
3837 		for (var i = 0, leni = indexes.length; i < leni; i++) {
3838 			items.push( this.items[ indexes[i] ] );
3839 		}
3840 		
3841 		return items;
3842 	}
3843 	
3844 	/**
3845 		@private
3846 		@name calculateIndex
3847 		@function
3848 		@description Calculate the index of the leftmost item in the spotlight.
3849 		@returns {number}
3850 	 */
3851 	function calculateIndex() {
3852 		var cindex = this.content[0].scrollLeft - (this._wingSize +this._spot.offset.left);
3853 		
3854 		cindex += this._spot.offset.left;
3855 		cindex /= this._itemDimensions.width;
3856 		
3857 		return cindex;
3858 	}
3859 	
3860 	/**
3861 		@name glow.ui.CarouselPane#moveTo
3862 		@function
3863 		@description Move the items so a given index is the leftmost active item.
3864 			This method respects the carousel's limits and its step. If it's
3865 			not possible to move the item so it's the leftmost item of the spotlight, it will
3866 			be placed as close to the left as possible.
3867 		
3868 		@param {number} itemIndex Item index to move to.
3869 		
3870 		@param opts
3871 		@param {undefined|string} opts.tween If undefined, use the default animation,
3872 		if empty string then no animation, if non-empty string then use the named tween.
3873 		@privateParam {Function} opts.callback Called when move animation is complete.
3874 		@privateParam {boolean} opts.jump Move without animation and without events.
3875 		
3876 		@returns this
3877 	*/
3878 	CarouselPaneProto.moveTo = function(itemIndex, opts) { /*debug*///glow.debug.log('moveTo('+itemIndex+')');
3879 		var willMove, // trying to move to the same place we already are?
3880 			destination, // in pixels
3881 			tween,
3882 			anim;
3883 		
3884 		if (this._inMotion) {
3885 			return false;
3886 		}
3887 		opts = opts || {};
3888 		
3889 		// will the last item be in the spotlight?
3890 		if (!this._opts.loop && itemIndex > this.items.length - this._spot.capacity) {
3891 			// if opts.page is on then allow a gap at the end, otherwise don't include gap
3892 			itemIndex = this.items.length - this._spot.capacity + (this._opts.page? this._gap.count : 0);
3893 		}
3894 		else if (!this._opts.loop && itemIndex < 0) {
3895 			itemIndex = 0;
3896 		}
3897 
3898 		willMove = ( itemIndex !== this._index && canGo.call(this, itemIndex) );
3899 		
3900 		// move event
3901 		if (!opts.jump) { // don't fire move event for jumps
3902 			var e = new glow.events.Event({
3903 				currentIndex: this._index,
3904 				moveBy: (this._index < itemIndex)? (itemIndex - this._index) : (-Math.abs(this._index - itemIndex))
3905 			});
3906 			
3907 			if (!opts.jump && willMove && this.fire('move', e).defaultPrevented() ) {
3908 				return this;
3909 			}
3910 			else {
3911 				itemIndex = this._index + e.moveBy;
3912 			}
3913 		}
3914 
3915 		// force items to stay in step when opts.page is on
3916 		if (this._opts.page) {
3917 			itemIndex = Math.floor(itemIndex / this._step) * this._step;
3918 		}
3919 		
3920 		// invalid itemIndex value?
3921 		if (itemIndex > this.items.length + this._step || itemIndex < 0 - this._step) { // moving more than 1 step
3922 			/*!debug*/
3923 				glow.debug.warn('[wrong value]  glow.ui.CarouselPane#moveTo - Trying to moveTo an item ('+itemIndex+') that is more than 1 step (' + this._step +' items) away is not possible.');
3924 			/*gubed!*/
3925 			itemIndex = this._index + (this._index < itemIndex)? -this._step : this._step;
3926 		}
3927 
3928 		destination = this._wingSize + itemIndex * this._itemDimensions.width;
3929 
3930 		swap.call(this, 'back');
3931 		
3932 		tween = opts.tween || this._opts.tween;
3933 		
3934 		var that = this;
3935 		if (opts.jump === true || opts.tween === '') { // jump
3936 			this.content[0].scrollLeft = destination;
3937 			
3938 			this._index = itemIndex;
3939 			// in case our clones are showing
3940 			jump.call(this);
3941 			swap.call(this);
3942 			
3943 			// force index to be a number from 0 to items.length
3944 			this._index = this._index % (this.items.length  + this._gap.count);
3945 			
3946 			if (!opts.jump && willMove) {
3947 				this.fire('afterMove', {currentIndex: this._index});
3948 			}
3949 			
3950 			this._inMotion = false;
3951 		}
3952 		else if (willMove) {
3953 			this._inMotion = true;
3954 			
3955 			anim = this.content.anim(
3956 				this._opts.duration,
3957 				{
3958 					scrollLeft: destination
3959 				},
3960 				{
3961 					tween: opts.tween || this._opts.tween
3962 				}
3963 			);
3964 			
3965 			this._index = itemIndex;
3966 			
3967 			
3968 			anim.on('complete', function() {
3969 				that._inMotion = false;
3970 				
3971 				// in case our clones are showing
3972 				jump.call(that);
3973 				swap.call(that);
3974 				
3975 				// force index to be a number from 0 to items.length
3976 				that._index = that._index % (that.items.length  + that._gap.count);
3977 				
3978 				that.fire('afterMove', {currentIndex: that._index});
3979 				
3980 				if (opts.callback) {
3981 					opts.callback();
3982 				}
3983 			});
3984 		}
3985 		
3986 		return this;
3987 	}
3988 	
3989 	/**
3990 		@private
3991 		@function
3992 		@name jump
3993 		@description Quickly move forward or back to a new set of items that look the same as
3994 		the current set of items.
3995 	 */
3996 	function jump() { /*debug*///console.log('jump()');
3997 		if (this._index < 0) {
3998 			this.moveTo(this.items.length + this._gap.count + this._index, {jump: true});
3999 		}
4000 		else if (this._index >= this.items.length) {
4001 			this.moveTo(this._index - (this.items.length + this._gap.count), {jump: true});
4002 		}
4003 	}
4004 	
4005 	/**
4006 		Move real items to stand-in for any clones that are in the spotlight, or
4007 		put the real items back again.
4008 		@name swap
4009 		@private
4010 		@param {boolean} back If a truthy value, will move the real items back.
4011 	 */
4012 	function swap(back) { /*debug*///console.log('swap('+back+')');
4013 		var swapItemIndex;
4014 		
4015 		if (!this._opts.loop) { return; } // no clones, so no swap possible
4016 		
4017 		if (back) {
4018 			this._itemList.place();
4019 		}
4020 		else {
4021 			for (var i = 0, leni = this._spot.capacity - this._gap.count; i < leni; i++) {
4022 				swapItemIndex = (this._index + i);
4023 				if (swapItemIndex >= this.items.length) { // a clone needs to have a real item swapped-in
4024 					this._itemList.swap(swapItemIndex, swapItemIndex % this.items.length);
4025 				}
4026 			}
4027 		}
4028 	}
4029 	
4030 	/**
4031 		@name glow.ui.CarouselPane#moveBy
4032 		@function
4033 		@description Move by a number of items.
4034 		
4035 		@param {number} amount Amount and direction to move.
4036 			Negative numbers will move backwards, positive number will move
4037 			forwards.
4038 			
4039 			This method respects the carousel's limits and its step. If it's
4040 			not possible to move the item so it's the leftmost item of the spotlight, it will
4041 			be placed as close to the left as possible.
4042 		
4043 		@returns this
4044 	*/
4045 	CarouselPaneProto.moveBy = function(amount) { /*debug*///console.log('moveBy('+amount+')');
4046 		this.moveTo(this._index + amount);
4047 		return this;
4048 	}
4049 	
4050 	/**
4051 		@name glow.ui.CarouselPane#next
4052 		@function
4053 		@description Move forward by the step.
4054 		@returns this
4055 	*/
4056 	CarouselPaneProto.next = function() { /*debug*///console.log('next()');
4057 		this.moveTo(this._index + this._step);
4058 		return this;
4059 	}
4060 	
4061 	/**
4062 		@name glow.ui.CarouselPane#prev
4063 		@function
4064 		@description Move backward by the step.
4065 		@returns this
4066 	*/
4067 	CarouselPaneProto.prev = function() { /*debug*///console.log('prev()');
4068 		this.moveTo(this._index - this._step);
4069 		return this;
4070 	}
4071 	
4072 	/**
4073 		@private
4074 		@name canGo
4075 		@description Determine if the CarouselPane can go to the desired index.
4076 		@param {number} itemIndex The desired index.
4077 		@returns {boolean}
4078 	 */
4079 	function canGo(itemIndex) { /*debug*///console.log('canGo('+itemIndex+')');
4080 		if (this._opts.loop) { return true; }
4081 		
4082 		// too far prev
4083 		if (itemIndex < 0) {
4084 			return false;
4085 		}
4086 
4087 		// too far next
4088 		if (itemIndex - this._step >= this.items.length - this._spot.capacity ) {
4089 			return false;
4090 		}
4091 		return true;
4092 	}
4093 	
4094 	/**
4095 		@private
4096 		@name getDimensions
4097 		@description Calculate the max height and width of all the items.
4098 		@param {glow.NodeList} items
4099 		@returns {Object} With properties `width` and 'height`.
4100 	 */
4101 	function getDimensions(items) {
4102 		var el,
4103 			maxInnerWidth = 0,
4104 			maxInnerHeight = 0,
4105 			maxWidth = 0,
4106 			maxHeight = 0,
4107 			margin = 0,
4108 			marginRight = 0,
4109 			marginLeft = 0,
4110 			marginTop = 0,
4111 			marginBottom = 0;
4112 			
4113 		items.each(function() {
4114 			el = glow(this);
4115 			maxHeight = Math.max(this.offsetHeight, maxHeight);
4116 			maxWidth = Math.max(this.offsetWidth, maxWidth);
4117 			maxInnerWidth = Math.max(el.width(), maxInnerWidth);
4118 			maxInnerHeight = Math.max(el.height(), maxInnerHeight);
4119 			marginRight = Math.max(autoToValue(el.css('margin-right')), marginRight);
4120 			marginLeft = Math.max(autoToValue(el.css('margin-left')), marginLeft);
4121 			marginTop = Math.max(autoToValue(el.css('margin-top')), marginTop);
4122 			marginBottom = Math.max(autoToValue(el.css('margin-bottom')), marginBottom);
4123 		});
4124 		
4125 		// simulate margin collapsing. see: http://www.howtocreate.co.uk/tutorials/css/margincollapsing
4126 		margin = Math.max(marginLeft, marginRight); // the larger of: the largest left matgin and the largest right margin
4127 		return { width: maxWidth+margin, height: maxHeight+marginTop+marginBottom, innerWidth: maxInnerWidth, innerHeight: maxInnerHeight, marginLeft: marginLeft, marginRight: marginRight, marginTop: marginTop, marginBottom: marginBottom };
4128 	}
4129 	
4130 	function autoToValue(v) {
4131 		if (v === 'auto') return 0;
4132 		else return parseInt(v);
4133 	}
4134 	
4135 	/**
4136 		@private
4137 		@name _getSpot
4138 		@description Calculate the bounds for the spotlighted area, within the viewport.
4139 		@private
4140 	 */
4141 	CarouselPane._getSpot = function(viewportWidth, items, itemDimensions, opts) {/*debug*///console.log('CarouselPane._getSpot()');
4142 		var spot = { capacity: 0, top: 0, left: 0, width: 0, height: 0, offset: { top: 0, right: 0, bottom: 0, left: 0 } },
4143 			opts = opts || {}
4144 		
4145 		if (!itemDimensions) { itemDimensions = getDimensions(items); }
4146 		
4147 		if (opts.axis = 'x') {
4148 			if (items.length === 0) {
4149 				spot.capacity = 0;
4150 			}
4151 			else if (opts.spotlight) {
4152 				if (opts.spotlight > items.length) {
4153 					throw new Error('spotlight cannot be larger than item count.');
4154 				}
4155 				spot.capacity = opts.spotlight;
4156 			}
4157 			else {
4158 				spot.capacity = Math.floor( viewportWidth / itemDimensions.width );
4159 			}
4160 
4161 			if (spot.capacity > items.length) {
4162 				spot.capacity = items.length;
4163 			}
4164 
4165 			spot.width = spot.capacity * itemDimensions.width + Math.min(itemDimensions.marginLeft, itemDimensions.marginRight);
4166 			spot.height = itemDimensions.height
4167 			
4168 			spot.offset.left = Math.floor( (viewportWidth - spot.width) / 2 );
4169 			spot.offset.right = viewportWidth - (spot.offset.left + spot.width);
4170 		}
4171 		else {
4172 			throw Error('y axis (vertical) not yet implemented');
4173 		}
4174 		
4175 		return spot;
4176 	}
4177 	
4178 	function getPosition(itemIndex) { /*debug*///console.log('getPosition('+itemIndex+')');
4179 		position = { top: 0, left: 0 };
4180 		
4181 		// TODO: memoise?
4182 		var size = this._itemDimensions.width,
4183 			offset = this._spot.offset.left + this._wingSize + this._itemDimensions.marginLeft,
4184 			gap = 0;
4185 			
4186 			if (this._opts.page && itemIndex < 0) {
4187 				gap = -(1 + Math.floor( Math.abs(itemIndex+this._gap.count) / this.items.length)) * this._gap.count * size;
4188 			}
4189 			else if (this._opts.page && itemIndex >= this.items.length) {
4190 				gap = Math.floor(itemIndex / this.items.length) * this._gap.count * size;
4191 			}
4192 
4193 			position.left = offset + (itemIndex * size) + gap;
4194 			position.top = this._itemDimensions.marginTop;
4195 
4196 			return position;
4197 	}
4198 	
4199 	function layout() {/*debug*///console.log('layout()');
4200 		var clone,
4201 			cloneOffset;
4202 					
4203 		this.content[0].scrollLeft = this._wingSize;
4204 
4205 		for (var i = 0, leni = this.items.length; i < leni; i++) {
4206 			// items were already added in ItemList constructor, just add meta now
4207 			this._itemList.addMeta(i, {offset:getPosition.call(this, i).left, isClone:false});
4208 
4209 			this.items.item(i).data('itemIndex', +i);
4210 		}
4211 		
4212 		if (this._opts.loop) { // send in the clones
4213 			this.stage.get('.carousel-clone').remove(); // kill any old clones
4214 			
4215 			// how many sets of clones (on each side) are needed to fill the off-spotlight portions of the stage?
4216 			var repsMax =  1 + Math.ceil(this._spot.offset.left / (this._itemDimensions.width*this.items.length + this._gap.size));	
4217 
4218 			for (var reps = 1; reps <= repsMax; reps++) {
4219 				i = this.items.length;
4220 				while (i--) {
4221  					// add clones to prev side
4222  					clone = this.items.item(i).copy();
4223  					clone.removeClass('carousel-item').addClass('carousel-clone').css({ 'z-index': 1, margin: 0 });
4224 					
4225  					cloneOffset = getPosition.call(this, 0 - (reps * this.items.length - i)).left;
4226  					this._itemList.addItem(0 - (reps * this.items.length - i), clone, {isClone:true, offset:cloneOffset});
4227  					this.stage[0].appendChild(clone[0]);
4228 					
4229  					// add clones to next side
4230  					clone = clone.copy();
4231  					cloneOffset = getPosition.call(this, reps*this.items.length + i).left;
4232  					this._itemList.addItem(reps*this.items.length + i + this._gap.count, clone, {isClone:true, offset:cloneOffset});
4233 					this.stage[0].appendChild(clone[0]);
4234  				}
4235  			}
4236 		}
4237 		
4238 		this.items.addClass('carousel-item');
4239 		// apply positioning to all items and clones
4240  		this._itemList.place(this._itemDimensions.marginTop, undefined);
4241 	}
4242 	
4243 	/**
4244 		@name glow.ui.CarouselPane#destroy
4245 		@function
4246 		@description Remove listeners and added HTML Elements from this instance.
4247 			CarouselPane items will not be destroyed.
4248 			
4249 		@returns undefined
4250 	*/
4251 	CarouselPaneProto.destroy = function() {
4252 		this.stage.get('.carousel-clone').remove();
4253 		detachEvents(this);
4254 		this.stage.insertBefore(this.container).children().css('position', '');
4255 		WidgetProto.destroy.call(this);
4256 	};
4257 	
4258 	/**
4259 		@name glow.ui.CarouselPane#event:select
4260 		@event
4261 		@description Fires when a carousel item is selected.
4262 			Items are selected by clicking, or pressing enter when a child is in the spotlight.
4263 		
4264 			Canceling this event prevents the default click/key action.
4265 		
4266 		@param {glow.events.Event} event Event Object
4267 		@param {glow.NodeList} event.item Item selected
4268 		@param {number} event.itemIndex The index of the selected item in {@link glow.ui.CarouselPane#items}.
4269 	*/
4270 	
4271 	/**
4272 		@name glow.ui.CarouselPane#event:move
4273 		@event
4274 		@description Fires when the carousel is about to move.
4275 			Canceling this event prevents the carousel from moving.
4276 			
4277 			This will fire for repeated move actions. Ie, this will fire many times
4278 			after #start is called.
4279 		
4280 		@param {glow.events.Event} e Event Object
4281 		@param {number} e.currentIndex Index of the current leftmost item.
4282 		@param {number} e.moveBy The number of items the Carousel will move by.
4283 			This is undefined for 'sliding' moves where the destination isn't known.
4284 			
4285 			This value can be overwritten, resulting in the carousel moving a different amount.
4286 			The carousel step will still be respected.
4287 			
4288 		@example
4289 			// double the amount a carousel will move by
4290 			myCarouselPane.on('move', function(e) {
4291 				e.moveBy *= 2;
4292 			});
4293 	*/
4294 
4295 	/**
4296 		@name glow.ui.CarouselPane#event:afterMove
4297 		@event
4298 		@description Fires when the carousel has finished moving.
4299 			Canceling this event prevents the carousel from moving.
4300 			
4301 			This will not fire for repeated move actions. Ie, after #start is
4302 			called this will not fire until the carousel reached an end point
4303 			or when it comes to rest after #stop is called.
4304 			
4305 		@param {glow.events.Event} e Event Object
4306 		@param {number} e.currentIndex Index of the current leftmost item.
4307 			
4308 		@example
4309 			// double the amount a carousel will move by
4310 			myCarouselPane.on('afterMove', function(e) {
4311 				// show content related to this.spotlightItems()[0]
4312 			});
4313 	*/
4314 	
4315 	// EXPORT
4316 	glow.ui.CarouselPane = CarouselPane;
4317 });
4318 Glow.provide(function(glow) {
4319 	var undefined,
4320 		CarouselProto,
4321 		Widget = glow.ui.Widget,
4322 		WidgetProto = Widget.prototype;
4323 	
4324 	/**
4325 		@name glow.ui.Carousel
4326 		@class
4327 		@extends glow.ui.Widget
4328 		@description Create a pane of elements that scroll from one to another.
4329 			
4330 		@param {glow.NodeList|selector|HTMLElement} itemContainer Container of the carousel items.
4331 			The direct children of this item will be treated as carousel items. They will
4332 			be positioned next to each other horizontally.
4333 			
4334 			Each item takes up the same horizontal space, equal to the width of the largest
4335 			item (including padding and border) + the largest of its horizontal margins (as set in CSS).
4336 			
4337 			The height of the container will be equal to the height of the largest item (including
4338 			padding and border) + the total of its vertical margins.
4339 			
4340 		@param {object} [opts] Options
4341 			@param {number} [opts.duration=0.2] Duration of scrolling animations in seconds.
4342 			
4343 			@param {string|function} [opts.tween='easeBoth'] Tween to use for animations.
4344 				This can be a property name of {@link glow.tweens} or a tweening function.
4345 			
4346 			@param {boolean|number} [opts.page=false] Move a whole page at a time.
4347 				If 'true', the page size will be the spotlight size, but you
4348 				can also set this to be an explicit number of items. Space will
4349 				be added to the end of the carousel so pages stay in sync.
4350 				
4351 				If 'false' or 1, the carousel moves one item at a time.
4352 				
4353 			@param {boolean} [opts.loop=false] Loop the carousel from the last item to the first.
4354 			
4355 			@param {number} [opts.spotlight] The number of items to treat as main spotlighted items.
4356 				A carousel may be wide enough to display 2 whole items, but setting
4357 				this to 1 will result in the spotlight item sitting in the middle, with
4358 				half of the previous item appearing before, and half the next item
4359 				appearing after.
4360 				
4361 				By default, this is the largest number of whole items that can exist in
4362 				the width of the container, allowing room for next & previous buttons.
4363 				Any remaining width will be used to partially show the previous/next item
4364 				beneath the next & previous buttons.
4365 				
4366 		@example
4367 			// This creates a carousel out of HTML like...
4368 			// <ol id="carouselItems">
4369 			//   <li>
4370 			//     <a href="anotherpage.html">
4371 			//       <img width="200" height="200" src="img.jpg" alt="" />
4372 			//	   </a>
4373 			//   </li>
4374 			//   ...more list items like above...
4375 			var myCarousel = new glow.ui.Carousel('#carouselItems', {
4376 				loop: true,
4377 				page: true,
4378 			});
4379 			
4380 		@example
4381 			// Make a carousel of thumbnails, which show the full image when clicked.
4382 			// Carousel items look like this...
4383 			// <li>
4384 			//   <a href="fullimage.jpg">
4385 			//     <img src="thumbnail.jpg" width="100" height="100" alt="whatever" />
4386 			//   </a>
4387 			// </li>
4388 			var fullImage = glow('#fullImage'),
4389 				myCarousel = new glow.ui.Carousel('#carouselItems', {
4390 					spotlight: 6
4391 				}).addPageNav('belowCenter').on('select', function(e) {
4392 					fullImage.prop( 'src', e.item.get('a').prop('href') );
4393 					return false;
4394 				});
4395 	*/
4396 	function Carousel(itemContainer, opts) {
4397 		var spot;
4398 		
4399 		Widget.call(this, 'Carousel', opts);
4400 		
4401 		opts = this._opts;
4402 		
4403 		// convert the options for CarouselPane
4404 		if (opts.page) {
4405 			opts.step = opts.page;
4406 			opts.page = true;
4407 		}
4408 		
4409 		this.itemContainer = itemContainer = glow(itemContainer).item(0);
4410 		
4411 		// see if we're going to get enough room for our prev/next buttons
4412 		spot = glow.ui.CarouselPane._getSpot(
4413 			itemContainer.parent().width(),
4414 			itemContainer.children().css('position', 'absolute'),
4415 			0,
4416 			opts
4417 		);
4418 		
4419 		// enfore our minimum back/fwd button size
4420 		if (spot.offset.left < 50) {
4421 			opts.spotlight = spot.capacity - 1;
4422 		}
4423 		
4424 		this._init();
4425 	};
4426 	glow.util.extend(Carousel, glow.ui.Widget);
4427 	CarouselProto = Carousel.prototype;
4428 	
4429 	/**
4430 		@name glow.ui.Carousel#_pane
4431 		@type glow.ui.CarouselPane
4432 		@description The carousel pane used by this Carousel
4433 	*/
4434 	
4435 	/**
4436 		@name glow.ui.Carousel#_prevBtn
4437 		@type glow.NodeList
4438 		@description Element acting as back button
4439 	*/
4440 	/**
4441 		@name glow.ui.Carousel#_nextBtn
4442 		@type glow.NodeList
4443 		@description Element acting as next button
4444 	*/
4445 	
4446 	/**
4447 		@name glow.ui.Carousel#items
4448 		@type glow.NodeList
4449 		@description Carousel items.
4450 	*/
4451 	
4452 	/**
4453 		@name glow.ui.Carousel#itemContainer
4454 		@type glow.NodeList
4455 		@description Parent element of the carousel items.
4456 	*/
4457 	
4458 	// life cycle methods
4459 	CarouselProto._init = function () {
4460 		WidgetProto._init.call(this);
4461 		this._build();
4462 	};
4463 	
4464 	CarouselProto._build = function () {
4465 		var content,
4466 			itemContainer = this.itemContainer,
4467 			pane,
4468 			items,
4469 			spot;
4470 		
4471 		WidgetProto._build.call( this, itemContainer.wrap('<div></div>').parent() );
4472 		content = this.content;
4473 		
4474 		pane = this._pane = new glow.ui.CarouselPane(itemContainer, this._opts);
4475 		spot = pane._spot
4476 		items = this.items = pane.items;
4477 		this.itemContainer = pane.itemContainer;
4478 		
4479 		pane.moveTo(0, {
4480 			tween: null
4481 		});
4482 		
4483 		// add next & prev buttons, autosizing them
4484 		this._prevBtn = glow('<div class="Carousel-prev"><div class="Carousel-btnIcon"></div></div>').prependTo(content).css({
4485 			width: spot.offset.left,
4486 			height: spot.height
4487 		});
4488 		this._nextBtn = glow('<div class="Carousel-next"><div class="Carousel-btnIcon"></div></div>').prependTo(content).css({
4489 			width: spot.offset.right,
4490 			height: spot.height
4491 		});
4492 		
4493 		updateButtons(this);
4494 		
4495 		this._bind();
4496 	};
4497 	
4498 	/**
4499 		@private
4500 		@function
4501 		@description Update the enabled / disabled state of the buttons.
4502 	*/
4503 	function updateButtons(carousel) {
4504 		// buttons are always active for a looping carousel
4505 		if (carousel._opts.loop) {
4506 			return;
4507 		}
4508 		
4509 		var indexes = carousel.spotlightIndexes(),
4510 			lastIndex = indexes[indexes.length - 1],
4511 			lastItemIndex = carousel.items.length - 1;
4512 
4513 		// add or remove the disabled class from the buttons
4514 		carousel._prevBtn[ (indexes[0] === 0) ? 'addClass' : 'removeClass' ]('Carousel-prev-disabled');
4515 		carousel._nextBtn[ (lastIndex === lastItemIndex) ? 'addClass' : 'removeClass' ]('Carousel-next-disabled');
4516 	}
4517 	
4518 	/**
4519 		@private
4520 		@function
4521 		@description Listener for CarouselPane's 'select' event.
4522 			'this' is the Carousel
4523 	*/
4524 	function paneSelect(event) {
4525 		this.fire('select', event);
4526 	}
4527 	
4528 	/**
4529 		@private
4530 		@function
4531 		@description Listener for CarouselPane's 'move' event.
4532 			'this' is the Carousel
4533 	*/
4534 	function paneMove(event) {
4535 		var pane = this._pane;
4536 		
4537 		if ( !this.fire('move', event).defaultPrevented() ) {
4538 			this._updateNav( (pane._index + event.moveBy) % this.items.length / pane._step );
4539 		}
4540 	}
4541 	
4542 	/**
4543 		@private
4544 		@function
4545 		@description Listener for CarouselPane's 'afterMove' event.
4546 			'this' is the Carousel
4547 	*/
4548 	function paneAfterMove(event) {
4549 		if ( !this.fire('afterMove', event).defaultPrevented() ) {
4550 			updateButtons(this);
4551 		}
4552 	}
4553 	
4554 	/**
4555 		@private
4556 		@function
4557 		@description Listener for back button's 'mousedown' event.
4558 			'this' is the Carousel
4559 	*/
4560 	function prevMouseDown(event) {
4561 		if (event.button === 0) {
4562 			this._pane.moveStart(true);
4563 			return false;
4564 		}
4565 	}
4566 	
4567 	/**
4568 		@private
4569 		@function
4570 		@description Listener for fwd button's 'mousedown' event.
4571 			'this' is the Carousel
4572 	*/
4573 	function nextMouseDown(event) {
4574 		if (event.button === 0) {
4575 			this._pane.moveStart();
4576 			return false;
4577 		}
4578 	}
4579 	
4580 	/**
4581 		@private
4582 		@function
4583 		@description Stop the pane moving.
4584 			This is used as a listener for various mouse events on the
4585 			back & forward buttons.
4586 			
4587 			`this` is the Carousel.
4588 	*/
4589 	function paneMoveStop() {
4590 		this._pane.moveStop();
4591 	}
4592 	
4593 	CarouselProto._bind = function () {
4594 		var pane = this._pane,
4595 			carousel = this;
4596 		
4597 		this._tie(pane);
4598 		
4599 		pane.on('select', paneSelect, this)
4600 			.on('afterMove', paneAfterMove, this)
4601 			.on('move', paneMove, this);
4602 		
4603 		this._prevBtn.on('mousedown', prevMouseDown, this)
4604 			.on('mouseup', paneMoveStop, this)
4605 			.on('mouseleave', paneMoveStop, this);
4606 		
4607 		this._nextBtn.on('mousedown', nextMouseDown, this)
4608 			.on('mouseup', paneMoveStop, this)
4609 			.on('mouseleave', paneMoveStop, this);
4610 		
4611 		WidgetProto._bind.call(this);
4612 	};
4613 	
4614 	/**
4615 		@name glow.ui.Carousel#spotlightItems
4616 		@function
4617 		@description Get the currently spotlighted items.
4618 		
4619 		@returns {glow.NodeList}
4620 	*/
4621 	CarouselProto.spotlightItems = function() {
4622 		return this._pane.spotlightItems();
4623 	};
4624 	
4625 	/**
4626 		@name glow.ui.Carousel#spotlightIndexes
4627 		@function
4628 		@description Gets an array of spotlighted indexes.
4629 			These are the indexes of the nodes within {@link glow.ui.Carousel#items}.
4630 		
4631 		@returns {number[]}
4632 	*/
4633 	CarouselProto.spotlightIndexes = function() {
4634 		return this._pane.spotlightIndexes();
4635 	};
4636 	
4637 	/**
4638 		@name glow.ui.Carousel#moveTo
4639 		@function
4640 		@description Move the items so a given index is in the spotlight.
4641 		
4642 		@param {number} itemIndex Item index to move to.
4643 		
4644 		@param {boolean} [animate=true] Transition to the item.
4645 			If false, the carousel will switch to the new index.
4646 		
4647 		@returns this
4648 	*/
4649 	CarouselProto.moveTo = function(itemIndex, animate) {
4650 		this._pane.moveTo(itemIndex, animate);
4651 		return this;
4652 	};
4653 	
4654 	/**
4655 		@private
4656 		@function
4657 		@decription Creates the prev & next functions
4658 		@param {number} direction Either 1 or -1
4659 	*/
4660 	function prevNext(direction) {
4661 		return function() {
4662 			this._pane.moveBy(this._pane._step * direction);
4663 			return this;
4664 		}
4665 	}
4666 	
4667 	/**
4668 		@name glow.ui.Carousel#next
4669 		@function
4670 		@description Move to the next page/item
4671 		
4672 		@returns this
4673 	*/
4674 	CarouselProto.next = prevNext(1);
4675 	
4676 	/**
4677 		@name glow.ui.Carousel#prev
4678 		@function
4679 		@description Move to the previous page/item
4680 		
4681 		@returns this
4682 	*/
4683 	CarouselProto.prev = prevNext(-1);
4684 	
4685 	/**
4686 		@name glow.ui.Carousel#destroy
4687 		@function
4688 		@description Remove listeners and styles from this instance.
4689 			Carousel items will not be destroyed.
4690 			
4691 		@returns undefined
4692 	*/
4693 	CarouselProto.destroy = function() {
4694 		// Move the pane outside our widget
4695 		this._pane.container.insertBefore(this.container);
4696 		WidgetProto.destroy.call(this);
4697 	};
4698 	
4699 	/*
4700 		@name glow.ui.Carousel#updateUi
4701 		@function
4702 		@description Refresh the carousel after moving/adding/removing items.
4703 			You can edit the items within the carousel using NodeLists such
4704 			as {@link glow.ui.Carousel#itemContainer}.
4705 			
4706 		@example
4707 			// Add a new carousel item
4708 			myCarousel.itemContainer.append('<li>New Item</li>');
4709 			// Move the new item into position & update page nav etc...
4710 			myCarousel.updateUi();
4711 			
4712 		@returns this
4713 	*/
4714 	// TODO: populate #items here & check back & fwd button sizes
4715 	
4716 	/**
4717 		@name glow.ui.Carousel#event:select
4718 		@event
4719 		@description Fires when a carousel item is selected.
4720 			Items are selected by clicking, or pressing enter when a child is in the spotlight.
4721 		
4722 			Canceling this event prevents the default click/key action.
4723 		
4724 		@param {glow.events.Event} event Event Object
4725 		@param {glow.NodeList} event.item Item selected
4726 		@param {number} event.itemIndex The index of the selected item in {@link glow.ui.Carousel#items}.
4727 	*/
4728 	
4729 	/**
4730 		@name glow.ui.Carousel#event:move
4731 		@event
4732 		@description Fires when the carousel is about to move.
4733 			Canceling this event prevents the carousel from moving.
4734 			
4735 			This will fire for repeated move actions. Ie, this will fire many times
4736 			while the mouse button is held on one of the arrows.
4737 		
4738 		@param {glow.events.Event} event Event Object
4739 		@param {number} event.moveBy The number of items we're moving by.
4740 			This will be positive for forward movements and negative for backward
4741 			movements.
4742 		
4743 			You can get the current index via `myCarousel.spotlightIndexes()[0]`.
4744 	*/
4745 	
4746 	/**
4747 		@name glow.ui.Carousel#event:afterMove
4748 		@event
4749 		@description Fires when the carousel has finished moving.
4750 			Canceling this event prevents the carousel from moving.
4751 			
4752 			This will not fire for repeated move actions. Ie, after #start is
4753 			called this will not fire until the carousel reached an end point
4754 			or when it comes to rest after #stop is called.
4755 			
4756 		@param {glow.events.Event} event Event Object
4757 			
4758 		@example
4759 			// double the amount a carousel will move by
4760 			myCarousel.on('afterMove', function(e) {
4761 				// show content related to this.spotlightIitems()[0]
4762 			});
4763 	*/
4764 	
4765 	// EXPORT
4766 	glow.ui.Carousel = Carousel;
4767 });
4768 Glow.provide(function(glow) {
4769 	var undefined,
4770 		CarouselProto = glow.ui.Carousel.prototype;
4771 
4772 	/**
4773 		@name glow.ui.Carousel#_pageNav
4774 		@type glow.NodeList
4775 		@description Element containing pageNav blocks
4776 	*/
4777 	/**
4778 		@name glow.ui.Carousel#_pageNavOpts
4779 		@type Object
4780 		@description Options for the page nav.
4781 			Same as the opts arg for #addPageNav
4782 	*/
4783 	
4784 	/**
4785 		@name glow.ui.Carousel#addPageNav
4786 		@function
4787 		@description Add page navigation to the carousel.
4788 			The page navigation show the user which position they are viewing
4789 			within the carousel.
4790 		
4791 		@param {Object} [opts] Options object.
4792 		@param {string|selector|HTMLElement} [opts.position='belowLast'] The position of the page navigation.
4793 			This can be a CSS selector pointing to an element, or one of the following
4794 			shortcuts:
4795 		
4796 			<dl>
4797 				<dt>belowLast</dt>
4798 				<dd>Display the nav beneath the last spotlight item</dd>
4799 				<dt>belowNext</dt>
4800 				<dd>Display the nav beneath the next button</dd>
4801 				<dt>belowMiddle</dt>
4802 				<dd>Display the nav beneath the carousel, centred</dd>
4803 				<dt>aboveLast</dt>
4804 				<dd>Display the nav above the last spotlight item</dd>
4805 				<dt>aboveNext</dt>
4806 				<dd>Display the nav above the next button</dd>
4807 				<dt>aboveMiddle</dt>
4808 				<dd>Display the nav above the carousel, centred</dd>
4809 			</dl>
4810 			
4811 		@param {boolean} [opts.useNumbers=false] Display as numbers rather than blocks.
4812 		
4813 		@returns this
4814 		
4815 		@example
4816 			new glow.ui.Carousel('#carouselContainer').addPageNav({
4817 				position: 'belowMiddle',
4818 				useNumbers: true
4819 			});
4820 	*/
4821 	CarouselProto.addPageNav = function(opts) {
4822 		opts = glow.util.apply({
4823 			position: 'belowLast'
4824 		}, opts);
4825 		
4826 		var className = 'Carousel-pageNav';
4827 		
4828 		if (opts.useNumbers) {
4829 			className += 'Numbers';
4830 		}
4831 		
4832 		this._pageNav = glow('<div class="' + className + '"></div>')
4833 			.delegate('click', 'div', pageNavClick, this);
4834 		
4835 		this._pageNavOpts = opts;
4836 		
4837 		initPageNav(this);
4838 		
4839 		return this;
4840 	};
4841 	
4842 	/**
4843 		@private
4844 		@function
4845 		@description Listener for one of the page buttons being clicked.
4846 			'this' is the carousel
4847 	*/
4848 	function pageNavClick(event) {
4849 		var targetPage = ( glow(event.attachedTo).text() - 1 ) * this._pane._step;
4850 		this.moveTo(targetPage);
4851 	}
4852 	
4853 	/**
4854 		@private
4855 		@function
4856 		@description Calculate the number of pages this carousel has
4857 	*/
4858 	function getNumberOfPages(carousel) {
4859 		var pane = carousel._pane,
4860 			itemsLength = carousel.items.length,
4861 			step = pane._step;
4862 		
4863 		if (carousel._opts.loop) {
4864 			r = Math.ceil( itemsLength / step );
4865 		}
4866 		else {
4867 			r = 1 + Math.ceil( (itemsLength - pane._spot.capacity) / step );
4868 		}
4869 		
4870 		// this can be less than one if there's less than 1 page worth or items
4871 		return Math.max(r, 0);
4872 	}
4873 	
4874 	/**
4875 		@private
4876 		@function
4877 		@description Position & populate the page nav.
4878 			Its position may need refreshed after updating the carousel ui.
4879 	*/
4880 	function initPageNav(carousel) {
4881 		var pageNav = carousel._pageNav,
4882 			position = carousel._pageNavOpts.position,
4883 			positionY = position.slice(0,5),
4884 			positionX = position.slice(5),
4885 			pane = carousel._pane,
4886 			numberOfPages = getNumberOfPages(carousel),
4887 			htmlStr = '';
4888 		
4889 		// either append or prepend the page nav, depending on option
4890 		carousel.container[ (positionY === 'below') ? 'append' : 'prepend' ](pageNav);
4891 		
4892 		// position in the center for Middle positions, otherwise right
4893 		pageNav.css('text-align', (positionX == 'Middle') ? 'center' : 'right');
4894 
4895 		// move it under the last item for *Last positions
4896 		if (positionX === 'Last') {
4897 			pageNav.css( 'margin-right', carousel._nextBtn.width() + pane._itemDimensions.marginRight )
4898 		}
4899 		
4900 		// build the html string
4901 		do {
4902 			htmlStr = '<div>' + numberOfPages + '</div>' + htmlStr;
4903 		} while (--numberOfPages);
4904 		
4905 		pageNav.html(htmlStr);
4906 		carousel._updateNav( pane._index / pane._step );
4907 	}
4908 	
4909 	/**
4910 		@name glow.ui.Carousel#_updateNav
4911 		@function
4912 		@description Activate a particular item on the pageNav
4913 		
4914 		@param {number} indexToActivate
4915 	*/
4916 	CarouselProto._updateNav = function(indexToActivate) {
4917 		if (this._pageNav) {
4918 			var activeClassName = 'active';
4919 			
4920 			this._pageNav.children()
4921 				.removeClass(activeClassName)
4922 				.item(indexToActivate).addClass(activeClassName);	
4923 		}
4924 	}
4925 });
4926 Glow.complete('ui', '2.0.0b1');
4927