PageSourceSearch

https://issei-after-service.vercel.app/wp-content/plugins/bb-plugin/js/fl-slideshow.js

js issei-after-service.vercel.app collected 2026-10-03 07:08:14 UTC 241,058 bytes, 10,641 lines download raw bytes

1
2/**
3 * Slideshow JS Bundle
4 */
5YUI.add('fl-event-move', function(Y) {
6
7/**
8 * Adds gesturemovevertical, gesturemoveverticalend, gesturemovehorizontal
9 * and gesturemovehorizontalend events.
10 *
11 * @module fl-event-move
12 */
13var _eventBase = {
14
15	_isEndEvent: false,
16
17	on: function(node, subscriber, ce)
18	{
19    	if(this.type.indexOf('end') > -1) {
20    		this._isEndEvent = true;
21    	}
22
23		subscriber._direction = this.type.replace('gesturemove', '').replace('end', '');
24
25		if(window.navigator.msPointerEnabled) {
26    		subscriber._startHandle = node.on('MSPointerDown', this._onStart, this, node, subscriber, ce);
27        	subscriber._moveHandle = node.on('MSPointerMove', this._onMove, this, node, subscriber, ce);
28        	subscriber._endHandle = node.on('MSPointerUp', this._onEnd, this, node, subscriber, ce);
29		}
30		else {
31    	    subscriber._startHandle = node.on('gesturemovestart', this._onStart, null, this, node, subscriber, ce);
32        	subscriber._moveHandle = node.on('gesturemove', this._onMove, null, this, node, subscriber, ce);
33        	subscriber._endHandle = node.on('gesturemoveend', this._onEnd, { standAlone: true }, this, node, subscriber, ce);
34		}
35    },
36
37    detach: function(node, subscriber, ce)
38    {
39        subscriber._startHandle.detach();
40        subscriber._startHandle = null;
41		subscriber._moveHandle.detach();
42        subscriber._moveHandle = null;
43		subscriber._endHandle.detach();
44		subscriber._endHandle = null;
45    },
46
47    _onStart: function(e, node, subscriber, ce)
48    {
49    	subscriber._doMove = null;
50		subscriber._startX = e.pageX;
51		subscriber._startY = e.pageY;
52	},
53
54	_onMove: function(e, node, subscriber, ce)
55	{
56		if(this._checkDirection(e, subscriber)) {
57			subscriber._doMove = true;
58		}
59		else {
60			subscriber._doMove = false;
61		}
62		if(subscriber._doMove && !this._isEndEvent) {
63			ce.fire(e);
64		}
65	},
66
67    _onEnd: function(e, node, subscriber, ce)
68    {
69    	if(subscriber._doMove && this._isEndEvent) {
70    		e.startPageX = subscriber._startX;
71    		e.startPageY = subscriber._startY;
72    		ce.fire(e);
73    	}
74
75    	subscriber._doMove = null;
76	},
77
78	_checkDirection: function(e, subscriber)
79	{
80		var xDelta = Math.abs(subscriber._startX - e.pageX),
81			yDelta = Math.abs(subscriber._startY - e.pageY);
82
83		if(yDelta > xDelta && subscriber._startY > e.pageY && subscriber._direction == 'vertical') {
84			return true;
85		}
86		else if(yDelta > xDelta && subscriber._startY < e.pageY && subscriber._direction == 'vertical') {
87			return true;
88		}
89		else if(yDelta < xDelta && subscriber._startX > e.pageX && subscriber._direction == 'horizontal') {
90			return true;
91		}
92		else if(yDelta < xDelta && subscriber._startX < e.pageX && subscriber._direction == 'horizontal') {
93			return true;
94		}
95
96		return false;
97	}
98};
99
100/**
101 * @event gesturemovevertical
102 * @param type {String} "gesturemovevertical"
103 * @param fn {Function} The method the event invokes.
104 * @param ctx {Object} Context for the method the event invokes.
105 */
106Y.Event.define('gesturemovevertical', _eventBase);
107
108/**
109 * @event gesturemoveverticalend
110 * @param type {String} "gesturemoveverticalend"
111 * @param fn {Function} The method the event invokes.
112 * @param ctx {Object} Context for the method the event invokes.
113 */
114Y.Event.define('gesturemoveverticalend', _eventBase);
115
116/**
117 * @event gesturemovehorizontal
118 * @param type {String} "gesturemovehorizontal"
119 * @param fn {Function} The method the event invokes.
120 * @param ctx {Object} Context for the method the event invokes.
121 */
122Y.Event.define('gesturemovehorizontal', _eventBase);
123
124/**
125 * @event gesturemovehorizontalend
126 * @param type {String} "gesturemovehorizontalend"
127 * @param fn {Function} The method the event invokes.
128 * @param ctx {Object} Context for the method the event invokes.
129 */
130Y.Event.define('gesturemovehorizontalend', _eventBase);
131
132
133}, '2.0.0' ,{requires:['event-move']});
134
135
136YUI.add('fl-slideshow', function(Y) {
137
138/**
139 * @module fl-slideshow
140 */
141
142/**
143 * Caption widget used in slideshows.
144 *
145 * @namespace FL
146 * @class SlideshowCaption
147 * @constructor
148 * @param config {Object} Configuration object
149 * @extends Widget
150 */
151Y.namespace('FL').SlideshowCaption = Y.Base.create('fl-slideshow-caption', Y.Widget, [Y.WidgetChild], {
152
153	/**
154	 * Flag for whether the text has been
155	 * toggled or not.
156	 *
157	 * @property _textToggled
158	 * @type Boolean
159	 * @default false
160	 * @protected
161	 */
162	_textToggled: false,
163
164	/**
165	 * An anchor node used for the toggle link.
166	 *
167	 * @property _textToggleLink
168	 * @type Object
169	 * @default null
170	 * @protected
171	 */
172	_textToggleLink: null,
173
174	/**
175	 * @method renderUI
176	 * @protected
177	 */
178	renderUI: function()
179	{
180        var root = this.get('root'),
181		    bb   = this.get('boundingBox');
182
183		this._textToggleLink = Y.Node.create('<a href="javascript:void(0);"></a>');
184		this._textToggleLink.addClass('fl-slideshow-caption-toggle');
185		this._textToggleLink.set('innerHTML', root.get('captionMoreLinkText'));
186
187		bb.appendChild(this._textToggleLink);
188	},
189
190	/**
191	 * @method bindUI
192	 * @protected
193	 */
194	bindUI: function()
195	{
196		this.get('root').on('imageLoadComplete', Y.bind(this._setText, this));
197		this._textToggleLink.on('click', Y.bind(this._toggleText, this));
198	},
199
200	/**
201	 * Sets the caption text and displays the
202	 * toggle link if necessary.
203	 *
204	 * @method _setText
205	 * @protected
206	 */
207
208	_setText: function()
209	{
210		var root 		= this.get('root'),
211			text 		= root.imageInfo.caption,
212			textLength	= root.get('captionTextLength'),
213			cb			= this.get('contentBox');
214
215		if(!root.imageInfo.caption || root.imageInfo.caption === '') {
216			cb.set('innerHTML', '');
217			this._textToggleLink.setStyle('display', 'none');
218			return;
219		}
220		else if(textLength > -1) {
221			if(!this._textToggled && textLength < text.length) {
222				text = this._shortenText(text);
223				this._textToggleLink.setStyle('display', 'inline-block');
224			}
225			else if(this._textToggled && textLength < text.length) {
226				text = this._stripTags(text);
227				this._textToggleLink.setStyle('display', 'inline-block');
228			}
229			else {
230				text = this._stripTags(text);
231				this._textToggleLink.setStyle('display', 'none');
232			}
233		}
234		else {
235			text = this._stripTags(text);
236		}
237
238		cb.set('innerHTML', text);
239	},
240
241	/**
242	 * Shows or hides the full text when the
243	 * toggle link is clicked.
244	 *
245	 * @method _toggleText
246	 * @protected
247	 */
248	_toggleText: function()
249	{
250		var root 		= this.get('root'),
251			text 		= root.imageInfo.caption,
252			cb			= this.get('contentBox');
253
254		if(this._textToggled) {
255			text = this._shortenText(text);
256			this._textToggleLink.set('innerHTML', root.get('captionMoreLinkText'));
257			this._textToggled = false;
258		}
259		else {
260			text = this._stripTags(text);
261			this._textToggleLink.set('innerHTML', root.get('captionLessLinkText'));
262			this._textToggled = true;
263		}
264
265		cb.set('innerHTML', text);
266	},
267
268	/**
269	 * Strips out HTML tags from the caption text.
270	 *
271	 * @method _stripTags
272	 * @param text {String} The text to strip HTML tags from.
273	 * @param ignoreSettings {Boolean} If true, will strip tags even if
274	 * the stripTags attribute is set to false.
275	 * @protected
276	 */
277	_stripTags: function(text, ignoreSettings)
278	{
279        var root = this.get('root'), textDiv;
280
281		if(ignoreSettings || root.get('captionStripTags')) {
282			textDiv = document.createElement('div');
283			textDiv.innerHTML = text;
284			text = textDiv.textContent || textDiv.innerText;
285		}
286
287		return text;
288	},
289
290	/**
291	 * Shortens the caption text to the length of
292	 * the textLength attribute.
293	 *
294	 * @method _shortenText
295	 * @protected
296	 */
297	_shortenText: function(text)
298	{
299        var root = this.get('root');
300
301		text = this._stripTags(text, true).substring(0, root.get('captionTextLength'));
302
303		return Y.Lang.trim(text.substring(0, text.lastIndexOf(' '))) + ' ...';
304	}
305
306}, {
307
308	/**
309	 * Custom CSS class name for the widget.
310	 *
311	 * @property CSS_PREFIX
312	 * @type String
313	 * @protected
314	 * @static
315	 */
316	CSS_PREFIX: 'fl-slideshow-caption',
317
318	/**
319	 * Static property used to define the default attribute configuration of
320	 * the Widget.
321	 *
322	 * @property ATTRS
323	 * @type Object
324	 * @protected
325	 * @static
326	 */
327	ATTRS: {
328
329	}
330});
331
332/**
333 * A widget for loading and transitioning between SlideshowImage
334 * instances. Each SlideshowImage instance is a child widget of
335 * SlideshowFrame. SlideshowFrame is a child widget of the main
336 * slideshow widget.
337 *
338 * @namespace FL
339 * @class SlideshowFrame
340 * @constructor
341 * @param config {Object} Configuration object
342 * @extends Widget
343 */
344Y.namespace('FL').SlideshowFrame = Y.Base.create('fl-slideshow-frame', Y.Widget, [Y.WidgetParent, Y.WidgetChild], {
345
346	/**
347	 * The imageInfo object used to load the active image.
348	 *
349	 * @property info
350	 * @type Object
351	 * @default null
352	 * @protected
353	 */
354	_imageInfo: null,
355
356	/**
357	 * The active FL.SlideshowImage instance in the frame.
358	 *
359	 * @property _activeImage
360	 * @type FL.SlideshowImage
361	 * @default null
362	 * @protected
363	 */
364	_activeImage: null,
365
366	/**
367	 * A FL.SlideshowImage instance used to load the
368	 * next image and transition it into the frame.
369	 *
370	 * @property _nextImage
371	 * @type FL.SlideshowImage
372	 * @default null
373	 * @protected
374	 */
375	_nextImage: null,
376
377	/**
378	 * Used to store imageInfo if a load request is
379	 * made while the frame is transitioning. If not null
380	 * when the transition completes, a new image will
381	 * be loaded using the imageInfo.
382	 *
383	 * @property _loadQueue
384	 * @type Object
385	 * @default false
386	 * @protected
387	 */
388	_loadQueue: null,
389
390	/**
391	 * An instance of FL.SlideshowTransition used for
392	 * the current transition in progress.
393	 *
394	 * @property _transition
395	 * @type FL.SlideshowTransition
396	 * @default null
397	 * @protected
398	 */
399	_transition: null,
400
401	/**
402	 * A flag for whether the frame is currently transitioning or not.
403	 *
404	 * @property _transitioning
405	 * @type Boolean
406	 * @default false
407	 * @protected
408	 */
409	_transitioning: false,
410
411	/**
412	 * Flag for whether to resize when the current transition
413	 * completes. Set to true when a resize request is made
414	 * during a transition.
415	 *
416	 * @property _resizeAfterTransition
417	 * @type Boolean
418	 * @default false
419	 * @protected
420	 */
421	_resizeAfterTransition: false,
422
423	/**
424	 * Provides functionality for gesture based transitions
425 	 * between the active and next images.
426	 *
427	 * @property _gestures
428	 * @type FL.SlideshowGestures
429	 * @default null
430	 * @protected
431	 */
432	_gestures: null,
433
434	/**
435	 * Creates new instances of FL.SlideshowImage used in the frame.
436	 *
437	 * @method initializer
438	 * @protected
439	 */
440	initializer: function()
441	{
442		var imageConfig = this.get('imageConfig');
443
444		this._activeImage = new Y.FL.SlideshowImage(imageConfig);
445		this._nextImage = new Y.FL.SlideshowImage(imageConfig);
446	},
447
448	/**
449	 * Renders the FL.SlideshowImage instances used in the frame.
450	 *
451	 * @method renderUI
452	 * @protected
453	 */
454	renderUI: function()
455	{
456		this.add(this._activeImage);
457		this.add(this._nextImage);
458	},
459
460	/**
461	 * @method bindUI
462	 * @protected
463	 */
464	bindUI: function()
465	{
466		var activeBB 	= this._activeImage.get('boundingBox'),
467			nextBB 		= this._nextImage.get('boundingBox'),
468			transition	= this.get('transition');
469
470		if(('ontouchstart' in window || window.navigator.msPointerEnabled) && this.get('touchSupport')) {
471
472			this._gestures = new Y.FL.SlideshowGestures({
473				direction: transition == 'slideVertical' ? 'vertical' : 'horizontal',
474				activeItem: activeBB,
475				nextItem: nextBB
476			});
477
478			this._gestures.on('moveStart', this._gesturesMoveStart, this);
479			this._gestures.on('endComplete', this._gesturesEndComplete, this);
480		}
481	},
482
483	/**
484	 * Functional styles for the UI.
485	 *
486	 * @method syncUI
487	 * @protected
488	 */
489	syncUI: function()
490	{
491		var activeBB 	= this._activeImage.get('boundingBox'),
492			nextBB	 	= this._nextImage.get('boundingBox'),
493			cb			= this.get('contentBox');
494
495		activeBB.setStyle('position', 'absolute');
496		activeBB.setStyle('top', '0px');
497		activeBB.setStyle('left', '-9999px');
498
499		nextBB.setStyle('position', 'absolute');
500		nextBB.setStyle('top', '0px');
501		nextBB.setStyle('left', '-9999px');
502
503		cb.setStyle('position', 'relative');
504		cb.setStyle('overflow', 'hidden');
505	},
506
507	/**
508	 * Checks whether the imageInfo should be loaded or queued.
509	 * Initializes a new transition if loading is ok.
510	 *
511	 * @method load
512	 * @param imageInfo {Object} The image info to load.
513	 */
514	load: function(imageInfo)
515	{
516		var activeInfo = this._activeImage._imageInfo;
517
518		if(this._transitioning) {
519			this._loadQueue = imageInfo;
520			return;
521		}
522		else if(activeInfo && activeInfo.largeURL == imageInfo.largeURL) {
523			return;
524		}
525
526		this._imageInfo = imageInfo;
527		this._transitionInit(imageInfo);
528	},
529
530	/**
531	 * Preloads the next image using the provided imageInfo.
532	 *
533	 * @method preload
534	 * @param imageInfo {Object} The imageInfo to preload.
535	 * @param width {Number} The width to preload.
536	 * @param height {Number} The height to preload.
537	 */
538	preload: function(imageInfo, width, height)
539	{
540		this._imageInfo = imageInfo;
541		this._nextImage.preload(imageInfo, width, height);
542	},
543
544	/**
545	 * Unloads the active and next image instances.
546	 *
547	 * @method unload
548	 */
549	unload: function()
550	{
551		this._imageInfo = null;
552		this._loadQueue = null;
553		this._transitioning = false;
554		this._transition = null;
555
556		this._activeImage.detachAll();
557		this._activeImage.unload();
558		this._activeImage.get('boundingBox').setStyle('left', '-9999px');
559
560		this._nextImage.detachAll();
561		this._nextImage.unload();
562		this._nextImage.get('boundingBox').setStyle('left', '-9999px');
563	},
564
565	/**
566	 * Resizes the bounding box and active image.
567	 *
568	 * @method resize
569	 * @param width {Number} The width value.
570	 * @param height {Number} The height value.
571	 */
572	resize: function(width, height)
573	{
574		if(!width || !height) {
575			return;
576		}
577
578		var bb 		= this.get('boundingBox'),
579			padding = [
580				parseInt(bb.getComputedStyle('paddingTop'), 10),
581				parseInt(bb.getComputedStyle('paddingRight'), 10),
582				parseInt(bb.getComputedStyle('paddingBottom'), 10),
583				parseInt(bb.getComputedStyle('paddingLeft'), 10)
584			];
585
586		width = width - padding[1] - padding[3];
587		height = height - padding[0] - padding[2];
588
589		this.set('width', width);
590		this.set('height', height);
591
592		if(this._transitioning) {
593			this._resizeAfterTransition = true;
594		}
595		else {
596			this._activeImage.resize(width, height);
597			this._nextImage.resize(width, height);
598		}
599	},
600
601	/**
602	 * Gets the current transition to use.
603	 *
604	 * @method _getTransition
605	 * @protected
606	 */
607	_getTransition: function()
608	{
609		var root            = this.get('root'),
610            lastIndex 		= root.albumInfo.images.length - 1,
611			direction		= 'next',
612			transition 		= root.get('transition');
613
614		if(root.lastImageIndex === null) {
615			direction = '';
616		}
617		else if(root.imageIndex == lastIndex && root.lastImageIndex === 0) {
618			direction = 'prev';
619		}
620		else if(root.imageIndex === 0 && root.lastImageIndex == lastIndex) {
621			direction = 'next';
622		}
623		else if(root.lastImageIndex > root.imageIndex) {
624			direction = 'prev';
625		}
626		else if(root.lastImageIndex < root.imageIndex) {
627			direction = 'next';
628		}
629
630		if(direction == 'next') {
631			transition = transition.replace('slideHorizontal', 'slideLeft');
632			transition = transition.replace('slideVertical', 'slideUp');
633		}
634		else if(direction == 'prev') {
635			transition = transition.replace('slideHorizontal', 'slideRight');
636			transition = transition.replace('slideVertical', 'slideDown');
637		}
638
639		return transition;
640	},
641
642	/**
643	 * Fires the transitionInit event and loads the next image.
644	 * The transition starts when the image's loadComplete
645	 * event is fired.
646	 *
647	 * @method _transitionInit
648	 * @param imageInfo {Object} The imageInfo to load before transitioning.
649	 * @protected
650	 */
651	_transitionInit: function(imageInfo)
652	{
653		this._transitioning = true;
654
655		// Disable gestures if set.
656		if(this._gestures) {
657			this._gestures.disable();
658		}
659
660		/**
661		 * Fires when the next image is loading before a new transition.
662		 *
663		 * @event transitionInit
664		 */
665		this.fire('transitionInit');
666
667		if(imageInfo) {
668			this._nextImage.once('loadComplete', this._transitionStart, this);
669			this._nextImage.load(imageInfo);
670		}
671		else {
672			this._transitionStart();
673		}
674	},
675
676	/**
677	 * Fires the transitionStart event and starts the transition
678	 * using a new instance of FL.SlideshowTransition.
679	 *
680	 * @method _transitionStart
681	 * @protected
682	 */
683	_transitionStart: function()
684	{
685        var root = this.get('root');
686
687		/**
688		 * Fires when the next image has finished loading
689		 * and a new transition starts.
690		 *
691		 * @event transitionStart
692		 */
693		this.fire('transitionStart');
694
695		this._transition = new Y.FL.SlideshowTransition({
696			itemIn: this._nextImage._imageInfo ? this._nextImage.get('boundingBox') : null,
697			itemOut: this._activeImage._imageInfo ? this._activeImage.get('boundingBox') : null,
698			type: this._getTransition(),
699			duration: root.get('transitionDuration'),
700			easing: root.get('transitionEasing'),
701			kenBurnsDuration: root.get('speed')/1000,
702			kenBurnsZoom: root.get('kenBurnsZoom')
703		});
704
705		if(this._nextImage._imageInfo) {
706			this._nextImage.get('boundingBox').setStyle('left', '0px');
707		}
708
709		this._transition.once('complete', this._transitionComplete, this);
710		this._transition.run();
711	},
712
713	/**
714	 * Switches the next and active image variables, unloads the
715	 * last image, fires the transitionComplete event and loads
716	 * or resizes if appropriate.
717	 *
718	 * @method _transitionComplete
719	 * @protected
720	 */
721	_transitionComplete: function()
722	{
723        var root = this.get('root');
724
725        // Swap image container references.
726		this._swapImageRefs();
727
728		/**
729		 * Fired when the current transition completes.
730		 *
731		 * @event transitionComplete
732		 */
733		this.fire('transitionComplete');
734		this._transition = null;
735		this._transitioning = false;
736
737		// Enable gestures if set.
738		if(this._gestures) {
739            if(root && root.albumInfo.images.length <= 1) {
740                this._gestures.disable();
741            }
742            else {
743                this._gestures.enable();
744            }
745		}
746
747		// Load from the queue?
748		if(this._loadQueue) {
749			this.load(this._loadQueue);
750			this._loadQueue = null;
751		}
752		// Resize the active image?
753		else if(this._resizeAfterTransition) {
754			this._resizeAfterTransition = false;
755			this._activeImage.resize(this.get('width'), this.get('height'));
756			this._nextImage.resize(this.get('width'), this.get('height'));
757		}
758	},
759
760	/**
761	 * @method _gesturesMoveStart
762	 * @param e {Object} The event object.
763	 * @protected
764	 */
765	_gesturesMoveStart: function(e)
766	{
767		var index 	= 0,
768			root 	= this.get('root');
769
770		index = e.direction == 'next' ? root.imageIndex + 1 : root.imageIndex - 1;
771        index = index < 0 ? root.albumInfo.images.length - 1 : index;
772		index = index >= root.albumInfo.images.length ? 0 : index;
773
774		root.pause();
775		root._hideLoadingImage();
776		root._showLoadingImageWithDelay();
777
778		Y.FL.SlideshowImageLoader.removeGroup(this._nextImage.get('loadGroup'));
779
780		this._nextImage.once('loadComplete', root._hideLoadingImage, root);
781		this._nextImage.load(root.albumInfo.images[index]);
782	},
783
784	/**
785	 * @method _gesturesEndComplete
786	 * @protected
787	 */
788	_gesturesEndComplete: function()
789	{
790		var root	= this.get('root'),
791			index	= 0;
792
793        if(this._nextImage._imageInfo){
794        	index = this._nextImage._imageInfo.index;
795        	this._swapImageRefs();
796			this._imageInfo = root.albumInfo.images[index];
797			root.loadImage(index);
798        }
799	},
800
801	/**
802	 * @method _swapImageRefs
803	 * @protected
804	 */
805	_swapImageRefs: function()
806	{
807		var active = this._activeImage;
808		this._activeImage = this._nextImage;
809		this._nextImage = active;
810
811		if(this._nextImage._imageInfo) {
812			this._nextImage.unload();
813			this._nextImage.get('boundingBox').setStyle('left', '-9999px');
814		}
815		if(this._gestures) {
816			this._gestures.set('activeItem', this._activeImage.get('boundingBox'));
817			this._gestures.set('nextItem', this._nextImage.get('boundingBox'));
818		}
819	}
820
821}, {
822
823	/**
824	 * Custom CSS class name for the widget.
825
826	 * @property CSS_PREFIX
827	 * @type String
828	 * @protected
829	 * @static
830	 */
831	CSS_PREFIX: 'fl-slideshow-frame',
832
833	/**
834	 * Static property used to define the default attribute configuration of
835	 * the Widget.
836	 *
837	 * @property ATTRS
838	 * @type Object
839	 * @protected
840	 * @static
841	 */
842	ATTRS: {
843
844		/**
845		 * The configuration object used to create new instances of
846		 * FL.SlideshowImage. See the API docs for {@link FL.SlideshowImage}
847		 * for a complete list of configuration attributes.
848		 *
849		 * @attribute imageConfig
850		 * @type Object
851		 * @default null
852		 */
853		imageConfig: {
854			value: null
855		},
856
857		/**
858		 * Whether to use touch gestures, when available,
859		 * to transition between images or not.
860		 *
861		 * @attribute touchSupport
862		 * @type Boolean
863		 * @default false
864		 */
865		touchSupport: {
866			value: false
867		}
868	}
869});
870
871/**
872 * A plugin for fullscreen slideshow functionality.
873 *
874 * @namespace FL
875 * @class SlideshowFullscreen
876 * @constructor
877 * @param config {Object} Configuration object
878 * @extends Plugin.Base
879 */
880Y.namespace('FL').SlideshowFullscreen = Y.Base.create('fl-slideshow-fullscreen', Y.Plugin.Base, [], {
881
882	/**
883	 * Flag for whether the slideshow is in
884	 * fullscreen mode.
885	 *
886	 * @property active
887	 * @type Boolean
888	 * @default false
889	 */
890	active: false,
891
892	/**
893	 * A div containing the close message.
894	 *
895	 * @property _closeMessage
896	 * @type Node
897	 * @default null
898	 * @protected
899	 */
900	_closeMessage: null,
901
902	/**
903	 * A timer for hiding the close message.
904	 *
905	 * @property _closeMessageTimer
906	 * @type Object
907	 * @default null
908	 * @protected
909	 */
910	_closeMessageTimer: null,
911
912	/**
913	 * The initial styles of the host's bounding box
914	 * before entering fullscreen mode.
915	 *
916	 * @property _initialStyles
917	 * @type Object
918	 * @protected
919	 */
920	_initialStyles: {
921		position: 'static',
922		top: '0px',
923		left: '0px'
924	},
925
926	/**
927	 * @method initializer
928	 * @protected
929	 */
930	initializer: function()
931	{
932		var host 	= this.get('host'),
933			bb 		= host.get('boundingBox'),
934			self 	= this;
935
936		bb.addClass('fl-fullscreen-enabled');
937
938		if(Y.FL.SlideshowFullscreen.OS_SUPPORT) {
939			document.addEventListener('fullscreenchange', function(){ self._osChange(); }, false);
940			document.addEventListener('mozfullscreenchange', function(){ self._osChange(); }, false);
941			document.addEventListener('webkitfullscreenchange', function(){ self._osChange(); }, false);
942		}
943		else {
944			this._renderCloseMessage();
945		}
946    },
947
948	/**
949	 * Exits fullscreen if it is currently active
950	 * otherwise it enters fullscreen.
951	 *
952	 * @method toggle
953	 */
954	toggle: function()
955	{
956		if(this.active) {
957			this.exit();
958		}
959		else {
960			this.enter();
961		}
962	},
963
964	/**
965	 * Enters OS fullscreen mode if supported, otherwise
966	 * the slideshow takes over the browser window.
967	 *
968	 * @method enter
969	 */
970	enter: function()
971	{
972		if(Y.FL.SlideshowFullscreen.OS_SUPPORT) {
973			this._osEnter();
974		}
975		else {
976			this._browserEnter();
977		}
978	},
979
980	/**
981	 * Exits fullscreen mode.
982	 *
983	 * @method exit
984	 */
985	exit: function()
986	{
987		if(Y.FL.SlideshowFullscreen.OS_SUPPORT) {
988			this._osExit();
989		}
990		else {
991			this._browserExit();
992		}
993	},
994
995	/**
996	 * Enters OS fullscreen mode.
997	 *
998	 * @method _osEnter
999	 * @protected
1000	 */
1001	_osEnter: function()
1002	{
1003		var bbNode = this.get('host').get('boundingBox')._node;
1004
1005		if(bbNode.webkitRequestFullScreen) {
1006			bbNode.webkitRequestFullScreen();
1007		}
1008		else if(bbNode.mozRequestFullScreen) {
1009			bbNode.mozRequestFullScreen();
1010		}
1011		else if(bbNode.requestFullScreen) {
1012			bbNode.requestFullScreen();
1013		}
1014	},
1015
1016	/**
1017	 * Exits OS fullscreen mode.
1018	 *
1019	 * @method _osExit
1020	 * @protected
1021	 */
1022	_osExit: function()
1023	{
1024		if(document.exitFullscreen) {
1025            document.exitFullscreen();
1026        }
1027        else if(document.mozCancelFullScreen) {
1028            document.mozCancelFullScreen();
1029        }
1030        else if(document.webkitCancelFullScreen) {
1031            document.webkitCancelFullScreen();
1032        }
1033	},
1034
1035	/**
1036	 * Called when the OS fullscreenchange event fires and enters
1037	 * or exits standard fullscreen mode which positions and
1038	 * resizes the slideshow.
1039	 *
1040	 * @method _osChange
1041	 * @protected
1042	 */
1043	_osChange: function()
1044	{
1045		var host = this.get('host');
1046
1047		// Transitions break on Safari while entering and
1048		// exiting fullscreen. This fixes them!
1049		if(host.frame && host.frame._transitioning) {
1050			host.frame._transitionComplete();
1051		}
1052
1053		if(this.active) {
1054			this._exit();
1055		}
1056		else {
1057			this._enter();
1058		}
1059	},
1060
1061	/**
1062	 * Enter browser fullscreen mode.
1063	 *
1064	 * @method _browserEnter
1065	 * @protected
1066	 */
1067	_browserEnter: function()
1068	{
1069		var bb = this.get('host').get('boundingBox');
1070
1071		this._initialStyles = {
1072    		position: bb.getStyle('position'),
1073    		top: bb.getStyle('top'),
1074    		left: bb.getStyle('left'),
1075    		zIndex: bb.getStyle('zIndex')
1076    	};
1077
1078		bb.setStyles({
1079			position: 'fixed',
1080			top: '0px',
1081			left: '0px',
1082			zIndex: 10000
1083		});
1084
1085		Y.Node.one('body').on('fl-fullscreen|keydown', Y.bind(this._onKey, this));
1086
1087		this._showCloseMessage();
1088		this._enter();
1089	},
1090
1091	/**
1092	 * Exit browser fullscreen mode.
1093	 *
1094	 * @method _browserExit
1095	 * @protected
1096	 */
1097	_browserExit: function()
1098	{
1099		var bb = this.get('host').get('boundingBox');
1100
1101		bb.setStyles({
1102			position: this._initialStyles.position,
1103			top: this._initialStyles.top,
1104			left: this._initialStyles.left,
1105			zIndex: this._initialStyles.zIndex
1106		});
1107
1108		Y.Node.one('body').detach('fl-fullscreen|keydown');
1109
1110		this._hideCloseMessage();
1111		this._exit();
1112	},
1113
1114	/**
1115	 * Enters fullscreen mode.
1116	 *
1117	 * @method _enter
1118	 * @protected
1119	 */
1120	_enter: function()
1121	{
1122		var host 	= this.get('host'),
1123			bb 		= host.get('boundingBox');
1124
1125		bb.addClass('fl-fullscreen-active');
1126
1127		this.active = true;
1128
1129		host.resize();
1130	},
1131
1132	/**
1133	 * Exits fullscreen mode.
1134	 *
1135	 * @method _exit
1136	 * @protected
1137	 */
1138	_exit: function()
1139	{
1140		var host 	= this.get('host'),
1141			bb 		= host.get('boundingBox');
1142
1143		bb.removeClass('fl-fullscreen-active');
1144
1145		this.active = false;
1146
1147		host.resize();
1148	},
1149
1150	/**
1151	 * Keyboard input for the esc button.
1152	 *
1153	 * @method _onKey
1154	 * @protected
1155	 */
1156	_onKey: function(e)
1157	{
1158		if(e.keyCode == 27) {
1159			this.exit();
1160			return false;
1161		}
1162	},
1163
1164	/**
1165	 * Creates the close message if one is
1166	 * not already available in the document.
1167	 *
1168	 * @method _initCloseMessage
1169	 * @protected
1170	 */
1171	_renderCloseMessage: function()
1172	{
1173		this._closeMessage = Y.Node.create('<div class="fl-fullscreen-close-message"></div>');
1174		this._closeMessage.set('innerHTML', '<span>Press the "esc" button to exit fullscreen mode.</span>');
1175		this._closeMessage.setStyle('display', 'none');
1176		this.get('host').get('boundingBox').insert(this._closeMessage);
1177	},
1178
1179	/**
1180	 * Shows the close message.
1181	 *
1182	 * @method _showCloseMessage
1183	 * @protected
1184	 */
1185	_showCloseMessage: function()
1186	{
1187		if(this._closeMessageTimer) {
1188			this._closeMessageTimer.cancel();
1189			this._closeMessageTimer = null;
1190		}
1191
1192		this._closeMessage.show(true);
1193		this._closeMessageTimer = Y.later(4000, this, this._hideCloseMessage);
1194	},
1195
1196	/**
1197	 * Hides the close message.
1198	 *
1199	 * @method _hideCloseMessage
1200	 * @protected
1201	 */
1202	_hideCloseMessage: function()
1203	{
1204		if(this._closeMessageTimer) {
1205			this._closeMessageTimer.cancel();
1206			this._closeMessageTimer = null;
1207		}
1208
1209		this._closeMessage.hide(true);
1210	}
1211
1212},	{
1213
1214	/**
1215	 * Namespace for the plugin.
1216	 *
1217	 * @property NS
1218	 * @type String
1219	 * @protected
1220	 * @static
1221	 */
1222	NS: 'fullscreen',
1223
1224	OS_SUPPORT: (function(){
1225
1226		var doc = document.documentElement;
1227
1228		return doc.webkitRequestFullScreen || doc.mozRequestFullScreen || doc.requestFullScreen;
1229	})()
1230});
1231
1232/**
1233 * Provides functionality for gesture based transitions
1234 * between two slideshow components.
1235 *
1236 * @namespace FL
1237 * @class SlideshowGestures
1238 * @constructor
1239 * @param config {Object} Configuration object
1240 * @extends Base
1241 */
1242Y.namespace('FL').SlideshowGestures = Y.Base.create('fl-slideshow-gestures', Y.Base, [], {
1243
1244	/**
1245	 * The x coordinate for where a gesture event starts.
1246	 *
1247	 * @property _startX
1248	 * @type Number
1249	 * @default null
1250	 * @protected
1251	 */
1252	_startX: null,
1253
1254	/**
1255	 * The y coordinate for where a gesture event starts.
1256	 *
1257	 * @property _startY
1258	 * @type Number
1259	 * @default null
1260	 * @protected
1261	 */
1262	_startY: null,
1263
1264	/**
1265	 * A flag for whether a gesture is moving or not.
1266	 *
1267	 * @property _moving
1268	 * @type Boolean
1269	 * @default false
1270	 * @protected
1271	 */
1272	_touchMoving: false,
1273
1274	/**
1275	 * Whether the gesture is moving or not.
1276	 *
1277	 * @property _moving
1278	 * @type Boolean
1279	 * @default false
1280	 * @protected
1281	 */
1282	_moving: false,
1283
1284	/**
1285	 * The direction the current gesture event
1286	 * is moving in (either next or prev).
1287	 *
1288	 * @property _movingDirection
1289	 * @type String
1290	 * @default null
1291	 * @protected
1292	 */
1293	_movingDirection: null,
1294
1295	/**
1296	 * A flag for whether a gesture gesture is currently
1297	 * transitioning or not.
1298	 *
1299	 * @property _transitioning
1300	 * @type Boolean
1301	 * @default false
1302	 * @protected
1303	 */
1304	_transitioning: false,
1305
1306	/**
1307	 * @method initializer
1308	 * @protected
1309	 */
1310	initializer: function()
1311	{
1312		this.enable();
1313	},
1314
1315	/**
1316	 * @method enable
1317	 */
1318	enable: function()
1319	{
1320		var id 			= this.get('id'),
1321			direction 	= this.get('direction'),
1322			active 		= this.get('activeItem'),
1323			next 		= this.get('nextItem');
1324
1325		active.on(id + '|gesturemovestart', Y.bind(this._onStart, this));
1326		next.on(id + '|gesturemovestart', Y.bind(this._onStart, this));
1327		next.on(id + '|transitionend', Y.bind(this._onEndComplete, this) );
1328		next.on(id + '|oTransitionEnd', Y.bind(this._onEndComplete, this) );
1329		next.on(id + '|webkitTransitionEnd', Y.bind(this._onEndComplete, this) );
1330
1331		if(direction == 'horizontal') {
1332			active.on(id + '|gesturemovehorizontal', Y.bind(this._onMoveHorizontal, this));
1333			active.on(id + '|gesturemovehorizontalend', Y.bind(this._onEndHorizontal, this));
1334			next.on(id + '|gesturemovehorizontal', Y.bind(this._onMoveHorizontal, this));
1335			next.on(id + '|gesturemovehorizontalend', Y.bind(this._onEndHorizontal, this));
1336		}
1337		else {
1338			active.on(id + '|gesturemovevertical', Y.bind(this._onMoveVertical, this));
1339			active.on(id + '|gesturemoveverticalend', Y.bind(this._onEndVertical, this));
1340			next.on(id + '|gesturemovevertical', Y.bind(this._onMoveVertical, this));
1341			next.on(id + '|gesturemoveverticalend', Y.bind(this._onEndVertical, this));
1342		}
1343	},
1344
1345	/**
1346	 * @method disable
1347	 */
1348	disable: function()
1349	{
1350		var id 		= this.get('id'),
1351			active 	= this.get('activeItem'),
1352			next 	= this.get('nextItem');
1353
1354		active.detach(id + '|*');
1355		next.detach(id + '|*');
1356	},
1357
1358	/**
1359	 * @method _onStart
1360	 * @param e {Object} The event object.
1361	 * @protected
1362	 */
1363	_onStart: function(e)
1364	{
1365		var direction = this.get('direction');
1366
1367		if(this._transitioning) {
1368			this._onEndComplete();
1369		}
1370
1371		if(direction == 'horizontal') {
1372			this._startX = e.pageX;
1373		}
1374		else {
1375			this._startY = e.pageY;
1376		}
1377
1378		/**
1379		 * @event start
1380		 */
1381		this.fire('start');
1382	},
1383
1384	/**
1385	 * @method _onMoveHorizontal
1386	 * @param e {Object} The event object.
1387	 * @protected
1388	 */
1389	_onMoveHorizontal: function(e)
1390	{
1391		var x 			= this._startX - e.pageX,
1392			active 		= this.get('activeItem'),
1393			next 		= this.get('nextItem'),
1394			width		= parseInt(active.getComputedStyle('width'), 10),
1395			translate 	= x < 0 ? Math.abs(x) : -x,
1396			direction	= x < 0 ? 'prev' : 'next';
1397
1398		e.preventDefault();
1399
1400		if(!this._moving || this._movingDirection != direction) {
1401
1402        	active.setStyle('left', 0);
1403
1404	        next.setStyles({
1405	        	'opacity': 1,
1406	        	'left': x < 0 ? -width : width
1407	        });
1408
1409	        this._moving = true;
1410        	this._movingDirection = direction;
1411
1412			/**
1413			 * @event moveStart
1414			 */
1415			this.fire('moveStart', { direction: direction });
1416		}
1417
1418		active.setStyle('-webkit-transform', 'translate('+ translate +'px, 0px) translateZ(0px)');
1419		active.setStyle('-ms-transform', 'translate('+ translate +'px, 0px) translateZ(0px)');
1420        next.setStyle('-webkit-transform', 'translate('+ translate +'px, 0px) translateZ(0px)');
1421        next.setStyle('-ms-transform', 'translate('+ translate +'px, 0px) translateZ(0px)');
1422
1423		/**
1424		 * @event move
1425		 */
1426		this.fire('move');
1427	},
1428
1429	/**
1430	 * @method _onMoveVertical
1431	 * @param e {Object} The event object.
1432	 * @protected
1433	 */
1434	_onMoveVertical: function(e)
1435	{
1436		var y 			= this._startY - e.pageY,
1437			active 		= this.get('activeItem'),
1438			next 		= this.get('nextItem'),
1439			height		= parseInt(active.getComputedStyle('height'), 10),
1440			translate 	= y < 0 ? Math.abs(y) : -y,
1441			direction	= y < 0 ? 'prev' : 'next';
1442
1443		e.preventDefault();
1444
1445		if(!this._moving || this._movingDirection != direction) {
1446
1447        	active.setStyle('top', 0);
1448
1449	        next.setStyles({
1450	        	'opacity': 1,
1451	        	'left' : 'auto',
1452	        	'top': y < 0 ? -height : height
1453	        });
1454
1455	        this._moving = true;
1456        	this._movingDirection = direction;
1457
1458			/**
1459			 * @event moveStart
1460			 */
1461			this.fire('moveStart', { direction: direction });
1462		}
1463
1464		active.setStyle('-webkit-transform', 'translate(0px, '+ translate +'px) translateZ(0px)');
1465		active.setStyle('-ms-transform', 'translate(0px, '+ translate +'px) translateZ(0px)');
1466        next.setStyle('-webkit-transform', 'translate(0px, '+ translate +'px) translateZ(0px)');
1467        next.setStyle('-ms-transform', 'translate(0px, '+ translate +'px) translateZ(0px)');
1468
1469		/**
1470		 * @event move
1471		 */
1472		this.fire('move');
1473	},
1474
1475	/**
1476	 * @method _onEndHorizontal
1477	 * @param e {Object} The event object.
1478	 * @protected
1479	 */
1480	_onEndHorizontal: function(e)
1481	{
1482		if(!this._moving) {
1483			return;
1484		}
1485
1486		var x 			= this._startX - e.pageX,
1487			active 		= this.get('activeItem'),
1488			next 		= this.get('nextItem'),
1489			width		= parseInt(next.getComputedStyle('width'), 10),
1490			translate 	= x < 0 ? width : -width;
1491
1492		active.transition({
1493			'transform': 'translate('+ translate +'px, 0px)'
1494		});
1495
1496		next.transition({
1497			'transform': 'translate('+ translate +'px, 0px)'
1498		});
1499
1500		this._transitioning = true;
1501
1502		/**
1503		 * @event end
1504		 */
1505		this.fire('end');
1506	},
1507
1508	/**
1509	 * @method _onEndVertical
1510	 * @param e {Object} The event object.
1511	 * @protected
1512	 */
1513	_onEndVertical: function(e)
1514	{
1515		if(!this._moving) {
1516			return;
1517		}
1518
1519		var y 			= this._startY - e.pageY,
1520			active 		= this.get('activeItem'),
1521			next 		= this.get('nextItem'),
1522			height		= parseInt(next.getComputedStyle('height'), 10),
1523			translate 	= y < 0 ? height : -height;
1524
1525		active.transition({
1526			'transform': 'translate(0px, '+ translate +'px)'
1527		});
1528
1529		next.transition({
1530			'transform': 'translate(0px, '+ translate +'px)'
1531		});
1532
1533		this._transitioning = true;
1534
1535		/**
1536		 * @event end
1537		 */
1538		this.fire('end');
1539	},
1540
1541	/**
1542	 * @method _onEndComplete
1543	 * @protected
1544	 */
1545	_onEndComplete: function()
1546	{
1547		var direction 	= this.get('direction'),
1548			active 		= this.get('activeItem'),
1549			next 		= this.get('nextItem');
1550
1551		active.setStyles({
1552        	'opacity': 0,
1553        	'-webkit-transform': '',
1554        	'-webkit-transition': '',
1555        	'-ms-transform': '',
1556        	'-ms-transition': ''
1557        });
1558
1559		next.setStyles({
1560        	'-webkit-transform': '',
1561        	'-webkit-transition': '',
1562        	'-ms-transform': '',
1563        	'-ms-transition': ''
1564        });
1565
1566        if(direction == 'horizontal') {
1567        	active.setStyle('left', '-9999px');
1568        	next.setStyle('left', '0px');
1569        }
1570        else {
1571        	active.setStyle('top', '-9999px');
1572        	next.setStyle('top', '0px');
1573        }
1574
1575		this.set('activeItem', next);
1576		this.set('nextItem', active);
1577		this._moving = false;
1578		this._movingDirection = null;
1579		this._transitioning = false;
1580
1581		/**
1582		 * @event endComplete
1583		 */
1584		this.fire('endComplete');
1585	}
1586
1587}, {
1588
1589	/**
1590	 * Static property used to define the default attribute configuration of
1591	 * the Widget.
1592	 *
1593	 * @property ATTRS
1594	 * @type Object
1595	 * @protected
1596	 * @static
1597	 */
1598	ATTRS: {
1599
1600		/**
1601		 * The gesture direction to use. Possible values are
1602		 * horizontal and vertical.
1603		 *
1604		 * @attribute direction
1605		 * @type String
1606		 * @default horizontal
1607		 */
1608		direction: {
1609			value: 'horizontal'
1610		},
1611
1612		/**
1613		 * The Node that is currently visible.
1614		 *
1615		 * @attribute activeItem
1616		 * @type Node
1617		 * @default null
1618		 */
1619		activeItem: {
1620			value: null
1621		},
1622
1623		/**
1624		 * The Node that will be transitioned in.
1625		 *
1626		 * @attribute nextItem
1627		 * @type Node
1628		 * @default null
1629		 */
1630		nextItem: {
1631			value: null
1632		}
1633	}
1634});
1635
1636/**
1637 * A load queue for slideshow images.
1638 *
1639 * @namespace FL
1640 * @class SlideshowImageLoader
1641 * @static
1642 */
1643Y.namespace('FL').SlideshowImageLoader = {
1644
1645	/**
1646	 * Whether an image is being loaded or not.
1647	 *
1648	 * @property _loading
1649	 * @type Boolean
1650	 * @default false
1651	 * @protected
1652	 */
1653	_loading: false,
1654
1655	/**
1656	 * An node for loading the next image.
1657	 *
1658	 * @property _currentImage
1659	 * @type Node
1660	 * @default null
1661	 * @protected
1662	 */
1663	_currentImage: null,
1664
1665	/**
1666	 * An object containing the group, src and callback
1667	 * for the current image that is being loaded.
1668	 *
1669	 * @property _currentImageData
1670	 * @type Object
1671	 * @default null
1672	 * @protected
1673	 */
1674	_currentImageData: null,
1675
1676	/**
1677	 * An array of image data objects that contain the group,
1678	 * src and callback for each image that will be loaded.
1679	 *
1680	 * @property _queue
1681	 * @type Array
1682	 * @default []
1683	 * @protected
1684	 */
1685	_queue: [],
1686
1687	/**
1688	 * Adds an image to the queue.
1689	 *
1690	 * @method add
1691	 * @param group {String} The group this image is associated with.
1692	 * Used to remove images in bulk.
1693	 * @param src {String} The image url to load.
1694	 * @param callback {Function} A function to call when the image
1695	 * has finished loading.
1696	 * @param bump {Boolean} If true, the image will be added to
1697	 * the first position in the queue.
1698	 */
1699	add: function(group, src, callback, bump)
1700	{
1701		var imgData = {
1702			group		: group,
1703			src			: src,
1704			callback	: callback
1705		};
1706
1707		if(bump) {
1708			this._queue.unshift(imgData);
1709		}
1710		else {
1711			this._queue.push(imgData);
1712		}
1713
1714		if(!this._loading) {
1715			this._load();
1716		}
1717	},
1718
1719	/**
1720	 * Removes a group of images from the queue.
1721	 *
1722	 * @method removeGroup
1723	 * @param group {String} The group to remove.
1724	 */
1725	removeGroup: function(group)
1726	{
1727		var i = this._queue.length - 1;
1728
1729		for( ; i > -1 ; i--) {
1730			if(this._queue[i].group == group) {
1731				this._queue.splice(i, 1);
1732			}
1733		}
1734
1735		if(this._currentImageData && this._currentImageData.group == group) {
1736			this._currentImage.detachAll();
1737			this._currentImage = null;
1738			this._currentImageData = null;
1739
1740			if(this._queue.length > 0) {
1741				this._load();
1742			}
1743			else {
1744				this._loading = false;
1745			}
1746		}
1747	},
1748
1749	/**
1750	 * Loads the next image in the queue.
1751	 *
1752	 * @method _load
1753	 * @protected
1754	 */
1755	_load: function()
1756	{
1757		this._loading = true;
1758		this._currentImageData = this._queue.shift();
1759		this._currentImage = Y.Node.create('<img />');
1760		this._currentImage.on('error', Y.bind(this._loadComplete, this));
1761		this._currentImage.on('load', Y.bind(this._loadComplete, this));
1762		this._currentImage.set('src', this._currentImageData.src);
1763	},
1764
1765	/**
1766	 * Calls the current image's callback function if set
1767	 * and loads the next image if the queue is not empty.
1768	 *
1769	 * @method _loadComplete
1770	 * @protected
1771	 */
1772	_loadComplete: function()
1773	{
1774		if(this._currentImageData.callback) {
1775			this._currentImageData.callback(this._currentImage);
1776		}
1777
1778		if(this._queue.length > 0) {
1779			this._load();
1780		}
1781		else {
1782			this._loading = false;
1783			this._currentImage = null;
1784			this._currentImageData = null;
1785		}
1786	}
1787};
1788
1789/**
1790 * Loads an image or video using the provided imageInfo object.
1791 *
1792 * @namespace FL
1793 * @class SlideshowImage
1794 * @constructor
1795 * @param config {Object} Configuration object
1796 * @extends Widget
1797 */
1798Y.namespace('FL').SlideshowImage = Y.Base.create('fl-slideshow-image', Y.Widget, [Y.WidgetChild], {
1799
1800	/**
1801	 * The imageInfo object used to load the image and
1802	 * its various sizes.
1803	 *
1804	 * @property info
1805	 * @type Object
1806	 * @default null
1807	 * @protected
1808	 */
1809	_imageInfo: null,
1810
1811	/**
1812	 * A reference to the current image node in the bounding box.
1813	 *
1814	 * @property _image
1815	 * @type Node
1816	 * @default null
1817	 * @protected
1818	 */
1819	_image: null,
1820
1821	/**
1822	 * Whether or not new imageInfo is loading.
1823	 *
1824	 * @property _loading
1825	 * @type Boolean
1826	 * @default false
1827	 * @protected
1828	 */
1829	_loading: false,
1830
1831	/**
1832	 * The URL that is currently being loaded.
1833	 *
1834	 * @property _loadingURL
1835	 * @type Boolean
1836	 * @default null
1837	 * @protected
1838	 */
1839	_loadingURL: null,
1840
1841	/**
1842	 * An anchor node used for the video play button.
1843	 *
1844	 * @property _videoButton
1845	 * @type Node
1846	 * @default null
1847	 * @protected
1848	 */
1849	_videoButton: null,
1850
1851	/**
1852	 * A div node used to hold the video iframe.
1853	 *
1854	 * @property _videoBox
1855	 * @type Node
1856	 * @default null
1857	 * @protected
1858	 */
1859	_videoBox: null,
1860
1861	/**
1862	 * An iframe node used to render the video.
1863	 *
1864	 * @property _video
1865	 * @type Node
1866	 * @default null
1867	 * @protected
1868	 */
1869	_video: null,
1870
1871	/**
1872	 * The default content template for the image
1873	 * inherited from Y.Widget. Set to null since
1874	 * only the bounding box is needed.
1875	 *
1876	 * @property CONTENT_TEMPLATE
1877	 * @type String
1878	 * @default null
1879	 * @protected
1880	 */
1881	CONTENT_TEMPLATE: null,
1882
1883	/**
1884	 * Initial styling for the bounding box.
1885	 *
1886	 * @method syncUI
1887	 * @protected
1888	 */
1889	syncUI: function()
1890	{
1891		var bb = this.get('boundingBox');
1892
1893		if(this.get('crop')) {
1894			bb.setStyle('overflow', 'hidden');
1895			bb.addClass('fl-slideshow-image-cropped');
1896		}
1897	},
1898
1899	/**
1900	 * Sets the imageInfo object and
1901	 * loads the appropriate image size.
1902	 *
1903	 * @method load
1904	 * @param imageInfo {Object} The imageInfo object.
1905	 */
1906	load: function(imageInfo)
1907	{
1908		this._imageInfo = imageInfo;
1909		this._loading = true;
1910		this._load();
1911	},
1912
1913	/**
1914	 * Sets the width and height of the bounding box and
1915	 * preloads an image using the provided imageInfo object.
1916	 *
1917	 * @method preload
1918	 * @param imageInfo {Object} The imageInfo to preload.
1919	 * @param width {Number} The width to preload.
1920	 * @param height {Number} The height to preload.
1921	 */
1922	preload: function(imageInfo, width, height)
1923	{
1924		var isVideo			= this._isVideo(),
1925			loadVideos 		= this.get('loadVideos'),
1926			showVideoButton = this.get('showVideoButton');
1927
1928		this.unload();
1929		this.set('width', width);
1930		this.set('height', height);
1931		this._imageInfo = imageInfo;
1932
1933		if(!isVideo || !loadVideos || (isVideo && loadVideos && showVideoButton)) {
1934			Y.FL.SlideshowImageLoader.add(
1935				this.get('loadGroup'),
1936				this._getImageURL(),
1937				Y.bind(this._imagePreloaded, this),
1938				this.get('loadPriority')
1939			);
1940		}
1941	},
1942
1943	/**
1944	 * Called when preloading completes.
1945	 *
1946	 * @method _imagePreloaded
1947	 * @param img {Object} The image that was preloaded.
1948	 * @protected
1949	 */
1950	_imagePreloaded: function(img)
1951	{
1952		this._image = img;
1953	},
1954
1955	/**
1956	 * Unloads the image if there is one loaded
1957	 * and sets the imageInfo object to null.
1958	 *
1959	 * @method unload
1960	 */
1961	unload: function()
1962	{
1963		if(this._image) {
1964			this._image.remove();
1965			this._image.detachAll();
1966			this._image.set('src', '');
1967			this._image = null;
1968		}
1969		if(this._video) {
1970			this._video.remove();
1971			this._video = null;
1972		}
1973		if(this._videoButton) {
1974			this._videoButton.remove();
1975			this._videoButton = null;
1976		}
1977		if(this._videoBox) {
1978			this._removeVideoBox();
1979		}
1980
1981		this._imageInfo = null;
1982		this._loading = false;
1983		this._loadingURL = null;
1984	},
1985
1986	/**
1987	 * Resizes the bounding box and loads the
1988	 * appropriate image size if necessary.
1989	 *
1990	 * @method resize
1991	 * @param width {Number} The width value.
1992	 * @param height {Number} The height value.
1993	 */
1994	resize: function(width, height)
1995	{
1996		var borderWidth = parseInt(this.get('boundingBox').getComputedStyle('borderTopWidth'), 10) * 2,
1997			bb			= this.get('boundingBox');
1998
1999		this.set('width', width - borderWidth);
2000		this.set('height', height - borderWidth);
2001		bb.setStyle('width', width - borderWidth + 'px');
2002		bb.setStyle('height', height - borderWidth + 'px');
2003
2004		if(this._videoButton) {
2005			this._positionVideoButton();
2006		}
2007		if(this._videoBox) {
2008			this._loadVideo();
2009		}
2010		if(!this._loading) {
2011			if(this._imageInfo) {
2012				this._load();
2013			}
2014			if(this._image) {
2015				this._positionImage();
2016			}
2017		}
2018	},
2019
2020	/**
2021	 * Loads (or reloads) the image or video.
2022	 *
2023	 * @method _load
2024	 * @protected
2025	 */
2026	_load: function()
2027	{
2028		var loadVideos 		= this.get('loadVideos'),
2029			showVideoButton = this.get('showVideoButton');
2030
2031		if(this._isVideo() && loadVideos && !showVideoButton && !('ontouchstart' in window)) {
2032			this._loadVideo();
2033		}
2034		else {
2035			this._loadImage();
2036		}
2037	},
2038
2039	/**
2040	 * Loads the appropriate image size if
2041	 * it is not already loading.
2042	 *
2043	 * @method _loadImage
2044	 * @protected
2045	 */
2046	_loadImage: function()
2047	{
2048		var url 		= this._getImageURL(),
2049			loadVideos 	= this.get('loadVideos');
2050
2051		// Already loading.
2052		if(url == this._loadingURL) {
2053			return;
2054		}
2055
2056		// New URL to load.
2057		this._loadingURL = url;
2058
2059		// Load the new image.
2060		Y.FL.SlideshowImageLoader.add(
2061			this.get('loadGroup'),
2062			this._loadingURL,
2063			Y.bind(this._loadImageComplete, this),
2064			this.get('loadPriority')
2065		);
2066
2067		// Initial load?
2068		if(this._loading) {
2069
2070			if(this._isVideo() && loadVideos) {
2071				this._insertVideoButton();
2072			}
2073
2074			/**
2075			 * Only fires when a new image is being
2076			 * loaded, not a different size.
2077			 *
2078			 * @event loadStart
2079			 */
2080			this.fire('loadStart');
2081		}
2082	},
2083
2084	/**
2085	 * Fires when the image has finished loading.
2086	 *
2087	 * @method _loadImageComplete
2088	 * @protected
2089	 */
2090	_loadImageComplete: function(img)
2091	{
2092		var bb 				= this.get('boundingBox'),
2093			showVideoButton = this.get('showVideoButton');
2094
2095		this._image = img;
2096		this._image.setStyle('visibility', 'hidden');
2097		this._image.addClass('fl-slideshow-image-img');
2098
2099		// Remove load events.
2100		this._image.detachAll();
2101
2102		// Remove previous videos.
2103		if(this._video && !showVideoButton) {
2104			this._video.remove();
2105			this._video = null;
2106		}
2107
2108		// Remove the old image.
2109		bb.all('img').remove();
2110
2111		// Append the new image.
2112		bb.append(this._image);
2113
2114		// Setup, scale and position the new image.
2115		this._setupImage();
2116		this._resizeImage();
2117		this._positionImage();
2118		this._image.setStyle('visibility', 'visible');
2119
2120		// Clear the loading url.
2121		this._loadingURL = null;
2122
2123		// Finish an initial load?
2124		if(this._loading) {
2125
2126			this._loading = false;
2127
2128			/**
2129			 * Only fires when a new image is being
2130			 * loaded, not a different size.
2131			 *
2132			 * @event loadComplete
2133			 */
2134			this.fire('loadComplete');
2135		}
2136	},
2137
2138	/**
2139	 * UI setup for the new image.
2140	 *
2141	 * @method _setupImage
2142	 * @protected
2143	 */
2144	_setupImage: function()
2145	{
2146        var bb = this.get('boundingBox');
2147
2148		// IE interpolation
2149		if(typeof this._image._node.style.msInterpolationMode != 'undefined') {
2150			this._image._node.style.msInterpolationMode = 'bicubic';
2151		}
2152
2153		// Protection
2154		if(this.get('protect')) {
2155            bb.delegate('contextmenu', this._protectImage, 'img');
2156            bb.delegate('mousedown', this._protectImage, 'img');
2157		}
2158	},
2159
2160	/**
2161	 * Fires on contextmenu or mousedown in attempt
2162	 * to keep the image from being copied.
2163	 *
2164	 * @method _protectImage
2165	 * @return {Boolean} Returns false to prevent the default event.
2166	 * @protected
2167	 */
2168	_protectImage: function(e)
2169	{
2170		e.preventDefault();
2171		return false;
2172	},
2173
2174	/**
2175	 * Resizes the image node.
2176	 *
2177	 * @method _resizeImage
2178	 * @protected
2179	 */
2180	_resizeImage: function()
2181	{
2182		var borderWidth 		= parseInt(this._image.getComputedStyle('borderTopWidth'), 10) * 2,
2183			imageWidth			= this._image.get('width'),
2184			imageHeight			= this._image.get('height'),
2185			targetWidth			= parseInt(this.get('boundingBox').getComputedStyle('width'), 10),
2186			targetHeight		= parseInt(this.get('boundingBox').getComputedStyle('height'), 10),
2187			newWidth 			= 0,
2188			newHeight  			= 0,
2189			xScale				= 0,
2190			yScale				= 0,
2191			cropHorizontalsOnly = this.get('cropHorizontalsOnly'),
2192			isHorizontal		= imageHeight > imageWidth,
2193			noCrop				= false;
2194
2195		if(this._imageInfo && this.get('checkFilenamesForNoCrop')) {
2196			noCrop = this._imageInfo.filename.indexOf('nocrop') > -1;
2197		}
2198
2199		if(this.get('crop') && !(cropHorizontalsOnly && isHorizontal) && !noCrop) {
2200			newWidth = targetWidth;
2201			newHeight = Math.round(imageHeight * targetWidth/imageWidth);
2202
2203			if(newHeight < targetHeight) {
2204				newHeight = targetHeight;
2205				newWidth = Math.round(imageWidth * targetHeight/imageHeight);
2206			}
2207		}
2208		else {
2209			xScale = imageWidth/targetWidth;
2210			yScale = imageHeight/targetHeight;
2211
2212			if (yScale > xScale){
2213				newWidth = Math.round(imageWidth * (1/yScale));
2214				newHeight = Math.round(imageHeight * (1/yScale));
2215			}
2216			else {
2217				newWidth = Math.round(imageWidth * (1/xScale));
2218				newHeight = Math.round(imageHeight * (1/xScale));
2219			}
2220		}
2221
2222		// Don't resize past the original size?
2223		if(!this.get('crop') && !this.get('upsize') && (newWidth > imageWidth || newHeight > imageHeight)) {
2224			newWidth = imageWidth;
2225			newHeight = imageHeight;
2226		}
2227
2228		// Compensate for borders.
2229		newWidth -= borderWidth;
2230		newHeight -= borderWidth;
2231
2232		// Resize the image.
2233		this._image.setStyle('width', newWidth + 'px');
2234		this._image.setStyle('height', newHeight + 'px');
2235
2236		// Constrain bounding box to image size.
2237		if(!this.get('crop') && this.get('constrainWidth')) {
2238			this.set('width', newWidth + 'px');
2239		}
2240		if(!this.get('crop') && this.get('constrainHeight')) {
2241			this.set('height', newHeight + 'px');
2242		}
2243	},
2244
2245	/**
2246	 * Positions the image within the bounding box.
2247	 *
2248	 * @method _positionImage
2249	 * @protected
2250	 */
2251	_positionImage: function()
2252	{
2253		var pos 			= this.get('position').split(' '),
2254			x 				= pos[0] === '' ? 'center' : pos[0],
2255			y 				= pos[1] === '' ? 'center' : pos[1],
2256			newX 			= 0,
2257			newY			= 0,
2258			bbWidth			= parseInt(this.get('boundingBox').getComputedStyle('width'), 10),
2259			bbHeight		= parseInt(this.get('boundingBox').getComputedStyle('height'), 10),
2260			borderWidth 	= parseInt(this._image.getComputedStyle('borderTopWidth'), 10) * 2,
2261			imageWidth		= parseInt(this._image.getComputedStyle('width'), 10) + borderWidth,
2262			imageHeight		= parseInt(this._image.getComputedStyle('height'), 10) + borderWidth;
2263
2264		if(isNaN(imageWidth) && isNaN(imageHeight)) {
2265			return;
2266		}
2267		if(x == 'left') {
2268			newX = 0;
2269		}
2270		if(x == 'center') {
2271			newX = 	(bbWidth - imageWidth)/2;
2272		}
2273		if(x == 'right') {
2274			newX = bbWidth - imageWidth;
2275		}
2276
2277		if(y == 'top') {
2278			newY = 0;
2279		}
2280		if(y == 'center') {
2281			newY = (bbHeight - imageHeight)/2;
2282		}
2283		if(y == 'bottom') {
2284			newY = bbHeight - imageHeight;
2285		}
2286
2287		this._image.setStyles({
2288			'left': newX,
2289			'top': newY
2290		});
2291	},
2292
2293	/**
2294	 * Gets the appropriate image url based
2295	 * on the size of the bounding box.
2296	 *
2297	 * @method _getImageURL
2298	 * @return {String} The url to load.
2299	 * @protected
2300	 */
2301	_getImageURL: function()
2302	{
2303		var imageWidth 		= 0,
2304			imageHeight 	= 0,
2305			size 			= 0,
2306			targetWidth		= this.get('width'),
2307			targetHeight	= this.get('height'),
2308			useThumbSizes	= this.get('useThumbSizes'),
2309			i 				= this._imageInfo,
2310			sizes = [
2311				i.tinyURL 		|| i.thumbURL 	|| i.largeURL,
2312				i.thumbURL 		|| i.largeURL,
2313				i.smallURL 		|| i.largeURL,
2314				i.mediumURL 	|| i.largeURL 	|| i.smallURL,
2315				i.largeURL 		|| i.mediumURL 	|| i.smallURL,
2316				i.xlargeURL 	|| i.largeURL 	|| i.mediumURL 	|| i.smallURL,
2317				i.x2largeURL 	|| i.largeURL 	|| i.mediumURL 	|| i.smallURL,
2318				i.x3largeURL 	|| i.x2largeURL || i.largeURL 	|| i.mediumURL || i.smallURL
2319			];
2320
2321		// Width
2322		if(useThumbSizes && targetWidth <= 100) {
2323			imageWidth = 0;
2324		}
2325		else if(useThumbSizes && targetWidth <= 150) {
2326			imageWidth = 1;
2327		}
2328		else if(targetWidth <= 400) {
2329			imageWidth = 2;
2330		}
2331		else if(targetWidth >= 400 && targetWidth <= 600) {
2332			imageWidth = 3;
2333		}
2334		else if(targetWidth >= 600 && targetWidth <= 800) {
2335			imageWidth = 4;
2336		}
2337		else if(targetWidth >= 800 && targetWidth <= 1024) {
2338			imageWidth = 5;
2339		}
2340		else if(targetWidth >= 1024 && targetWidth <= 1280) {
2341			imageWidth = 6;
2342		}
2343		else {
2344			imageWidth = 7;
2345		}
2346
2347		// Height
2348		if(useThumbSizes && targetHeight <= 100) {
2349			imageHeight = 0;
2350		}
2351		else if(useThumbSizes && targetHeight <= 150) {
2352			imageHeight = 1;
2353		}
2354		else if(targetHeight <= 300) {
2355			imageHeight = 2;
2356		}
2357		else if(targetHeight >= 300 && targetHeight <= 450) {
2358			imageHeight = 3;
2359		}
2360		else if(targetHeight >= 450 && targetHeight <= 600) {
2361			imageHeight = 4;
2362		}
2363		else if(targetHeight >= 600 && targetHeight <= 768) {
2364			imageHeight = 5;
2365		}
2366		else if(targetHeight >= 768 && targetHeight <= 960) {
2367			imageHeight = 6;
2368		}
2369		else {
2370			imageHeight = 7;
2371		}
2372
2373		// Get the size number.
2374		size = Math.max(imageWidth, imageHeight);
2375
2376		return sizes[size];
2377	},
2378
2379	/**
2380	 * Checks whether this is a video or not.
2381	 *
2382	 * @method _isVideo
2383	 * @protected
2384	 */
2385	_isVideo: function()
2386	{
2387		if(!this._imageInfo) {
2388			return false;
2389		}
2390		else if(this._imageInfo.format == 'mp4' && this._imageInfo.sourceType == 'smugmug') {
2391			return true;
2392		}
2393		else if(this._imageInfo.iframe !== '') {
2394			return true;
2395		}
2396
2397		return false;
2398	},
2399
2400	/**
2401	 * @method _loadVideo
2402	 * @protected
2403	 */
2404	_loadVideo: function()
2405	{
2406		var bb 				= this.get('boundingBox'),
2407			showVideoButton = this.get('showVideoButton'),
2408			autoPlay 		= showVideoButton ? true : false;
2409
2410		// Remove previous videos
2411		if(this._video) {
2412			this._video.remove();
2413			this._video = null;
2414		}
2415
2416		// Get the video code
2417		if(this._imageInfo.format == 'mp4' && this._imageInfo.sourceType == 'smugmug') {
2418			this._video = this._getSmugMugVideoEmbed(this._imageInfo, autoPlay);
2419		}
2420		else if(this._imageInfo.iframe !== '') {
2421			this._video = this._getIframeVideoEmbed(this._imageInfo, autoPlay);
2422		}
2423
2424		// Insert the video
2425		if(this._videoBox) {
2426			this._videoBox.one('.fl-slideshow-video-wrap').insert(this._video);
2427		}
2428		else {
2429			bb.all('img').remove();
2430			bb.append(this._video);
2431		}
2432
2433		// Finish an initial load?
2434		if(this._loading) {
2435			this._loading = false;
2436			this.fire('loadComplete');
2437		}
2438	},
2439
2440	/**
2441	 * @method _insertVideoButton
2442	 * @protected
2443	 */
2444	_insertVideoButton: function()
2445	{
2446		var bb 		= this.get('boundingBox'),
2447			event 	= 'ontouchstart' in window ? 'touchstart' : 'click';
2448
2449		this._videoButton = Y.Node.create('<a class="fl-slideshow-video-button" href="javascript:void(0);"></a>');
2450		this._videoButton.on(event, Y.bind(this._showVideoBox, this));
2451		bb.insert(this._videoButton);
2452		this._positionVideoButton();
2453	},
2454
2455	/**
2456	 * @method _positionVideoButton
2457	 * @protected
2458	 */
2459	_positionVideoButton: function()
2460	{
2461		var bbWidth			= this.get('width'),
2462			bbHeight		= this.get('height'),
2463			buttonWidth		= parseInt(this._videoButton.getStyle('width'), 10),
2464			buttonHeight 	= parseInt(this._videoButton.getStyle('height'), 10);
2465
2466		this._videoButton.setStyles({
2467			left: (bbWidth - buttonWidth)/2,
2468			top: (bbHeight - buttonHeight)/2
2469		});
2470	},
2471
2472	/**
2473	 * @method _showVideoBox
2474	 * @protected
2475	 */
2476	_showVideoBox: function()
2477	{
2478		var root 	= this.get('root'),
2479			wrap 	= Y.Node.create('<div class="fl-slideshow-video-wrap"></div>'),
2480			close 	= Y.Node.create('<a class="fl-slideshow-video-close" href="javascript:void(0);"></a>'),
2481			event 	= 'ontouchstart' in window ? 'touchstart' : 'click';
2482
2483		this._videoBox = Y.Node.create('<div class="fl-slideshow-video"></div>');
2484		this._videoBox.setStyle('padding', root.get('boundingBox').getStyle('padding'));
2485		this._videoBox.insert(wrap);
2486		this._videoBox.insert(close);
2487		this._videoBox.on(event, Y.bind(this._removeVideoBox, this));
2488		close.on(event, Y.bind(this._removeVideoBox, this));
2489
2490		if(typeof YUI.Env.mods['sm-fonticon'] !== 'undefined') {
2491            close.addClass('sm-fonticon sm-fonticon-XCrossEncircled sm-button-skin-default sm-button-nochrome');
2492        }
2493
2494		Y.one('body').insert(this._videoBox);
2495		this._loadVideo();
2496
2497		Y.one('body').on('fl-slideshow-image|keydown', this._onKey, this);
2498	},
2499
2500	/**
2501	 * Get the embed code for a SmugMug video.
2502	 *
2503	 * @method _getSmugMugVideoEmbed
2504	 * @param imageInfo {Object} The image info for the embed.
2505	 * @param autoPlay {Boolean} Whether to auto play videos or not.
2506	 * @protected
2507	 */
2508	_getSmugMugVideoEmbed: function(imageInfo, autoPlay)
2509	{
2510		var test		= document.createElement('video'),
2511			width		= 0,
2512			mp4 		= '',
2513			vars		= '',
2514			code 		= '';
2515
2516		if(Y.UA.mobile !== null && !!test.canPlayType && test.canPlayType('video/mp4')) {
2517			width = this.get('width');
2518			mp4 = 'https://www.smugmug.com/photos/' + imageInfo.id + '_' + imageInfo.key + '-' + width + '.mp4';
2519			code += '<video width="100%" height="100%" poster="'+ this._getImageURL() +'" controls preload="none"';
2520
2521			if(autoPlay) {
2522				code += ' autoplay';
2523			}
2524
2525			code += '>';
2526			code += '<source src="'+ mp4 +'" type="video/mp4" />';
2527			code += '</video>';
2528		}
2529		else {
2530			vars = 'imageId=' + imageInfo.id;
2531			vars += '&amp;imageKey=' + imageInfo.key;
2532			vars += '&amp;albumId=' + imageInfo.albumId;
2533			vars += '&amp;albumKey=' + imageInfo.albumKey;
2534			vars += '&amp;apiURL=https://api.smugmug.com/&amp;hostLevel=live&amp;isPro=true';
2535
2536			if(autoPlay) {
2537				vars += '&amp;autoPlay=true';
2538			}
2539			else {
2540				vars += '&amp;autoPlay=false';
2541			}
2542
2543			code += '<object type="application/x-shockwave-flash" width="100%" height="100%" data="https://cdn.smugmug.com/img/ria/SmugPlayer/2012102601.swf">';
2544			code += '<param name="movie" value="https://cdn.smugmug.com/img/ria/SmugPlayer/2012102601.swf">';
2545			code += '<param name="allowFullScreen" value="true">';
2546			code += '<param name="wmode" value="transparent">';
2547			code += '<param name="flashVars" value="' + vars + '">';
2548			code += '<embed src="https://cdn.smugmug.com/img/ria/SmugPlayer/2012102601.swf" flashvars="'+ vars +'" width="100%" height="100%" type="application/x-shockwave-flash" allowfullscreen="true" wmode="transparent">';
2549			code += '</object>';
2550		}
2551
2552		return Y.Node.create(code);
2553	},
2554
2555	/**
2556	 * Get the iframe video embed code.
2557	 *
2558	 * @method _getIframeVideoEmbed
2559	 * @param imageInfo {Object} The image info for the embed.
2560	 * @param autoPlay {Boolean} Whether to auto play videos or not.
2561	 * @protected
2562	 */
2563	_getIframeVideoEmbed: function(imageInfo, autoPlay)
2564	{
2565		var code 	= '<iframe width="100%" height="100%" frameborder="0" allowfullscreen ',
2566			url	 	= imageInfo.iframe;
2567
2568		if(autoPlay) {
2569			url += url.indexOf('?') > -1 ? '&autoplay=1' : '?autoplay=1';
2570		}
2571
2572		code += 'src="'+ url +'"></iframe>';
2573
2574		return Y.Node.create(code);
2575	},
2576
2577	/**
2578	 * @method _removeVideoBox
2579	 * @protected
2580	 */
2581	_removeVideoBox: function(e)
2582	{
2583		if(typeof e !== 'undefined' && e.target) {
2584			if(e.target.get('className').indexOf('fl-slideshow-video') < 0) {
2585				return;
2586			}
2587		}
2588
2589		if(this._videoBox !== null) {
2590    		this._videoBox.remove();
2591    		this._videoBox = null;
2592    		this._video = null;
2593        }
2594
2595		Y.one('body').detach('fl-slideshow-image|keydown', this._onKey);
2596	},
2597
2598	/**
2599	 * Keyboard input for the esc button.
2600	 *
2601	 * @method _onKey
2602	 * @protected
2603	 */
2604	_onKey: function(e)
2605	{
2606		if(e.keyCode == 27) {
2607			this._removeVideoBox();
2608			return false;
2609		}
2610	}
2611
2612}, {
2613
2614	/**
2615	 * Custom CSS class name for the widget.
2616
2617	 * @property CSS_PREFIX
2618	 * @type String
2619	 * @protected
2620	 * @static
2621	 */
2622	CSS_PREFIX: 'fl-slideshow-image',
2623
2624	/**
2625	 * Static property used to define the default attribute configuration of
2626	 * the Widget.
2627	 *
2628	 * @property ATTRS
2629	 * @type Object
2630	 * @protected
2631	 * @static
2632	 */
2633	ATTRS: {
2634
2635		/**
2636		 * @attribute loadGroup
2637		 * @type String
2638		 * @default none
2639		 */
2640		loadGroup: {
2641			value: 'none'
2642		},
2643
2644		/**
2645		 * @attribute loadPriority
2646		 * @type Boolean
2647		 * @default false
2648		 */
2649		loadPriority: {
2650			value: false
2651		},
2652
2653		/**
2654		 * Whether to crop the image.
2655		 *
2656		 * @attribute crop
2657		 * @type Boolean
2658		 * @default false
2659		 */
2660		crop: {
2661			value: false
2662		},
2663
2664		/**
2665		 * Checks whether the filename has nocrop in it or not.
2666		 * If it does, the image will not be cropped.
2667		 *
2668		 * @attribute checkFilenamesForNoCrop
2669		 * @type Boolean
2670		 * @default true
2671		 */
2672		checkFilenamesForNoCrop: {
2673			value: true
2674		},
2675
2676		/**
2677		 * Whether to only crop horizontal images or not.
2678		 *
2679		 * @attribute cropHorizontalsOnly
2680		 * @type Boolean
2681		 * @default false
2682		 */
2683		cropHorizontalsOnly: {
2684			value: false
2685		},
2686
2687		/**
2688		 * The x and y position of the image
2689		 * within the bounding box.
2690		 *
2691		 * @attribute position
2692		 * @type String
2693		 * @default center center
2694		 */
2695		position: {
2696			value: 'center center'
2697		},
2698
2699		/**
2700		 * Whether to right click protect the image.
2701		 *
2702		 * @attribute protect
2703		 * @type Boolean
2704		 * @default true
2705		 */
2706		protect: {
2707			value: true
2708		},
2709
2710		/**
2711		 * Whether to resize the image past
2712		 * its original width and height.
2713		 *
2714		 * @attribute upsize
2715		 * @type Boolean
2716		 * @default true
2717		 */
2718        upsize: {
2719			value: true
2720		},
2721
2722		/**
2723		 * Whether to load thumb sizes. Defaults
2724		 * to false since thumb sizes are square.
2725		 *
2726		 * @attribute useThumbSizes
2727		 * @type Boolean
2728		 * @default false
2729		 */
2730		useThumbSizes: {
2731			value: false
2732		},
2733
2734		/**
2735		 * Whether to constrain the width of the
2736		 * bounding box to the width of the image.
2737		 *
2738		 * @attribute constrainWidth
2739		 * @type Boolean
2740		 * @default false
2741		 */
2742		constrainWidth: {
2743			value: false
2744		},
2745
2746		/**
2747		 * Whether to constrain the height of the
2748		 * bounding box to the height of the image.
2749		 *
2750		 * @attribute constrainHeight
2751		 * @type Boolean
2752		 * @default false
2753		 */
2754		constrainHeight: {
2755			value: false
2756		},
2757
2758		/**
2759		 * Whether to load videos or not. The poster
2760		 * image will be loaded if set to false.
2761		 *
2762		 * @attribute loadVideos
2763		 * @type Boolean
2764		 * @default true
2765		 */
2766		loadVideos: {
2767			value: true
2768		},
2769
2770		/**
2771		 * Whether to show the video play button or not.
2772		 * When clicked, videos will be displayed in a
2773		 * lightbox instead of the slideshow itself.
2774		 *
2775		 * @attribute showVideoButton
2776		 * @type Boolean
2777		 * @default true
2778		 */
2779		showVideoButton: {
2780			value: true
2781		}
2782	}
2783});
2784
2785/**
2786 * A plugin that turns the cursor into a prev or next arrow when
2787 * it is over the left or right side of the slideshow.
2788 *
2789 * @namespace FL
2790 * @class SlideshowMouseNav
2791 * @constructor
2792 * @param config {Object} Configuration object
2793 * @extends Plugin.Base
2794 */
2795Y.namespace('FL').SlideshowMouseNav = Y.Base.create('fl-slideshow-mouse-nav', Y.Plugin.Base, [], {
2796
2797	/**
2798	 * @method initializer
2799	 * @protected
2800	 */
2801	initializer: function()
2802	{
2803		var trigger = this.get('trigger');
2804
2805		trigger.on('click', this._triggerClick, this);
2806		trigger.on('mousemove', this._showArrow, this);
2807		trigger.on('mouseleave', this._hideArrow, this);
2808    },
2809
2810	/**
2811	 * @method _triggerClick
2812	 * @protected
2813	 */
2814    _triggerClick: function(e)
2815    {
2816    	var host 			= this.get('host'),
2817    		trigger 		= this.get('trigger'),
2818    		triggerWidth	= parseInt(trigger.getStyle('width'), 10),
2819    		triggerRegion 	= trigger.get('region'),
2820    		layerX 			= e.pageX - triggerRegion.left + 5;
2821
2822    	if(layerX >= triggerWidth/2) {
2823    		host.nextImage();
2824    	}
2825    	else {
2826    		host.prevImage();
2827    	}
2828    },
2829
2830	/**
2831	 * @method _showArrow
2832	 * @protected
2833	 */
2834    _showArrow: function(e)
2835    {
2836    	var host 			= this.get('host'),
2837    	    trigger 		= this.get('trigger'),
2838    		triggerWidth	= parseInt(trigger.getStyle('width'), 10),
2839    		triggerRegion 	= trigger.get('region'),
2840    		layerX 			= e.pageX - triggerRegion.left + 5;
2841
2842    	if(host.albumInfo !== null && host.albumInfo.images.length > 1) {
2843        	if(layerX >= triggerWidth/2) {
2844        		trigger.removeClass('fl-slideshow-mouse-nav-prev');
2845        		trigger.addClass('fl-slideshow-mouse-nav-next');
2846        	}
2847        	else {
2848        		trigger.removeClass('fl-slideshow-mouse-nav-next');
2849        		trigger.addClass('fl-slideshow-mouse-nav-prev');
2850        	}
2851        }
2852    },
2853
2854	/**
2855	 * @method _hideArrow
2856	 * @protected
2857	 */
2858    _hideArrow: function()
2859    {
2860    	var trigger = this.get('trigger');
2861
2862    	trigger.removeClass('fl-slideshow-mouse-nav-next');
2863    	trigger.removeClass('fl-slideshow-mouse-nav-prev');
2864    }
2865
2866},	{
2867
2868	/**
2869	 * Namespace for the plugin.
2870	 *
2871	 * @property NS
2872	 * @type String
2873	 * @protected
2874	 * @static
2875	 */
2876	NS: 'mouseNav',
2877
2878	/**
2879	 * Static property used to define the default attribute configuration of
2880	 * the Plugin.
2881	 *
2882	 * @property ATTRS
2883	 * @type Object
2884	 * @protected
2885	 * @static
2886	 */
2887	ATTRS: {
2888
2889		/**
2890		 * A Node that triggers the arrows.
2891		 *
2892		 * @attribute trigger
2893		 * @type Node
2894		 * @default null
2895		 */
2896		trigger: {
2897			value: null
2898		}
2899	}
2900});
2901
2902/**
2903 * Ken Burns effect for slideshow images.
2904 *
2905 * @namespace FL
2906 * @class SlideshowKenBurns
2907 * @constructor
2908 * @param config {Object} Configuration object
2909 * @extends Base
2910 */
2911Y.namespace('FL').SlideshowKenBurns = Y.Base.create('fl-slideshow-ken-burns', Y.Base, [], {
2912
2913	/**
2914	 * Runs the Ken Burns effect.
2915	 *
2916	 * @method run
2917	 */
2918	run: function()
2919	{
2920        var imageNode = null,
2921            transform = null;
2922
2923		if(Y.FL.Utils.cssSupport('transform')) {
2924
2925            // Image node
2926            imageNode = this.get('image').one('img');
2927
2928            // Transform object
2929            transform = this._getTransform();
2930
2931            // Apply the start transform
2932            imageNode.setStyles({
2933                '-webkit-transform-origin': transform.origin,
2934                '-moz-transform-origin': transform.origin,
2935                '-ms-transform-origin': transform.origin,
2936                'transform-origin': transform.origin,
2937                'transform': transform.start
2938            });
2939
2940            // Transition to the end transform
2941            imageNode.transition({
2942                easing: 'ease-out',
2943                duration : this.get('duration'),
2944                'transform' : transform.end
2945            });
2946        }
2947	},
2948
2949	/**
2950	 * @method _getTransform
2951	 * @protected
2952	 */
2953	_getTransform: function()
2954	{
2955        var zoom            = this.get('zoom'),
2956            image           = this.get('image'),
2957            i               = 0,
2958            zoomDirection   = null,
2959            transform       = null;
2960
2961        // Random zoom direction
2962        i = Math.floor(Math.random() * Y.FL.SlideshowKenBurns.ZOOM_DIRECTIONS.length);
2963        zoomDirection = Y.FL.SlideshowKenBurns.ZOOM_DIRECTIONS[i];
2964
2965        // Random transform
2966        i = Math.floor(Math.random() * Y.FL.SlideshowKenBurns.TRANSFORMS.length);
2967        transform = Y.FL.SlideshowKenBurns.TRANSFORMS[i];
2968
2969        // Get the start and end transforms
2970        if(!image.hasClass('fl-slideshow-image-cropped') && zoomDirection == 'in') {
2971            i = Math.floor(Math.random() * 2);
2972            transform.start = i === 0 ? 'scale(1) translate(100px, 0)' : 'scale(1) translate(-100px, 0)';
2973            transform.end = 'scale(' + zoom + ') translate(0, 0)';
2974            transform.origin = 'center center';
2975        }
2976        else if(zoomDirection == 'out') {
2977            transform.start = 'scale(' + zoom + ') ' + transform.translate;
2978            transform.end = 'scale(1) translate(0, 0)';
2979        }
2980        else {
2981            transform.start = 'scale(1) translate(0, 0)';
2982            transform.end = 'scale(' + zoom + ') ' + transform.translate;
2983        }
2984
2985        return transform;
2986	}
2987
2988}, {
2989
2990	/**
2991	 * Static property used to define the default attribute configuration of
2992	 * the Widget.
2993	 *
2994	 * @property ATTRS
2995	 * @type Object
2996	 * @protected
2997	 * @static
2998	 */
2999	ATTRS: {
3000
3001		/**
3002		 * An instance of FL.Slideshow image to apply the
3003		 * Ken Burns effect on.
3004		 *
3005		 * @attribute image
3006		 * @type FL.Slideshow
3007		 * @default null
3008		 */
3009		image: {
3010			value: null
3011		},
3012
3013		/**
3014		 * The amount to zoom the image. Zooming
3015		 * in our out is done randomly by this class.
3016		 *
3017		 * @attribute scale
3018		 * @type Number
3019		 * @default 1.2
3020		 */
3021		zoom: {
3022		  value: 1.2
3023		},
3024
3025		/**
3026		 * The duration of the effect in seconds.
3027		 *
3028		 * @attribute duration
3029		 * @type Number
3030		 * @default 2
3031		 */
3032		duration: {
3033			value: 2
3034		}
3035	},
3036
3037	/**
3038	 * The zoom directions that can be applied to an image.
3039	 *
3040	 * @property ZOOM_DIRECTIONS
3041	 * @type Object
3042	 * @readOnly
3043	 * @protected
3044	 * @static
3045	 */
3046	ZOOM_DIRECTIONS: [
3047        'in',
3048        'out'
3049    ],
3050
3051	/**
3052	 * The types of transforms that can be applied to an image.
3053	 *
3054	 * @property TRANSFORMS
3055	 * @type Object
3056	 * @readOnly
3057	 * @protected
3058	 * @static
3059	 */
3060    TRANSFORMS: [
3061        {
3062            origin    : 'left top',
3063            translate : 'translate(-30px, -15px)'
3064        },{
3065            origin    : 'left center',
3066            translate : 'translate(-30px, 0)'
3067        },{
3068            origin    : 'left bottom',
3069            translate : 'translate(-30px, 15px)'
3070        },{
3071            origin    : 'right top',
3072            translate : 'translate(30px, -15px)'
3073        },{
3074            origin    : 'right center',
3075            translate : 'translate(30px, 0)'
3076        },{
3077            origin    : 'right bottom',
3078            translate : 'translate(30px, 15px)'
3079        }
3080    ]
3081});
3082
3083/**
3084 * Navigation buttons widget for controlling a slideshow instance
3085 * and its child widgets.
3086 *
3087 * @namespace FL
3088 * @class SlideshowNav
3089 * @constructor
3090 * @param config {Object} Configuration object
3091 * @extends Widget
3092 */
3093Y.namespace('FL').SlideshowNav = Y.Base.create('fl-slideshow-nav', Y.Widget, [Y.WidgetChild], {
3094
3095	/**
3096	 * An object containing the anchor nodes for all buttons.
3097	 *
3098	 * @property _buttons
3099	 * @type Object
3100	 * @default null
3101	 * @protected
3102	 */
3103	_buttons: null,
3104
3105	/**
3106	 * An div node containing the anchor nodes for the main buttons.
3107	 *
3108	 * @property _buttonsContainer
3109	 * @type Object
3110	 * @default null
3111	 * @protected
3112	 */
3113	_buttonsContainer: null,
3114
3115	/**
3116	 * An div node containing the anchor nodes for the left buttons.
3117	 *
3118	 * @property _buttonsLeftContainer
3119	 * @type Object
3120	 * @default null
3121	 * @protected
3122	 */
3123	_buttonsLeftContainer: null,
3124
3125	/**
3126	 * An div node containing the anchor nodes for the right buttons.
3127	 *
3128	 * @property _buttonsRightContainer
3129	 * @type Object
3130	 * @default null
3131	 * @protected
3132	 */
3133	_buttonsRightContainer: null,
3134
3135	/**
3136	 * Property map for rendering SmugMug font icons.
3137	 *
3138	 * @property _fontIcons
3139	 * @type Object
3140	 * @protected
3141	 */
3142	_fontIcons: {
3143        buy: 'Cart',
3144        caption: 'InfoEncircled',
3145        close: 'XCrossEncircled',
3146        fullscreen: 'ScreenExpand',
3147        next: 'ArrowRight',
3148        nextPage: 'ArrowRight',
3149        pause: 'PlayerPause',
3150        play: 'PlayerPlay',
3151        prev: 'ArrowLeft',
3152        prevPage: 'ArrowLeft',
3153        social: 'Heart',
3154        thumbs: 'ViewThumbGrid'
3155    },
3156
3157	/**
3158	 * The default content template for the nav
3159	 * inherited from Y.Widget. Set to null since
3160	 * only the bounding box is needed.
3161	 *
3162	 * @property CONTENT_TEMPLATE
3163	 * @type String
3164	 * @default null
3165	 * @protected
3166	 */
3167	CONTENT_TEMPLATE: null,
3168
3169	/**
3170	 * Renders the buttons.
3171	 *
3172	 * @method renderUI
3173	 * @protected
3174	 */
3175	renderUI: function()
3176	{
3177		this._renderContainers();
3178		this._renderButtons();
3179		this._renderFontIcons();
3180	},
3181
3182	/**
3183	 * Binds events to the root slideshow for each button.
3184	 *
3185	 * @method bindUI
3186	 * @protected
3187	 */
3188	bindUI: function()
3189	{
3190		var root 		= this.get('root'),
3191			id 			= this.get('id');
3192
3193		if(this._buttons.prev) {
3194			this._buttons.prev.on('click', root.prevImage, root);
3195		}
3196		if(this._buttons.next) {
3197			this._buttons.next.on('click', root.nextImage, root);
3198		}
3199		if(this._buttons.play) {
3200			this._buttons.play.on('click', this._playClicked, this);
3201			root.on(id + '|played', this._showPauseButton, this);
3202			root.on(id + '|paused', this._showPlayButton, this);
3203
3204			if(root._playing) {
3205				this._showPauseButton();
3206			}
3207			else {
3208				this._showPlayButton();
3209			}
3210		}
3211		if(this._buttons.buy) {
3212
3213			root.on(id + '|albumLoadComplete', this._updateBuy, this);
3214
3215			if(root.albumInfo !== null) {
3216				this._updateBuy();
3217			}
3218		}
3219		if(this._buttons.count) {
3220			root.on(id + '|imageLoadComplete', this._updateCount, this);
3221		}
3222		if(this._buttons.thumbs) {
3223			this._buttons.thumbs.on('click', root._toggleThumbs, root);
3224		}
3225		if(this._buttons.caption) {
3226			root.on(id + '|imageLoadComplete', this._updateCaption, this);
3227			this._updateCaption();
3228		}
3229		if(this._buttons.social) {
3230			this._buttons.social.on('click', root._toggleSocial, root);
3231		}
3232		if(this._buttons.fullscreen && root.fullscreen) {
3233			this._buttons.fullscreen.on('click', root.fullscreen.toggle, root.fullscreen);
3234		}
3235		if(this._buttons.close) {
3236			this._buttons.close.on('click', root.hide, root);
3237		}
3238	},
3239
3240	/**
3241	 * @method destructor
3242	 * @protected
3243	 */
3244	destructor: function()
3245	{
3246		var root 	= this.get('root'),
3247			id 		= this.get('id');
3248
3249		root.detach(id + '|*');
3250	},
3251
3252	/**
3253	 * Renders the button left, right and main button containers.
3254	 *
3255	 * @method _renderContainers
3256	 * @protected
3257	 */
3258	_renderContainers: function()
3259	{
3260		var cb				= this.get('contentBox'),
3261			buttonsLeft		= this.get('buttonsLeft'),
3262			buttonsRight	= this.get('buttonsRight');
3263
3264		this._buttonsContainer = Y.Node.create('<div></div>');
3265		this._buttonsContainer.addClass('fl-slideshow-nav-buttons');
3266		cb.appendChild(this._buttonsContainer);
3267
3268		if(buttonsLeft.length > 0) {
3269			this._buttonsLeftContainer = Y.Node.create('<div></div>');
3270			this._buttonsLeftContainer.addClass('fl-slideshow-nav-buttons-left');
3271			cb.appendChild(this._buttonsLeftContainer);
3272		}
3273		if(buttonsRight.length > 0) {
3274			this._buttonsRightContainer = Y.Node.create('<div></div>');
3275			this._buttonsRightContainer.addClass('fl-slideshow-nav-buttons-right');
3276			cb.appendChild(this._buttonsRightContainer);
3277		}
3278	},
3279
3280	/**
3281	 * Renders the buttons based on the buttons array
3282	 * passed in the configuration object.
3283	 *
3284	 * @method _renderButtons
3285	 * @protected
3286	 */
3287	_renderButtons: function()
3288	{
3289		var name 		= '',
3290			i 			= 0,
3291			k			= 0,
3292			b = [
3293			{
3294				names: this.get('buttons'),
3295				container: this._buttonsContainer
3296			},
3297			{
3298				names: this.get('buttonsLeft'),
3299				container: this._buttonsLeftContainer
3300			},
3301			{
3302				names: this.get('buttonsRight'),
3303				container: this._buttonsRightContainer
3304			}
3305		];
3306
3307		this._buttons = {};
3308
3309		for( ; i < b.length; i++) {
3310			for(k = 0; k < b[i].names.length; k++) {
3311
3312				name = b[i].names[k];
3313
3314				if(name.indexOf('count') > -1) {
3315					this._buttons[name] = Y.Node.create('<span></span>');
3316					this._updateCount();
3317				}
3318				else {
3319					this._buttons[name] = Y.Node.create('<a href="javascript:void(0);"></a>');
3320				}
3321
3322				if(name.indexOf('buy') > -1) {
3323					this._buttons[name].setStyle('display', 'none');
3324				}
3325
3326				this._buttons[name].set('name', name);
3327				this._buttons[name].addClass('fl-slideshow-nav-' + name);
3328				b[i].container.appendChild(this._buttons[name]);
3329			}
3330		}
3331	},
3332
3333	/**
3334	 * Renders SmugMug font icons for each button.
3335	 *
3336	 * @method _renderFontIcons
3337	 * @protected
3338	 */
3339	_renderFontIcons: function()
3340	{
3341        var name = null;
3342
3343        if(this.get('useFontIcons') && typeof YUI.Env.mods['sm-fonticon'] !== 'undefined') {
3344            for(name in this._buttons) {
3345                if(typeof this._buttons[name] !== 'undefined' && typeof this._fontIcons[name] !== 'undefined') {
3346                    this._buttons[name].addClass('sm-fonticon-' + this._fontIcons[name]);
3347                    this._buttons[name].addClass('sm-fonticon sm-button-skin-default sm-button-nochrome');
3348                }
3349                else if(name.indexOf('count') > -1) {
3350                    this._buttons[name].addClass('fonticons-enabled');
3351                }
3352            }
3353        }
3354	},
3355
3356	/**
3357	 * Updates the image count.
3358	 *
3359	 * @method _updateCount
3360	 * @protected
3361	 */
3362	_updateCount: function()
3363	{
3364		var html 		= '',
3365			countText	= Y.FL.SlideshowNav.COUNT_TEXT,
3366			current 	= 1,
3367			total 		= 1;
3368
3369		if(this.get('root').albumInfo) {
3370			current 	= this.get('root').imageInfo.index + 1;
3371			total 		= this.get('root').albumInfo.images.length;
3372		}
3373
3374		html = countText.replace('{current}', current).replace('{total}', total);
3375		this._buttons.count.set('innerHTML', html);
3376	},
3377
3378	/**
3379	 * Shows the caption button if the current image
3380	 * has a caption, hides it if the image does not
3381	 * have a caption.
3382	 *
3383	 * @method _updateCaption
3384	 * @protected
3385	 */
3386	_updateCaption: function()
3387	{
3388		var root		= this.get('root'),
3389			imageInfo 	= root.imageInfo;
3390
3391		if(imageInfo && imageInfo.caption === '') {
3392			root.caption.slideshowOverlay.enable();
3393			root.caption.slideshowOverlay.hide();
3394			this._buttons.caption.detach('click');
3395			this._buttons.caption.addClass('fl-slideshow-nav-caption-disabled');
3396		}
3397		else {
3398			this._buttons.caption.on('click', root._toggleCaption, root);
3399			this._buttons.caption.removeClass('fl-slideshow-nav-caption-disabled');
3400		}
3401	},
3402
3403	/**
3404	 * Checks if buying has been enabled for the current album.
3405	 *
3406	 * @method _updateBuy
3407	 * @protected
3408	 */
3409	_updateBuy: function()
3410	{
3411		var sm 				= null,
3412			root			= this.get('root'),
3413			rootSource 		= root.get('source')[root.albumIndex],
3414			albumIndex		= root.albumIndex,
3415			source 			= root.get('source')[albumIndex];
3416
3417		if(rootSource && rootSource.type == 'smugmug') {
3418			if(typeof root.albumInfo.printable !== 'undefined') {
3419				this._updateBuyComplete();
3420			}
3421			else {
3422				sm = new Y.FL.SmugMugAPI();
3423				sm.addParam('method', 'smugmug.albums.getInfo');
3424				sm.addParam('AlbumID', source.id);
3425				sm.addParam('AlbumKey', source.key);
3426				sm.on('complete', this._updateBuyComplete, this);
3427				sm.request();
3428			}
3429		}
3430	},
3431
3432	/**
3433	 * Shows the buy button and updates the buy url
3434	 * if buying has been enabled.
3435	 *
3436	 * @method _updateBuyComplete
3437	 * @param e {Object} The custom event object passed to this function.
3438	 * @protected
3439	 */
3440	_updateBuyComplete: function(e)
3441	{
3442		var root		= this.get('root'),
3443			printable 	= typeof e == 'undefined' ? root.albumInfo.printable : e.Album.Printable,
3444			link 		= root.albumInfo.link;
3445
3446		if(printable) {
3447			root.albumInfo.printable = true;
3448			this._buttons.buy.set('href', 'https://secure.smugmug.com/cart/batchadd/?url=' + encodeURIComponent(link));
3449			this._buttons.buy.setStyle('display', 'inline-block');
3450		}
3451		else {
3452			root.albumInfo.printable = false;
3453			this._buttons.buy.setStyle('display', 'none');
3454		}
3455
3456		this.fire('resize');
3457	},
3458
3459	/**
3460	 * Pauses the slideshow if it is playing and
3461	 * plays the slideshow if it is paused.
3462	 *
3463	 * @method _playClicked
3464	 * @protected
3465	 */
3466	_playClicked: function()
3467	{
3468		var root = this.get('root');
3469
3470		if(root._playing) {
3471			root.pause();
3472		}
3473		else {
3474			root.play();
3475		}
3476	},
3477
3478	/**
3479	 * Toggles the button class for the play button
3480	 * so pause is hidden and play is shown.
3481	 *
3482	 * @method _showPlayButton
3483	 * @protected
3484	 */
3485	_showPlayButton: function()
3486	{
3487		this._buttons.play.removeClass('fl-slideshow-nav-pause');
3488		this._buttons.play.addClass('fl-slideshow-nav-play');
3489
3490		if(this.get('useFontIcons') && typeof YUI.Env.mods['sm-fonticon'] !== 'undefined') {
3491            this._buttons.play.removeClass('sm-fonticon-PlayerPause');
3492            this._buttons.play.addClass('sm-fonticon-PlayerPlay');
3493		}
3494	},
3495
3496	/**
3497	 * Toggles the button class for the play button
3498	 * so pause is shown and play is hidden.
3499	 *
3500	 * @method _showPauseButton
3501	 * @protected
3502	 */
3503	_showPauseButton: function()
3504	{
3505		this._buttons.play.removeClass('fl-slideshow-nav-play');
3506		this._buttons.play.addClass('fl-slideshow-nav-pause');
3507
3508		if(this.get('useFontIcons') && typeof YUI.Env.mods['sm-fonticon'] !== 'undefined') {
3509            this._buttons.play.removeClass('sm-fonticon-PlayerPlay');
3510            this._buttons.play.addClass('sm-fonticon-PlayerPause');
3511		}
3512	}
3513
3514}, {
3515
3516	/**
3517	 * Custom CSS class name for the widget.
3518	 *
3519	 * @property CSS_PREFIX
3520	 * @type String
3521	 * @protected
3522	 * @static
3523	 */
3524	CSS_PREFIX: 'fl-slideshow-nav',
3525
3526	/**
3527	 * Static string used for displaying the image count. Use {current}
3528	 * for the current image and {total} for the total number of images.
3529	 * Those placeholders will be replaced when the count node is created.
3530	 *
3531	 * @property COUNT_TEXT
3532	 * @type String
3533	 * @protected
3534	 * @static
3535	 */
3536	COUNT_TEXT: '{current} of {total}',
3537
3538	/**
3539	 * Static property used to define the default attribute configuration of
3540	 * the Widget.
3541	 *
3542	 * @property ATTRS
3543	 * @type Object
3544	 * @protected
3545	 * @static
3546	 */
3547	ATTRS: {
3548
3549		/**
3550		 * An array of button names that is used to render the main buttons.
3551		 *
3552		 * @attribute buttons
3553		 * @type Array
3554		 * @default []
3555		 * @writeOnce
3556		 */
3557		buttons: {
3558			value: [],
3559			writeOnce: true
3560		},
3561
3562		/**
3563		 * An array of button names that is used to render the left buttons.
3564		 *
3565		 * @attribute buttonsLeft
3566		 * @type Array
3567		 * @default []
3568		 * @writeOnce
3569		 */
3570		buttonsLeft: {
3571			value: [],
3572			writeOnce: true
3573		},
3574
3575		/**
3576		 * An array of button names that is used to render the right buttons.
3577		 *
3578		 * @attribute buttonsRight
3579		 * @type Array
3580		 * @default []
3581		 * @writeOnce
3582		 */
3583		buttonsRight: {
3584			value: [],
3585			writeOnce: true
3586		},
3587
3588		/**
3589		 * Whether to use font icons when available.
3590		 *
3591		 * @attribute useFontIcons
3592		 * @type Boolean
3593		 * @default true
3594		 * @writeOnce
3595		 */
3596		useFontIcons: {
3597			value: true,
3598			writeOnce: true
3599		}
3600	}
3601});
3602
3603/**
3604 * A plugin for overlaying widgets in a slideshow
3605 * with specialized show and hide functionality.
3606 *
3607 * @namespace FL
3608 * @class SlideshowOverlay
3609 * @constructor
3610 * @param config {Object} Configuration object
3611 * @extends Plugin.Base
3612 */
3613Y.namespace('FL').SlideshowOverlay = Y.Base.create('fl-slideshow-overlay', Y.Plugin.Base, [], {
3614
3615	/**
3616	 * Flag for whether the mouse has entered
3617	 * the host's bounding box.
3618	 *
3619	 * @property _focus
3620	 * @type Boolean
3621	 * @default false
3622	 * @protected
3623	 */
3624	_focus: false,
3625
3626	/**
3627	 * Flag for whether the host's bounding box is visible.
3628	 *
3629	 * @property _visible
3630	 * @type Boolean
3631	 * @default true
3632	 * @protected
3633	 */
3634	_visible: true,
3635
3636	/**
3637	 * Flag for whether show and hide functionality
3638	 * has been disabled.
3639	 *
3640	 * @property _disabled
3641	 * @type Boolean
3642	 * @default false
3643	 * @protected
3644	 */
3645	_disabled: false,
3646
3647	/**
3648	 * An object containing properties for the show transition.
3649	 *
3650	 * @property _showProps
3651	 * @type Object
3652	 * @protected
3653	 */
3654	_showProps: {
3655	    duration: 0.5,
3656	    easing: 'ease-out',
3657	    opacity: 1
3658	},
3659
3660	/**
3661	 * An object containing properties for the hide transition.
3662	 *
3663	 * @property _hideProps
3664	 * @type Object
3665	 * @protected
3666	 */
3667	_hideProps: {
3668	    duration: 0.5,
3669	    easing: 'ease-out',
3670	    opacity: 0
3671	},
3672
3673	/**
3674	 * A timer object for delaying the hide transition.
3675	 *
3676	 * @property _hideTimer
3677	 * @type Object
3678	 * @default null
3679	 * @protected
3680	 */
3681	_hideTimer: null,
3682
3683	/**
3684	 * @method initializer
3685	 * @protected
3686	 */
3687	initializer: function()
3688	{
3689		var bb = this.get('host').get('boundingBox');
3690
3691		this.afterHostEvent('render', this._initFocus);
3692		this.afterHostEvent('render', this._initVisibility);
3693
3694		if(this.get('closeButton')) {
3695			this._initCloseButton();
3696		}
3697
3698		bb.addClass('fl-slideshow-overlay');
3699    },
3700
3701	/**
3702	 * @method destructor
3703	 * @protected
3704	 */
3705	destructor: function()
3706	{
3707		this._hideTimerCancel();
3708	},
3709
3710	/**
3711	 * Binds the mouseenter and mouseleave events for setting focus.
3712	 *
3713	 * @method _initFocus
3714	 * @protected
3715	 */
3716	_initFocus: function()
3717	{
3718		var bb = this.get('host').get('boundingBox');
3719		bb.on('mouseenter', Y.bind(this._setFocusOnMouseenter, this));
3720		bb.on('mouseleave', Y.bind(this._setFocusOnMouseleave, this));
3721	},
3722
3723	/**
3724	 * Sets the initial visibility of the host's bounding box.
3725	 *
3726	 * @method _initVisibility
3727	 * @protected
3728	 */
3729	_initVisibility: function()
3730	{
3731		var bb 			= this.get('host').get('boundingBox'),
3732			hideStyle 	= this.get('hideStyle');
3733
3734		if(!this.get('visible')) {
3735
3736			if(hideStyle == 'display') {
3737				bb.setStyle('display', 'none');
3738			}
3739			else if(hideStyle == 'left') {
3740				bb.setStyle('left', '-99999px');
3741			}
3742
3743			bb.setStyle('opacity', '0');
3744			this._visible = false;
3745		}
3746	},
3747
3748	/**
3749	 * Creates and inserts the close button.
3750	 *
3751	 * @method _initCloseButton
3752	 * @protected
3753	 */
3754	_initCloseButton: function()
3755	{
3756		var bb 			= this.get('host').get('boundingBox'),
3757			closeButton = null;
3758
3759		closeButton = Y.Node.create('<a class="fl-slideshow-overlay-close" href="javascript:void(0);"></a>');
3760		closeButton.on('click', Y.bind(this._closeButtonClick, this));
3761
3762		if(typeof YUI.Env.mods['sm-fonticon'] !== 'undefined') {
3763            closeButton.addClass('sm-fonticon sm-fonticon-XCrossEncircled sm-button-skin-default sm-button-nochrome');
3764        }
3765
3766		bb.insert(closeButton);
3767	},
3768
3769	/**
3770	 * Hides the overlay when the close button is clicked.
3771	 *
3772	 * @method _closeButtonClick
3773	 * @protected
3774	 */
3775	_closeButtonClick: function()
3776	{
3777		var bb = this.get('host').get('boundingBox');
3778		bb.transition(this._hideProps, Y.bind(this._hideComplete, this));
3779	},
3780
3781	/**
3782	 * Sets the focus flag to true.
3783	 *
3784	 * @method _setFocusOnMouseenter
3785	 * @protected
3786	 */
3787	_setFocusOnMouseenter: function()
3788	{
3789		this._focus = true;
3790	},
3791
3792	/**
3793	 * Sets the focus flag to false.
3794	 *
3795	 * @method _setFocusOnMouseleave
3796	 * @protected
3797	 */
3798	_setFocusOnMouseleave: function()
3799	{
3800		this._focus = false;
3801	},
3802
3803	/**
3804	 * Disables show and hide functionality.
3805	 *
3806	 * @method disable
3807	 * @public
3808	 */
3809	disable: function()
3810	{
3811		this._disabled = true;
3812	},
3813
3814	/**
3815	 * Enables show and hide functionality.
3816	 *
3817	 * @method enable
3818	 * @public
3819	 */
3820	enable: function()
3821	{
3822		this._disabled = false;
3823	},
3824
3825	/**
3826	 * Shows the host's bounding box with a fade in transition.
3827	 *
3828	 * @method show
3829	 * @public
3830	 */
3831	show: function()
3832	{
3833		var bb 			= this.get('host').get('boundingBox'),
3834			hideStyle 	= this.get('hideStyle');
3835
3836		if(this._disabled) {
3837			return;
3838		}
3839
3840		if(hideStyle == 'display') {
3841			bb.setStyle('display', 'block');
3842		}
3843		else if(hideStyle == 'left') {
3844			bb.setStyle('left', 'auto');
3845		}
3846
3847		bb.transition(this._showProps, Y.bind(this._showComplete, this));
3848
3849		/**
3850		 * @event hideStart
3851		 */
3852		this.fire('showStart');
3853	},
3854
3855	/**
3856	 * @method _showComplete
3857	 * @protected
3858	 */
3859	_showComplete: function()
3860	{
3861		this._visible = true;
3862		this.hideWithTimer();
3863
3864		/**
3865		 * @event showComplete
3866		 */
3867		this.fire('showComplete');
3868	},
3869
3870	/**
3871	 * Hides the host's bounding box with a fade out transition.
3872	 *
3873	 * @method hide
3874	 * @public
3875	 */
3876	hide: function()
3877	{
3878		if(this._focus || this._disabled) {
3879			return;
3880		}
3881
3882		var bb = this.get('host').get('boundingBox');
3883		bb.transition(this._hideProps, Y.bind(this._hideComplete, this));
3884
3885		/**
3886		 * @event hideStart
3887		 */
3888		this.fire('hideStart');
3889	},
3890
3891	/**
3892	 * Hides the host's bounding box with a fade out transition
3893	 * after a timer completes.
3894	 *
3895	 * @method hideWithTimer
3896	 * @public
3897	 */
3898	hideWithTimer: function()
3899	{
3900		this._hideTimerCancel();
3901		this._hideTimer = Y.later(this.get('hideDelay'), this, this.hide);
3902	},
3903
3904	/**
3905	 * Cancels the hide timer.
3906	 *
3907	 * @method _hideTimerCancel
3908	 * @protected
3909	 */
3910	_hideTimerCancel: function()
3911	{
3912		if(this._hideTimer) {
3913			this._hideTimer.cancel();
3914			this._hideTimer = null;
3915		}
3916	},
3917
3918	/**
3919	 * @method _hideComplete
3920	 * @protected
3921	 */
3922	_hideComplete: function()
3923	{
3924		var bb 			= this.get('host').get('boundingBox'),
3925			hideStyle 	= this.get('hideStyle');
3926
3927		if(hideStyle == 'display') {
3928			bb.setStyle('display', 'none');
3929		}
3930		else if(hideStyle == 'left') {
3931			bb.setStyle('left', '-99999px');
3932		}
3933
3934		this._visible = false;
3935
3936		/**
3937		 * @event hideComplete
3938		 */
3939		this.fire('hideComplete');
3940	}
3941
3942},	{
3943
3944	/**
3945	 * Namespace for the plugin.
3946	 *
3947	 * @property NS
3948	 * @type String
3949	 * @protected
3950	 * @static
3951	 */
3952	NS: 'slideshowOverlay',
3953
3954	/**
3955	 * Static property used to define the default attribute configuration of
3956	 * the plugin.
3957	 *
3958	 * @property ATTRS
3959	 * @type Object
3960	 * @protected
3961	 * @static
3962	 */
3963	ATTRS: {
3964
3965		/**
3966		 * Whether to use the close button or not.
3967		 *
3968		 * @attribute closeButton
3969		 * @type Boolean
3970		 * @default false
3971		 * @writeOnce
3972		 */
3973		closeButton: {
3974			value: false,
3975			writeOnce: true
3976		},
3977
3978		/**
3979		 * The time to wait before hiding the host's bounding box.
3980		 * Measured in milliseconds.
3981		 *
3982		 * @attribute hideDelay
3983		 * @type Number
3984		 * @default 3000
3985		 * @writeOnce
3986		 */
3987		hideDelay: {
3988			value: 3000,
3989			writeOnce: true
3990		},
3991
3992		/**
3993		 * The style to use for hiding the image. Possible
3994		 * values are display and left.
3995		 *
3996		 * @attribute hideStyle
3997		 * @type String
3998		 * @default display
3999		 * @writeOnce
4000		 */
4001		hideStyle: {
4002			value: 'display',
4003			writeOnce: true
4004		},
4005
4006		/**
4007		 * Sets the initial visibility of the host's boudning box.
4008		 *
4009		 * @attribute visible
4010		 * @type Boolean
4011		 * @default true
4012		 * @writeOnce
4013		 */
4014		visible: {
4015			value: true,
4016			writeOnce: true
4017		}
4018	}
4019});
4020
4021/**
4022 * Social buttons widget used in slideshows.
4023 *
4024 * @namespace FL
4025 * @class SlideshowSocial
4026 * @constructor
4027 * @param config {Object} Configuration object
4028 * @extends Widget
4029 */
4030
4031Y.namespace('FL').SlideshowSocial = Y.Base.create('fl-slideshow-social', Y.Widget, [Y.WidgetChild], {
4032
4033	/**
4034	 * An object containing the social button nodes.
4035	 *
4036	 * @property _buttons
4037	 * @type Object
4038	 * @default null
4039	 * @protected
4040	 */
4041	_buttons: null,
4042
4043	/**
4044	 * @method renderUI
4045	 * @protected
4046	 */
4047	renderUI: function()
4048	{
4049		this._buttons = {};
4050
4051		if(this.get('root').get('googlePlusButtonEnabled')) {
4052			this._renderGooglePlusButton();
4053		}
4054	},
4055
4056	/**
4057	 * @method bindUI
4058	 * @protected
4059	 */
4060	bindUI: function()
4061	{
4062		var root = this.get('root');
4063
4064		if(root.get('likeButtonEnabled')) {
4065			root.on('imageLoadComplete', Y.bind(this._updateLikeButton, this));
4066		}
4067		if(root.get('tweetButtonEnabled')) {
4068			root.on('imageLoadComplete', Y.bind(this._updateTweetButton, this));
4069		}
4070		if(root.get('googlePlusButtonEnabled')) {
4071			root.on('imageLoadComplete', Y.bind(this._updateGooglePlusButton, this));
4072		}
4073		if(root.get('pinterestButtonEnabled')) {
4074			root.on('imageLoadComplete', Y.bind(this._updatePinterestButton, this));
4075		}
4076	},
4077
4078	/**
4079	 * @method _updateLikeButton
4080	 * @protected
4081	 */
4082	_updateLikeButton: function()
4083	{
4084		var src				= null,
4085			cb				= this.get('contentBox'),
4086			root			= this.get('root'),
4087			albumIndex		= root.albumIndex,
4088			rootSource 		= root.get('source')[albumIndex],
4089			imageInfo 		= root.imageInfo;
4090
4091		if(this._buttons.like) {
4092			this._buttons.like.remove();
4093			this._buttons.like = null;
4094		}
4095
4096		if(rootSource.type == 'smugmug') {
4097			src = 'https://www.facebook.com/plugins/like.php?';
4098			src += 'href=' + 'https://www.smugmug.com/services/graph/gallery/';
4099			src += rootSource.id + '_' + rootSource.key +'/' + imageInfo.id + '_' + imageInfo.key;
4100		}
4101		else {
4102			src = 'https://www.facebook.com/plugins/like.php?';
4103			src += 'href=' + encodeURIComponent(imageInfo.largeURL);
4104		}
4105
4106		src += '&send=false';
4107		src += '&layout=button_count';
4108		src += '&width=90';
4109		src += '&show_faces=false';
4110		src += '&action=like';
4111		src += '&colorscheme=light';
4112		src += '&height=21';
4113
4114		this._buttons.like = Y.Node.create('<iframe src="'+ src +'" scrolling="no" frameborder="0" allowTransparency="true"></iframe>');
4115
4116		this._buttons.like.setStyles({
4117			overflow: 'hidden',
4118			width: '90px',
4119			height: '21px'
4120		});
4121
4122		cb.appendChild(this._buttons.like);
4123	},
4124
4125	/**
4126	 * @method _updateTweetButton
4127	 * @protected
4128	 */
4129	_updateTweetButton: function()
4130	{
4131		var src			= null,
4132			imageInfo 	= this.get('root').imageInfo,
4133			cb			= this.get('contentBox');
4134
4135		if(this._buttons.tweet) {
4136			this._buttons.tweet.remove();
4137			this._buttons.tweet = null;
4138		}
4139
4140		src = 'https://platform.twitter.com/widgets/tweet_button.html?';
4141		src += 'url=' + encodeURIComponent(imageInfo.largeURL);
4142		src += '&count=none';
4143
4144		this._buttons.tweet = Y.Node.create('<iframe src="'+ src +'" scrolling="no" frameborder="0" allowTransparency="true"></iframe>');
4145
4146		this._buttons.tweet.setStyles({
4147			overflow: 'hidden',
4148			width: '90px',
4149			height: '21px'
4150		});
4151
4152		cb.appendChild(this._buttons.tweet);
4153	},
4154
4155	/**
4156	 * @method _renderGooglePlusButton
4157	 * @protected
4158	 */
4159	_renderGooglePlusButton: function()
4160	{
4161		var po, head;
4162
4163		po = document.createElement('script');
4164		po.type = 'text/javascript';
4165    	po.src = 'https://apis.google.com/js/plusone.js';
4166
4167		head = document.getElementsByTagName('head')[0];
4168		head.parentNode.appendChild(po);
4169	},
4170
4171	/**
4172	 * @method _updateGooglePlusButton
4173	 * @protected
4174	 */
4175	_updateGooglePlusButton: function()
4176	{
4177		if(typeof gapi == 'undefined') {
4178			setTimeout(Y.bind(this._updateGooglePlusButtonCallback, this), 500);
4179		}
4180		else {
4181			this._updateGooglePlusButtonCallback();
4182		}
4183	},
4184
4185	/**
4186	 * @method _updateGooglePlusButtonCallback
4187	 * @protected
4188	 */
4189	_updateGooglePlusButtonCallback: function()
4190	{
4191		var imageInfo 	= this.get('root').imageInfo,
4192			cb			= this.get('contentBox');
4193
4194		if(this._buttons.plus) {
4195			this._buttons.plus.remove();
4196			this._buttons.plus = null;
4197		}
4198		if(typeof gapi != 'undefined') {
4199			this._buttons.plus = Y.Node.create('<div></div>');
4200			cb.appendChild(this._buttons.plus);
4201
4202			gapi.plusone.render(this._buttons.plus._node, {
4203				href: encodeURIComponent(imageInfo.largeURL),
4204				annotation: 'bubble',
4205				size: 'medium'
4206			});
4207		}
4208	},
4209
4210	/**
4211	 * @method _updatePinterestButton
4212	 * @protected
4213	 */
4214	_updatePinterestButton: function()
4215	{
4216		var href		= 'https://pinterest.com/pin/create/button/',
4217			imageInfo 	= this.get('root').imageInfo,
4218			cb			= this.get('contentBox');
4219
4220		if(this._buttons.pin) {
4221			this._buttons.pin.remove();
4222			this._buttons.pin = null;
4223		}
4224
4225		href += '?url=' + encodeURIComponent(window.location.href);
4226		href += '&media='+ encodeURIComponent(imageInfo.mediumURL);
4227		href += '&description='+ encodeURIComponent(imageInfo.caption);
4228
4229		this._buttons.pin = Y.Node.create('<a></a>');
4230		this._buttons.pin.setAttribute('data-pin-config', 'none');
4231		this._buttons.pin.setAttribute('data-pin-do', 'buttonPin');
4232		this._buttons.pin.setAttribute('href', href);
4233		this._buttons.pin.setAttribute('target', '_blank');
4234		this._buttons.pin.set('innerHTML', '<img src="https://assets.pinterest.com/images/pidgets/pin_it_button.png" border="0" />');
4235
4236		cb.appendChild(this._buttons.pin);
4237	}
4238
4239}, {
4240
4241	/**
4242	 * Custom CSS class name for the widget.
4243	 *
4244	 * @property CSS_PREFIX
4245	 * @type String
4246	 * @protected
4247	 * @static
4248	 */
4249	CSS_PREFIX: 'fl-slideshow-social',
4250
4251	/**
4252	 * Static property used to define the default attribute configuration of
4253	 * the Widget.
4254	 *
4255	 * @property ATTRS
4256	 * @type Object
4257	 * @protected
4258	 * @static
4259	 */
4260	ATTRS: {
4261
4262	}
4263});
4264
4265/**
4266 * Creates a grid of FL.SlideshowImage instances.
4267 *
4268 * @namespace FL
4269 * @class SlideshowThumbs
4270 * @constructor
4271 * @param config {Object} Configuration object
4272 * @extends Widget
4273 */
4274Y.namespace('FL').SlideshowThumbs = Y.Base.create('fl-slideshow-thumbs', Y.Widget, [Y.WidgetParent, Y.WidgetChild], {
4275
4276	/**
4277	 * A div node used to hide the overflow when
4278	 * transitioning between pages.
4279	 *
4280	 * @property _clipBox
4281	 * @type Object
4282	 * @default null
4283	 * @protected
4284	 */
4285	_clipBox: null,
4286
4287	/**
4288	 * A div node used to hold the pages.
4289	 *
4290	 * @property _pagesBox
4291	 * @type Object
4292	 * @default null
4293	 * @protected
4294	 */
4295	_pagesBox: null,
4296
4297	/**
4298	 * A reference to the active page div node. Holds a grid
4299	 * of FL.SlideshowImage instances.
4300	 *
4301	 * @property _activePageBox
4302	 * @type Object
4303	 * @default null
4304	 * @protected
4305	 */
4306	_activePageBox: null,
4307
4308	/**
4309	 * The index of the active page of thumbs.
4310	 *
4311	 * @property _activePageIndex
4312	 * @type Number
4313	 * @default 0
4314	 * @protected
4315	 */
4316	_activePageIndex: 0,
4317
4318	/**
4319	 * A reference to the next page div node. Holds a grid
4320	 * of FL.SlideshowImage instances.
4321	 *
4322	 * @property _nextPageBox
4323	 * @type Object
4324	 * @default null
4325	 * @protected
4326	 */
4327	_nextPageBox: null,
4328
4329	/**
4330	 * An array of FL.SlideshowImage instances in the active page.
4331	 *
4332	 * @property _activeImages
4333	 * @type Array
4334	 * @default null
4335	 * @protected
4336	 */
4337	_activeImages: null,
4338
4339	/**
4340	 * An array of FL.SlideshowImage instances used to
4341	 * preload the next page of images.
4342	 *
4343	 * @property _nextImages
4344	 * @type Array
4345	 * @default null
4346	 * @protected
4347	 */
4348	_nextImages: null,
4349
4350	/**
4351	 * An array of FL.SlideshowImage instances used to
4352	 * preload the previous page of images.
4353	 *
4354	 * @property _prevImages
4355	 * @type Array
4356	 * @default null
4357	 * @protected
4358	 */
4359	_prevImages: null,
4360
4361	/**
4362	 * An instance of FL.SlideshowNav used for the left nav.
4363	 *
4364	 * @property _leftNav
4365	 * @type Object
4366	 * @default null
4367	 * @protected
4368	 */
4369	_leftNav: null,
4370
4371	/**
4372	 * An instance of FL.SlideshowNav used for the right nav.
4373	 *
4374	 * @property _rightNav
4375	 * @type Object
4376	 * @default null
4377	 * @protected
4378	 */
4379	_rightNav: null,
4380
4381	/**
4382	 * An instance of FL.SlideshowNav used for the top nav.
4383	 *
4384	 * @property _topNav
4385	 * @type Object
4386	 * @default null
4387	 * @protected
4388	 */
4389	_topNav: null,
4390
4391	/**
4392	 * An instance of FL.SlideshowNav used for the bottom nav.
4393	 *
4394	 * @property _bottomNav
4395	 * @type Object
4396	 * @default null
4397	 * @protected
4398	 */
4399	_bottomNav: null,
4400
4401	/**
4402	 * Height of the bounding box.
4403	 *
4404	 * @property _bbHeight
4405	 * @type Number
4406	 * @default 0
4407	 * @protected
4408	 */
4409	_bbHeight: 0,
4410
4411	/**
4412	 * Width of the bounding box.
4413	 *
4414	 * @property _bbWidth
4415	 * @type Number
4416	 * @default 0
4417	 * @protected
4418	 */
4419	_bbWidth: 0,
4420
4421	/**
4422	 * Width of the content box.
4423	 *
4424	 * @property _cbWidth
4425	 * @type Number
4426	 * @default 0
4427	 * @protected
4428	 */
4429	_cbWidth: 0,
4430
4431	/**
4432	 * Left margin of the clip box.
4433	 *
4434	 * @property _clipBoxMarginLeft
4435	 * @type Number
4436	 * @default 0
4437	 * @protected
4438	 */
4439	_clipBoxMarginLeft: 0,
4440
4441	/**
4442	 * Top position of the clip box.
4443	 *
4444	 * @property _clipBoxTop
4445	 * @type Number
4446	 * @default 0
4447	 * @protected
4448	 */
4449	_clipBoxTop: 0,
4450
4451	/**
4452	 * The number of columns per page.
4453	 *
4454	 * @property _colsPerPage
4455	 * @type Number
4456	 * @default 0
4457	 * @protected
4458	 */
4459	_colsPerPage: 0,
4460
4461	/**
4462	 * The number of rows per page.
4463	 *
4464	 * @property _rowsPerPage
4465	 * @type Number
4466	 * @default 0
4467	 * @protected
4468	 */
4469	_rowsPerPage: 0,
4470
4471	/**
4472	 * The number of images per page.
4473	 *
4474	 * @property _imagesPerPage
4475	 * @type Number
4476	 * @default 0
4477	 * @protected
4478	 */
4479	_imagesPerPage: 0,
4480
4481	/**
4482	 * The number of pages.
4483	 *
4484	 * @property _numPages
4485	 * @type Number
4486	 * @default 0
4487	 * @protected
4488	 */
4489	_numPages: 0,
4490
4491	/**
4492	 * Height of the pages.
4493	 *
4494	 * @property _pageHeight
4495	 * @type Number
4496	 * @default 0
4497	 * @protected
4498	 */
4499	_pageHeight: 0,
4500
4501	/**
4502	 * Width of the pages.
4503	 *
4504	 * @property _pageWidth
4505	 * @type Number
4506	 * @default 0
4507	 * @protected
4508	 */
4509	_pageWidth: 0,
4510
4511	/**
4512	 * The horizontal spacing between thumbs.
4513	 *
4514	 * @property _horizontalSpacing
4515	 * @type Number
4516	 * @default 0
4517	 * @protected
4518	 */
4519	_horizontalSpacing: 0,
4520
4521	/**
4522	 * The vertical spacing between thumbs.
4523	 *
4524	 * @property _verticalSpacing
4525	 * @type Number
4526	 * @default 0
4527	 * @protected
4528	 */
4529	_verticalSpacing: 0,
4530
4531	/**
4532	 * Width of the left nav.
4533	 *
4534	 * @property _leftNavWidth
4535	 * @type Number
4536	 * @default 0
4537	 * @protected
4538	 */
4539	_leftNavWidth: 0,
4540
4541	/**
4542	 * Width of the right nav.
4543	 *
4544	 * @property _rightNavWidth
4545	 * @type Number
4546	 * @default 0
4547	 * @protected
4548	 */
4549	_rightNavWidth: 0,
4550
4551	/**
4552	 * An instance of FL.SlideshowTransition for the current transition.
4553	 *
4554	 * @property _transition
4555	 * @type FL.SlideshowTransition
4556	 * @default null
4557	 * @protected
4558	 */
4559	_transition: null,
4560
4561	/**
4562	 * Whether the pages are currently transitioning or not.
4563	 *
4564	 * @property _verticalSpacing
4565	 * @type Boolean
4566	 * @default false
4567	 * @protected
4568	 */
4569	_transitioning: false,
4570
4571	/**
4572	 * Direction of the current transition.
4573	 *
4574	 * @property _transitionDirection
4575	 * @type String
4576	 * @default next
4577	 * @protected
4578	 */
4579	_transitionDirection: 'next',
4580
4581	/**
4582	 * Provides functionality for gesture based transitions
4583 	 * between the active and next pages.
4584	 *
4585	 * @property _gestures
4586	 * @type FL.SlideshowGestures
4587	 * @default null
4588	 * @protected
4589	 */
4590	_gestures: null,
4591
4592	/**
4593	 * Initialize image vars.
4594	 *
4595	 * @method initializer
4596	 * @protected
4597	 */
4598	initializer: function()
4599	{
4600		this._activeImages = [];
4601		this._nextImages = [];
4602		this._prevImages = [];
4603	},
4604
4605	/**
4606	 * Renders the UI boxes.
4607	 *
4608	 * @method renderUI
4609	 * @protected
4610	 */
4611	renderUI: function()
4612	{
4613		this._renderBoxes();
4614		this._renderNavs();
4615	},
4616
4617	/**
4618	 * Binds the UI events.
4619	 *
4620	 * @method bindUI
4621	 * @protected
4622	 */
4623	bindUI: function()
4624	{
4625		var root 		= this.get('root'),
4626			id 			= this.get('id'),
4627			transition 	= this.get('transition');
4628
4629		root.on(id + '|albumLoadComplete', this._albumLoadComplete, this);
4630
4631		if('ontouchstart' in window && this.get('touchSupport')) {
4632
4633			this._gestures = new Y.FL.SlideshowGestures({
4634				direction: transition == 'slideVertical' ? 'vertical' : 'horizontal',
4635				activeItem: this._activePageBox,
4636				nextItem: this._nextPageBox
4637			});
4638
4639			this._gestures.on('moveStart', this._gesturesMoveStart, this);
4640			this._gestures.on('endComplete', this._gesturesEndComplete, this);
4641		}
4642	},
4643
4644	/**
4645	 * Syncs the UI boxes.
4646	 *
4647	 * @method syncUI
4648	 * @protected
4649	 */
4650	syncUI: function()
4651	{
4652		this._syncBoxes();
4653		this._syncNavs();
4654	},
4655
4656	/**
4657	 * @method destructor
4658	 * @protected
4659	 */
4660	destructor: function()
4661	{
4662		var root 	= this.get('root'),
4663			id 		= this.get('id');
4664
4665		root.detach(id + '|*');
4666
4667		Y.FL.SlideshowImageLoader.removeGroup('thumbs');
4668	},
4669
4670	/**
4671	 * Unload all images.
4672	 *
4673	 * @method unload
4674	 */
4675	unload: function()
4676	{
4677		var root 	= this.get('root'),
4678			id 		= this.get('id'),
4679			i 		= 0;
4680
4681		root.detach(id + '|imageLoadComplete');
4682
4683		Y.FL.SlideshowImageLoader.removeGroup('thumbs');
4684
4685		for( ; i < this._activeImages.length; i++) {
4686			this._activeImages[i].unload();
4687		}
4688	},
4689
4690	/**
4691	 * Resizes the UI boxes.
4692	 *
4693	 * @method resize
4694	 */
4695	resize: function()
4696	{
4697		this._setSizeInfo();
4698		this._togglePageButtons();
4699		this._resizeBoxes();
4700		this._resizeNavs();
4701
4702		if(this.get('root').albumInfo) {
4703			Y.FL.SlideshowImageLoader.removeGroup('thumbs');
4704			this._renderActivePage();
4705			this._preloadNextPage();
4706			this._preloadPrevPage();
4707		}
4708
4709		// Enable or disable gestures.
4710		if(this._gestures && this._numPages < 2) {
4711			this._gestures.disable();
4712		}
4713		else if(this._gestures) {
4714			this._gestures.enable();
4715		}
4716	},
4717
4718	/**
4719	 * Transitions to the previous page.
4720	 *
4721	 * @method prevPage
4722	 * @protected
4723	 */
4724	prevPage: function()
4725	{
4726		if(this._transitioning) {
4727			return;
4728		}
4729
4730		this._transitionStart('prev');
4731	},
4732
4733	/**
4734	 * Transitions to the next page.
4735	 *
4736	 * @method nextPage
4737	 * @protected
4738	 */
4739	nextPage: function()
4740	{
4741		if(this._transitioning) {
4742			return;
4743		}
4744
4745		this._transitionStart('next');
4746	},
4747
4748	/**
4749	 * Called when an album is loaded into the root slideshow widget.
4750	 *
4751	 * @method _albumLoadComplete
4752	 * @protected
4753	 */
4754	_albumLoadComplete: function()
4755	{
4756		var root 	= this.get('root'),
4757			id 		= this.get('id');
4758
4759		root.once(id + '|imageLoadComplete', this.resize, this);
4760		root.on(id + '|imageLoadComplete', this._imageLoadComplete, this);
4761	},
4762
4763	/**
4764	 * Called when an image is loaded into the root slideshow widget.
4765	 *
4766	 * @method _imageLoadComplete
4767	 * @protected
4768	 */
4769	_imageLoadComplete: function()
4770	{
4771		var albumInfo 	= this.get('root').albumInfo,
4772			lastActive 	= Y.one('.fl-slideshow-image-active'),
4773			lastInfo	= lastActive ? lastActive._imageInfo : null,
4774			nextActive 	= null,
4775			nextInfo	= this.get('root').imageInfo;
4776
4777		this._setActiveImage(this._activeImages);
4778
4779		nextActive = Y.one('.fl-slideshow-image-active');
4780
4781		if(lastActive && !nextActive) {
4782			if(nextInfo.index === 0 && lastInfo.index === albumInfo.images.length - 1) {
4783				this.nextPage();
4784			}
4785			else if(lastInfo.index === 0 && nextInfo.index === albumInfo.images.length - 1) {
4786				this.prevPage();
4787			}
4788			else if(lastInfo.index < nextInfo.index) {
4789				this.nextPage();
4790			}
4791			else if(lastInfo.index > nextInfo.index) {
4792				this.prevPage();
4793			}
4794		}
4795	},
4796
4797	/**
4798	 * Renders the boxes.
4799	 *
4800	 * @method _renderBoxes
4801	 * @protected
4802	 */
4803	_renderBoxes: function()
4804	{
4805		// Clip box
4806		this._clipBox = Y.Node.create('<div></div>');
4807		this._clipBox.addClass('fl-slideshow-thumbs-clip');
4808		this.get('contentBox').insert(this._clipBox);
4809
4810		// Pages box
4811		this._pagesBox = Y.Node.create('<div></div>');
4812		this._pagesBox.addClass('fl-slideshow-thumbs-pages');
4813		this._clipBox.insert(this._pagesBox);
4814
4815		// Active page box
4816		this._activePageBox = Y.Node.create('<div></div>');
4817		this._activePageBox.addClass('fl-slideshow-thumbs-page');
4818		this._pagesBox.insert(this._activePageBox);
4819
4820		// Next page box
4821		this._nextPageBox = Y.Node.create('<div></div>');
4822		this._nextPageBox.addClass('fl-slideshow-thumbs-page');
4823		this._pagesBox.insert(this._nextPageBox);
4824	},
4825
4826	/**
4827	 * Syncs the boxes.
4828	 *
4829	 * @method _syncBoxes
4830	 * @protected
4831	 */
4832	_syncBoxes: function()
4833	{
4834		// Active page box
4835		this._activePageBox.setStyle('left', '0');
4836
4837		// Next page box
4838		this._nextPageBox.setStyle('left', '-9999px');
4839	},
4840
4841	/**
4842	 * Resizes the boxes.
4843	 *
4844	 * @method _resizeBoxes
4845	 * @protected
4846	 */
4847	_resizeBoxes: function()
4848	{
4849		this.set('width', this._bbWidth);
4850		this.set('height', this._bbHeight);
4851
4852		this.get('contentBox').setStyle('width', this._cbWidth + 'px');
4853
4854		this._clipBox.setStyle('width', this._pageWidth + 'px');
4855		this._clipBox.setStyle('height', this._pageHeight + 'px');
4856		this._clipBox.setStyle('padding', this._verticalSpacing + 'px 0 0 ' + this._horizontalSpacing + 'px ');
4857		this._clipBox.setStyle('margin',  '0 0 0 ' + this._clipBoxMarginLeft + 'px');
4858		this._clipBox.setStyle('top', this._clipBoxTop);
4859
4860		this._pagesBox.setStyle('width', this._pageWidth + 'px');
4861		this._pagesBox.setStyle('height', this._pageHeight + 'px');
4862
4863		this._activePageBox.setStyle('width', this._pageWidth + 'px');
4864		this._activePageBox.setStyle('height', this._pageHeight + 'px');
4865
4866		this._nextPageBox.setStyle('width', this._pageWidth + 'px');
4867		this._nextPageBox.setStyle('height', this._pageHeight + 'px');
4868	},
4869
4870	/**
4871	 * Renders the active page of images.
4872	 *
4873	 * @method _renderActivePage
4874	 * @protected
4875	 */
4876	_renderActivePage: function()
4877	{
4878		var i 			= 0,
4879			root		= this.get('root'),
4880			imageIndex 	= this._imagesPerPage * this._activePageIndex,
4881			endIndex	= imageIndex + this._imagesPerPage,
4882			images 		= root.albumInfo.images;
4883
4884		this._clearActiveImage();
4885
4886		// Remove current images
4887		for( ; i < this._activeImages.length; i++) {
4888			this._activeImages[i].remove();
4889			this._activeImages[i].unload();
4890			this._activeImages[i].get('boundingBox')._imageInfo = null;
4891			this._activeImages[i].get('boundingBox').remove();
4892		}
4893
4894		// Draw images
4895		for(i = 0; imageIndex < endIndex; imageIndex++) {
4896
4897			if(!images[imageIndex]) {
4898				break;
4899			}
4900
4901			this._renderImage(this._activeImages, i, this._activePageBox, images[imageIndex]);
4902			i++;
4903		}
4904
4905		this._setActiveImage(this._activeImages);
4906	},
4907
4908	/**
4909	 * Renders the next page of images.
4910	 *
4911	 * @method _renderNextPage
4912	 * @protected
4913	 */
4914	_renderNextPage: function()
4915	{
4916		var i 			= 0,
4917			imageArray 	= this._transitionDirection == 'next' ? this._nextImages : this._prevImages;
4918
4919		this._nextPageBox.get('children').remove();
4920
4921		for( ; i < imageArray.length; i++) {
4922			if(imageArray[i]._imageInfo) {
4923				this._renderImage(imageArray, i, this._nextPageBox, imageArray[i]._imageInfo);
4924			}
4925			else {
4926				break;
4927			}
4928		}
4929
4930		this._setActiveImage(imageArray);
4931	},
4932
4933	/**
4934	 * Preloads the next page of images.
4935	 *
4936	 * @method _preloadNextPage
4937	 * @protected
4938	 */
4939	_preloadNextPage: function()
4940	{
4941		var pageIndex = this._activePageIndex + 1 >= this._numPages ? 0 : this._activePageIndex + 1;
4942
4943		this._preloadPage(pageIndex, this._nextImages);
4944	},
4945
4946	/**
4947	 * Preloads the previous page of images.
4948	 *
4949	 * @method _preloadPrevPage
4950	 * @protected
4951	 */
4952	_preloadPrevPage: function()
4953	{
4954		var pageIndex = this._activePageIndex - 1 < 0 ? this._numPages - 1 : this._activePageIndex - 1;
4955
4956		this._preloadPage(pageIndex, this._prevImages);
4957	},
4958
4959	/**
4960	 * Preloads a page of images.
4961	 *
4962	 * @method _preloadPage
4963	 * @param imageIndex {Number} The image index to start preloading from.
4964	 * @param imageArray {Array} The array to store the preloaded images.
4965	 * @protected
4966	 */
4967	_preloadPage: function(pageIndex, imageArray)
4968	{
4969		var i 			= 0,
4970			root		= this.get('root'),
4971			images 		= root.albumInfo.images,
4972			imageIndex	= pageIndex * this._imagesPerPage,
4973			endIndex	= imageIndex + this._imagesPerPage,
4974			imageConfig = this.get('imageConfig'),
4975			width 		= imageConfig.width,
4976			height 		= imageConfig.height;
4977
4978		if(this._numPages > 1) {
4979
4980			// Unload existing images
4981			for( ; i < imageArray.length; i++) {
4982				imageArray[i].remove();
4983				imageArray[i].unload();
4984			}
4985
4986			// Preload the images
4987			for(i = 0; imageIndex < endIndex; imageIndex++) {
4988
4989				if(!images[imageIndex]) {
4990					continue;
4991				}
4992
4993				this._renderImage(imageArray, i);
4994				imageArray[i].preload(images[imageIndex], width, height);
4995				i++;
4996			}
4997		}
4998	},
4999
5000	/**
5001	 * Renders an image.
5002	 *
5003	 * @method _renderImage
5004	 * @protected
5005	 */
5006	_renderImage: function(imageArray, i, page, imageInfo)
5007	{
5008		var imageBB		= null,
5009			imageConfig = this.get('imageConfig');
5010
5011		// Create the image?
5012		if(typeof imageArray[i] == 'undefined') {
5013			imageConfig.loadGroup = 'thumbs';
5014			imageConfig.useThumbSizes = true;
5015			imageConfig.loadVideos = false;
5016			imageArray[i] = new Y.FL.SlideshowImage(imageConfig);
5017			imageBB = imageArray[i].get('boundingBox');
5018			imageBB.on('click', this._imageClick, this);
5019			imageBB.on('mouseover', this._imageMouseover, this);
5020			imageBB.on('mouseout', this._imageMouseout, this);
5021		}
5022
5023		// Image bounding box
5024		imageBB = imageArray[i].get('boundingBox');
5025		imageBB.setStyle('margin', '0 ' + this._horizontalSpacing + 'px ' + this._verticalSpacing + 'px 0');
5026
5027		// Add the image to a page?
5028		if(page) {
5029			this._childrenContainer = page;
5030			this.add(imageArray[i]);
5031			imageArray[i].resize(imageConfig.width, imageConfig.height);
5032		}
5033
5034		// Load the image?
5035		if(imageInfo) {
5036			imageArray[i].load(imageInfo);
5037			imageBB._imageInfo = imageInfo;
5038		}
5039	},
5040
5041	/**
5042     * Overrides the WidgetParent _uiAddChild method so _renderImage
5043     * will render to the appropriate page.
5044     *
5045     * @method _uiAddChild
5046     * @protected
5047     * @param child {Widget} The child Widget instance to render.
5048     * @param parentNode {Object} The Node under which the
5049     * child Widget is to be rendered. Set to the appropriate page
5050     * in the _renderImage method by setting _childrenContainer.
5051     */
5052    _uiAddChild: function (child, parentNode)
5053    {
5054        child.render(parentNode);
5055        parentNode.appendChild(child.get('boundingBox'));
5056    },
5057
5058	/**
5059	 * Called when an image is clicked.
5060	 *
5061	 * @method _imageClick
5062	 * @protected
5063	 */
5064	_imageClick: function(e)
5065	{
5066		var root = this.get('root');
5067
5068		if(this.get('pauseOnClick')) {
5069			root.pause();
5070		}
5071
5072		root.loadImage(e.currentTarget._imageInfo.index);
5073
5074		/**
5075		 * Fires when an image is clicked.
5076		 *
5077		 * @event imageClick
5078		 */
5079		this.fire('imageClick');
5080	},
5081
5082	/**
5083	 * Sets the active image.
5084	 *
5085	 * @method _setActiveImage
5086	 * @param imageArray {Array} The image array to check for the active image.
5087	 * @protected
5088	 */
5089	_setActiveImage: function(imageArray)
5090	{
5091		var i = 0;
5092
5093		this._clearActiveImage();
5094
5095		for( ; i < imageArray.length; i++) {
5096			if(imageArray[i]._imageInfo) {
5097				if(imageArray[i]._imageInfo.index == this.get('root').imageInfo.index) {
5098					imageArray[i].get('boundingBox').addClass('fl-slideshow-image-active');
5099					break;
5100				}
5101			}
5102		}
5103	},
5104
5105	/**
5106	 * Removes the class name 'fl-slideshow-image-active'
5107	 * from the active image.
5108	 *
5109	 * @method _clearActiveImage
5110	 * @protected
5111	 */
5112	_clearActiveImage: function()
5113	{
5114		var active = Y.one('.fl-slideshow-image-active');
5115
5116		if(active) {
5117			active.removeClass('fl-slideshow-image-active');
5118		}
5119	},
5120
5121	/**
5122	 * Gets the transition type.
5123	 *
5124	 * @method _getTransition
5125	 * @protected
5126	 */
5127	_getTransition: function()
5128	{
5129		var transition = this.get('transition');
5130
5131		if(transition == 'slideHorizontal' && this._transitionDirection == 'next') {
5132			return 'slideLeft';
5133		}
5134		else if(transition == 'slideHorizontal' && this._transitionDirection == 'prev') {
5135			return 'slideRight';
5136		}
5137		else if(transition == 'slideVertical' && this._transitionDirection == 'next') {
5138			return 'slideUp';
5139		}
5140		else if(transition == 'slideVertical' && this._transitionDirection == 'prev') {
5141			return 'slideDown';
5142		}
5143
5144		return transition;
5145	},
5146
5147	/**
5148	 * Starts the transition, moving in the provided direction.
5149	 *
5150	 * @method _transitionStart
5151	 * @param direction {String} The direction to transition.
5152	 * @protected
5153	 */
5154	_transitionStart: function(direction)
5155	{
5156		if(this._numPages > 1) {
5157
5158			Y.FL.SlideshowImageLoader.removeGroup('thumbs');
5159
5160			this._transitionDirection = direction;
5161			this._transitioning = true;
5162			this._nextPageBox.setStyle('left', '0px');
5163			this._renderNextPage();
5164
5165			this._transition = new Y.FL.SlideshowTransition({
5166				itemIn: this._nextPageBox,
5167				itemOut: this._activePageBox,
5168				type: this._getTransition(),
5169				duration: this.get('transitionDuration'),
5170				easing: this.get('transitionEasing')
5171			});
5172
5173			this._transition.once('complete', this._transitionComplete, this);
5174			this._transition.run();
5175
5176			// Disable gestures if set.
5177			if(this._gestures) {
5178				this._gestures.disable();
5179			}
5180		}
5181	},
5182
5183	/**
5184	 * Transition cleanup called when the current transition ends.
5185	 *
5186	 * @method _transitionComplete
5187	 * @protected
5188	 */
5189	_transitionComplete: function()
5190	{
5191		this._swapPageRefs();
5192		this._transitioning = false;
5193		this._transitionDirection = '';
5194		this._transition = null;
5195
5196		// Enable gestures if set.
5197		if(this._gestures) {
5198			this._gestures.enable();
5199		}
5200
5201		/**
5202		 * Fires when a page transition completes.
5203		 *
5204		 * @event transitionComplete
5205		 */
5206		this.fire('transitionComplete');
5207	},
5208
5209	/**
5210	 * @method _gesturesMoveStart
5211	 * @param e {Object} The event object.
5212	 * @protected
5213	 */
5214	_gesturesMoveStart: function(e)
5215	{
5216		Y.FL.SlideshowImageLoader.removeGroup('thumbs');
5217
5218		this._transitionDirection = e.direction;
5219		this._renderNextPage();
5220	},
5221
5222	/**
5223	 * @method _gesturesEndComplete
5224	 * @protected
5225	 */
5226	_gesturesEndComplete: function()
5227	{
5228		this._swapPageRefs();
5229		this._transitionDirection = '';
5230		this.fire('transitionComplete');
5231	},
5232
5233	/**
5234	 * Swaps the active page and next page references when
5235	 * a transition completes and sets the active page index.
5236	 *
5237	 * @method _swapPageRefs
5238	 * @protected
5239	 */
5240	_swapPageRefs: function()
5241	{
5242		var lastBox 	= this._activePageBox,
5243			lastImages 	= this._activeImages;
5244
5245		this._activePageBox = this._nextPageBox;
5246		this._nextPageBox = lastBox;
5247		this._nextPageBox.setStyle('left', '-9999px');
5248
5249		if(this._transitionDirection == 'next') {
5250			this._activeImages = this._nextImages;
5251			this._nextImages = lastImages;
5252		}
5253		else {
5254			this._activeImages = this._prevImages;
5255			this._prevImages = lastImages;
5256		}
5257
5258		// Active page index
5259		if(this._transitionDirection == 'next' && this._activePageIndex + 1 < this._numPages) {
5260			this._activePageIndex++;
5261		}
5262		else if(this._transitionDirection == 'next') {
5263			this._activePageIndex = 0;
5264		}
5265		else if(this._transitionDirection == 'prev' && this._activePageIndex - 1 > -1) {
5266			this._activePageIndex--;
5267		}
5268		else if(this._transitionDirection == 'prev') {
5269			this._activePageIndex = this._numPages - 1;
5270		}
5271
5272		// Swap gesture refs
5273		if(this._gestures) {
5274			this._gestures.set('activeItem', this._activePageBox);
5275			this._gestures.set('nextItem', this._nextPageBox);
5276		}
5277
5278		this._preloadNextPage();
5279		this._preloadPrevPage();
5280	},
5281
5282	/**
5283	 * Renders the enabled navs.
5284	 *
5285	 * @method _renderNavs
5286	 * @protected
5287	 */
5288	_renderNavs: function()
5289	{
5290		var topNavButtons 		= this.get('topNavButtons'),
5291			rightNavButtons 	= this.get('rightNavButtons'),
5292			bottomNavButtons 	= this.get('bottomNavButtons'),
5293			leftNavButtons 		= this.get('leftNavButtons');
5294
5295		if(this.get('topNavEnabled') && topNavButtons.length > 0) {
5296			this._topNav = new Y.FL.SlideshowNav({ buttons: topNavButtons });
5297			this._topNav.get('boundingBox').addClass('fl-slideshow-thumbs-top-nav');
5298			this.add(this._topNav);
5299			this._topNav.render(this.get('contentBox'));
5300			this._clipBox.insert(this._topNav.get('boundingBox'), 'before');
5301			this._bindNavEvents(this._topNav);
5302		}
5303		if(this.get('rightNavEnabled') && rightNavButtons.length > 0) {
5304			this._rightNav = new Y.FL.SlideshowNav({ buttons: rightNavButtons });
5305			this._rightNav.get('boundingBox').addClass('fl-slideshow-thumbs-right-nav');
5306			this.add(this._rightNav);
5307			this._rightNav.render(this.get('contentBox'));
5308			this._bindNavEvents(this._rightNav);
5309		}
5310		if(this.get('bottomNavEnabled') && bottomNavButtons.length > 0) {
5311			this._bottomNav = new Y.FL.SlideshowNav({ buttons: bottomNavButtons });
5312			this._bottomNav.get('boundingBox').addClass('fl-slideshow-thumbs-bottom-nav');
5313			this.add(this._bottomNav);
5314			this._bottomNav.render(this.get('contentBox'));
5315			this._bindNavEvents(this._bottomNav);
5316		}
5317		if(this.get('leftNavEnabled') && leftNavButtons.length > 0) {
5318			this._leftNav = new Y.FL.SlideshowNav({ buttons: leftNavButtons });
5319			this._leftNav.get('boundingBox').addClass('fl-slideshow-thumbs-left-nav');
5320			this.add(this._leftNav);
5321			this._leftNav.render(this.get('contentBox'));
5322			this._bindNavEvents(this._leftNav);
5323		}
5324	},
5325
5326	/**
5327	 * Syncs the navs.
5328	 *
5329	 * @method _syncNavs
5330	 * @protected
5331	 */
5332	_syncNavs: function()
5333	{
5334		var rightNavBB, bottomNavBB, leftNavBB;
5335
5336		if(this._rightNav) {
5337			rightNavBB = this._rightNav.get('boundingBox');
5338			rightNavBB.setStyle('position', 'absolute');
5339			rightNavBB.setStyle('top', '0px');
5340			rightNavBB.setStyle('right', '0px');
5341		}
5342		if(this._bottomNav) {
5343			bottomNavBB = this._bottomNav.get('boundingBox');
5344			bottomNavBB.setStyle('position', 'absolute');
5345			bottomNavBB.setStyle('bottom', '0px');
5346			bottomNavBB.setStyle('width', '100%');
5347		}
5348		if(this._leftNav) {
5349			leftNavBB = this._leftNav.get('boundingBox');
5350			leftNavBB.setStyle('position', 'absolute');
5351			leftNavBB.setStyle('top', '0px');
5352			leftNavBB.setStyle('left', '0px');
5353		}
5354	},
5355
5356	/**
5357	 * Resizes the navs.
5358	 *
5359	 * @method _resizeNavs
5360	 * @protected
5361	 */
5362	_resizeNavs: function()
5363	{
5364		var rightNavBB,
5365			leftNavBB,
5366			marginTop;
5367
5368		if(this._rightNav) {
5369			rightNavBB = this._rightNav.get('boundingBox');
5370			marginTop = this._bbHeight/2 - parseInt(rightNavBB.getComputedStyle('height'), 10)/2;
5371			rightNavBB.setStyle('marginTop', marginTop + 'px');
5372		}
5373		if(this._leftNav) {
5374			leftNavBB = this._leftNav.get('boundingBox');
5375			marginTop = this._bbHeight/2 - parseInt(leftNavBB.getComputedStyle('height'), 10)/2;
5376			leftNavBB.setStyle('marginTop', marginTop + 'px');
5377		}
5378	},
5379
5380	/**
5381	 * Binds events to the provided nav.
5382	 *
5383	 * @method _bindNavEvents
5384	 * @param nav {Object} The nav to bind to.
5385	 * @protected
5386	 */
5387	_bindNavEvents: function(nav)
5388	{
5389		if(nav._buttons.prevPage) {
5390			nav._buttons.prevPage.on('click', this.prevPage, this);
5391		}
5392		if(nav._buttons.nextPage) {
5393			nav._buttons.nextPage.on('click', this.nextPage, this);
5394		}
5395
5396		nav.on('resize', this.resize, this);
5397	},
5398
5399	/**
5400	 * Hides the prev page and next page buttons
5401	 * if there is only one page of thumbs.
5402	 *
5403	 * @method _togglePageButtons
5404	 * @protected
5405	 */
5406	_togglePageButtons: function()
5407	{
5408		var buttons = this.get('boundingBox').all('.fl-slideshow-nav-prevPage, .fl-slideshow-nav-nextPage'),
5409			display = buttons.getStyle('display')[0];
5410
5411		if(this._numPages == 1 && display == 'inline-block') {
5412			buttons.setStyle('display', 'none');
5413			this._setSizeInfo();
5414		}
5415		else if(this._numPages > 1 && display == 'none') {
5416			buttons.setStyle('display', 'inline-block');
5417			this._setSizeInfo();
5418		}
5419	},
5420
5421	/**
5422	 * Sets the size info used when resizing and loading pages.
5423	 *
5424	 * @method _setSizeInfo
5425	 * @protected
5426	 */
5427	_setSizeInfo: function()
5428	{
5429		var root					= this.get('root'),
5430			bb						= this.get('boundingBox'),
5431			bbPosition              = bb.getStyle('position'),
5432			bbLeftMargin			= parseInt(bb.getStyle('marginLeft'), 10),
5433			bbRightMargin			= parseInt(bb.getStyle('marginRight'), 10),
5434			bbTopMargin				= parseInt(bb.getStyle('marginTop'), 10),
5435			bbBottomMargin			= parseInt(bb.getStyle('marginBottom'), 10),
5436			bbLeftPadding			= parseInt(bb.getStyle('paddingLeft'), 10),
5437			bbRightPadding			= parseInt(bb.getStyle('paddingRight'), 10),
5438			bbTopPadding			= parseInt(bb.getStyle('paddingTop'), 10),
5439			bbBottomPadding			= parseInt(bb.getStyle('paddingBottom'), 10),
5440			parent 					= bb.get('parentNode'),
5441			parentWidth 			= parseInt(parent.getComputedStyle('width'), 10),
5442			parentHeight 			= parseInt(parent.getComputedStyle('height'), 10),
5443			bbWidth 				= parentWidth - bbLeftPadding - bbRightPadding - bbLeftMargin - bbRightMargin,
5444			bbHeight 				= parentHeight - bbTopPadding - bbBottomPadding - bbTopMargin - bbBottomMargin,
5445			cbWidth					= bbWidth,
5446			pageWidth 				= bbWidth,
5447			pageHeight 				= bbHeight,
5448			columns 				= this.get('columns'),
5449			rows 					= this.get('rows'),
5450			imageConfig				= this.get('imageConfig'),
5451			horizontalSpacing		= this.get('horizontalSpacing'),
5452			verticalSpacing			= this.get('verticalSpacing'),
5453			spaceEvenly				= this.get('spaceEvenly'),
5454			centerSinglePage		= this.get('centerSinglePage'),
5455			leftNavWidth			= 0,
5456			rightNavWidth			= 0,
5457			topNavHeight			= 0,
5458			bottomNavHeight			= 0,
5459			colsPerPage 			= columns,
5460			rowsPerPage				= rows,
5461			imagesPerPage			= 0,
5462			numPages				= 1,
5463			clipBoxMarginLeft		= 0,
5464			clipBoxTop				= 0,
5465			availHorizSpace			= 0,
5466			availVerticalSpace		= 0;
5467
5468        // Position absolute causes some resizing bugs.
5469        bb.setStyle('position', 'relative');
5470
5471		// Bounding box width
5472		if(!isNaN(columns)) {
5473			bbWidth = pageWidth = columns * (imageConfig.width + horizontalSpacing) + horizontalSpacing;
5474		}
5475
5476		// Bounding box height
5477		if(!isNaN(rows)) {
5478			bbHeight = pageHeight = rows * (imageConfig.height + verticalSpacing) + verticalSpacing;
5479		}
5480
5481		// Compensate for the navs
5482		if(this._leftNav) {
5483
5484			leftNavWidth = parseInt(this._leftNav.get('boundingBox').getComputedStyle('width'), 10);
5485
5486			if(isNaN(columns)) {
5487				pageWidth -= leftNavWidth;
5488			}
5489			else {
5490				bbWidth += leftNavWidth;
5491			}
5492		}
5493		if(this._rightNav) {
5494
5495			rightNavWidth = parseInt(this._rightNav.get('boundingBox').getComputedStyle('width'), 10);
5496
5497			if(isNaN(columns)) {
5498				pageWidth -= rightNavWidth;
5499			}
5500			else {
5501				bbWidth += rightNavWidth;
5502			}
5503		}
5504		if(this._topNav) {
5505
5506			topNavHeight = parseInt(this._topNav.get('boundingBox').getComputedStyle('height'), 10);
5507
5508			if(isNaN(rows)) {
5509				pageHeight -= topNavHeight;
5510			}
5511			else {
5512				bbHeight += topNavHeight;
5513			}
5514		}
5515		if(this._bottomNav) {
5516
5517			bottomNavHeight = parseInt(this._bottomNav.get('boundingBox').getComputedStyle('height'), 10);
5518
5519			if(isNaN(rows)) {
5520				pageHeight -= bottomNavHeight;
5521			}
5522			else {
5523				bbHeight += bottomNavHeight;
5524			}
5525		}
5526
5527		// Columns per page
5528		if(isNaN(columns)) {
5529			colsPerPage = Math.floor(pageWidth/(imageConfig.width + horizontalSpacing));
5530			colsPerPage = colsPerPage < 1 ? 1 : colsPerPage;
5531		}
5532
5533		// Rows per page
5534		if(isNaN(rows)) {
5535			rowsPerPage = Math.floor(pageHeight/(imageConfig.height + verticalSpacing));
5536			rowsPerPage = rowsPerPage < 1 ? 1 : rowsPerPage;
5537		}
5538
5539		// Images per page
5540		imagesPerPage = colsPerPage * rowsPerPage;
5541
5542		// Number of pages
5543		if(root.albumInfo) {
5544			numPages = Math.ceil(root.albumInfo.images.length/imagesPerPage);
5545		}
5546
5547		// Horizontal spacing
5548		if(isNaN(columns) && spaceEvenly) {
5549			horizontalSpacing = Math.floor((pageWidth - (imageConfig.width * colsPerPage))/(colsPerPage + 1));
5550		}
5551
5552		// Vertical spacing
5553		if(isNaN(rows) && spaceEvenly) {
5554			verticalSpacing = Math.floor((pageHeight - (imageConfig.height * rowsPerPage))/(rowsPerPage + 1));
5555		}
5556
5557		// Content container width
5558		if(root.albumInfo && centerSinglePage && numPages == 1 && rowsPerPage == 1) {
5559
5560			cbWidth = root.albumInfo.images.length * imageConfig.width;
5561			cbWidth += horizontalSpacing * (root.albumInfo.images.length + 1);
5562
5563			if(this._leftNav) {
5564				cbWidth += leftNavWidth;
5565			}
5566			if(this._rightNav) {
5567				cbWidth += rightNavWidth;
5568			}
5569		}
5570		else {
5571			cbWidth = bbWidth;
5572		}
5573
5574		// Final page width and height
5575		if(root.albumInfo && centerSinglePage && numPages == 1 && rowsPerPage == 1) {
5576			pageWidth = root.albumInfo.images.length * imageConfig.width;
5577			pageWidth += horizontalSpacing * root.albumInfo.images.length;
5578		}
5579		else {
5580			pageWidth = colsPerPage * (imageConfig.width + horizontalSpacing);
5581		}
5582
5583		pageHeight = rowsPerPage * (imageConfig.height + verticalSpacing);
5584
5585		// Clip box margin left
5586		if(numPages < 2) {
5587			clipBoxMarginLeft = leftNavWidth;
5588		}
5589		else {
5590			availHorizSpace = bbWidth;
5591
5592			if(this._rightNav) {
5593				availHorizSpace -= rightNavWidth;
5594			}
5595			if(this._leftNav) {
5596				availHorizSpace -= leftNavWidth;
5597				clipBoxMarginLeft = leftNavWidth + (availHorizSpace - pageWidth - horizontalSpacing)/2;
5598			}
5599			else {
5600				clipBoxMarginLeft = (availHorizSpace - pageWidth - horizontalSpacing)/2;
5601			}
5602		}
5603
5604		// Clip box margin top
5605		if(numPages > 1 && !spaceEvenly) {
5606
5607			availVerticalSpace = bbHeight;
5608
5609			if(this._topNav) {
5610				availVerticalSpace -= topNavHeight;
5611			}
5612			if(this._bottomNav) {
5613				availVerticalSpace -= bottomNavHeight;
5614			}
5615
5616			clipBoxTop = (availVerticalSpace - (verticalSpacing + pageHeight))/2;
5617		}
5618
5619		// Set the info
5620		this._bbHeight = bbHeight;
5621		this._bbWidth = bbWidth;
5622		this._cbWidth = cbWidth;
5623		this._clipBoxMarginLeft = clipBoxMarginLeft;
5624		this._clipBoxTop = clipBoxTop;
5625		this._colsPerPage = colsPerPage;
5626		this._rowsPerPage = rowsPerPage;
5627		this._imagesPerPage = imagesPerPage;
5628		this._numPages = numPages;
5629		this._pageHeight = pageHeight;
5630		this._pageWidth = pageWidth;
5631		this._leftNavWidth = leftNavWidth;
5632		this._rightNavWidth = rightNavWidth;
5633		this._horizontalSpacing = horizontalSpacing;
5634		this._verticalSpacing = verticalSpacing;
5635		this._activePageIndex = Math.floor(root.imageIndex/this._imagesPerPage);
5636
5637		// Set back to the initial position.
5638        bb.setStyle('position', bbPosition);
5639	}
5640
5641}, {
5642
5643	/**
5644	 * Custom CSS class name for the widget.
5645	 *
5646	 * @property CSS_PREFIX
5647	 * @type String
5648	 * @protected
5649	 * @static
5650	 */
5651	CSS_PREFIX: 'fl-slideshow-thumbs',
5652
5653	/**
5654	* Static property used to define the default attribute configuration of
5655	* the Widget.
5656	*
5657	* @property ATTRS
5658	* @type Object
5659	* @protected
5660	* @static
5661	*/
5662	ATTRS: {
5663
5664		/**
5665		 * The number of thumbnail columns. If set to auto, the number of
5666		 * columns will be calculated based on the width of the parent node.
5667		 *
5668		 * @attribute columns
5669		 * @type String or Number
5670		 * @default auto
5671		 */
5672		columns: {
5673			value: 'auto'
5674		},
5675
5676		/**
5677		 * The number of thumbnail rows. If set to auto, the number of
5678		 * rows will be calculated based on the height of the parent node.
5679		 *
5680		 * @attribute rows
5681		 * @type String or Number
5682		 * @default auto
5683		 */
5684		rows: {
5685			value: 'auto'
5686		},
5687
5688		/**
5689		 * The horizontal spacing between thumbs.
5690		 *
5691		 * @attribute horizontalSpacing
5692		 * @type Number
5693		 * @default 15
5694		 */
5695		horizontalSpacing: {
5696			value: 15
5697		},
5698
5699		/**
5700		 * The vertical spacing between thumbs.
5701		 *
5702		 * @attribute verticalSpacing
5703		 * @type Number
5704		 * @default 15
5705		 */
5706		verticalSpacing: {
5707			value: 15
5708		},
5709
5710		/**
5711		 * Whether to space the thumbs evenly within a page.
5712		 *
5713		 * @attribute spaceEvenly
5714		 * @type Boolean
5715		 * @default true
5716		 */
5717		spaceEvenly: {
5718			value: true
5719		},
5720
5721		/**
5722		 * Whether to center single pages of thumbs.
5723		 *
5724		 * @attribute centerSinglePage
5725		 * @type Boolean
5726		 * @default false
5727		 */
5728		centerSinglePage: {
5729			value: true
5730		},
5731
5732		/**
5733		 * Whether to pause the parent slideshow when a thumb is clicked.
5734		 *
5735		 * @attribute pauseOnClick
5736		 * @type Boolean
5737		 * @default false
5738		 */
5739		pauseOnClick: {
5740			value: false
5741		},
5742
5743		/**
5744		 * The type of transition to use between pages.
5745		 *
5746		 * @attribute transition
5747		 * @type String
5748		 * @default slideHorizontal
5749		 */
5750		transition: {
5751			value: 'slideHorizontal'
5752		},
5753
5754		/**
5755		 * The duration of the transition between pages.
5756		 *
5757		 * @attribute transitionDuration
5758		 * @type Number
5759		 * @default 0.8
5760		 */
5761		transitionDuration: {
5762			value: 0.8
5763		},
5764
5765		/**
5766		 * The type of transition easing to use between pages.
5767		 *
5768		 * @attribute transitionEasing
5769		 * @type String
5770		 * @default ease-out
5771		 */
5772		transitionEasing: {
5773			value: 'ease-out'
5774		},
5775
5776		/**
5777		 * The configuration object used to create new instances of
5778		 * FL.SlideshowImage. See the API docs for {@link FL.SlideshowImage}
5779		 * for a complete list of configuration attributes.
5780		 *
5781		 * @attribute imageConfig
5782		 * @type Object
5783		 * @default {}
5784		 */
5785		imageConfig: {
5786			value: {
5787				crop: true,
5788				width: 50,
5789				height: 50
5790			}
5791		},
5792
5793		/**
5794		 * Whether to use the top nav or not.
5795		 *
5796		 * @attribute topNavEnabled
5797		 * @type Boolean
5798		 * @default false
5799		 */
5800		topNavEnabled: {
5801			value: false
5802		},
5803
5804		/**
5805		 * An array of button names used to render the top nav buttons.
5806		 *
5807		 * @attribute topNavButtons
5808		 * @type Array
5809		 * @default prevPage, nextPage
5810		 */
5811		topNavButtons: {
5812			value: ['prevPage', 'nextPage']
5813		},
5814
5815		/**
5816		 * Whether to use the right nav or not.
5817		 *
5818		 * @attribute rightNavEnabled
5819		 * @type Boolean
5820		 * @default true
5821		 */
5822		rightNavEnabled: {
5823			value: true
5824		},
5825
5826		/**
5827		 * An array of button names used to render the right nav buttons.
5828		 *
5829		 * @attribute rightNavButtons
5830		 * @type Array
5831		 * @default nextPage
5832		 */
5833		rightNavButtons: {
5834			value: ['nextPage']
5835		},
5836
5837		/**
5838		 * Whether to use the bottom nav or not.
5839		 *
5840		 * @attribute bottomNavEnabled
5841		 * @type Boolean
5842		 * @default false
5843		 */
5844		bottomNavEnabled: {
5845			value: false
5846		},
5847
5848		/**
5849		 * An array of button names used to render the bottom nav buttons.
5850		 *
5851		 * @attribute bottomNavButtons
5852		 * @type Array
5853		 * @default prevPage, nextPage
5854		 */
5855		bottomNavButtons:{
5856			value: ['prevPage', 'nextPage']
5857		},
5858
5859		/**
5860		 * Whether to use the left nav or not.
5861		 *
5862		 * @attribute leftNavEnabled
5863		 * @type Boolean
5864		 * @default true
5865		 */
5866		leftNavEnabled: {
5867			value: true
5868		},
5869
5870		/**
5871		 * An array of button names used to render the left nav buttons.
5872		 *
5873		 * @attribute leftNavButtons
5874		 * @type Array
5875		 * @default prevPage
5876		 */
5877		leftNavButtons:{
5878			value: ['prevPage']
5879		},
5880
5881		/**
5882		 * Whether to use touch gestures, when available,
5883		 * to transition between pages or not.
5884		 *
5885		 * @attribute touchSupport
5886		 * @type Boolean
5887		 * @default false
5888		 */
5889		touchSupport: {
5890			value: false
5891		}
5892	}
5893});
5894
5895/**
5896 * Provides functionality for transitions between slideshow components.
5897 *
5898 * @namespace FL
5899 * @class SlideshowTransition
5900 * @constructor
5901 * @param config {Object} Configuration object
5902 * @extends Base
5903 */
5904Y.namespace('FL').SlideshowTransition = Y.Base.create('fl-slideshow-transition', Y.Base, [], {
5905
5906	/**
5907	 * The transition function to use when run is called.
5908	 *
5909	 * @property _transitionFunction
5910	 * @type String
5911	 * @default _transitionFade
5912	 * @protected
5913	 */
5914	_transitionFunction: '_transitionFade',
5915
5916	/**
5917	 * The current transition type.
5918	 *
5919	 * @property _type
5920	 * @type String
5921	 * @default fade
5922	 * @protected
5923	 */
5924	_type: 'fade',
5925
5926	/**
5927	 * Parses the transition type and sets the _transitionFunction
5928	 * used when run is called.
5929	 *
5930	 * @method initializer
5931	 * @protected
5932	 */
5933	initializer: function()
5934	{
5935		var type 		               = this.get('type'),
5936			typeArray	               = [],
5937			types                      = Y.FL.SlideshowTransition.TYPES,
5938			slideshowImageTypes        = Y.FL.SlideshowTransition.SLIDESHOW_IMAGE_TYPES,
5939			isSlideshowImageTransition = Y.Array.indexOf(slideshowImageTypes, type) > -1,
5940			isSlideshowImage           = this._isSlideshowImage(),
5941			itemIn 				       = this.get('itemIn'),
5942			itemOut 			       = this.get('itemOut');
5943
5944        // Check for random transitions.
5945		if(type.indexOf(',') > -1) {
5946			typeArray = type.split(',');
5947			typeArray.sort(function() { return 0.5 - Math.random(); });
5948			type = typeArray[0];
5949		}
5950
5951		// Make sure we can run this transition, otherwise set a fallback.
5952		if(!isSlideshowImage && isSlideshowImageTransition) {
5953            type = 'fade';
5954		}
5955		else if(isSlideshowImage) {
5956            if((itemIn && itemIn.one('img') === null) || (itemOut && itemOut.one('img') === null)) {
5957                type = 'none';
5958            }
5959            else if(isSlideshowImageTransition) {
5960                if((Y.UA.gecko && Y.UA.gecko < 5) || Y.UA.opera > 0 || (Y.UA.ie > 0 && Y.UA.ie < 9)) {
5961                    type = 'fade';
5962                }
5963            }
5964        }
5965
5966		// Set the transition function and type.
5967		if(Y.FL.SlideshowTransition.TYPES[type]) {
5968			this._transitionFunction = types[type];
5969			this._type = type;
5970		}
5971
5972		// Setup the items.
5973		this._setupItems();
5974	},
5975
5976	/**
5977	 * Fires the start event and calls the transition function.
5978	 *
5979	 * @method run
5980	 */
5981	run: function()
5982	{
5983		/**
5984		 * Fires when the transition starts.
5985		 *
5986		 * @event start
5987		 */
5988		this.fire('start');
5989
5990		this[this._transitionFunction].call(this);
5991	},
5992
5993	/**
5994	 * Set initial styles for the items.
5995	 *
5996	 * @method _setupItems
5997	 * @protected
5998	 */
5999	_setupItems: function()
6000	{
6001        var itemIn  = this.get('itemIn'),
6002			itemOut = this.get('itemOut');
6003
6004		if(itemIn) {
6005			itemIn.setStyle('zIndex', 2);
6006			itemIn.setStyle('opacity', 1);
6007
6008			if(Y.FL.Utils.cssSupport('transform')) {
6009    			itemIn.setStyle('transform', 'translate(0, 0)');
6010    		}
6011    		else {
6012    			itemIn.setStyle('top', '0');
6013    			itemIn.setStyle('left', '0');
6014    		}
6015		}
6016		if(itemOut) {
6017			itemOut.setStyle('zIndex', 1);
6018		}
6019	},
6020
6021	/**
6022	 * Checks if the transition is being run
6023	 * on an instance of FL.SlideshowImage or not.
6024	 *
6025	 * @method _isSlideshowImage
6026	 * @protected
6027	 */
6028	_isSlideshowImage: function()
6029	{
6030        var itemIn 	= this.get('itemIn'),
6031			itemOut = this.get('itemOut');
6032
6033        if(itemIn && itemIn.hasClass('fl-slideshow-image')) {
6034			return true;
6035		}
6036		else if(itemOut && itemOut.hasClass('fl-slideshow-image')) {
6037			return true;
6038		}
6039
6040		return false;
6041	},
6042
6043	/**
6044	 * Starts the transtion using the provided property objects.
6045	 *
6046	 * @method _transitionStart
6047	 * @param propsIn {Object} The properties to animate in.
6048	 * @param propsOut {Object} The properties to animate out.
6049	 * @protected
6050	 */
6051	_transitionStart: function(propsIn, propsOut)
6052	{
6053		var itemIn 				= this.get('itemIn'),
6054			itemOut 			= this.get('itemOut'),
6055			itemInCallback		= Y.bind(this._transitionComplete, this),
6056			itemOutCallback 	= !itemIn ? itemInCallback : null,
6057			duration 			= this.get('duration'),
6058			easing 				= this.get('easing');
6059
6060		if(itemIn) {
6061			propsIn.duration = propsIn.duration || duration;
6062			propsIn.easing = propsIn.easing || easing;
6063			itemIn.transition(propsIn);
6064		}
6065		if(itemOut) {
6066			propsOut.duration = propsOut.duration || duration;
6067			propsOut.easing = propsOut.easing || easing;
6068			itemOut.transition(propsOut);
6069		}
6070
6071		if(itemInCallback) {
6072			Y.later(propsIn.duration * 1000 + 100, null, itemInCallback);
6073		}
6074		else if(itemOutCallback) {
6075			Y.later(propsOut.duration * 1000 + 100, null, itemOutCallback);
6076		}
6077	},
6078
6079	/**
6080	 * Clean up method called when the transition completes.
6081	 *
6082	 * @method _transitionComplete
6083	 * @protected
6084	 */
6085	_transitionComplete: function()
6086	{
6087		this._set('itemIn', null);
6088		this._set('itemOut', null);
6089
6090		/**
6091		 * Fires when the transition completes.
6092		 *
6093		 * @event complete
6094		 */
6095		this.fire('complete');
6096	},
6097
6098	/**
6099	 * No transition.
6100	 *
6101	 * @method _transitionNone
6102	 * @protected
6103	 */
6104	_transitionNone: function()
6105	{
6106		var itemIn  = this.get('itemIn'),
6107			itemOut = this.get('itemOut');
6108
6109		if(itemIn) {
6110			itemIn.setStyle('opacity', 1);
6111		}
6112		if(itemOut) {
6113			itemOut.setStyle('opacity', 0);
6114		}
6115
6116		this._transitionComplete();
6117	},
6118
6119	/**
6120	 * Fade transition.
6121	 *
6122	 * @method _transitionFade
6123	 * @protected
6124	 */
6125	_transitionFade: function()
6126	{
6127		var itemIn = this.get('itemIn');
6128
6129		if(itemIn) {
6130			itemIn.setStyle('opacity', 0);
6131		}
6132
6133		this._transitionStart({ opacity: 1 },{ opacity: 0 });
6134	},
6135
6136	/**
6137	 * Slide left transition.
6138	 *
6139	 * @method _transitionSlideLeft
6140	 * @protected
6141	 */
6142	_transitionSlideLeft: function()
6143	{
6144		if(Y.FL.Utils.cssSupport('transform')) {
6145			this._cssTransitionSlide({
6146				inStart: 'translate(100%, 0)',
6147				inEnd: 'translate(0, 0)',
6148				outStart: 'translate(0, 0)',
6149				outEnd: 'translate(-100%, 0)'
6150			});
6151		}
6152		else {
6153			this._jsTransitionSlide('left');
6154		}
6155	},
6156
6157	/**
6158	 * Slide right transition.
6159	 *
6160	 * @method _transitionSlideRight
6161	 * @protected
6162	 */
6163	_transitionSlideRight: function()
6164	{
6165		if(Y.FL.Utils.cssSupport('transform')) {
6166			this._cssTransitionSlide({
6167				inStart: 'translate(-100%, 0)',
6168				inEnd: 'translate(0, 0)',
6169				outStart: 'translate(0, 0)',
6170				outEnd: 'translate(100%, 0)'
6171			});
6172		}
6173		else {
6174			this._jsTransitionSlide('right');
6175		}
6176	},
6177
6178	/**
6179	 * Slide up transition.
6180	 *
6181	 * @method _transitionSlideUp
6182	 * @protected
6183	 */
6184	_transitionSlideUp: function()
6185	{
6186		if(Y.FL.Utils.cssSupport('transform')) {
6187			this._cssTransitionSlide({
6188				inStart: 'translate(0, 100%)',
6189				inEnd: 'translate(0, 0)',
6190				outStart: 'translate(0, 0)',
6191				outEnd: 'translate(0, -100%)'
6192			});
6193		}
6194		else {
6195			this._jsTransitionSlide('up');
6196		}
6197	},
6198
6199	/**
6200	 * Slide down transition.
6201	 *
6202	 * @method _transitionSlideDown
6203	 * @protected
6204	 */
6205	_transitionSlideDown: function()
6206	{
6207		if(Y.FL.Utils.cssSupport('transform')) {
6208			this._cssTransitionSlide({
6209				inStart: 'translate(0, -100%)',
6210				inEnd: 'translate(0, 0)',
6211				outStart: 'translate(0, 0)',
6212				outEnd: 'translate(0, 100%)'
6213			});
6214		}
6215		else {
6216			this._jsTransitionSlide('down');
6217		}
6218	},
6219
6220	/**
6221	 * JavaScript slide transition.
6222	 *
6223	 * @method _jsTransitionSlide
6224	 * @protected
6225	 */
6226	_jsTransitionSlide: function(direction)
6227	{
6228		var itemIn 		= this.get('itemIn'),
6229			itemOut 	= this.get('itemOut'),
6230			itemOutEnd	= 0;
6231
6232		// Item Out
6233		if(itemOut && direction == 'left') {
6234			itemOutEnd = -parseInt(itemOut.getStyle('width'), 10);
6235		}
6236		if(itemOut && direction == 'right') {
6237			itemOutEnd = parseInt(itemOut.getStyle('width'), 10);
6238		}
6239		if(itemOut && direction == 'up') {
6240			itemOutEnd = -parseInt(itemOut.getStyle('height'), 10);
6241		}
6242		if(itemOut && direction == 'down') {
6243			itemOutEnd = parseInt(itemOut.getStyle('height'), 10);
6244		}
6245
6246		// Item In
6247		if(itemIn) {
6248			itemIn.setStyle('opacity', 1);
6249		}
6250		if(itemIn && direction == 'left') {
6251			itemIn.setStyle('left', itemIn.getStyle('width'));
6252		}
6253		if(itemIn && direction == 'right') {
6254			itemIn.setStyle('left', '-' + itemIn.getStyle('width'));
6255		}
6256		if(itemIn && direction == 'up') {
6257			itemIn.setStyle('top', itemIn.getStyle('height'));
6258		}
6259		if(itemIn && direction == 'down') {
6260			itemIn.setStyle('top', '-' + itemIn.getStyle('height'));
6261		}
6262
6263		// Transition Start
6264		if(direction == 'left' || direction == 'right') {
6265			this._transitionStart({ left: 0 },{ left: itemOutEnd });
6266		}
6267		else {
6268			this._transitionStart({ top: 0 },{ top: itemOutEnd });
6269		}
6270	},
6271
6272	/**
6273	 * CSS slide transition.
6274	 *
6275	 * @method _cssTransitionSlide
6276	 * @protected
6277	 */
6278	_cssTransitionSlide: function(props)
6279	{
6280		var itemIn 	        = this.get('itemIn'),
6281			itemOut         = this.get('itemOut'),
6282			transformProp   = Y.UA.chrome < 36 ? 'transform' : '-webkit-transform',
6283			inProps         = {},
6284			outProps        = {};
6285
6286        inProps[transformProp] = props.inEnd;
6287        outProps[transformProp] = props.outEnd;
6288
6289		if(itemIn) {
6290			itemIn.setStyle('transition', '');
6291			itemIn.setStyle('opacity', 1);
6292			itemIn.setStyle(transformProp, props.inStart);
6293		}
6294		if(itemOut) {
6295			itemOut.setStyle('transition', '');
6296			itemOut.setStyle(transformProp, props.outStart);
6297		}
6298
6299		this._transitionStart(inProps, outProps);
6300	},
6301
6302	/**
6303	 * Bars and blinds transition.
6304	 *
6305	 * @method _transitionBars
6306	 * @protected
6307	 */
6308	_transitionBars: function()
6309	{
6310        // Hide the image until the slices have transitioned in.
6311        this.get('itemIn').one('.fl-slideshow-image-img').setStyle('opacity', 0);
6312
6313        var numBars   = this.get('bars'),
6314            slices    = this._renderSlices(1, numBars),
6315            duration  = this.get('duration'),
6316            delay     = 0,
6317            increment = 100,
6318            last      = false,
6319            i         = 0,
6320            clone     = null,
6321            props     = {
6322                duration: duration,
6323                opacity: 1
6324            };
6325
6326        // barsRandom
6327        if(this._type == 'barsRandom') {
6328            slices = this._randomizeSlices(slices);
6329        }
6330
6331        // Transition the slices.
6332        for( ; i < slices.length; i++) {
6333
6334            // Make a clone of our transition properties.
6335            clone = Y.clone(props);
6336
6337            // blinds
6338            if(this._type == 'blinds') {
6339                clone.width = parseFloat(slices[i].getComputedStyle('width'), 10) + 'px';
6340                slices[i].setStyle('width', '0px');
6341                increment = 50;
6342            }
6343
6344            // Run the transition.
6345            last = i == slices.length - 1 ? true : false;
6346            Y.later(delay, this, this._transitionSlice, [slices[i], clone, last]);
6347            delay += increment;
6348        }
6349
6350        this._transitionSlicesFadeLast(delay);
6351	},
6352
6353	/**
6354	 * Boxes transition.
6355	 *
6356	 * @method _transitionBoxes
6357	 * @protected
6358	 */
6359	_transitionBoxes: function()
6360	{
6361        // Hide the image until the slices have transitioned in.
6362        this.get('itemIn').one('.fl-slideshow-image-img').setStyle('opacity', 0);
6363
6364        var numCols     = this.get('boxCols'),
6365            numRows     = this.get('boxRows'),
6366            numSlices   = numCols * numRows,
6367            multi       = this._type != 'boxesRandom',
6368            slices      = this._renderSlices(numRows, numCols, multi),
6369            duration    = this.get('duration'),
6370            delay       = 0,
6371            increment   = 150,
6372            last      = false,
6373            i           = 0,
6374            row         = 0,
6375            col         = 0,
6376            startCol    = -1,
6377            clone       = null,
6378            props       = {
6379                duration: duration,
6380                opacity: 1
6381            };
6382
6383        // boxesRandom
6384        if(!multi) {
6385
6386            slices = this._randomizeSlices(slices);
6387            increment = 30;
6388
6389            for( ; i < slices.length; i++) {
6390                clone = Y.clone(props);
6391                last = i == slices.length - 1 ? true : false;
6392                Y.later(delay, this, this._transitionSlice, [slices[i], clone, last]);
6393                delay += increment;
6394            }
6395        }
6396        // boxes
6397        else {
6398            while(i < numSlices) {
6399                for(row = 0; row < numRows; row++) {
6400                    if(row === 0) {
6401                        startCol++;
6402                        col = startCol;
6403                    }
6404                    if(col > -1 && col < numCols) {
6405                        i++;
6406                        clone = Y.clone(props);
6407
6408                        // boxesGrow
6409                        if(this._type == 'boxesGrow') {
6410                            clone.height = parseFloat(slices[row][col].getComputedStyle('height'), 10) + 'px';
6411                            clone.width = parseFloat(slices[row][col].getComputedStyle('width'), 10) + 'px';
6412                            slices[row][col].setStyle('height', '0px');
6413                            slices[row][col].setStyle('width', '0px');
6414                            increment = 50;
6415                        }
6416
6417                        last = i == numSlices - 1 ? true : false;
6418                        Y.later(delay, this, this._transitionSlice, [slices[row][col], clone, last]);
6419                    }
6420                    col--;
6421                }
6422                delay += increment;
6423            }
6424        }
6425
6426        this._transitionSlicesFadeLast(delay);
6427	},
6428
6429	/**
6430	 * Renders the divs for slice based transitions.
6431	 *
6432	 * @method _renderSlices
6433	 * @protected
6434	 */
6435	_renderSlices: function(numRows, numCols, multidimensional)
6436	{
6437        var itemIn      = this.get('itemIn'),
6438            itemHeight  = parseFloat(itemIn.getComputedStyle('height'), 10),
6439            itemWidth   = parseFloat(itemIn.getComputedStyle('width'), 10),
6440            img         = itemIn.one('img'),
6441            imgSrc      = img.get('src'),
6442            imgHeight   = parseFloat(img.getComputedStyle('height'), 10),
6443            imgWidth    = parseFloat(img.getComputedStyle('width'), 10),
6444            imgLeft     = parseFloat(img.getComputedStyle('left'), 10),
6445            imgTop      = parseFloat(img.getComputedStyle('top'), 10),
6446            col         = 0,
6447            row         = 0,
6448            sliceHeight = Math.round(itemHeight/numRows),
6449            sliceWidth  = Math.round(itemWidth/numCols),
6450            slice       = null,
6451            sliceImg    = null,
6452            slices      = [];
6453
6454        for( ; row < numRows; row++) {
6455
6456            if(typeof multidimensional !== 'undefined' && multidimensional) {
6457                slices[row] = [];
6458            }
6459            for(col = 0; col < numCols; col++) {
6460
6461                slice = Y.Node.create('<div class="fl-slideshow-transition-slice"></div>');
6462                sliceImg = Y.Node.create('<img src="'+ imgSrc +'" />');
6463
6464                slice.setStyles({
6465                    left: (sliceWidth * col) + 'px',
6466                    top: (sliceHeight * row) + 'px',
6467                    width: col == numCols - 1 ? (itemWidth - (sliceWidth * col)) + 'px' : sliceWidth + 'px',
6468                    height: row == numRows - 1 ? (itemHeight - (sliceHeight * row)) + 'px' : sliceHeight + 'px',
6469                    opacity: 0
6470                });
6471
6472                sliceImg.setStyles({
6473                    height: imgHeight + 'px',
6474                    width: imgWidth + 'px',
6475                    top: imgTop - ((sliceHeight + (row * sliceHeight)) - sliceHeight) + 'px',
6476                    left: imgLeft - ((sliceWidth + (col * sliceWidth)) - sliceWidth) + 'px'
6477                });
6478
6479                slice.append(sliceImg);
6480                itemIn.append(slice);
6481
6482                if(typeof multidimensional !== 'undefined' && multidimensional) {
6483                    slices[row].push(slice);
6484                }
6485                else {
6486                    slices.push(slice);
6487                }
6488            }
6489        }
6490
6491        return slices;
6492	},
6493
6494	/**
6495	 * Fade the itemOut node.
6496	 *
6497	 * @method _transitionSlicesFadeLast
6498	 * @protected
6499	 */
6500	_transitionSlicesFadeLast: function(delay)
6501	{
6502        var itemOut = this.get('itemOut');
6503
6504        if(itemOut && !itemOut.hasClass('fl-slideshow-image-cropped')) {
6505			itemOut.transition({
6506                duration: delay/1000 + this.get('duration'),
6507                opacity: 0
6508			});
6509		}
6510	},
6511
6512	/**
6513	 * Transitions a single slice.
6514	 *
6515	 * @method _transitionSlice
6516	 * @protected
6517	 */
6518	_transitionSlice: function(slice, props, last)
6519	{
6520        var callback = last ? Y.bind(this._transitionSlicesComplete, this) : null;
6521
6522        slice.transition(props, callback);
6523	},
6524
6525	/**
6526	 * Complete callback for slice based transitions.
6527	 *
6528	 * @method _transitionSlicesComplete
6529	 * @protected
6530	 */
6531	_transitionSlicesComplete: function()
6532	{
6533        var itemIn = this.get('itemIn');
6534
6535        itemIn.all('.fl-slideshow-transition-slice').remove();
6536        itemIn.one('.fl-slideshow-image-img').setStyle('opacity', 1);
6537        this._transitionComplete();
6538	},
6539
6540	/**
6541	 * Randomizes a slices array.
6542	 *
6543	 * @method _radomizeSlices
6544	 * @protected
6545	 */
6546	_randomizeSlices: function(slices)
6547	{
6548        var i = slices.length, j, temp;
6549
6550        if(i === 0) {
6551            return;
6552        }
6553        while(--i) {
6554            j = Math.floor( Math.random() * ( i + 1 ) );
6555            temp = slices[i];
6556            slices[i] = slices[j];
6557            slices[j] = temp;
6558        }
6559
6560        return slices;
6561	},
6562
6563	_transitionKenBurns: function()
6564	{
6565        var kbDuration  = this.get('kenBurnsDuration'),
6566            duration    = this.get('duration'),
6567            itemIn      = this.get('itemIn'),
6568            zoom        = this.get('kenBurnsZoom');
6569
6570        this._transitionFade();
6571
6572        (new Y.FL.SlideshowKenBurns({
6573            duration: kbDuration + duration + 4,
6574            image: itemIn,
6575            zoom: zoom
6576        })).run();
6577	}
6578
6579}, {
6580
6581	/**
6582	 * Static property used to define the default attribute configuration of
6583	 * the Widget.
6584	 *
6585	 * @property ATTRS
6586	 * @type Object
6587	 * @protected
6588	 * @static
6589	 */
6590	ATTRS: {
6591
6592		/**
6593		 * The Node to transition in.
6594		 *
6595		 * @attribute itemIn
6596		 * @type Node
6597		 * @default null
6598		 */
6599		itemIn: {
6600			value: null
6601		},
6602
6603		/**
6604		 * The Node to transition out.
6605		 *
6606		 * @attribute itemOut
6607		 * @type Node
6608		 * @default null
6609		 */
6610		itemOut: {
6611			value: null
6612		},
6613
6614		/**
6615		 * The duration of the transition in seconds.
6616		 *
6617		 * @attribute duration
6618		 * @type Number
6619		 * @default 0.5
6620		 */
6621		duration: {
6622			value: 0.5
6623		},
6624
6625		/**
6626		 * The type of easing to use.
6627		 *
6628		 * @attribute easing
6629		 * @type String
6630		 * @default ease-out
6631		 */
6632		easing: {
6633			value: 'ease-out'
6634		},
6635
6636		/**
6637		 * The type of transition to use.
6638		 *
6639		 * @attribute type
6640		 * @type String
6641		 * @default fade
6642		 */
6643		type: {
6644			value: 'fade'
6645		},
6646
6647		/**
6648		 * The number of bars to use for
6649		 * transitions such as blinds.
6650		 *
6651		 * @attribute bars
6652		 * @type Number
6653		 * @default 15
6654		 */
6655		bars: {
6656			value: 15
6657		},
6658
6659		/**
6660		 * The number of columns to use for
6661		 * transitions such as boxes.
6662		 *
6663		 * @attribute boxCols
6664		 * @type Number
6665		 * @default 8
6666		 */
6667		boxCols: {
6668			value: 8
6669		},
6670
6671		/**
6672		 * The number of rows to use for
6673		 * transitions such as boxes.
6674		 *
6675		 * @attribute boxRows
6676		 * @type Number
6677		 * @default 4
6678		 */
6679		boxRows: {
6680			value: 4
6681		},
6682
6683		/**
6684		 * The duration the ken burns effect will
6685		 * last, measured in seconds.
6686		 *
6687		 * @attribute kenBurnsDuration
6688		 * @type Number
6689		 * @default 4
6690		 */
6691		kenBurnsDuration: {
6692            value: 4
6693		},
6694
6695		/**
6696		 * The amount of zoom to use for the Ken Burns effect.
6697		 *
6698		 * @attribute kenBurnsZoom
6699		 * @type Number
6700		 * @default 1.2
6701		 */
6702		kenBurnsZoom: {
6703            value: 1.2
6704		}
6705	},
6706
6707	/**
6708	 * The types of transitions and associated functions.
6709	 *
6710	 * @property TYPES
6711	 * @type Object
6712	 * @readOnly
6713	 * @protected
6714	 * @static
6715	 */
6716	TYPES: {
6717		fade: '_transitionFade',
6718		none: '_transitionNone',
6719		slideLeft: '_transitionSlideLeft',
6720		slideRight: '_transitionSlideRight',
6721		slideUp: '_transitionSlideUp',
6722		slideDown: '_transitionSlideDown',
6723		blinds: '_transitionBars',
6724		bars: '_transitionBars',
6725		barsRandom: '_transitionBars',
6726		boxes: '_transitionBoxes',
6727		boxesRandom: '_transitionBoxes',
6728		boxesGrow: '_transitionBoxes',
6729		kenBurns: '_transitionKenBurns'
6730	},
6731
6732	/**
6733	 * The types of transitions that can only be
6734	 * run on FL.SlideshowImage widgets.
6735	 *
6736	 * @property SLIDESHOW_IMAGE_TYPES
6737	 * @type Object
6738	 * @readOnly
6739	 * @protected
6740	 * @static
6741	 */
6742	SLIDESHOW_IMAGE_TYPES: [
6743        'blinds',
6744        'bars',
6745        'barsRandom',
6746        'boxes',
6747        'boxesRandom',
6748        'boxesGrow',
6749        'kenBurns'
6750	]
6751});
6752
6753/**
6754 * A highly configurable slideshow widget.
6755 *
6756 * @namespace FL
6757 * @class Slideshow
6758 * @constructor
6759 * @param config {Object} Configuration object
6760 * @extends FL.SlideshowBase
6761 */
6762Y.namespace('FL').Slideshow = Y.Base.create('fl-slideshow', Y.FL.SlideshowBase, [], {
6763
6764	/**
6765	 * A FL.SlideshowFrame instance used for the main image.
6766	 *
6767	 * @property frame
6768	 * @type FL.SlideshowFrame
6769	 * @default null
6770	 */
6771	frame: null,
6772
6773	/**
6774	 * A FL.SlideshowNav instance used for the main nav.
6775	 *
6776	 * @property nav
6777	 * @type FL.SlideshowNav
6778	 * @default null
6779	 */
6780	nav: null,
6781
6782	/**
6783	 * A FL.SlideshowNav instance used for the image nav's left button.
6784	 *
6785	 * @property imageNavLeft
6786	 * @type FL.SlideshowNav
6787	 * @default null
6788	 */
6789	imageNavLeft: null,
6790
6791	/**
6792	 * A FL.SlideshowNav instance used for the image nav's right button.
6793	 *
6794	 * @property imageNavRight
6795	 * @type FL.SlideshowNav
6796	 * @default null
6797	 */
6798	imageNavRight: null,
6799
6800	/**
6801	 * A FL.SlideshowThumbs instance used for the thumbnail grid.
6802	 *
6803	 * @property thumbs
6804	 * @type FL.SlideshowThumbs
6805	 * @default null
6806	 */
6807	thumbs: null,
6808
6809	/**
6810	 * A FL.SlideshowThumbs instance used for the vertical thumbnail grid.
6811	 *
6812	 * @property verticalThumbs
6813	 * @type FL.SlideshowThumbs
6814	 * @default null
6815	 */
6816	verticalThumbs: null,
6817
6818	/**
6819	 * A FL.SlideshowCaption instance.
6820	 *
6821	 * @property caption
6822	 * @type FL.SlideshowCaption
6823	 * @default null
6824	 */
6825	caption: null,
6826
6827	/**
6828	 * A FL.SlideshowSocial instance.
6829	 *
6830	 * @property social
6831	 * @type FL.SlideshowSocial
6832	 * @default null
6833	 */
6834	social: null,
6835
6836	/**
6837	 * A FL.SlideshowImage instance used to preload
6838	 * the next image.
6839	 *
6840	 * @property _nextImagePreloader
6841	 * @type FL.SlideshowImage
6842	 * @default null
6843	 * @protected
6844	 */
6845	_nextImagePreloader: null,
6846
6847	/**
6848	 * An object that holds the initial nav settings
6849	 * when the mini nav has been enabled for a responsive layout.
6850	 *
6851	 * @property _initialNavSettings
6852	 * @type Object
6853	 * @default null
6854	 * @protected
6855	 */
6856	_initialNavSettings: null,
6857
6858	/**
6859	 * Initializes the preloaders, nav buttons, fullscreen and captions.
6860	 *
6861	 * @method initializer
6862	 * @protected
6863	 */
6864	initializer: function()
6865	{
6866		// Preloader config
6867		var imageConfig = {
6868			loadGroup: 'main-preload',
6869			crop: this.get('crop'),
6870			position: this.get('position'),
6871			protect: this.get('protect'),
6872			upsize: this.get('upsize')
6873		};
6874
6875		// Preloader
6876		this._nextImagePreloader = new Y.FL.SlideshowImage(imageConfig);
6877
6878		// Nav buttons not needed for touch
6879		if(this._isMobile()) {
6880			this._removeNavButton('prevPage');
6881			this._removeNavButton('nextPage');
6882			this._removeNavButton('fullscreen');
6883		}
6884
6885		// Fullscreen
6886		if(this._hasNavButton('fullscreen')) {
6887			if(Y.FL.SlideshowFullscreen.OS_SUPPORT) {
6888				this.plug(Y.FL.SlideshowFullscreen);
6889			}
6890			else {
6891				this._removeNavButton('fullscreen');
6892			}
6893		}
6894	},
6895
6896	/**
6897	 * Calls the FL.SlideshowBase superclass renderUI method
6898	 * and renders the child widgets.
6899	 *
6900	 * @method renderUI
6901	 * @protected
6902	 */
6903	renderUI: function()
6904	{
6905		Y.FL.Slideshow.superclass.renderUI.apply(this, arguments);
6906
6907		this._renderFrame();
6908		this._renderVerticalThumbs();
6909		this._renderNavAndThumbs();
6910		this._renderImageNav();
6911		this._renderMouseNav();
6912		this._renderCaption();
6913		this._renderSocial();
6914	},
6915
6916	/**
6917	 * Calls the FL.SlideshowBase superclass bindUI method, binds
6918	 * _resizeChildWidgets to fire after the resize method inherited
6919	 * from FL.SlideshowBase, shows the loading image, binds overlay events
6920	 * and binds an event to load an image into the frame.
6921	 *
6922	 * @method bindUI
6923	 * @protected
6924	 */
6925	bindUI: function()
6926	{
6927		var ssBB 			= this.get('boundingBox'),
6928			frameBB         = this.frame.get('boundingBox'),
6929			navOverlay		= this.get('navOverlay'),
6930			navType			= this.get('navType'),
6931			nav 			= this._getNav(),
6932			clickAction     = this.get('clickAction');
6933
6934		// Call superclass bindUI
6935		Y.FL.Slideshow.superclass.bindUI.apply(this, arguments);
6936
6937		// Resize child widgets after the superclass resize method.
6938		Y.Do.after(this._resizeChildWidgets, this, 'resize');
6939
6940		// Loading events
6941		this.on('albumLoadStart', this._albumLoadStart, this);
6942		this.on('albumLoadComplete', this._albumLoadComplete, this);
6943		this.on('imageLoadComplete', this._loadFrame, this);
6944
6945		// Loading image
6946		if(this.get('loadingImageAlwaysEnabled')) {
6947			this.frame.on('transitionInit', Y.bind(this._showLoadingImageWithDelay, this));
6948			this.frame.on('transitionStart', Y.bind(this._hideLoadingImage, this));
6949		}
6950
6951		// Overlay events
6952		if(this.get('overlayHideOnMousemove')) {
6953			if(nav && navOverlay) {
6954				this.frame.once('transitionComplete', nav.slideshowOverlay.hideWithTimer, nav.slideshowOverlay);
6955				ssBB.on('mousemove', Y.bind(this._toggleNav, this));
6956			}
6957			if(navType == 'buttons' || navType == 'thumbs' || navType == 'custom') {
6958				ssBB.on('mouseenter', Y.bind(this._checkOverlaysOnMouseenter, this));
6959				ssBB.on('mouseleave', Y.bind(this._hideAllOverlays, this));
6960			}
6961		}
6962
6963		ssBB.delegate('click', Y.bind(this._overlayCloseClick, this), '.fl-slideshow-overlay-close');
6964
6965		// Click action
6966		if(clickAction == 'gallery' || clickAction == 'url') {
6967            frameBB.delegate('click', Y.bind(this._frameClick, this), '.fl-slideshow-image-img');
6968		}
6969	},
6970
6971	/**
6972	 * Calls the FL.SlideshowBase superclass syncUI method
6973	 * and makes the bounding box unselectable.
6974	 *
6975	 * @method syncUI
6976	 * @protected
6977	 */
6978	syncUI: function()
6979	{
6980		var bb = this.get('boundingBox');
6981
6982		Y.FL.Slideshow.superclass.syncUI.apply(this, arguments);
6983
6984		bb._node.onselectstart = function() { return false; };
6985		bb._node.unselectable = "on";
6986		bb._node.style.MozUserSelect = "none";
6987
6988		if(this.get('clickAction') != 'none') {
6989            this.frame.get('boundingBox').addClass('fl-click-action-enabled');
6990		}
6991	},
6992
6993	/**
6994	 * Checks to see if the current device is mobile.
6995	 *
6996	 * @since 1.9.3
6997	 * @access private
6998	 * @method _isMobile
6999	 * @return {Boolean}
7000	 */
7001	_isMobile: function()
7002	{
7003	    return /Mobile|Android|Silk\/|Kindle|BlackBerry|Opera Mini|Opera Mobi|webOS/i.test( navigator.userAgent );
7004	},
7005
7006	/**
7007	 * Unload all slideshow images and pause
7008	 * the slideshow.
7009	 *
7010	 * @method unload
7011	 */
7012	unload: function()
7013	{
7014		this.pause();
7015		this.frame.unload();
7016
7017		if(this.thumbs !== null) {
7018			this.thumbs.unload();
7019		}
7020	},
7021
7022	/**
7023	 * @method _albumLoadStart
7024	 * @protected
7025	 */
7026	_albumLoadStart: function()
7027	{
7028		this._showLoadingImage();
7029	},
7030
7031	/**
7032	 * @method _albumLoadComplete
7033	 * @protected
7034	 */
7035	_albumLoadComplete: function()
7036	{
7037		this.frame.once('transitionStart', Y.bind(this._hideLoadingImage, this));
7038	},
7039
7040	/**
7041	 * Resizes all enabled child widgets.
7042	 *
7043	 * @method _resizeChildWidgets
7044	 * @protected
7045	 */
7046	_resizeChildWidgets: function()
7047	{
7048		var bb				= this.get('boundingBox'),
7049			cb				= this.get('contentBox'),
7050			imageNavEnabled = this.get('imageNavEnabled');
7051
7052		this._renderNavAndThumbs();
7053
7054		if(this.get('verticalThumbsOverlay')) {
7055			this._resizeFrame(cb.get('offsetWidth'), bb.get('offsetHeight'));
7056			this._resizeVerticalThumbs();
7057		}
7058		else {
7059			this._resizeVerticalThumbs();
7060			this._resizeFrame(cb.get('offsetWidth'), bb.get('offsetHeight'));
7061		}
7062
7063		if(imageNavEnabled) {
7064			this._positionImageNav();
7065		}
7066
7067		this._positionLoadingImage();
7068	},
7069
7070	/**
7071	 * @method _renderVerticalThumbs
7072	 * @protected
7073	 */
7074	_renderVerticalThumbs: function()
7075	{
7076		var threshold 	= this.get('responsiveThreshold'),
7077			ssBB 		= this.get('boundingBox'),
7078			bbWidth 	= ssBB.get('offsetWidth'),
7079			vtBB;
7080
7081		if(this.get('verticalThumbsEnabled') && bbWidth > threshold) {
7082
7083			this.verticalThumbs = new Y.FL.SlideshowThumbs(this._getVerticalThumbsConfig());
7084			this.add(this.verticalThumbs);
7085			this.verticalThumbs.render(ssBB);
7086
7087			vtBB = this.verticalThumbs.get('boundingBox');
7088			vtBB.addClass('fl-slideshow-vertical-thumbs');
7089			vtBB.setStyle(this.get('verticalThumbsPosition'), 0);
7090			ssBB.append(vtBB);
7091
7092			if(this.get('verticalThumbsOverlay')) {
7093				this.verticalThumbs.plug(Y.FL.SlideshowOverlay, {
7094					hideDelay: this.get('overlayHideDelay'),
7095					hideStyle: 'left'
7096				});
7097
7098				this.frame.get('boundingBox').append(vtBB);
7099				this.verticalThumbs.resize();
7100			}
7101			else {
7102				this.verticalThumbs.resize();
7103				this._adjustContentForVerticalThumbs();
7104			}
7105
7106			this._bindVerticalThumbs();
7107		}
7108	},
7109
7110	/**
7111	 * Prepares and returns the vertical thumbs config object.
7112	 *
7113	 * @method _getVerticalThumbsConfig
7114	 * @protected
7115	 * @returns Object
7116	 */
7117	_getVerticalThumbsConfig: function()
7118	{
7119		var attrs = this.getAttrs(),
7120			config = {
7121				columns: 				attrs.verticalThumbsColumns,
7122				rows: 					'auto',
7123				centerSinglePage: 		false,
7124				horizontalSpacing: 		attrs.verticalThumbsHorizontalSpacing,
7125				verticalSpacing:	 	attrs.verticalThumbsVerticalSpacing,
7126				spaceEvenly: 			attrs.verticalThumbsSpaceEvenly,
7127				rightNavEnabled: 		false,
7128				leftNavEnabled: 		false,
7129				topNavEnabled: 			attrs.verticalThumbsTopNavEnabled,
7130				topNavButtons:			attrs.verticalThumbsTopNavButtons,
7131				bottomNavEnabled: 		attrs.verticalThumbsBottomNavEnabled,
7132				bottomNavButtons:		attrs.verticalThumbsBottomNavButtons,
7133				pauseOnClick: 			attrs.verticalThumbsPauseOnClick,
7134				transition:				attrs.verticalThumbsTransition,
7135				transitionDirection: 	attrs.verticalThumbsTransitionDirection,
7136				transitionEasing:		attrs.verticalThumbsTransitionEasing,
7137				touchSupport:			true,
7138				imageConfig: {
7139					crop: 				attrs.verticalThumbsImageCrop,
7140					width: 				attrs.verticalThumbsImageWidth,
7141					height: 			attrs.verticalThumbsImageHeight
7142				}
7143			};
7144
7145		return config;
7146	},
7147
7148	_bindVerticalThumbs: function()
7149	{
7150		var ssBB 		= this.get('boundingBox'),
7151			hideOnMouse = this.get('overlayHideOnMousemove'),
7152			vtOverlay 	= this.get('verticalThumbsOverlay'),
7153			vt			= this.verticalThumbs;
7154
7155		if(vt && hideOnMouse && vtOverlay) {
7156			this.frame.once('transitionComplete', vt.slideshowOverlay.hideWithTimer, vt.slideshowOverlay);
7157			ssBB.on('mousemove', Y.bind(this._toggleVerticalThumbs, this));
7158			ssBB.on('mouseenter', Y.bind(this._toggleVerticalThumbs, this));
7159		}
7160	},
7161
7162	/**
7163	 * Resizes the vertical thumbs.
7164	 *
7165	 * @method _resizeVerticalThumbs
7166	 * @protected
7167	 */
7168	_resizeVerticalThumbs: function()
7169	{
7170		var vtEnabled = this.get('verticalThumbsEnabled'),
7171			vtOverlay,
7172			threshold,
7173			ssBB,
7174			bbWidth,
7175			navOverlay,
7176			navType,
7177			nav,
7178			navBB;
7179
7180		if(vtEnabled) {
7181			vtOverlay	= this.get('verticalThumbsOverlay');
7182			threshold 	= this.get('responsiveThreshold');
7183			ssBB 		= this.get('boundingBox');
7184			bbWidth 	= ssBB.get('offsetWidth');
7185			navOverlay	= this.get('navOverlay');
7186			navType		= this.get('navType');
7187			nav			= this._getNav();
7188
7189			if(this.verticalThumbs && bbWidth > threshold) {
7190
7191				this.verticalThumbs.get('boundingBox').setStyle('display', 'block');
7192				this.verticalThumbs.resize();
7193
7194				if(!vtOverlay) {
7195					this._adjustContentForVerticalThumbs();
7196				}
7197				else if(nav && navOverlay) {
7198
7199					navBB = nav.get('boundingBox');
7200
7201					if(navType == 'thumbs') {
7202						this._adjustOverlayForVerticalThumbs(navBB, true);
7203						this.thumbs.resize();
7204					}
7205					else {
7206						this._adjustOverlayForVerticalThumbs(navBB);
7207					}
7208				}
7209			}
7210			else if(!this.verticalThumbs && bbWidth > threshold) {
7211				this._renderVerticalThumbs();
7212			}
7213			else if(this.verticalThumbs && bbWidth <= threshold) {
7214
7215				this.verticalThumbs.get('boundingBox').setStyle('display', 'none');
7216
7217				if(!vtOverlay) {
7218					this.get('contentBox').setStyles({
7219						left: 'auto',
7220						position: 'relative',
7221						right: 'auto',
7222						width: 'auto'
7223					});
7224				}
7225			}
7226		}
7227	},
7228
7229	/**
7230	 * Toggles the visibility of the vertical thumbs.
7231	 *
7232	 * @method _toggleVerticalThumbs
7233	 * @protected
7234	 */
7235	_toggleVerticalThumbs: function()
7236	{
7237		if(this.verticalThumbs) {
7238			if(this.verticalThumbs.slideshowOverlay._visible) {
7239				this.verticalThumbs.slideshowOverlay.hideWithTimer();
7240			}
7241			else {
7242				this.verticalThumbs.slideshowOverlay.show();
7243			}
7244		}
7245	},
7246
7247	/**
7248	 * Adjusts the content position and width
7249	 * for the vertical thumbs.
7250	 *
7251	 * @method _adjustContentForVerticalThumbs
7252	 * @protected
7253	 */
7254	_adjustContentForVerticalThumbs: function()
7255	{
7256		var ssBB 	= this.get('boundingBox'),
7257			vtBB 	= this.verticalThumbs.get('boundingBox'),
7258			vtPos 	= this.get('verticalThumbsPosition'),
7259			ssCB 	= this.get('contentBox'),
7260			cbPos 	= vtPos == 'left' ? 'right' : 'left',
7261			cbWidth = ssBB.get('offsetWidth') - vtBB.get('offsetWidth');
7262
7263		ssCB.setStyle('position', 'absolute');
7264		ssCB.setStyle(cbPos, 0);
7265		ssCB.setStyle('width', cbWidth);
7266	},
7267
7268	/**
7269	 * Adjusts an overlay's position for the vertical
7270	 * thumbs when they are overlaid as well.
7271	 *
7272	 * @method _adjustOverlayForVerticalThumbs
7273	 * @protected
7274	 */
7275	_adjustOverlayForVerticalThumbs: function(node, useMargin)
7276	{
7277		var vtEnabled 	= this.get('verticalThumbsEnabled'),
7278			vtOverlay 	= this.get('verticalThumbsOverlay'),
7279			vtBB		= null,
7280			vtPos		= null,
7281			margin		= typeof useMargin === 'undefined' ? '' : 'margin-',
7282			vtWidth		= 0;
7283
7284		if(this.verticalThumbs && vtEnabled && vtOverlay) {
7285			vtBB = this.verticalThumbs.get('boundingBox');
7286			vtWidth = vtBB.get('offsetWidth');
7287			vtPos = this.get('verticalThumbsPosition');
7288
7289			if(vtPos == 'left') {
7290				node.setStyle(margin + 'left', vtWidth + 'px');
7291			}
7292			else {
7293				node.setStyle(margin + 'right', vtWidth + 'px');
7294			}
7295		}
7296	},
7297
7298	/**
7299	 * Creates and renders a new instance of FL.SlideshowFrame
7300	 * used for the main image.
7301	 *
7302	 * @method _renderFrame
7303	 * @protected
7304	 */
7305	_renderFrame: function()
7306	{
7307		this.frame = new Y.FL.SlideshowFrame({
7308			imageConfig: {
7309				loadGroup: 'main',
7310				loadPriority: true,
7311				crop: this.get('crop'),
7312				cropHorizontalsOnly: this.get('cropHorizontalsOnly'),
7313				position: this.get('position'),
7314				protect: this.get('protect'),
7315				upsize: this.get('upsize'),
7316				showVideoButton: this.get('navOverlay')
7317			},
7318			touchSupport: this.get('touchSupport')
7319		});
7320
7321		this.add(this.frame);
7322		this.frame.render(this.get('contentBox'));
7323		this.frame.get('boundingBox').addClass('fl-slideshow-main-image');
7324		this._setPlayingTimerEvent(this.frame, 'transitionComplete');
7325		this._loadingImageContainer = this.frame.get('contentBox');
7326	},
7327
7328	/**
7329	 * Resizes the frame used for the main image.
7330	 *
7331	 * @method _resizeMainImage
7332	 * @param width {Number} The width to resize to.
7333	 * @param height {Number} The height to resize to.
7334	 * @protected
7335	 */
7336	_resizeFrame: function(width, height)
7337	{
7338		var navOverlay	= this.get('navOverlay'),
7339			nav 		= this._getNav();
7340
7341		if(nav && !navOverlay) {
7342			height -= parseInt(nav.get('boundingBox').getComputedStyle('height'), 10);
7343		}
7344
7345		this.frame.resize(width, height);
7346	},
7347
7348	/**
7349	 * Called when the imageLoadComplete event fires.
7350	 * Loads an image into the frame and preloads the next image.
7351	 *
7352	 * @method _loadFrame
7353	 * @param e {Object} Event object containing the image info.
7354	 * @protected
7355	 */
7356	_loadFrame: function(e)
7357	{
7358		var activeIndex		= this.imageInfo.index,
7359			images			= this.albumInfo.images,
7360			nextIndex 		= activeIndex + 1 >= images.length ? 0 : activeIndex + 1,
7361			width			= this.frame.get('width'),
7362			height			= this.frame.get('height');
7363
7364		// Load the frame.
7365		this.frame.load(e.imageInfo);
7366
7367		// Remove main preload images from the load queue.
7368		Y.FL.SlideshowImageLoader.removeGroup('main-preload');
7369
7370		// Preload the next image.
7371		this._nextImagePreloader.preload(images[nextIndex], width, height);
7372	},
7373
7374	/**
7375	 * Fired when the frame img tag is clicked.
7376	 *
7377	 * @method _frameClick
7378	 * @protected
7379	 */
7380	_frameClick: function()
7381	{
7382        var clickAction    = this.get('clickAction'),
7383            clickActionUrl = this.get('clickActionUrl');
7384
7385        if(clickAction == 'url') {
7386            window.location.href = clickActionUrl;
7387        }
7388        else if(clickAction == 'gallery') {
7389            window.location.href = this.imageInfo.link;
7390        }
7391	},
7392
7393	/**
7394	 * Sets attributes to display a compact nav
7395	 * for responsive layouts.
7396	 *
7397	 * @method _initMiniNav
7398	 * @protected
7399	 */
7400	_initMiniNav: function()
7401	{
7402		var buttons = [];
7403
7404		if(this._hasNavButton('prev')) {
7405			buttons.push('prev');
7406		}
7407		if(this._hasNavButton('thumbs') || this.get('navType') == 'thumbs') {
7408			buttons.push('thumbs');
7409		}
7410		if(this._hasNavButton('caption')) {
7411			buttons.push('caption');
7412		}
7413		if(this._hasNavButton('social')) {
7414			buttons.push('social');
7415		}
7416		if(this._hasNavButton('buy')) {
7417			buttons.push('buy');
7418		}
7419		if(this._hasNavButton('play')) {
7420			buttons.push('play');
7421		}
7422		if(this._hasNavButton('fullscreen') && !('ontouchstart' in window)) {
7423			buttons.push('fullscreen');
7424		}
7425		if(this._hasNavButton('next')) {
7426			buttons.push('next');
7427		}
7428
7429		this._initialNavSettings = {
7430			buttons: this.get('navButtons'),
7431			buttonsLeft: this.get('navButtonsLeft'),
7432			buttonsRight: this.get('navButtonsRight'),
7433			type: this.get('navType')
7434		};
7435
7436		this._set('navButtons', buttons);
7437		this._set('navButtonsLeft', []);
7438		this._set('navButtonsRight', []);
7439		this._set('navType', 'buttons');
7440	},
7441
7442	/**
7443	 * Renders the nav and thumbs layout based on the
7444	 * current window size.
7445	 *
7446	 * @method _renderNavAndThumbs
7447	 * @protected
7448	 */
7449	_renderNavAndThumbs: function()
7450	{
7451		var navType		= this.get('navType'),
7452			renderNav	= false,
7453			bbWidth,
7454			threshold;
7455
7456		if(navType == 'buttons' || navType == 'thumbs') {
7457			bbWidth		= this.get('boundingBox').get('offsetWidth');
7458			threshold	= this.get('responsiveThreshold');
7459
7460			if(bbWidth <= threshold && this._initialNavSettings === null) {
7461				this._initMiniNav();
7462				renderNav = true;
7463			}
7464			else if(bbWidth > threshold && this._initialNavSettings !== null) {
7465				this._set('navButtons', this._initialNavSettings.buttons);
7466				this._set('navButtonsLeft', this._initialNavSettings.buttonsLeft);
7467				this._set('navButtonsRight', this._initialNavSettings.buttonsRight);
7468				this._set('navType', this._initialNavSettings.type);
7469				this._initialNavSettings = null;
7470				renderNav = true;
7471			}
7472
7473			// Button nav
7474			if(renderNav || this.nav === null) {
7475				this._renderNav();
7476			}
7477
7478			// Thumbs nav
7479			if(renderNav || this.thumbs === null) {
7480				this._renderThumbs();
7481			}
7482			else if(this._thumbsEnabled()) {
7483				this._resizeThumbs();
7484			}
7485
7486			// Caption
7487			if(renderNav && this.caption !== null) {
7488				this._syncCaption();
7489			}
7490
7491			// Social
7492			if(renderNav && this.social !== null) {
7493				this._syncSocial();
7494			}
7495		}
7496	},
7497
7498	/**
7499	 * Creates and renders a new instance of FL.SlideshowNav
7500	 * used for the main nav.
7501	 *
7502	 * @method _renderNav
7503	 * @protected
7504	 */
7505	_renderNav: function()
7506	{
7507		var frameBB		= this.frame.get('boundingBox'),
7508			navBB 		= null,
7509			navOverlay	= this.get('navOverlay'),
7510			navPosition = this.get('navPosition');
7511
7512		// Destroy old instances
7513		this._destroyNav();
7514
7515		// Create a new instance
7516		if(this.get('navType') == 'buttons') {
7517
7518			// Create the nav
7519			this.nav = new Y.FL.SlideshowNav({
7520				buttons: this.get('navButtons'),
7521				buttonsLeft: this.get('navButtonsLeft'),
7522				buttonsRight: this.get('navButtonsRight')
7523			});
7524
7525			// Add to widget parent and render
7526			this.add(this.nav);
7527			this.nav.render(this.get('contentBox'));
7528			navBB = this.nav.get('boundingBox');
7529
7530			// Plug overlay?
7531			if(navOverlay) {
7532				this.nav.plug(Y.FL.SlideshowOverlay, {
7533					hideDelay: this.get('overlayHideDelay')
7534				});
7535
7536				navBB.setStyle('position', 'absolute');
7537				navBB.setStyle(navPosition, '0px');
7538			}
7539
7540			// Insert
7541			if(navPosition == 'top') {
7542				frameBB.insert(navBB, 'before');
7543			}
7544			else {
7545				frameBB.insert(navBB, 'after');
7546			}
7547
7548			// CSS class name
7549			navBB.addClass('fl-slideshow-main-nav');
7550		}
7551	},
7552
7553	/**
7554	 * Destroy the current nav instance.
7555	 *
7556	 * @method _destroyNav
7557	 * @protected
7558	 */
7559	_destroyNav: function()
7560	{
7561        if(this.nav !== null) {
7562			if(this.nav.slideshowOverlay) {
7563				this.nav.slideshowOverlay.destroy();
7564			}
7565
7566            this.nav.get('boundingBox').remove();
7567			this.remove(this.nav);
7568			try { this.nav.destroy(true); } catch(e) {}
7569			this.nav = null;
7570		}
7571	},
7572
7573	/**
7574	 * Returns the nav object or null if navType is
7575	 * set to none or custom.
7576	 *
7577	 * @method _getNav
7578	 * @protected
7579	 */
7580	_getNav: function()
7581	{
7582		var navType = this.get('navType');
7583
7584		if(navType == 'buttons') {
7585			return this.nav;
7586		}
7587		else if(navType == 'thumbs') {
7588			return this.thumbs;
7589		}
7590		else {
7591			return null;
7592		}
7593	},
7594
7595	/**
7596	 * Toggles the visibility of the nav or thumbs nav
7597	 * if navOverlay is set to true.
7598	 *
7599	 * @method _toggleNav
7600	 * @protected
7601	 */
7602	_toggleNav: function()
7603	{
7604		var nav = this._getNav();
7605
7606		if(nav.slideshowOverlay) {
7607			if(nav.slideshowOverlay._visible) {
7608				nav.slideshowOverlay.hideWithTimer();
7609			}
7610			else {
7611				nav.slideshowOverlay.show();
7612			}
7613		}
7614	},
7615
7616	/**
7617	 * Creates and renders two instances of FL.SlideshowNav for the
7618	 * prev and next button that will be overlaid on the main image.
7619	 *
7620	 * @method _renderImageNav
7621	 * @protected
7622	 */
7623	_renderImageNav: function()
7624	{
7625		var ssBB;
7626
7627		if(this.get('imageNavEnabled')) {
7628			if(this._isMobile()) {
7629				this._set('imageNavEnabled', false);
7630			}
7631			else {
7632				ssBB = this.get('boundingBox');
7633
7634				this.imageNavLeft = new Y.FL.SlideshowNav({
7635				    buttons: ['prev'],
7636				    useFontIcons: false
7637				});
7638
7639				this.imageNavRight = new Y.FL.SlideshowNav({
7640				    buttons: ['next'],
7641				    useFontIcons: false
7642				});
7643
7644				this.add(this.imageNavLeft);
7645				this.add(this.imageNavRight);
7646
7647				this.imageNavLeft.render(this.frame.get('boundingBox'));
7648				this.imageNavRight.render(this.frame.get('boundingBox'));
7649
7650				this.imageNavLeft.plug(Y.FL.SlideshowOverlay, { hideDelay: this.get('overlayHideDelay') });
7651				this.imageNavRight.plug(Y.FL.SlideshowOverlay, { hideDelay: this.get('overlayHideDelay') });
7652
7653				if(this.get('overlayHideOnMousemove')) {
7654					this.frame.once('transitionComplete', this.imageNavLeft.slideshowOverlay.hideWithTimer, this.imageNavLeft.slideshowOverlay);
7655					this.frame.once('transitionComplete', this.imageNavRight.slideshowOverlay.hideWithTimer, this.imageNavRight.slideshowOverlay);
7656					ssBB.on('mousemove', Y.bind(this._toggleImageNav, this));
7657					ssBB.on('mouseenter', Y.bind(this._toggleImageNav, this));
7658				}
7659
7660				this.imageNavLeft.get('boundingBox').addClass('fl-slideshow-image-nav-left');
7661				this.imageNavRight.get('boundingBox').addClass('fl-slideshow-image-nav-right');
7662			}
7663		}
7664	},
7665
7666	/**
7667	 * @method _positionImageNav
7668	 * @protected
7669	 */
7670	_positionImageNav: function()
7671	{
7672		var leftBB			= this.imageNavLeft.get('boundingBox'),
7673			rightBB			= this.imageNavRight.get('boundingBox'),
7674			imageNavHeight 	= leftBB.get('offsetHeight'),
7675			frameHeight 	= this.frame.get('boundingBox').get('offsetHeight'),
7676			top 			= frameHeight/2 - imageNavHeight/2,
7677			styles          = {
7678                top: top + 'px',
7679                display: 'block'
7680			};
7681
7682		leftBB.setStyles(styles);
7683		rightBB.setStyles(styles);
7684
7685		this._adjustOverlayForVerticalThumbs(leftBB);
7686		this._adjustOverlayForVerticalThumbs(rightBB);
7687	},
7688
7689	/**
7690	 * Toggles the visibility of the image nav buttons.
7691	 *
7692	 * @method _toggleImageNav
7693	 * @protected
7694	 */
7695	_toggleImageNav: function()
7696	{
7697		if(this.imageNavLeft.slideshowOverlay._visible) {
7698			this.imageNavLeft.slideshowOverlay.hideWithTimer();
7699		}
7700		else {
7701			this.imageNavLeft.slideshowOverlay.show();
7702		}
7703
7704		if(this.imageNavRight.slideshowOverlay._visible) {
7705			this.imageNavRight.slideshowOverlay.hideWithTimer();
7706		}
7707		else {
7708			this.imageNavRight.slideshowOverlay.show();
7709		}
7710	},
7711
7712	/**
7713	 * @method _renderMouseNav
7714	 * @protected
7715	 */
7716	_renderMouseNav: function()
7717	{
7718		if(this.get('mouseNavEnabled') && !('ontouchstart' in window) && !window.navigator.msPointerEnabled) {
7719			this.plug(Y.FL.SlideshowMouseNav, {
7720				trigger: this.frame.get('boundingBox')
7721			});
7722		}
7723	},
7724
7725	/**
7726	 * Checks whether the thumbs are enabled.
7727	 *
7728	 * @method _thumbsEnabled
7729	 * @protected
7730	 * @returns Boolean
7731	 */
7732	_thumbsEnabled: function()
7733	{
7734		var navType = this.get('navType');
7735
7736		if(navType == 'thumbs') {
7737			return true;
7738		}
7739		if((navType == 'buttons' || navType == 'custom') && this._hasNavButton('thumbs')) {
7740			return true;
7741		}
7742		else {
7743			return false;
7744		}
7745	},
7746
7747	/**
7748	 * Creates and renders a new instance of FL.SlideshowThumbs.
7749	 *
7750	 * @method _renderThumbs
7751	 * @protected
7752	 */
7753	_renderThumbs: function()
7754	{
7755		var frameBB, navOverlay, navPosition, navType;
7756
7757		// Destroy old instances
7758		this._destroyThumbs();
7759
7760		// Create a new instance
7761		if(this._thumbsEnabled()) {
7762			frameBB			= this.frame.get('boundingBox');
7763			navOverlay		= this.get('navOverlay');
7764			navPosition 	= this.get('navPosition');
7765			navType			= this.get('navType');
7766
7767			// Create the thumbs
7768			this.thumbs = new Y.FL.SlideshowThumbs(this._getThumbsConfig());
7769
7770			// This breaks sometimes on SM Next. Try/catch bandaid for now.
7771			try { this.add(this.thumbs); } catch(e) {}
7772
7773			// Overlay setup
7774			if(navType == 'buttons' || navType == 'custom') {
7775				this.thumbs.plug(Y.FL.SlideshowOverlay, {
7776					hideDelay: this.get('overlayHideDelay'),
7777					hideStyle: 'left',
7778					visible: false
7779				});
7780			}
7781			else if(navType == 'thumbs' && navOverlay) {
7782				this.thumbs.plug(Y.FL.SlideshowOverlay, {
7783					hideDelay: this.get('overlayHideDelay'),
7784					hideStyle: 'left'
7785				});
7786			}
7787
7788			// Insert
7789			this.thumbs.render(this.get('contentBox'));
7790
7791			if(navPosition == 'top') {
7792				frameBB.insert(this.thumbs.get('boundingBox'), 'before');
7793			}
7794			else {
7795				frameBB.insert(this.thumbs.get('boundingBox'), 'after');
7796			}
7797
7798			// Hide overlay thumbs on click
7799			if(this.get('thumbsHideOnClick') && navType != 'thumbs') {
7800				this.thumbs.on('imageClick', Y.bind(this._hideThumbsOnImageClick, this));
7801			}
7802
7803			this._syncThumbs();
7804		}
7805	},
7806
7807	/**
7808	 * Destroy the current thumbs instance.
7809	 *
7810	 * @method _destroyThumbs
7811	 * @protected
7812	 */
7813	_destroyThumbs: function()
7814	{
7815        if(this.thumbs !== null) {
7816			if(this.thumbs.slideshowOverlay) {
7817				this.thumbs.slideshowOverlay.destroy();
7818			}
7819
7820            this.thumbs.get('boundingBox').remove();
7821			this.remove(this.thumbs);
7822			try { this.thumbs.destroy(true); } catch(e) {}
7823			this.thumbs = null;
7824		}
7825	},
7826
7827	/**
7828	 * Syncs the thumbs UI styles.
7829	 *
7830	 * @method _syncThumbs
7831	 * @protected
7832	 */
7833	_syncThumbs: function()
7834	{
7835		var thumbsBB			= this.thumbs.get('boundingBox'),
7836			navOverlay			= this.get('navOverlay'),
7837			navPosition 		= this.get('navPosition'),
7838			navType				= this.get('navType'),
7839			paddingType			= 'padding' + navPosition.charAt(0).toUpperCase() + navPosition.slice(1),
7840			navHeight			= 0;
7841
7842		if(navType == 'buttons') {
7843			navHeight = parseInt(this.nav.get('boundingBox').getComputedStyle('height'), 10);
7844			thumbsBB.setStyle('position', 'absolute');
7845
7846			if(navOverlay) {
7847				thumbsBB.setStyle(paddingType, navHeight + 'px');
7848				thumbsBB.setStyle(navPosition, '0px');
7849			}
7850			else {
7851				thumbsBB.setStyle(navPosition, navHeight + 'px');
7852			}
7853		}
7854		if(navType == 'custom' || (navType == 'thumbs' && navOverlay)) {
7855			thumbsBB.setStyle('position', 'absolute');
7856			thumbsBB.setStyle(navPosition, '0px');
7857		}
7858
7859		this.thumbs.resize();
7860	},
7861
7862	/**
7863	 * Prepares and returns the thumbs config object.
7864	 *
7865	 * @method _getThumbsConfig
7866	 * @protected
7867	 * @returns Object
7868	 */
7869	_getThumbsConfig: function()
7870	{
7871		var attrs 					= this.getAttrs(),
7872			navType					= this.get('navType'),
7873			imageConfig 			= {
7874				crop: attrs.thumbsImageCrop,
7875				width: attrs.thumbsImageWidth,
7876				height: attrs.thumbsImageHeight
7877			},
7878			config 					= {
7879				columns: 				'auto',
7880				rows: 					1,
7881				horizontalSpacing: 		attrs.thumbsHorizontalSpacing,
7882				verticalSpacing:	 	attrs.thumbsVerticalSpacing,
7883				spaceEvenly: 			attrs.thumbsSpaceEvenly,
7884				centerSinglePage:	 	attrs.thumbsCenterSinglePage,
7885				pauseOnClick: 			attrs.thumbsPauseOnClick,
7886				transition:				attrs.thumbsTransition,
7887				transitionDirection: 	attrs.thumbsTransitionDirection,
7888				transitionEasing:		attrs.thumbsTransitionEasing,
7889				leftNavButtons: 		attrs.navButtonsLeft,
7890				rightNavButtons: 		attrs.navButtonsRight,
7891				imageConfig:			imageConfig,
7892				touchSupport:			true
7893			};
7894
7895			if(navType == 'buttons' || navType == 'custom') {
7896				if('ontouchstart' in window) {
7897					config.leftNavEnabled = false;
7898					config.rightNavEnabled = false;
7899				}
7900				else {
7901					config.centerSinglePage = false;
7902					config.leftNavButtons 	= ['prevPage'];
7903					config.rightNavButtons 	= ['nextPage'];
7904				}
7905			}
7906
7907			return config;
7908	},
7909
7910	/**
7911	 * Resizes the thumbs.
7912	 *
7913	 * @method _resizeThumbs
7914	 * @protected
7915	 */
7916	_resizeThumbs: function()
7917	{
7918		if(this.thumbs) {
7919			this.thumbs.resize();
7920		}
7921	},
7922
7923	/**
7924	 * Shows or hides the thumbs.
7925	 *
7926	 * @method _toggleThumbs
7927	 * @protected
7928	 */
7929	_toggleThumbs: function()
7930	{
7931		this._toggleOverlay(this.thumbs.slideshowOverlay);
7932	},
7933
7934	/**
7935	 * Hides the thumbs when a thumb image is clicked.
7936	 *
7937	 * @method _hideThumbsOnImageClick
7938	 * @protected
7939	 */
7940	_hideThumbsOnImageClick: function()
7941	{
7942		if(this.thumbs.slideshowOverlay) {
7943			this.thumbs.slideshowOverlay._focus = false;
7944			this.thumbs.slideshowOverlay.enable();
7945			this.thumbs.slideshowOverlay.hide();
7946
7947			if(this.nav && this.nav.slideshowOverlay) {
7948				this.nav.slideshowOverlay.enable();
7949			}
7950		}
7951	},
7952
7953	/**
7954	 * Creates and renders a new instance of FL.SlideshowCaption.
7955	 *
7956	 * @method _renderCaption
7957	 * @protected
7958	 */
7959	_renderCaption: function()
7960	{
7961		if(this._hasNavButton('caption')) {
7962
7963			this.caption = new Y.FL.SlideshowCaption({
7964				lessLinkText: this.get('captionLessLinkText'),
7965				moreLinkText: this.get('captionMoreLinkText'),
7966				textLength: this.get('captionTextLength'),
7967				stripTags: this.get('captionStripTags')
7968			});
7969
7970			this.add(this.caption);
7971
7972			this.caption.plug(Y.FL.SlideshowOverlay, {
7973				hideDelay: this.get('overlayHideDelay'),
7974				visible: false,
7975				closeButton: true
7976			});
7977
7978			this._syncCaption();
7979		}
7980	},
7981
7982	/**
7983	 * Syncs the caption UI styles.
7984	 *
7985	 * @method _syncCaption
7986	 * @protected
7987	 */
7988	_syncCaption: function()
7989	{
7990		var captionBB		= this.caption.get('boundingBox'),
7991			navOverlay		= this.get('navOverlay'),
7992			navPosition 	= this.get('navPosition'),
7993			nav 			= this._getNav(),
7994			paddingType		= 'padding' + navPosition.charAt(0).toUpperCase() + navPosition.slice(1),
7995			navHeight		= 0;
7996
7997		captionBB.setStyle('position', 'absolute');
7998
7999		if(nav) {
8000			navHeight = parseInt(nav.get('boundingBox').getComputedStyle('height'), 10);
8001		}
8002
8003		if(nav && navOverlay) {
8004			captionBB.setStyle(paddingType, navHeight + 'px');
8005			captionBB.setStyle(navPosition, '0px');
8006		}
8007		else {
8008			captionBB.setStyle(navPosition, navHeight + 'px');
8009		}
8010	},
8011
8012	/**
8013	 * Shows or hides the caption.
8014	 *
8015	 * @method _toggleCaption
8016	 * @protected
8017	 */
8018	_toggleCaption: function()
8019	{
8020		this._toggleOverlay(this.caption.slideshowOverlay);
8021	},
8022
8023	/**
8024	 * Creates and renders a new instance of FL.SlideshowSocial.
8025	 *
8026	 * @method _renderSocial
8027	 * @protected
8028	 */
8029	_renderSocial: function()
8030	{
8031		if(this._hasNavButton('social')) {
8032			this.social = new Y.FL.SlideshowSocial();
8033			this.add(this.social);
8034
8035			this.social.plug(Y.FL.SlideshowOverlay, {
8036				hideDelay: this.get('overlayHideDelay'),
8037				visible: false,
8038				closeButton: true
8039			});
8040
8041			this._syncSocial();
8042		}
8043	},
8044
8045	/**
8046	 * Syncs the social UI styles.
8047	 *
8048	 * @method _syncSocial
8049	 * @protected
8050	 */
8051	_syncSocial: function()
8052	{
8053		var socialBB		= this.social.get('boundingBox'),
8054			navOverlay		= this.get('navOverlay'),
8055			navPosition 	= this.get('navPosition'),
8056			nav 			= this._getNav(),
8057			paddingType		= 'padding' + navPosition.charAt(0).toUpperCase() + navPosition.slice(1),
8058			navHeight		= 0;
8059
8060		socialBB.setStyle('position', 'absolute');
8061
8062		if(nav) {
8063			navHeight = parseInt(nav.get('boundingBox').getComputedStyle('height'), 10);
8064		}
8065
8066		if(nav && navOverlay) {
8067			socialBB.setStyle(paddingType, navHeight + 'px');
8068			socialBB.setStyle(navPosition, '0px');
8069		}
8070		else {
8071			socialBB.setStyle(navPosition, navHeight + 'px');
8072		}
8073	},
8074
8075	/**
8076	 * Shows or hides the social buttons.
8077	 *
8078	 * @method _toggleSocial
8079	 * @protected
8080	 */
8081	_toggleSocial: function()
8082	{
8083		this._toggleOverlay(this.social.slideshowOverlay);
8084	},
8085
8086	/**
8087	 * Shows or hides an overlaid widget based
8088	 * on its current visibility.
8089	 *
8090	 * @method _toggleOverlay
8091	 * @param overlay {Object} The overlay to toggle.
8092	 * @protected
8093	 */
8094	_toggleOverlay: function(overlay)
8095	{
8096		var navType	= this.get('navType'),
8097			nav 	= this._getNav();
8098
8099		if(overlay._visible) {
8100			if(nav && nav.slideshowOverlay) {
8101				nav.slideshowOverlay.enable();
8102			}
8103			overlay.enable();
8104			overlay.hide();
8105		}
8106		else {
8107			if(nav && nav.slideshowOverlay) {
8108				nav.slideshowOverlay.disable();
8109			}
8110			overlay.show();
8111			overlay.disable();
8112		}
8113
8114		if(this.thumbs && navType != 'thumbs' && this.thumbs.slideshowOverlay !== overlay) {
8115			this.thumbs.slideshowOverlay.enable();
8116			this.thumbs.slideshowOverlay.hide();
8117		}
8118		if(this.caption && this.caption.slideshowOverlay !== overlay) {
8119			this.caption.slideshowOverlay.enable();
8120			this.caption.slideshowOverlay.hide();
8121		}
8122		if(this.social && this.social.slideshowOverlay !== overlay) {
8123			this.social.slideshowOverlay.enable();
8124			this.social.slideshowOverlay.hide();
8125		}
8126	},
8127
8128	/**
8129	 * Called when an overlay's close button is clicked.
8130	 *
8131	 * @method _overlayCloseClick
8132	 * @protected
8133	 */
8134	_overlayCloseClick: function()
8135	{
8136		if(this.nav && this.nav.slideshowOverlay) {
8137			this.nav.slideshowOverlay.enable();
8138		}
8139		if(this.thumbs && this.thumbs.slideshowOverlay) {
8140			this.thumbs.slideshowOverlay.enable();
8141		}
8142		if(this.caption) {
8143			this.caption.slideshowOverlay.enable();
8144		}
8145		if(this.social) {
8146			this.social.slideshowOverlay.enable();
8147		}
8148		if(this.imageNavLeft) {
8149			this.imageNavLeft.slideshowOverlay.enable();
8150			this.imageNavRight.slideshowOverlay.enable();
8151		}
8152	},
8153
8154	/**
8155	 * Hides all overlaid widgets.
8156	 *
8157	 * @method _hideAllOverlays
8158	 * @protected
8159	 */
8160	_hideAllOverlays: function()
8161	{
8162		if(this.nav && this.nav.slideshowOverlay && this.nav.slideshowOverlay._visible) {
8163			this.nav.slideshowOverlay.enable();
8164			this.nav.slideshowOverlay.hideWithTimer();
8165		}
8166		if(this.thumbs && this.thumbs.slideshowOverlay && this.thumbs.slideshowOverlay._visible) {
8167			this.thumbs.slideshowOverlay.enable();
8168			this.thumbs.slideshowOverlay.hideWithTimer();
8169		}
8170		if(this.caption && this.caption.slideshowOverlay._visible) {
8171			this.caption.slideshowOverlay.enable();
8172			this.caption.slideshowOverlay.hideWithTimer();
8173		}
8174		if(this.social && this.social.slideshowOverlay._visible) {
8175			this.social.slideshowOverlay.enable();
8176			this.social.slideshowOverlay.hideWithTimer();
8177		}
8178		if(this.imageNavLeft) {
8179			this.imageNavLeft.slideshowOverlay.enable();
8180			this.imageNavLeft.slideshowOverlay.hideWithTimer();
8181			this.imageNavRight.slideshowOverlay.enable();
8182			this.imageNavRight.slideshowOverlay.hideWithTimer();
8183		}
8184	},
8185
8186	/**
8187	 * Checks if overlays are still visible when the mouse enters
8188	 * the bounding box. If they are, overlay functionality is disabled
8189	 * until the overlays are closed by a button or the mouse leaves
8190	 * the bounding box. If only the nav overlay is visible, this
8191	 * function does nothing.
8192	 *
8193	 * @method _checkOverlaysOnMouseenter
8194	 * @protected
8195	 */
8196	_checkOverlaysOnMouseenter: function()
8197	{
8198		var navType			= this.get('navType'),
8199			navOverlay		= this.get('navOverlay'),
8200			nav 			= this._getNav(),
8201			overlayVisible 	= false;
8202
8203		if(this.thumbs && navType != 'thumbs' && this.thumbs.slideshowOverlay._visible) {
8204			overlayVisible = true;
8205			this.thumbs.slideshowOverlay.disable();
8206		}
8207		else if(this.caption && this.caption.slideshowOverlay._visible) {
8208			overlayVisible = true;
8209			this.caption.slideshowOverlay.disable();
8210		}
8211		else if(this.social && this.social.slideshowOverlay._visible) {
8212			overlayVisible = true;
8213			this.social.slideshowOverlay.disable();
8214		}
8215
8216		if(nav && overlayVisible && navOverlay) {
8217			nav.slideshowOverlay.disable();
8218		}
8219	},
8220
8221	/**
8222	 * Checks whether a nav button is set or not.
8223	 *
8224	 * @method _hasNavButton
8225	 * @protected
8226	 * @param button {String} The button to look for.
8227	 * @returns Boolean
8228	 */
8229	_hasNavButton: function(button)
8230	{
8231		var navType = this.get('navType');
8232
8233		if(navType == 'buttons' || navType == 'thumbs' || navType == 'custom') {
8234			if(Y.Array.indexOf(this.get('navButtons'), button) > -1) {
8235				return true;
8236			}
8237			else if(Y.Array.indexOf(this.get('navButtonsLeft'), button) > -1) {
8238				return true;
8239			}
8240			else if(Y.Array.indexOf(this.get('navButtonsRight'), button) > -1) {
8241				return true;
8242			}
8243			else {
8244				return false;
8245			}
8246		}
8247		else {
8248			return false;
8249		}
8250	},
8251
8252	/**
8253	 * @method _removeNavButton
8254	 * @param button {String} The name of the button to remove.
8255	 * @protected
8256	 */
8257	_removeNavButton: function(button)
8258	{
8259		var buttons 			= this.get('navButtons'),
8260			buttonsLeft 		= this.get('navButtonsLeft'),
8261			buttonsRight 		= this.get('navButtonsRight'),
8262			vtTopNavButtons 	= this.get('verticalThumbsTopNavButtons'),
8263			vtBottomNavButtons 	= this.get('verticalThumbsBottomNavButtons');
8264
8265		if(Y.Array.indexOf(buttons, button) > -1) {
8266			buttons.splice(Y.Array.indexOf(buttons, button), 1);
8267		}
8268		if(Y.Array.indexOf(buttonsLeft, button) > -1) {
8269			buttonsLeft.splice(Y.Array.indexOf(buttonsLeft, button), 1);
8270		}
8271		if(Y.Array.indexOf(buttonsRight, button) > -1) {
8272			buttonsRight.splice(Y.Array.indexOf(buttonsRight, button), 1);
8273		}
8274		if(Y.Array.indexOf(vtTopNavButtons, button) > -1) {
8275			vtTopNavButtons.splice(Y.Array.indexOf(vtTopNavButtons, button), 1);
8276		}
8277		if(Y.Array.indexOf(vtBottomNavButtons, button) > -1) {
8278			vtBottomNavButtons.splice(Y.Array.indexOf(vtBottomNavButtons, button), 1);
8279		}
8280
8281		this._set('navButtons', buttons);
8282		this._set('navButtonsLeft', buttonsLeft);
8283		this._set('navButtonsRight', buttonsRight);
8284		this._set('verticalThumbsTopNavButtons', vtTopNavButtons);
8285		this._set('verticalThumbsBottomNavButtons', vtBottomNavButtons);
8286	}
8287
8288}, {
8289
8290	/**
8291	 * Custom CSS class name for the widget.
8292	 *
8293	 * @property CSS_PREFIX
8294	 * @type String
8295	 * @protected
8296	 * @static
8297	 */
8298	CSS_PREFIX: 'fl-slideshow',
8299
8300	/**
8301	 * Static property used to define the default attribute configuration of
8302	 * the Widget.
8303	 *
8304	 * @property ATTRS
8305	 * @type Object
8306	 * @protected
8307	 * @static
8308	 */
8309	ATTRS: {
8310
8311		/**
8312		 * What should happen when the main image is clicked.
8313		 * Options are none, gallery and url. If url is chosen,
8314		 * clickActionUrl must be set.
8315		 *
8316		 * @attribute clickAction
8317		 * @type String
8318		 * @default none
8319		 */
8320		clickAction: {
8321			value: 'none'
8322		},
8323
8324		/**
8325		 * The redirect url to use when clickAction is set to url.
8326		 *
8327		 * @attribute clickActionUrl
8328		 * @type String
8329		 * @default none
8330		 */
8331		clickActionUrl: {
8332			value: ''
8333		},
8334
8335		/**
8336		 * Whether to crop the main image.
8337		 *
8338		 * @attribute crop
8339		 * @type Boolean
8340		 * @default false
8341		 */
8342		crop: {
8343			value: false
8344		},
8345
8346		/**
8347		 * Whether to only crop horizontal images or not.
8348		 *
8349		 * @attribute cropHorizontalsOnly
8350		 * @type Boolean
8351		 * @default false
8352		 */
8353		cropHorizontalsOnly: {
8354			value: false
8355		},
8356
8357		/**
8358		 * Whether to always use the loading image between images
8359		 * or to only use it between albums.
8360		 *
8361		 * @attribute loadingImageAlwaysEnabled
8362		 * @type Boolean
8363		 * @default true
8364		 */
8365		loadingImageAlwaysEnabled: {
8366			value: true
8367		},
8368
8369		/**
8370		 * The x and y position of the main image
8371		 * within the bounding box.
8372		 *
8373		 * @attribute position
8374		 * @type String
8375		 * @default center center
8376		 */
8377		position: {
8378			value: 'center center'
8379		},
8380
8381		/**
8382		 * Whether to right click protect the main image.
8383		 *
8384		 * @attribute protect
8385		 * @type Boolean
8386		 * @default true
8387		 */
8388		protect: {
8389			value: true
8390		},
8391
8392		/**
8393		 * Whether to resize the main image past
8394		 * its original width and height.
8395		 *
8396		 * @attribute upsize
8397		 * @type Boolean
8398		 * @default true
8399		 */
8400        upsize: {
8401			value: true
8402		},
8403
8404		/**
8405		 * The type of transition to use. Possible values are
8406		 * none, fade, slideHorizontal and slideVertical. The
8407		 * value can also be a common seperated string of transitions
8408		 * that will be randomly chosen for each image.
8409		 *
8410		 * @attribute transition
8411		 * @type String
8412		 * @default fade
8413		 */
8414		transition: {
8415			value: 'fade'
8416		},
8417
8418		/**
8419		 * The duration of the transition, measured in seconds.
8420		 *
8421		 * @attribute transitionDuration
8422		 * @type Number
8423		 * @default 1
8424		 */
8425		transitionDuration: {
8426			value: 1
8427		},
8428
8429		/**
8430		 * The type of transition easing to use.
8431		 *
8432		 * @attribute transitionEasing
8433		 * @type String
8434		 * @default ease-out
8435		 */
8436		transitionEasing: {
8437			value: 'ease-out'
8438		},
8439
8440		/**
8441		 * The amount of zoom to use for the Ken Burns effect.
8442		 *
8443		 * @attribute kenBurnsZoom
8444		 * @type Number
8445		 * @default 1.2
8446		 */
8447		kenBurnsZoom: {
8448            value: 1.2
8449		},
8450
8451		/**
8452		 * The type of navigation to use. Possible values are
8453		 * buttons, thumbs, custon and none.
8454		 *
8455		 * @attribute navType
8456		 * @type String
8457		 * @default none
8458		 */
8459		navType: {
8460			value: 'none'
8461		},
8462
8463		/**
8464		 * The position of the main nav. Possible values are top and bottom.
8465		 *
8466		 * @attribute navPosition
8467		 * @type String
8468		 * @default bottom
8469		 */
8470		navPosition: {
8471			value: 'bottom'
8472		},
8473
8474		/**
8475		 * Whether to overlay the nav on top of the main image.
8476		 *
8477		 * @attribute navOverlay
8478		 * @type Boolean
8479		 * @default false
8480		 */
8481		navOverlay: {
8482			value: false
8483		},
8484
8485		/**
8486		 * An array of button names used to render the main nav's buttons.
8487		 *
8488		 * @attribute navButtons
8489		 * @type Array
8490		 * @default []
8491		 */
8492		navButtons: {
8493			value: []
8494		},
8495
8496		/**
8497		 * An array of button names used to render the main nav's left buttons.
8498		 *
8499		 * @attribute navButtonsLeft
8500		 * @type Array
8501		 * @default []
8502		 */
8503		navButtonsLeft: {
8504			value: []
8505		},
8506
8507		/**
8508		 * An array of button names used to render the main nav's right buttons.
8509		 *
8510		 * @attribute navButtonsRight
8511		 * @type Array
8512		 * @default []
8513		 */
8514		navButtonsRight: {
8515			value: []
8516		},
8517
8518		/**
8519		 * Whether to hide the overlays when the mouse moves or not.
8520		 *
8521		 * @attribute overlayHideOnMousemove
8522		 * @type String
8523		 * @default mouseover
8524		 */
8525		overlayHideOnMousemove: {
8526			value: true
8527		},
8528
8529		/**
8530		 * How long to wait before hiding the overlays.
8531		 * Measured in milliseconds.
8532		 *
8533		 * @attribute overlayHideDelay
8534		 * @type Number
8535		 * @default false
8536		 */
8537		overlayHideDelay: {
8538			value: 3000
8539		},
8540
8541		/**
8542		 * Whether to use the image nav or not. If true, a prev
8543		 * and next button will be overlaid on the main image.
8544		 *
8545		 * @attribute imageNavEnabled
8546		 * @type Boolean
8547		 * @default false
8548		 */
8549		imageNavEnabled: {
8550			value: false
8551		},
8552
8553		/**
8554		 * Whether to use the mouse nav or not. If true, the cursor
8555		 * will turn into a prev or next button when over the slideshow.
8556		 *
8557		 * @attribute mouseNavEnabled
8558		 * @type Boolean
8559		 * @default false
8560		 */
8561		mouseNavEnabled: {
8562			value: false
8563		},
8564
8565		/**
8566		 * Whether to hide the thumbs when clicking on a thumbnail
8567		 * image or not. Thumbs always hide navType is set to buttons.
8568		 *
8569		 * @attribute thumbsHideOnClick
8570		 * @type Boolean
8571		 * @default false
8572		 */
8573		thumbsHideOnClick: {
8574			value: true
8575		},
8576
8577		/**
8578		 * The horizontal spacing between thumbs.
8579		 *
8580		 * @attribute thumbsHorizontalSpacing
8581		 * @type Number
8582		 * @default 15
8583		 */
8584		thumbsHorizontalSpacing: {
8585			value: 15
8586		},
8587
8588		/**
8589		 * The vertical spacing between thumbs.
8590		 *
8591		 * @attribute thumbsVerticalSpacing
8592		 * @type Number
8593		 * @default 15
8594		 */
8595		thumbsVerticalSpacing: {
8596			value: 15
8597		},
8598
8599		/**
8600		 * Whether to space the thumbs evenly within a page.
8601		 *
8602		 * @attribute thumbsSpaceEvenly
8603		 * @type Boolean
8604		 * @default true
8605		 */
8606		thumbsSpaceEvenly: {
8607			value: true
8608		},
8609
8610		/**
8611		 * Whether to center single pages of thumbs.
8612		 *
8613		 * @attribute thumbsCenterSinglePage
8614		 * @type Boolean
8615		 * @default false
8616		 */
8617		thumbsCenterSinglePage: {
8618			value: true
8619		},
8620
8621		/**
8622		 * Whether to pause the slideshow when a thumb is clicked.
8623		 *
8624		 * @attribute thumbsPauseOnClick
8625		 * @type Boolean
8626		 * @default false
8627		 */
8628		thumbsPauseOnClick: {
8629			value: false
8630		},
8631
8632		/**
8633		 * The type of transition to use between pages of thumbs.
8634		 *
8635		 * @attribute thumbsTransition
8636		 * @type String
8637		 * @default slideHorizontal
8638		 */
8639		thumbsTransition: {
8640			value: 'slideHorizontal'
8641		},
8642
8643		/**
8644		 * The duration of the transition between pages of thumbs.
8645		 *
8646		 * @attribute thumbsTransitionDuration
8647		 * @type Number
8648		 * @default 0.8
8649		 */
8650		thumbsTransitionDuration: {
8651			value: 0.8
8652		},
8653
8654		/**
8655		 * The type of transition easing to use between pages of thumbs.
8656		 *
8657		 * @attribute thumbsTransitionEasing
8658		 * @type String
8659		 * @default ease-out
8660		 */
8661		thumbsTransitionEasing: {
8662			value: 'ease-out'
8663		},
8664
8665		/**
8666		 * Whether to crop the thumbnails.
8667		 *
8668		 * @attribute thumbsImageCrop
8669		 * @type Boolean
8670		 * @default true
8671		 */
8672		thumbsImageCrop: {
8673			value: true
8674		},
8675
8676		/**
8677		 * The width of each thumbnail.
8678		 *
8679		 * @attribute thumbsImageWidth
8680		 * @type Number
8681		 * @default 50
8682		 */
8683		thumbsImageWidth: {
8684			value: 50
8685		},
8686
8687		/**
8688		 * The height of each thumbnail.
8689		 *
8690		 * @attribute thumbsImageHeight
8691		 * @type Number
8692		 * @default 50
8693		 */
8694		thumbsImageHeight: {
8695			value: 50
8696		},
8697
8698		/**
8699		 * The text to use for the "read less" toggle link.
8700		 *
8701		 * @attribute captionLessLinkText
8702		 * @type String
8703		 * @default Read Less
8704		 */
8705		captionLessLinkText: {
8706			value: 'Read Less'
8707		},
8708
8709		/**
8710		 * The text to use for the "read more" toggle link.
8711		 *
8712		 * @attribute captionMoreLinkText
8713		 * @type String
8714		 * @default Read More
8715		 */
8716		captionMoreLinkText: {
8717			value: 'Read More'
8718		},
8719
8720		/**
8721		 * The length of the caption to show. If greater than -1,
8722		 * the text will be truncated and a read more link will
8723		 * be displayed. If set to -1, the entire caption will be shown.
8724		 *
8725		 * @attribute captionTextLength
8726		 * @type Number
8727		 * @default 200
8728		 */
8729		captionTextLength: {
8730			value: 200
8731		},
8732
8733		/**
8734		 * Whether to strip out HTML tags in the caption
8735		 * text or not.
8736		 *
8737		 * @attribute captionStripTags
8738		 * @type Boolean
8739		 * @default false
8740		 */
8741		captionStripTags: {
8742			value: false
8743		},
8744
8745		/**
8746		 * Whether to use the vertical thumbs or not.
8747		 *
8748		 * @attribute verticalThumbsEnabled
8749		 * @type Boolean
8750		 * @default false
8751		 */
8752		verticalThumbsEnabled: {
8753			value: false
8754		},
8755
8756		/**
8757		 * Position of the vertical thumbs. Possible values
8758		 * are either left or right.
8759		 *
8760		 * @attribute verticalThumbsPosition
8761		 * @type String
8762		 * @default left
8763		 */
8764		verticalThumbsPosition: {
8765			value: 'left'
8766		},
8767
8768		/**
8769		 * Whether to overlay the vertical thumbs
8770		 * on the main image or not.
8771		 *
8772		 * @attribute verticalThumbsOverlay
8773		 * @type Boolean
8774		 * @default false
8775		 */
8776		verticalThumbsOverlay: {
8777			value: false
8778		},
8779
8780		/**
8781		 * The number of columns for the vertical thumbs.
8782		 *
8783		 * @attribute verticalThumbsColumns
8784		 * @type Number
8785		 * @default 1
8786		 */
8787		verticalThumbsColumns: {
8788			value: 1
8789		},
8790
8791		/**
8792		 * Whether to use the vertical thumbs top nav or not.
8793		 *
8794		 * @attribute verticalThumbsTopNavEnabled
8795		 * @type Boolean
8796		 * @default false
8797		 */
8798		verticalThumbsTopNavEnabled: {
8799			value: false
8800		},
8801
8802		/**
8803		 * An array of button names used to render
8804		 * the vertical thumbs top nav buttons.
8805		 *
8806		 * @attribute verticalThumbsTopNavButtons
8807		 * @type Array
8808		 * @default prevPage, nextPage
8809		 */
8810		verticalThumbsTopNavButtons: {
8811			value: ['prevPage', 'nextPage']
8812		},
8813
8814		/**
8815		 * Whether to use the vertical thumbs bottom nav or not.
8816		 *
8817		 * @attribute verticalThumbsBottomNavEnabled
8818		 * @type Boolean
8819		 * @default false
8820		 */
8821		verticalThumbsBottomNavEnabled: {
8822			value: true
8823		},
8824
8825		/**
8826		 * An array of button names used to render
8827		 * the vertical thumbs top nav buttons.
8828		 *
8829		 * @attribute verticalThumbsBottomNavButtons
8830		 * @type Array
8831		 * @default prevPage, nextPage
8832		 */
8833		verticalThumbsBottomNavButtons: {
8834			value: ['prevPage', 'nextPage']
8835		},
8836
8837		/**
8838		 * The horizontal spacing between vertical thumbs.
8839		 *
8840		 * @attribute verticalThumbsHorizontalSpacing
8841		 * @type Number
8842		 * @default 15
8843		 */
8844		verticalThumbsHorizontalSpacing: {
8845			value: 15
8846		},
8847
8848		/**
8849		 * The vertical spacing between vertical thumbs.
8850		 *
8851		 * @attribute verticalThumbsVerticalSpacing
8852		 * @type Number
8853		 * @default 15
8854		 */
8855		verticalThumbsVerticalSpacing: {
8856			value: 15
8857		},
8858
8859		/**
8860		 * Whether to space the vertical thumbs evenly within a page.
8861		 *
8862		 * @attribute verticalThumbsSpaceEvenly
8863		 * @type Boolean
8864		 * @default false
8865		 */
8866		verticalThumbsSpaceEvenly: {
8867			value: false
8868		},
8869
8870		/**
8871		 * Whether to pause the slideshow when a vertical thumb is clicked.
8872		 *
8873		 * @attribute verticalThumbsPauseOnClick
8874		 * @type Boolean
8875		 * @default false
8876		 */
8877		verticalThumbsPauseOnClick: {
8878			value: false
8879		},
8880
8881		/**
8882		 * Whether to crop the vertical thumbs or not.
8883		 *
8884		 * @attribute verticalThumbsImageCrop
8885		 * @type Boolean
8886		 * @default true
8887		 */
8888		verticalThumbsImageCrop: {
8889			value: true
8890		},
8891
8892		/**
8893		 * The width of each vertical thumbnail.
8894		 *
8895		 * @attribute verticalThumbsImageWidth
8896		 * @type Number
8897		 * @default 75
8898		 */
8899		verticalThumbsImageWidth: {
8900			value: 75
8901		},
8902
8903		/**
8904		 * The height of each vertical thumbnail.
8905		 *
8906		 * @attribute verticalThumbsImageHeight
8907		 * @type Number
8908		 * @default 75
8909		 */
8910		verticalThumbsImageHeight: {
8911			value: 75
8912		},
8913
8914		/**
8915		 * The type of transition to use between pages of vertical thumbs.
8916		 *
8917		 * @attribute verticalThumbsTransition
8918		 * @type String
8919		 * @default slideVertical
8920		 */
8921		verticalThumbsTransition: {
8922			value: 'slideVertical'
8923		},
8924
8925		/**
8926		 * The duration of the transition between pages of vertical thumbs.
8927		 *
8928		 * @attribute verticalThumbsTransitionDuration
8929		 * @type Number
8930		 * @default 0.8
8931		 */
8932		verticalThumbsTransitionDuration: {
8933			value: 0.8
8934		},
8935
8936		/**
8937		 * The type of transition easing to use between pages of vertical thumbs.
8938		 *
8939		 * @attribute verticalThumbsTransitionEasing
8940		 * @type String
8941		 * @default ease-out
8942		 */
8943		verticalThumbsTransitionEasing: {
8944			value: 'ease-out'
8945		},
8946
8947		/**
8948		 * Whether to use the Google Plus button or not.
8949		 *
8950		 * @attribute googlePlusButtonEnabled
8951		 * @type Boolean
8952		 * @default true
8953		 */
8954		googlePlusButtonEnabled: {
8955			value: true
8956		},
8957
8958		/**
8959		 * Whether to use the Facebook like button or not.
8960		 *
8961		 * @attribute likeButtonEnabled
8962		 * @type Boolean
8963		 * @default true
8964		 */
8965		likeButtonEnabled: {
8966			value: true
8967		},
8968
8969		/**
8970		 * Whether to use the Pinterest button or not.
8971		 *
8972		 * @attribute pinterestButtonEnabled
8973		 * @type Boolean
8974		 * @default true
8975		 */
8976		pinterestButtonEnabled: {
8977			value: true
8978		},
8979
8980		/**
8981		 * Whether to use the Tweet button or not.
8982		 *
8983		 * @attribute tweetButtonEnabled
8984		 * @type Boolean
8985		 * @default true
8986		 */
8987		tweetButtonEnabled: {
8988			value: true
8989		},
8990
8991		/**
8992		 * Whether to use touch gestures, when available,
8993		 * to transition between images or not.
8994		 *
8995		 * @attribute touchSupport
8996		 * @type Boolean
8997		 * @default true
8998		 */
8999		touchSupport: {
9000			value: true
9001		}
9002	}
9003});
9004
9005
9006}, '2.0.0' ,{requires:['anim', 'event-mouseenter', 'plugin', 'transition', 'fl-event-move', 'fl-slideshow-css', 'fl-slideshow-base', 'fl-utils', 'sm-fonticon']});
9007
9008
9009YUI.add('fl-slideshow-album-loader', function(Y) {
9010
9011/**
9012 * @module fl-slideshow-album-loader
9013 */
9014
9015/**
9016 * Loads slideshow albums using a provided source object.
9017 *
9018 * @namespace FL
9019 * @class SlideshowAlbumLoader
9020 * @constructor
9021 * @param config {Object} Configuration object
9022 * @extends Base
9023 */
9024Y.namespace('FL').SlideshowAlbumLoader = Y.Base.create('fl-slideshow-album-loader', Y.Base, [], {
9025
9026	/**
9027	* The source object used for loading.
9028	*
9029	* @property _source
9030	* @type Object
9031	* @default null
9032	* @protected
9033	*/
9034	_source: null,
9035
9036	/**
9037	 * Loads slideshow album data using the provided source object.
9038	 *
9039	 * @method load
9040	 * @param source {Object} The source object to use for loading.
9041	 */
9042	load: function(source)
9043	{
9044		this._source = source;
9045
9046		/**
9047		 * Fires before a new load request is made.
9048		 *
9049		 * @event start
9050		 */
9051		this.fire('start');
9052
9053		this[Y.FL.SlideshowAlbumLoader.TYPES[source.type]].call(this);
9054	},
9055
9056	 /**
9057	 * Called when a source type completes loading
9058	 * and fires the complete event.
9059	 *
9060	 * @method _loadComplete
9061	 * @param o {Object} Passed to complete event subscribers.
9062	 * @protected
9063	 */
9064	_loadComplete: function(o)
9065	{
9066		o = this._randomize(o);
9067
9068		/**
9069		 * Fires after a new load request is made.
9070		 *
9071		 * @event complete
9072		 */
9073		this.fire('complete', o);
9074	},
9075
9076	/**
9077	 * Randomizes images in an album.
9078	 *
9079	 * @method _randomize
9080	 * @param album {Object} The album to randomize.
9081	 * @protected
9082	 */
9083	_randomize: function(o)
9084	{
9085		var i;
9086
9087		if(this.get('randomize')) {
9088			o.albumInfo.images.sort(function() { return 0.5 - Math.random(); });
9089
9090			for(i = 0; i < o.albumInfo.images.length; i++) {
9091				o.albumInfo.images[i].index = i;
9092			}
9093		}
9094
9095		return o;
9096	},
9097
9098	/**
9099	 * Loads slideshow album data from SmugMug.
9100	 *
9101	 * @method _loadSmugMug
9102	 * @protected
9103	 */
9104	_loadSmugMug: function()
9105	{
9106		var sm = new Y.FL.SmugMugAPI();
9107
9108		sm.on('complete', this._loadSmugMugSuccess, this);
9109		sm.addParam('method', 'smugmug.images.get');
9110		sm.addParam('AlbumID', this._source.id);
9111		sm.addParam('AlbumKey', this._source.key);
9112		sm.addParam('Extras', 'Caption,Format,FileName');
9113
9114		// Gallery password
9115		if(this._source.password) {
9116			sm.addParam('Password', this._source.password);
9117		}
9118
9119		// Site-wide password
9120		if(this._source.sp) {
9121			sm.addParam('SitePassword', this._source.sp);
9122		}
9123
9124		sm.request();
9125	},
9126
9127	/**
9128	 * Processes slideshow album data loaded from SmugMug.
9129	 *
9130	 * @method _loadSmugMugSuccess
9131	 * @param e {Object} The custom event object passed to this function.
9132	 * @protected
9133	 */
9134	_loadSmugMugSuccess: function(e)
9135	{
9136		var images 		= e.Album.Images,
9137			album 		= {},
9138			proxy       = typeof this._source.proxy !== 'undefined' ? this._source.proxy : '',
9139			buyBase		= '',
9140			baseURL 	= '',
9141			ext 		= '',
9142			format		= '',
9143			i			= 0,
9144			temp		= null,
9145			iframe		= null;
9146
9147		album.index = this._source.index;
9148		album.id = e.Album.id;
9149		album.key = e.Album.Key;
9150		album.link = e.Album.URL;
9151		album.title = this._source.title ? this._source.title : '';
9152		album.images = [];
9153		buyBase = album.link.replace('https://', '').split('/').shift();
9154		buyBase = 'https://' + buyBase + '/buy/' + e.Album.id + '_' + e.Album.Key + '/';
9155
9156		for(i = 0; i < images.length; i++)
9157		{
9158			baseURL = proxy + e.Album.URL + '/' + images[i].id + '_' + images[i].Key;
9159			format = images[i].Format.toLowerCase();
9160			ext = format == 'mp4' ? '.jpg' : '.' + format;
9161
9162			album.images[i] = {};
9163			album.images[i].index = i;
9164			album.images[i].sourceType = 'smugmug';
9165			album.images[i].albumId = e.Album.id;
9166			album.images[i].albumKey = e.Album.Key;
9167			album.images[i].id = images[i].id;
9168			album.images[i].key = images[i].Key;
9169			album.images[i].filename = images[i].FileName;
9170			album.images[i].format = format;
9171			album.images[i].caption = images[i].Caption || '';
9172			album.images[i].link = e.Album.URL + '#' + images[i].id + '_' + images[i].Key;
9173			album.images[i].tinyURL = baseURL + '-Ti' + ext;
9174			album.images[i].thumbURL = baseURL + '-Th' + ext;
9175			album.images[i].smallURL = baseURL + '-S' + ext;
9176			album.images[i].mediumURL = baseURL + '-M' + ext;
9177			album.images[i].largeURL = baseURL + '-L' + ext;
9178			album.images[i].xlargeURL = baseURL + '-XL' + ext;
9179			album.images[i].x2largeURL = baseURL + '-X2' + ext;
9180			album.images[i].x3largeURL = baseURL + '-X3' + ext;
9181			album.images[i].buyURL = buyBase + images[i].id + '_' + images[i].Key;
9182			album.images[i].iframe = '';
9183
9184			if(album.images[i].caption.indexOf('iframe')) {
9185				temp = Y.Node.create('<div>'+ album.images[i].caption +'</div>');
9186				iframe = temp.one('iframe');
9187
9188				if(iframe) {
9189					album.images[i].iframe = iframe.getAttribute('src');
9190					album.images[i].caption = album.images[i].caption.replace(/<iframe.*>.*<\/iframe>/gi, '');
9191				}
9192			}
9193		}
9194
9195		this._loadComplete({ 'albumInfo': album });
9196	},
9197
9198	/**
9199	 * Loads slideshow album data from an array of urls.
9200	 *
9201	 * NOTE: You must have a large URL.
9202	 *
9203	 * @method _loadUrls
9204	 * @protected
9205	 */
9206	_loadUrls: function()
9207	{
9208		var album 	= {},
9209			i		= 0;
9210
9211		album.index = this._source.index;
9212		album.title = this._source.title ? this._source.title : '';
9213		album.images = [];
9214
9215		for( ; i < this._source.urls.length; i++)
9216		{
9217			album.images[i] = {};
9218			album.images[i].index = i;
9219			album.images[i].sourceType = 'urls';
9220			album.images[i].filename = this._source.urls[i].largeURL.split('/').pop();
9221			album.images[i].format = '';
9222			album.images[i].caption = this._source.urls[i].caption || '';
9223			album.images[i].link = this._source.urls[i].largeURL;
9224			album.images[i].thumbURL = this._source.urls[i].thumbURL || this._source.urls[i].largeURL;
9225			album.images[i].smallURL = this._source.urls[i].smallURL || this._source.urls[i].largeURL;
9226			album.images[i].mediumURL = this._source.urls[i].mediumURL || this._source.urls[i].largeURL;
9227			album.images[i].largeURL = this._source.urls[i].largeURL; // Must have a large URL
9228			album.images[i].xlargeURL = this._source.urls[i].xlargeURL || this._source.urls[i].largeURL;
9229			album.images[i].x2largeURL = this._source.urls[i].x2largeURL || this._source.urls[i].largeURL;
9230			album.images[i].x3largeURL = this._source.urls[i].x3largeURL || this._source.urls[i].largeURL;
9231			album.images[i].buyURL = this._source.urls[i].buyURL || '';
9232			album.images[i].iframe = this._source.urls[i].iframe || '';
9233		}
9234
9235		this._loadComplete({ 'albumInfo': album });
9236	}
9237
9238}, {
9239
9240	/**
9241	* Static property used to define the default attribute configuration of
9242	* the Widget.
9243	*
9244	* @property ATTRS
9245	* @type Object
9246	* @protected
9247	* @static
9248	*/
9249	ATTRS: {
9250
9251		/**
9252		 * If true, the images will be randomized after loading.
9253		 *
9254		 * @attribute randomize
9255		 * @type Boolean
9256		 * @default false
9257		 */
9258		randomize: {
9259			value: false
9260		}
9261	},
9262
9263	/**
9264	* The types of source data that can be loaded
9265	* and associated functions.
9266	*
9267	* @property TYPES
9268	* @type Object
9269	* @readOnly
9270	* @protected
9271	* @static
9272	*/
9273	TYPES: {
9274		'smugmug': '_loadSmugMug',
9275		'flickr': '_loadFlickr',
9276		'picasa': '_loadPicasa',
9277		'urls': '_loadUrls',
9278		'html': '_loadHtml'
9279	}
9280});
9281
9282
9283}, '2.0.0' ,{requires:['base', 'fl-smugmug-api']});
9284
9285
9286YUI.add('fl-slideshow-base', function(Y) {
9287
9288/**
9289 * @module fl-slideshow-base
9290 */
9291
9292/**
9293 * The base class that gets extended when creating new
9294 * slideshow widgets. Manages loading, playing, and resizing.
9295 * <p>
9296 * While SlideshowBase can be instantiated, it is only meant to
9297 * be extended and does not display any images.
9298 *
9299 * @namespace FL
9300 * @class SlideshowBase
9301 * @constructor
9302 * @param config {Object} Configuration object
9303 * @extends Widget
9304 */
9305Y.namespace('FL').SlideshowBase = Y.Base.create('fl-slideshow-base', Y.Widget, [Y.WidgetParent], {
9306
9307	/**
9308	 * FL.SlideshowAlbumLoader instance used to load albums.
9309	 *
9310	 * @property _loader
9311	 * @type FL.SlideshowAlbumLoader
9312	 * @default null
9313	 * @protected
9314	 */
9315	_albumLoader: null,
9316
9317	/**
9318	* An array of albums loaded from the source attribute.
9319	* Each album is an array of objects containing image info.
9320	*
9321	* @property albums
9322	* @type Array
9323	* @default []
9324	*/
9325	albums: [],
9326
9327	/**
9328	 * Info for the active album.
9329	 *
9330	 * @property albumInfo
9331	 * @type Object
9332	 * @default null
9333	 */
9334	albumInfo: null,
9335
9336	/**
9337	 * A number that represents the index of the active
9338	 * album in the albums array.
9339	 *
9340	 * @property albumIndex
9341	 * @type Number
9342	 * @default null
9343	 */
9344	albumIndex: null,
9345
9346	/**
9347	 * Info for the active image.
9348	 *
9349	 * @property imageInfo
9350	 * @type Object
9351	 * @default null
9352	 */
9353	imageInfo: null,
9354
9355	/**
9356	 * A number that represents the index of the active
9357	 * image in the albumInfo array.
9358	 *
9359	 * @property imageIndex
9360	 * @type Number
9361	 * @default null
9362	 */
9363	imageIndex: null,
9364
9365	/**
9366	 * A number that represents the index of the last
9367	 * image that was loaded in the albumInfo array.
9368	 *
9369	 * @property lastImageIndex
9370	 * @type Number
9371	 * @default null
9372	 */
9373	lastImageIndex: null,
9374
9375	/**
9376	 * Timer for the delay before resizing if one is set.
9377	 *
9378	 * @property _resizeTimer
9379	 * @type Object
9380	 * @default null
9381	 * @protected
9382	 */
9383	_resizeTimer: null,
9384
9385	/**
9386	 * Flag for whether the slideshow is currently playing or not.
9387	 *
9388	 * @property playing
9389	 * @type Boolean
9390	 * @default false
9391	 * @protected
9392	 */
9393	_playing: false,
9394
9395	/**
9396	 * Timer for the break in between images when
9397	 * the slideshow is playing.
9398	 *
9399	 * @property _playingTimer
9400	 * @type Object
9401	 * @default null
9402	 * @protected
9403	 */
9404	_playingTimer: null,
9405
9406	/**
9407	 * If set, the slideshow will only auto start when
9408	 * this event is fired.
9409	 *
9410	 * @property _playingTimerEvent
9411	 * @type Object
9412	 * @default null
9413	 * @protected
9414	 */
9415	_playingTimerEvent: null,
9416
9417	/**
9418	 * An instance of FL.Spinner that is shown and hidden
9419	 * using _showLoadingImage and _hideLoadingImage when
9420	 * a loading activity occurs.
9421	 *
9422	 * @property _loadingImage
9423	 * @type FL.Spinner
9424	 * @default null
9425	 * @protected
9426	 */
9427	_loadingImage: null,
9428
9429	/**
9430	 * An div node that wraps the loading image.
9431	 *
9432	 * @property _loadingImageWrap
9433	 * @type Node
9434	 * @default null
9435	 * @protected
9436	 */
9437	_loadingImageWrap: null,
9438
9439	/**
9440	 * Whether the loading image is visible or not.
9441	 *
9442	 * @property _loadingImageVisible
9443	 * @type Boolean
9444	 * @default false
9445	 * @protected
9446	 */
9447	_loadingImageVisible: false,
9448
9449	/**
9450	 * A timer to delay the display of the loading image.
9451	 *
9452	 * @property _loadingImageTimer
9453	 * @type Object
9454	 * @default null
9455	 * @protected
9456	 */
9457	_loadingImageTimer: null,
9458
9459	/**
9460	 * The container to insert the loading image into. If
9461	 * no container is set, the loading image will be inserted
9462	 * into the widget's bounding box.
9463	 *
9464	 * @property _loadingImageContainer
9465	 * @type Object
9466	 * @default null
9467	 * @protected
9468	 */
9469	_loadingImageContainer: null,
9470
9471	/**
9472	 * The intial height of the slideshow. Used to resize
9473	 * back to the starting height when exiting stretchy.
9474	 *
9475	 * @property _initialHeight
9476	 * @type Number
9477	 * @default null
9478	 * @protected
9479	 */
9480	_initialHeight: null,
9481
9482	/**
9483	 * The intial width of the slideshow. Used to resize
9484	 * back to the starting width when exiting stretchy.
9485	 *
9486	 * @property _initialWidth
9487	 * @type Number
9488	 * @default null
9489	 * @protected
9490	 */
9491	_initialWidth: null,
9492
9493	/**
9494	 * @method initializer
9495	 * @protected
9496	 */
9497	initializer: function()
9498	{
9499		// Loader
9500		this._albumLoader = new Y.FL.SlideshowAlbumLoader({
9501			randomize: this.get('randomize')
9502		});
9503	},
9504
9505	/**
9506	 * @method renderUI
9507	 * @protected
9508	 */
9509	renderUI: function()
9510	{
9511		this._renderLoadingImage();
9512	},
9513
9514	/**
9515	 * @method bindUI
9516	 * @protected
9517	 */
9518	bindUI: function()
9519	{
9520		// Album load complete
9521		this._albumLoader.on('complete', this._loadAlbumComplete, this);
9522
9523		// Resize Events
9524		Y.one(window).on('fl-slideshow-base|resize', this._delayResize, this);
9525		Y.one(window).on('fl-slideshow-base|orientationchange', this._delayResize, this);
9526
9527		// Key Events
9528		Y.Node.one('body').on('keydown', Y.bind(this._onKey, this));
9529	},
9530
9531	/**
9532	 * @method syncUI
9533	 * @protected
9534	 */
9535	syncUI: function()
9536	{
9537		this.get('boundingBox').addClass('fl-slideshow-' + this.get('color'));
9538
9539		this.resize();
9540
9541		if(this.get('loadOnRender')) {
9542			this.loadAlbum(this.get('defaultAlbum'), this.get('defaultImage'));
9543		}
9544	},
9545
9546	/**
9547	 * Add album data to the source object.
9548	 *
9549	 * @method addAlbum
9550	 * @protected
9551	 */
9552	addAlbum: function(data)
9553	{
9554		var source = this.get('source'),
9555            i      = source.length;
9556
9557		source[i] = data;
9558		source[i].index = i;
9559
9560		this.set('source', source);
9561	},
9562
9563	/**
9564	 * Loads an album from the source array with the provided albumIndex.
9565	 * If no albumIndex is provided, the first album in the array will be loaded.
9566	 * An image to load can also be specified using imageIndex.
9567	 *
9568	 * @method loadAlbum
9569	 * @param albumIndex {Number} The album index to load from the source array.
9570	 * @param imageIndex {Number} The image index to load from the album array.
9571	 */
9572	loadAlbum: function(albumIndex, imageIndex)
9573	{
9574		var source 			= this.get('source'),
9575			loadImageIndex 	= typeof imageIndex == 'undefined' ? 0 : imageIndex;
9576
9577		// Reset internal image indexes.
9578		this.imageIndex = null;
9579		this.lastImageIndex = null;
9580
9581		/**
9582		 * Fires before a new album request is made.
9583		 *
9584		 * @event albumLoadStart
9585		 */
9586		this.fire('albumLoadStart');
9587
9588		// Load an image after the album.
9589		this.once('albumLoadComplete', Y.bind(this.loadImage, this, loadImageIndex));
9590
9591		// Load data passed from another slideshow instance.
9592		if(source[albumIndex] && source[albumIndex].type == 'album-data') {
9593			this.albums[albumIndex] = source[albumIndex].data;
9594			this._loadAlbumComplete({albumInfo: this.albums[albumIndex]});
9595		}
9596		// Load the album from the albums array.
9597		else if(source[albumIndex] && this.albums[albumIndex]) {
9598			this._loadAlbumComplete({albumInfo: this.albums[albumIndex]});
9599		}
9600		// Load the album using the album loader.
9601		else {
9602			this._albumLoader.load(source[albumIndex] || source[0]);
9603		}
9604	},
9605
9606	/**
9607	 * Processes the loaded album and fires the albumLoadComplete event.
9608	 *
9609	 * @method _loadAlbumComplete
9610	 * @param o {Object} The custom event object passed to this method.
9611	 * @protected
9612	 */
9613	_loadAlbumComplete: function(o)
9614	{
9615		this.albums[o.albumInfo.index] = o.albumInfo;
9616		this.albumInfo = o.albumInfo;
9617		this.albumIndex = o.albumInfo.index;
9618
9619		/**
9620		 * Fires after a new album request is made.
9621		 *
9622		 * @event albumLoadComplete
9623		 */
9624		this.fire('albumLoadComplete');
9625
9626		// Auto Play
9627		if(this.get('autoPlay')) {
9628			this._playingTimerStart();
9629			this.fire('played');
9630			this._playing = true;
9631		}
9632	},
9633
9634	/**
9635	 * Sets the active image index and fires the imageLoadComplete event.
9636	 *
9637	 * @method loadImage
9638	 * @param index {Number} The image index to load.
9639	 */
9640	loadImage: function(index)
9641	{
9642		if(this._playing) {
9643			this._playingTimerStart();
9644		}
9645
9646		index = index < 0 ? this.albumInfo.images.length - 1 : index;
9647		index = index >= this.albumInfo.images.length ? 0 : index;
9648
9649		this.lastImageIndex = this.imageIndex;
9650		this.imageIndex = index;
9651		this.imageInfo = this.albumInfo.images[index];
9652
9653		/**
9654		 * Fires after a new image index is set.
9655		 *
9656		 * @event imageLoadComplete
9657		 */
9658		this.fire('imageLoadComplete', { 'imageInfo': this.imageInfo });
9659	},
9660
9661	/**
9662	 * Loads the previous image.
9663	 *
9664	 * @method prevImage
9665	 */
9666	prevImage: function()
9667	{
9668		if(this.get('pauseOnNextOrPrev')) {
9669			this.pause();
9670		}
9671
9672		this.loadImage(this.imageIndex - 1);
9673
9674		/**
9675		 * Fires when the previous image is loaded.
9676		 *
9677		 * @event prevImage
9678		 */
9679		this.fire('prevImage');
9680	},
9681
9682	/**
9683	 * Loads the next image.
9684	 *
9685	 * @method nextImage
9686	 */
9687	nextImage: function()
9688	{
9689		if(this.get('pauseOnNextOrPrev')) {
9690			this.pause();
9691		}
9692
9693		this.loadImage(this.imageIndex + 1);
9694
9695		/**
9696		 * Fires when the next image is loaded.
9697		 *
9698		 * @event nextImage
9699		 */
9700		this.fire('nextImage');
9701	},
9702
9703	/**
9704	 * Keyboard navigation for the next and prev images.
9705	 *
9706	 * @method _onKey
9707	 * @protected
9708	 */
9709	_onKey: function(e)
9710	{
9711		switch(e.keyCode) {
9712
9713			case 37:
9714			this.prevImage();
9715			break;
9716
9717			case 39:
9718			this.nextImage();
9719			break;
9720		}
9721	},
9722
9723	/**
9724	 * Resizes the slideshow using either the
9725	 * stretchy or standard functions.
9726	 *
9727	 * @method resize
9728	 */
9729	resize: function()
9730	{
9731		var stretchy 		= this.get('stretchy'),
9732			stretchyType 	= this.get('stretchyType'),
9733			width 			= parseInt(Y.one('body').get('winWidth'), 10),
9734			threshold		= this.get('responsiveThreshold');
9735
9736        // Stretchy resize to the window only if the parent width is greater
9737        // than the responsive threshold and stretchyType is set to window.
9738		if(width > threshold && stretchy && stretchyType == 'window') {
9739			this._stretchyWindowResize();
9740		}
9741
9742		// Ratio resize if the parent width is less than the responsive
9743		// threshold or if stretchyType is set to ratio.
9744		else if((width <= threshold) || (stretchy && stretchyType == 'ratio')) {
9745			this._stretchyRatioResize();
9746		}
9747
9748		// Do a standard resize based on the height and
9749		// width passed to the constructor function.
9750		else {
9751			this._standardResize();
9752		}
9753
9754		/**
9755		 * Fires when the slideshow is resized.
9756		 *
9757		 * @event resize
9758		 */
9759		this.fire('resize');
9760	},
9761
9762	/**
9763	 * @method _standardResize
9764	 * @protected
9765	 */
9766	_standardResize: function()
9767	{
9768		var stretchy 		= this.get('stretchy'),
9769		    stretchyType 	= this.get('stretchyType'),
9770		    bb		        = this.get('boundingBox'),
9771			parent	        = bb.get('parentNode'),
9772			parentHeight 	= parseInt(parent.getComputedStyle('height'), 10),
9773			parentWidth 	= parseInt(parent.getComputedStyle('width'), 10),
9774			height 	        = this.get('height'),
9775			width 	        = this.get('width');
9776
9777        // Window resize if we are in fullscreen.
9778        if(bb.hasClass('fl-fullscreen-active')) {
9779			this._stretchyWindowResize();
9780			return;
9781		}
9782
9783		// Resize to the width and height of the parent.
9784		else if(stretchy && stretchyType == 'contain') {
9785			bb.setStyle('height', parentHeight + 'px');
9786    		bb.setStyle('width', parentWidth + 'px');
9787		}
9788
9789		// Ratio resize if we don't have a height defined.
9790		else if(!Y.Lang.isNumber(height)) {
9791			this._stretchyRatioResize();
9792			return;
9793		}
9794
9795		// Resize to the defined width and height.
9796		else {
9797
9798    		bb.setStyle('height', height + 'px');
9799
9800    		if(width) {
9801    			bb.setStyle('width', width + 'px');
9802    		}
9803    		else {
9804        		bb.setStyle('width', parentWidth + 'px');
9805    		}
9806		}
9807	},
9808
9809	/**
9810	 * Resizes to the height of the window, compensating
9811	 * for any padding.
9812	 *
9813	 * @method _stretchyWindowResize
9814	 * @protected
9815	 */
9816	_stretchyWindowResize: function()
9817	{
9818		var bb				= this.get('boundingBox'),
9819			verticalSpace	= this.get('stretchyVerticalSpace'),
9820			paddingTop 		= parseInt(bb.getStyle('paddingTop'), 10),
9821			paddingBottom 	= parseInt(bb.getStyle('paddingBottom'), 10),
9822			height 			= parseInt(Y.one('body').get('winHeight'), 10),
9823			width			= '';
9824
9825		// Set the vertical space to 0 and width to the
9826		// window's width if we are in fullscreen mode.
9827		if(bb.hasClass('fl-fullscreen-active')) {
9828			verticalSpace = 0;
9829			width = parseInt(Y.one('body').get('winWidth'), 10) + 'px';
9830		}
9831
9832		height =  (height - paddingTop - paddingBottom - verticalSpace) + 'px';
9833
9834		bb.setStyle('height', height);
9835		bb.setStyle('width', width);
9836	},
9837
9838	/**
9839	 * Resizes the height by multiplying the width and stretchyRatio value.
9840	 *
9841	 * @method _stretchyRatioResize
9842	 * @protected
9843	 */
9844	_stretchyRatioResize: function()
9845	{
9846		var bb				= this.get('boundingBox'),
9847			parent			= bb.get('parentNode'),
9848			verticalSpace	= 0,
9849			stretchyRatio	= this.get('stretchyRatio'),
9850			paddingTop 		= parseInt(bb.getStyle('paddingTop'), 10),
9851			paddingBottom 	= parseInt(bb.getStyle('paddingBottom'), 10),
9852			computedWidth 	= parseInt(parent.getComputedStyle('width'), 10),
9853			winHeight		= parseInt(Y.one('body').get('winHeight'), 10),
9854			winWidth		= parseInt(Y.one('body').get('winWidth'), 10),
9855			height 			= computedWidth * stretchyRatio,
9856			width			= '';
9857
9858		// Use the window's height and width if we are in fullscreen mode.
9859		if(bb.hasClass('fl-fullscreen-active')) {
9860			height = winHeight;
9861			width = winWidth;
9862		}
9863
9864		height = (height - paddingTop - paddingBottom - verticalSpace) + 'px';
9865
9866		bb.setStyle('height', height);
9867		bb.setStyle('width', width);
9868	},
9869
9870	/**
9871	 * Resizes the slideshow after the resize timer completes.
9872	 *
9873	 * @method _delayResize
9874	 * @protected
9875	 */
9876	_delayResize: function()
9877	{
9878		if(this._resizeTimer) {
9879			this._resizeTimer.cancel();
9880		}
9881
9882		this._resizeTimer = Y.later(300, this, this.resize);
9883	},
9884
9885	/**
9886	 * Starts a new playing timer and fires the played event.
9887	 *
9888	 * @method play
9889	 */
9890	play: function()
9891	{
9892		this._playingTimer = Y.later(this.get('speed'), this, this._playingTimerComplete);
9893
9894		/**
9895		 * Fires when the playing timer starts.
9896		 *
9897		 * @event played
9898		 */
9899		this.fire('played');
9900		this._playing = true;
9901	},
9902
9903	/**
9904	 * Cancels the current playing timer and fires the paused event.
9905	 *
9906	 * @method pause
9907	 */
9908	pause: function()
9909	{
9910		this._playingTimerCancel();
9911
9912		/**
9913		 * Fires when the playing timer is canceled.
9914		 *
9915		 * @event paused
9916		 */
9917		this.fire('paused');
9918		this._playing = false;
9919	},
9920
9921	/**
9922	 * A new playing timer will start when this event is fired.
9923	 *
9924	 * @method _setPlayingTimerEvent
9925	 * @param obj {Object} The event's host object.
9926	 * @param e {String} The event to fire on the host object.
9927	 * @protected
9928	 */
9929	_setPlayingTimerEvent: function(obj, e)
9930	{
9931		this._playingTimerEvent = {
9932			'obj': obj,
9933			'e': e
9934		};
9935	},
9936
9937	/**
9938	 * Cancels the playing timer if it is running and starts a new one.
9939	 * The next image is loaded when the timer completes.
9940	 *
9941	 * @method _playingTimerStart
9942	 * @protected
9943	 */
9944	_playingTimerStart: function(e)
9945	{
9946		this._playingTimerCancel();
9947
9948		if(!e && this._playingTimerEvent !== null) {
9949			this._playingTimerEvent.obj.once('fl-slideshow-base|' + this._playingTimerEvent.e, Y.bind(this._playingTimerStart, this));
9950		}
9951		else {
9952			this._playingTimer = Y.later(this.get('speed'), this, this._playingTimerComplete);
9953		}
9954	},
9955
9956	/**
9957	 * Fires when the playing timer completes, starts a
9958	 * new timer and loads the next image.
9959	 *
9960	 * @method _playingTimerComplete
9961	 * @protected
9962	 */
9963	_playingTimerComplete: function()
9964	{
9965		this.loadImage(this.imageIndex + 1);
9966
9967		/**
9968		 * Fires when the playing timer completes.
9969		 *
9970		 * @event albumLoadStart
9971		 */
9972		this.fire('playingTimerComplete');
9973	},
9974
9975	/**
9976	 * Cancels the playing timer.
9977	 *
9978	 * @method _playingTimerCancel
9979	 * @protected
9980	 */
9981	_playingTimerCancel: function()
9982	{
9983		if(this._playingTimer) {
9984			this._playingTimer.cancel();
9985		}
9986		if(this._playingTimerEvent) {
9987			this._playingTimerEvent.obj.detach('fl-slideshow-base|' + this._playingTimerEvent.e);
9988		}
9989	},
9990
9991	/**
9992	 * Creates the loading image.
9993	 *
9994	 * @method _renderLoadingImage
9995	 * @protected
9996	 */
9997	_renderLoadingImage: function()
9998	{
9999        var defaults = {
10000                lines: 11, // The number of lines to draw
10001                length: 6, // The length of each line
10002                width: 2, // The line thickness
10003                radius: 7, // The radius of the inner circle
10004                color: '', // #rbg or #rrggbb
10005                speed: 1, // Rounds per second
10006                trail: 60, // Afterglow percentage
10007                shadow: false // Whether to render a shadow
10008            },
10009            settings = Y.merge(defaults, this.get('loadingImageSettings'));
10010
10011		if(this.get('loadingImageEnabled')) {
10012
10013            // Loading image
10014            if(settings.color === '') {
10015                settings.color = this._colorToHex(Y.one('body').getStyle('color'));
10016            }
10017
10018            this._loadingImage = new Y.FL.Spinner(settings);
10019
10020            // Loading image wrap
10021            this._loadingImageWrap = Y.Node.create('<div class="fl-loading-image"></div>');
10022
10023            this._loadingImageWrap.setStyles({
10024                position    : 'absolute',
10025                'z-index'   : '1000'
10026			});
10027		}
10028	},
10029
10030	/**
10031	 * Inserts the loading image.
10032	 *
10033	 * @method _showLoadingImage
10034	 * @protected
10035	 */
10036	_showLoadingImage: function()
10037	{
10038		if(this._loadingImage && !this._loadingImageVisible) {
10039
10040            this._loadingImageVisible = true;
10041            this._loadingImage.spin();
10042			this._loadingImageWrap.insert(this._loadingImage.el);
10043
10044			if(this._loadingImageContainer !== null) {
10045                this._loadingImageContainer.insert(this._loadingImageWrap);
10046			}
10047			else {
10048                this.get('contentBox').insert(this._loadingImageWrap);
10049			}
10050
10051			this._positionLoadingImage();
10052		}
10053	},
10054
10055	/**
10056	 * Inserts the loading image div node after
10057	 * a timer completes.
10058	 *
10059	 * @method _showLoadingImageWithDelay
10060	 * @protected
10061	 */
10062	_showLoadingImageWithDelay: function()
10063	{
10064		if(this._loadingImage) {
10065			this._loadingImageTimer = Y.later(1000, this, this._showLoadingImage);
10066		}
10067	},
10068
10069	/**
10070	 * Removes the loading image div node.
10071	 *
10072	 * @method _hideLoadingImage
10073	 * @protected
10074	 */
10075	_hideLoadingImage: function()
10076	{
10077		if(this._loadingImageTimer) {
10078			this._loadingImageTimer.cancel();
10079			this._loadingImageTimer = null;
10080		}
10081		if(this._loadingImage && this._loadingImageVisible) {
10082            this._loadingImageVisible = false;
10083			this._loadingImage.stop();
10084			this._loadingImageWrap.remove();
10085		}
10086	},
10087
10088	/**
10089	 * Centers the loading image in the content box.
10090	 *
10091	 * @method _positionLoadingImage
10092	 * @protected
10093	 */
10094	_positionLoadingImage: function()
10095	{
10096		if(this._loadingImage && this._loadingImageVisible) {
10097
10098            var wrap            = this._loadingImageWrap,
10099        		wrapHeight      = parseInt(wrap.getComputedStyle('height'), 10),
10100                wrapWidth       = parseInt(wrap.getComputedStyle('width'), 10),
10101        		parent          = wrap.get('parentNode'),
10102        		parentHeight    = parseInt(parent.getComputedStyle('height'), 10),
10103                parentWidth     = parseInt(parent.getComputedStyle('width'), 10),
10104        		left            = (parentWidth - wrapWidth)/2,
10105        		top             = (parentHeight - wrapHeight)/2;
10106
10107			wrap.setStyles({
10108                left        : left + 'px',
10109                top         : top + 'px'
10110			});
10111
10112			Y.one(this._loadingImage.el).setStyles({
10113                left        : '50%',
10114                top         : '50%'
10115			});
10116		}
10117	},
10118
10119	/**
10120	 * Convert RGB color value to a hex value.
10121	 *
10122	 * @method _colorToHex
10123	 * @protected
10124	 */
10125	_colorToHex: function(color)
10126	{
10127        var digits, red, green, blue, rgb;
10128
10129        if(color.substr(0, 1) === '#') {
10130            return color;
10131        }
10132
10133        digits = /(.*?)rgb\((\d+), (\d+), (\d+)\)/.exec(color);
10134
10135        if ( null === digits ) {
10136	        return '#000';
10137        }
10138
10139        red = parseInt(digits[2], 10);
10140        green = parseInt(digits[3], 10);
10141        blue = parseInt(digits[4], 10);
10142        rgb = blue | (green << 8) | (red << 16);
10143        rgb = rgb.toString(16);
10144
10145        if(rgb === '0') {
10146            rgb = '000';
10147        }
10148
10149        return digits[1] + '#' + rgb;
10150    }
10151
10152}, {
10153
10154	/**
10155	 * Custom CSS class name for the widget.
10156	 *
10157	 * @property CSS_PREFIX
10158	 * @type String
10159	 * @protected
10160	 * @static
10161	 */
10162	CSS_PREFIX: 'fl-slideshow-base',
10163
10164	/**
10165	 * Static property used to define the default attribute configuration of
10166	 * the Widget.
10167	 *
10168	 * @property ATTRS
10169	 * @type Object
10170	 * @protected
10171	 * @static
10172	 */
10173	ATTRS: {
10174
10175		/**
10176		 * Used to create the color class that gets added to the bounding box
10177		 * when the widget is rendered. The color class is used to create new
10178		 * CSS color themes. The default CSS provided includes dark and light themes.
10179		 *
10180		 * @attribute color
10181		 * @type String
10182		 * @default dark
10183		 * @writeOnce
10184		 */
10185		color: {
10186			value: 'dark',
10187			writeOnce: true
10188		},
10189
10190		/**
10191		 * An array of source objects used to load albums. Each object must have
10192		 * a type property and can have a title property as well.
10193		 * <p>
10194		 * In addition to those properties, each object has additional required
10195		 * properties specific to its type. The types currently supported are
10196		 * smugmug and urls with planned support for flickr and picasa.
10197		 * See the user guide for information on loading different types.
10198		 *
10199		 * @attribute source
10200		 * @type Array
10201		 * @default []
10202		 * @writeOnce
10203		 */
10204		source: {
10205			value: [],
10206			setter: function(source) {
10207
10208				if(source.constructor == Object) {
10209					source = [source];
10210				}
10211
10212				for(var i = 0; i < source.length; i++) {
10213					source[i].index = i;
10214				}
10215
10216				return source;
10217			}
10218		},
10219
10220		/**
10221		 * The default album index to load.
10222		 *
10223		 * @attribute defaultAlbum
10224		 * @type Number
10225		 * @default 0
10226		 */
10227		defaultAlbum: {
10228			value: 0
10229		},
10230
10231		/**
10232		 * The default image index to load.
10233		 *
10234		 * @attribute defaultImage
10235		 * @type Number
10236		 * @default 0
10237		 */
10238		defaultImage: {
10239			value: 0
10240		},
10241
10242		/**
10243		 * If true, the slideshow will be loaded after rendering.
10244		 *
10245		 * @attribute loadOnRender
10246		 * @type Boolean
10247		 * @default true
10248		 */
10249		loadOnRender: {
10250			value: true
10251		},
10252
10253		/**
10254		 * If true, the slideshow will start playing after loading.
10255		 *
10256		 * @attribute autoPlay
10257		 * @type Boolean
10258		 * @default true
10259		 */
10260		autoPlay: {
10261			value: true
10262		},
10263
10264		/**
10265		 * Whether to pause when the next or previous image is loaded
10266		 * using nextImage or prevImage. The slideshow will not be paused
10267		 * if the next or previous image is loaded using loadImage as is the
10268		 * case when the slideshow is playing.
10269		 *
10270		 * @attribute pauseOnNextOrPrev
10271		 * @type Boolean
10272		 * @default true
10273		 */
10274		pauseOnNextOrPrev: {
10275			value: true
10276		},
10277
10278		/**
10279		 * If true, the images will be randomized after loading.
10280		 *
10281		 * @attribute randomize
10282		 * @type Boolean
10283		 * @default false
10284		 */
10285		randomize: {
10286			value: false
10287		},
10288
10289		/**
10290		 * The time between images when playing, measured in milliseconds.
10291		 *
10292		 * @attribute speed
10293		 * @type Number
10294		 * @default 4000
10295		 */
10296		speed: {
10297			value: 4000
10298		},
10299
10300		/**
10301		 * The minimum width of the parent node at which
10302		 * responsive features are enabled. Set to 0 to
10303		 * disable responsive features as they are enabled
10304		 * whether stretchy is set to true or not.
10305		 *
10306		 * @attribute responsiveThreshold
10307		 * @type Number
10308		 * @default 600
10309		 */
10310		responsiveThreshold: {
10311			value: 600
10312		},
10313
10314		/**
10315		 * Whether stretchy resizing should be enabled.
10316		 *
10317		 * @attribute stretchy
10318		 * @type Boolean
10319		 * @default false
10320		 */
10321		stretchy: {
10322			value: false
10323		},
10324
10325		/**
10326		 * The type of stretchy logic to use. Possible values are
10327		 * window and ratio. Both types resize the width of the
10328		 * slideshow to the width of its parent node. With window, the
10329		 * height of the slideshow is resized to the height of the window.
10330		 * With ratio, the height of the slideshow is resized based
10331		 * on the ratio set with stretchyRatio or the height of the window
10332		 * if the ratio height is greater than the window height.
10333		 *
10334		 * @attribute stretchyType
10335		 * @type String
10336		 * @default ratio
10337		 */
10338		stretchyType: {
10339			value: 'ratio'
10340		},
10341
10342		/**
10343		 * The number of pixels to subtract from the height of
10344		 * the slideshow when stretchy is set to true.
10345		 *
10346		 * @attribute stretchyVerticalSpace
10347		 * @type Number
10348		 * @default 0
10349		 */
10350		stretchyVerticalSpace: {
10351			value: 0
10352		},
10353
10354		/**
10355		 * Used to calculate the height of the slideshow when stretchyType
10356		 * is set to ratio.
10357		 *
10358		 * @attribute stretchyRatio
10359		 * @type Number
10360		 * @default 0.7
10361		 */
10362		stretchyRatio: {
10363			value: 0.7
10364		},
10365
10366		/**
10367		 * Whether to use the loading image or not.
10368		 *
10369		 * @attribute loadingImageEnabled
10370		 * @type Boolean
10371		 * @default true
10372		 */
10373		loadingImageEnabled: {
10374			value: true
10375		},
10376
10377		/**
10378		 * Property object for setting up the spin.js loading image.
10379		 * For a complete list of properties see:
10380		 * http://effinroot.eiremedia.netdna-cdn.com/repo/plugins/misc/spin.js/index.html
10381		 *
10382		 * @attribute loadingImageSettings
10383		 * @type Object
10384		 */
10385		loadingImageSettings: {
10386			value: {}
10387		}
10388	}
10389});
10390
10391
10392}, '2.0.0' ,{requires:['node', 'base', 'widget', 'widget-parent', 'widget-child', 'fl-slideshow-album-loader', 'fl-spinner']});
10393
10394
10395YUI.add('fl-smugmug-api', function(Y) {
10396
10397/**
10398 * @module fl-smugmug-api
10399 */
10400
10401/**
10402 * SmugMug API wrapper.
10403 *
10404 * NOTE: Only anonymous logins are currently supported.
10405 *
10406 * @namespace FL
10407 * @class SmugMugAPI
10408 * @constructor
10409 * @param config {Object} Configuration object
10410 * @extends Base
10411 */
10412Y.namespace('FL').SmugMugAPI = Y.Base.create('fl-smugmug-api', Y.Base, [], {
10413
10414	/**
10415	 * ID for the current session.
10416	 *
10417	 * @property _sessionID
10418	 * @type String
10419	 * @default null
10420	 * @protected
10421	 */
10422	_sessionID: null,
10423
10424	/**
10425	 * URL with parameters for the next API request.
10426	 * Reset after each request.
10427	 *
10428	 * @property _requestURL
10429	 * @type String
10430	 * @default null
10431	 * @protected
10432	 */
10433	_requestURL: null,
10434
10435	/**
10436	 * Lifecycle method. Initializes the request url.
10437     *
10438     * @method initializer
10439     * @protected
10440     */
10441	initializer: function()
10442	{
10443		this._resetRequestURL();
10444	},
10445
10446	/**
10447	 * Adds a key/value pair to the request url.
10448	 *
10449	 * @method addParam
10450	 * @param key {String} The name of the parameter (example: key=val).
10451	 * @param val {String} The value of the parameter (example: key=val).
10452	 */
10453	addParam: function(key, val)
10454	{
10455		this._requestURL = this._requestURL + '&' + key + '=' + val;
10456	},
10457
10458	/**
10459	 * Requests an anonymous login session.
10460	 *
10461	 * @method loginAnon
10462	 */
10463	loginAnon: function()
10464	{
10465	    this.addParam('method', 'smugmug.login.anonymously');
10466	    this.once('complete', this._loginAnonComplete);
10467	    this.request();
10468	},
10469
10470	/**
10471	 * Anonymous login success handler.
10472	 *
10473	 * @method _loginAnonComplete
10474	 * @param data {Object} A jsonp data object.
10475	 * @protected
10476	 */
10477	_loginAnonComplete: function(data)
10478	{
10479    	if(data.Login) {
10480    		this._sessionID = data.Login.Session.id;
10481    	}
10482	},
10483
10484	/**
10485	 * Sends an API request using the request url.
10486	 *
10487	 * @method request
10488	 */
10489	request: function()
10490	{
10491		this.addParam('Callback', '{callback}');
10492
10493	   	Y.jsonp(this._requestURL, {
10494		    on: {
10495		        success: this._requestComplete,
10496		        timeout: function(){} // TODO: Handle timeouts
10497		    },
10498		    context: this,
10499		    timeout: 60000,
10500		    args: []
10501		});
10502	},
10503
10504	/**
10505	 * API request complete handler.
10506	 *
10507	 * @method _requestComplete
10508	 * @param data {Object} A jsonp data object.
10509	 * @protected
10510	 */
10511	_requestComplete: function(data)
10512	{
10513		this._resetRequestURL();
10514
10515		/**
10516		 * Fires when a request is complete.
10517		 *
10518		 * @event complete
10519		 */
10520		this.fire('complete', data);
10521	},
10522
10523	/**
10524	 * Clears all parameters on the request url except
10525	 * the API key and session ID.
10526	 *
10527	 * @method _resetRequestURL
10528	 * @protected
10529	 */
10530	_resetRequestURL: function()
10531	{
10532		this._requestURL = this.get('apiURL') + '?APIKey=' + this.get('apiKey');
10533
10534		if(this._sessionID) {
10535	        this.addParam('SessionID', this._sessionID);
10536	    }
10537	}
10538
10539}, {
10540
10541	/**
10542	* Static property used to define the default attribute configuration of
10543	* the Widget.
10544	*
10545	* @property ATTRS
10546	* @type Object
10547	* @protected
10548	* @static
10549	*/
10550	ATTRS: {
10551
10552        /**
10553		* SmugMug API url to use for requests.
10554		*
10555		* @attribute apiUrl
10556		* @type String
10557		* @default https://api.smugmug.com/services/api/json/1.3.0/
10558		*/
10559		apiURL: {
10560        	value: 'https://api.smugmug.com/services/api/json/1.3.0/'
10561        },
10562
10563        /**
10564		* SmugMug API key.
10565		*
10566		* @attribute apiKey
10567		* @type String
10568		* @default 7w6kuU5Ee6KSgRRExf2KLgppdkez9JD2
10569		*/
10570		apiKey: {
10571        	value: '7w6kuU5Ee6KSgRRExf2KLgppdkez9JD2'
10572        }
10573	}
10574});
10575
10576
10577}, '2.0.0' ,{requires:['base', 'jsonp']});
10578
10579
10580YUI.add('fl-spinner', function(Y) {
10581
10582(function(window,document,undefined){var width="width",length="length",radius="radius",lines="lines",trail="trail",color="color",opacity="opacity",speed="speed",shadow="shadow",style="style",height="height",left="left",top="top",px="px",childNodes="childNodes",firstChild="firstChild",parentNode="parentNode",position="position",relative="relative",absolute="absolute",animation="animation",transform="transform",Origin="Origin",Timeout="Timeout",coord="coord",black="#000",styleSheets=style+"Sheets",prefixes="webkit0Moz0ms0O".split(0),animations={},useCssAnimations;function eachPair(args,it){var end=~~((args[length]-1)/2);for(var i=1;i<=end;i++){it(args[i*2-1],args[i*2])}}function createEl(tag){var el=document.createElement(tag||"div");eachPair(arguments,function(prop,val){el[prop]=val});return el}function ins(parent,child1,child2){if(child2&&!child2[parentNode]){ins(parent,child2)}parent.insertBefore(child1,child2||null);return parent}ins(document.getElementsByTagName("head")[0],createEl(style));var sheet=document[styleSheets][document[styleSheets][length]-1];function addAnimation(to,end){var name=[opacity,end,~~(to*100)].join("-"),dest="{"+opacity+":"+to+"}",i;if(!animations[name]){for(i=0;i<prefixes[length];i++){try{sheet.insertRule("@"+(prefixes[i]&&"-"+prefixes[i].toLowerCase()+"-"||"")+"keyframes "+name+"{0%{"+opacity+":1}"+end+"%"+dest+"to"+dest+"}",sheet.cssRules[length])}catch(err){}}animations[name]=1}return name}function vendor(el,prop){var s=el[style],pp,i;if(s[prop]!==undefined){return prop}prop=prop.charAt(0).toUpperCase()+prop.slice(1);for(i=0;i<prefixes[length];i++){pp=prefixes[i]+prop;if(s[pp]!==undefined){return pp}}}function css(el){eachPair(arguments,function(n,val){el[style][vendor(el,n)||n]=val});return el}function defaults(obj){eachPair(arguments,function(prop,val){if(obj[prop]===undefined){obj[prop]=val}});return obj}var Spinner=function Spinner(o){this.opts=defaults(o||{},lines,12,trail,100,length,7,width,5,radius,10,color,black,opacity,1/4,speed,1)},proto=Spinner.prototype={spin:function(target){var self=this,el=self.el=self[lines](self.opts);if(target){ins(target,css(el,left,~~(target.offsetWidth/2)+px,top,~~(target.offsetHeight/2)+px),target[firstChild])}if(!useCssAnimations){var o=self.opts,i=0,f=20/o[speed],ostep=(1-o[opacity])/(f*o[trail]/100),astep=f/o[lines];(function anim(){i++;for(var s=o[lines];s;s--){var alpha=Math.max(1-(i+s*astep)%f*ostep,o[opacity]);self[opacity](el,o[lines]-s,alpha,o)}self[Timeout]=self.el&&window["set"+Timeout](anim,50)})()}return self},stop:function(){var self=this,el=self.el;window["clear"+Timeout](self[Timeout]);if(el&&el[parentNode]){el[parentNode].removeChild(el)}self.el=undefined;return self}};proto[lines]=function(o){var el=css(createEl(),position,relative),animationName=addAnimation(o[opacity],o[trail]),i=0,seg;function fill(color,shadow){return css(createEl(),position,absolute,width,(o[length]+o[width])+px,height,o[width]+px,"background",color,"boxShadow",shadow,transform+Origin,left,transform,"rotate("+~~(360/o[lines]*i)+"deg) translate("+o[radius]+px+",0)","borderRadius","100em")}for(;i<o[lines];i++){seg=css(createEl(),position,absolute,top,1+~(o[width]/2)+px,transform,"translate3d(0,0,0)",animation,animationName+" "+1/o[speed]+"s linear infinite "+(1/o[lines]/o[speed]*i-1/o[speed])+"s");if(o[shadow]){ins(seg,css(fill(black,"0 0 4px "+black),top,2+px))}ins(el,ins(seg,fill(o[color],"0 0 1px rgba(0,0,0,.1)")))}return el};proto[opacity]=function(el,i,val){el[childNodes][i][style][opacity]=val};var behavior="behavior",URL_VML="url(#default#VML)",tag="group0roundrect0fill0stroke".split(0);(function(){var s=css(createEl(tag[0]),behavior,URL_VML),i;if(!vendor(s,transform)&&s.adj){for(i=0;i<tag[length];i++){sheet.addRule(tag[i],behavior+":"+URL_VML)}proto[lines]=function(){var o=this.opts,r=o[length]+o[width],s=2*r;function grp(){return css(createEl(tag[0],coord+"size",s+" "+s,coord+Origin,-r+" "+-r),width,s,height,s)}var g=grp(),margin=~(o[length]+o[radius]+o[width])+px,i;function seg(i,dx,filter){ins(g,ins(css(grp(),"rotation",360/o[lines]*i+"deg",left,~~dx),ins(css(createEl(tag[1],"arcsize",1),width,r,height,o[width],left,o[radius],top,-o[width]/2,"filter",filter),createEl(tag[2],color,o[color],op
10582acity,o[opacity]),createEl(tag[3],opacity,0))))}if(o[shadow]){for(i=1;i<=o[lines];i++){seg(i,-2,"progid:DXImage"+transform+".Microsoft.Blur(pixel"+radius+"=2,make"+shadow+"=1,"+shadow+opacity+"=.3)")}}for(i=1;i<=o[lines];i++){seg(i)}return ins(css(createEl(),"margin",margin+" 0 0 "+margin,position,relative),g)};proto[opacity]=function(el,i,val,o){o=o[shadow]&&o[lines]||0;el[firstChild][childNodes][i+o][firstChild][firstChild][opacity]=val}}else{useCssAnimations=vendor(s,animation)}})();Y.namespace('FL').Spinner=Spinner})(window,document);
10583
10584
10585}, '2.0.0' );
10586
10587
10588YUI.add('fl-utils', function(Y) {
10589
10590/**
10591 * @module fl-utils
10592 */
10593
10594/**
10595 * General helper functions for all FastLine modules.
10596 *
10597 * @namespace FL
10598 * @class Utils
10599 * @constructor
10600 * @static
10601 */
10602Y.namespace('FL').Utils = {
10603
10604    /**
10605	 * Checks for support of the provided CSS property.
10606	 * Method adapted from: https://gist.github.com/556448
10607	 *
10608	 * @method cssSupport
10609	 * @param p {String} The property to check.
10610	 * @returns Boolean
10611	 */
10612	cssSupport: function(p)
10613	{
10614		var b = document.body || document.documentElement,
10615	    	s = b.style,
10616	    	v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms', 'Icab'],
10617	    	i = 0;
10618
10619	    // Transform not working well in these browsers
10620	    if(p == 'transform' && Y.UA.gecko && Y.UA.gecko < 4) { return false; }
10621	    if(p == 'transform' && Y.UA.opera > 0) { return false; }
10622	    if(p == 'transform' && Y.UA.ie > 0 && Y.UA.ie < 10) { return false; }
10623	    if(p == 'transform' && navigator.userAgent.match(/Trident/)) { return false; }
10624
10625	    // No css support detected
10626	    if(typeof s == 'undefined') { return false; }
10627
10628	    // Tests for standard prop
10629	    if(typeof s[p] == 'string') { return true; }
10630
10631	    // Tests for vendor specific prop
10632	    p = p.charAt(0).toUpperCase() + p.substr(1);
10633
10634	    for( ; i < v.length; i++) {
10635			if(typeof s[v[i] + p] == 'string') { return true; }
10636	    }
10637	}
10638};
10639
10640
10641}, '2.0.0' );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.