PageSourceSearch

https://www.canfitpro.com/wp-content/plugins/bb-plugin/js/fl-slideshow.js?ver=2.11.1

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