PageSourceSearch

https://www.serres.gr/wp-content/plugins/bb-plugin/js/fl-slideshow.js?ver=2.7.4.2

js serres.gr collected 2026-10-02 07:42:12 UTC 239,956 bytes, 10,582 lines download raw bytes

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