Alloy UI

aui-image-viewer  1.0.1

 
Filters
AUI.add('aui-image-viewer-gallery', function(A) {
/**
 * The ImageGallery Utility
 *
 * @module aui-image-viewer
 * @submodule aui-image-viewer-gallery
 */

var L = A.Lang,
	isBoolean = L.isBoolean,
	isNumber = L.isNumber,
	isObject = L.isObject,
	isString = L.isString,

	AUTO_PLAY = 'autoPlay',
	BODY = 'body',
	CONTENT = 'content',
	CURRENT_INDEX = 'currentIndex',
	DELAY = 'delay',
	DOT = '.',
	ENTRY = 'entry',
	HANDLER = 'handler',
	HIDDEN = 'hidden',
	HREF = 'href',
	IMAGE_GALLERY = 'image-gallery',
	IMG = 'img',
	LEFT = 'left',
	LINKS = 'links',
	OFFSET_WIDTH = 'offsetWidth',
	OVERLAY = 'overlay',
	PAGE = 'page',
	PAGINATOR = 'paginator',
	PAGINATOR_EL = 'paginatorEl',
	PAGINATOR_INSTANCE = 'paginatorInstance',
	PAUSE = 'pause',
	PAUSED = 'paused',
	PAUSED_LABEL = 'pausedLabel',
	PLAY = 'play',
	PLAYER = 'player',
	PLAYING = 'playing',
	PLAYING_LABEL = 'playingLabel',
	PX = 'px',
	REPEAT = 'repeat',
	SHOW_PLAYER = 'showPlayer',
	SPACE = ' ',
	SRC = 'src',
	THUMB = 'thumb',
	TOOLBAR = 'toolbar',
	TOTAL_LINKS = 'totalLinks',
	USE_ORIGINAL_IMAGE = 'useOriginalImage',
	VIEWPORT_REGION = 'viewportRegion',
	VISIBLE = 'visible',

	concat = function() {
		return Array.prototype.slice.call(arguments).join(SPACE);
	},

	getCN = A.ClassNameManager.getClassName,

	CSS_IMAGE_GALLERY_PAGINATOR = getCN(IMAGE_GALLERY, PAGINATOR),
	CSS_IMAGE_GALLERY_PAGINATOR_CONTENT = getCN(IMAGE_GALLERY, PAGINATOR, CONTENT),
	CSS_IMAGE_GALLERY_PAGINATOR_ENTRY = getCN(IMAGE_GALLERY, PAGINATOR, ENTRY),
	CSS_IMAGE_GALLERY_PAGINATOR_LINKS = getCN(IMAGE_GALLERY, PAGINATOR, LINKS),
	CSS_IMAGE_GALLERY_PAGINATOR_THUMB = getCN(IMAGE_GALLERY, PAGINATOR, THUMB),
	CSS_IMAGE_GALLERY_PLAYER = getCN(IMAGE_GALLERY, PLAYER),
	CSS_IMAGE_GALLERY_PLAYER_CONTENT = getCN(IMAGE_GALLERY, PLAYER, CONTENT),
	CSS_OVERLAY_HIDDEN = getCN(OVERLAY, HIDDEN),

	TEMPLATE_PLAYING_LABEL = '(playing)',
	TEMPLATE_PAGINATOR = '<div class="'+CSS_IMAGE_GALLERY_PAGINATOR_CONTENT+'">{PageLinks}</div>',

	TPL_LINK = '<span class="' + CSS_IMAGE_GALLERY_PAGINATOR_ENTRY + '"><span class="' + CSS_IMAGE_GALLERY_PAGINATOR_THUMB+'"></span></span>',
	TPL_LINK_CONTAINER = '<div class="' + CSS_IMAGE_GALLERY_PAGINATOR_LINKS + '"></div>',
	TPL_PAGINATOR_CONTAINER = '<div class="'+concat(CSS_OVERLAY_HIDDEN, CSS_IMAGE_GALLERY_PAGINATOR)+'"></div>',
	TPL_PLAYER_CONTAINER = '<div class="'+CSS_IMAGE_GALLERY_PLAYER+'"></div>',
	TPL_PLAYER_CONTENT = '<span class="'+CSS_IMAGE_GALLERY_PLAYER_CONTENT+'"></span>';

/**
 * <p><img src="assets/images/aui-image-viewer-gallery/main.png"/></p>
 *
 * A base class for ImageGallery, providing:
 * <ul>
 *    <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
 *    <li>Displays an image in a Overlay</li>
 *    <li>Displays list of thumbnails of the images as a control</li>
 *    <li>Slide show functionalities (i.e., play, pause etc)</li>
 *    <li>Keyboard navigation support</li>
 * </ul>
 *
 * Quick Example:<br/>
 *
 * <pre><code>var instance = new A.ImageGallery({
 *   links: '#gallery1 a',
 *   caption: 'Liferay Champion Soccer',
 *   captionFromTitle: true,
 *   preloadNeighborImages: true,
 *   preloadAllImages: true,
 *   showInfo: true
 * }).render();
 * </code></pre>
 *
 * Check the list of <a href="ImageGallery.html#configattributes">Configuration Attributes</a> available for
 * ImageGallery.
 *
 * @param config {Object} Object literal specifying widget configuration properties.
 *
 * @class ImageGallery
 * @constructor
 * @extends ImageViewer
 */
var ImageGallery = A.Component.create(
	{
		/**
		 * Static property provides a string to identify the class.
		 *
		 * @property ImageGallery.NAME
		 * @type String
		 * @static
		 */
		NAME: IMAGE_GALLERY,

		/**
		 * Static property used to define the default attribute
		 * configuration for the ImageGallery.
		 *
		 * @property ImageGallery.ATTRS
		 * @type Object
		 * @static
		 */
		ATTRS: {
			/**
			 * If <code>true</code> the slide show will be played when the
	         * ImageGallery is displayed.
			 *
			 * @attribute autoPlay
			 * @default false
			 * @type boolean
			 */
			autoPlay: {
				value: false,
				validator: isBoolean
			},

			/**
			 * Delay in milliseconds to change to the next image.
			 *
			 * @attribute delay
			 * @default 7000
			 * @type Number
			 */
			delay: {
				value: 7000,
				validator: isNumber
			},

			/**
			 * <a href="Paginator.html">Paginator</a> configuration Object. The
	         * <code>Paginator</code> handles the thumbnails control.
			 *
			 * @attribute paginator
			 * @default <a href="Paginator.html">Paginator</a> configuration Object.
			 * @type Object
			 */
			paginator: {
				value: {},
				setter: function(value) {
					var instance = this;
					var paginatorEl = instance.get(PAGINATOR_EL);
					var totalLinks = instance.get(TOTAL_LINKS);

					return A.merge(
						{
							containers: paginatorEl,
							pageContainerTemplate: TPL_LINK_CONTAINER,
							pageLinkContent: A.bind(instance._setThumbContent, instance),
							pageLinkTemplate: TPL_LINK,
							template: TEMPLATE_PAGINATOR,
							total: totalLinks,
							on: {
								changeRequest: function(event) {
									// fire changeRequest from ImageGallery passing the "state" object from Paginator
									instance.fire('changeRequest', { state: event.state })
								}
							}
						},
						value
					);
				},
				validator: isObject
			},

			/**
			 * Element which contains the <a href="Paginator.html">Paginator</a>
	         * with the thumbnails.
			 *
			 * @attribute paginatorEl
			 * @default Generated HTML div.
			 * @readOnly
			 * @type Node
			 */
			paginatorEl: {
				readyOnly: true,
				valueFn: function() {
					return A.Node.create(TPL_PAGINATOR_CONTAINER);
				}
			},

			/**
			 * Stores the <a href="Paginator.html">Paginator</a> instance.
			 *
			 * @attribute paginatorInstance
			 * @default null
			 * @type Paginator
			 */
			paginatorInstance: {
				value: null
			},

			/**
			 * If <code>true</code> the slide show is paused.
			 *
			 * @attribute paused
			 * @default false
			 * @type boolean
			 */
			paused: {
				value: false,
				validator: isBoolean
			},

			/**
			 * Label to display when the slide show is paused.
			 *
			 * @attribute pausedLabel
			 * @default ''
			 * @type String
			 */
			pausedLabel: {
				value: '',
				validator: isString
			},

			/**
			 * If <code>true</code> the slide show is playing.
			 *
			 * @attribute playing
			 * @default false
			 * @type boolean
			 */
			playing: {
				value: false,
				validator: isBoolean
			},

			/**
			 * Label to display when the slide show is playing.
			 *
			 * @attribute playingLabel
			 * @default '(Playing)'
			 * @type String
			 */
			playingLabel: {
				value: TEMPLATE_PLAYING_LABEL,
				validator: isString
			},

			/**
			 * Restart the navigation when reach the last element.
			 *
			 * @attribute repeat
			 * @default true
			 * @type boolean
			 */
			repeat: {
				value: true,
				validator: isBoolean
			},

			/**
			 * Show the player controls (i.e., pause and show buttons).
			 *
			 * @attribute showPlayer
			 * @default true
			 * @type boolean
			 */
			showPlayer: {
				value: true,
				validator: isBoolean
			},

			/**
			 * <a href="Toolbar.html">Toolbar</a> with a play, and pause buttons.
			 *
			 * @attribute toolbar
			 * @default Generated Toolbar with a play, and pause buttons.
			 * @type Toolbar constructor.
			 */
			toolbar: {
				value: {},
				setter: function(value) {
					var instance = this;

					return A.merge(
						{
							children: [
								{
									id: PLAY,
									icon: PLAY
								},
								{
									id: PAUSE,
									icon: PAUSE
								}
							]
						},
						value
					);
				},
				validator: isObject
			},

			/**
			 * If <code>true</code> will use the original image as thumbnails.
			 *
			 * @attribute useOriginalImage
			 * @default false
			 * @type boolean
			 */
			useOriginalImage: {
				value: false,
				validator: isBoolean
			}
		},

		EXTENDS: A.ImageViewer,

		prototype: {
			/**
			 * Toolbar instance reference.
			 *
			 * @property toolbar
			 * @type Toolbar
			 * @protected
			 */
			toolbar: null,

			/**
			 * Stores the <code>A.later</code> reference.
			 *
			 * @property _timer
			 * @type Number
			 * @protected
			 */
			_timer: null,

			/**
			 * Create the DOM structure for the ImageGallery. Lifecycle.
			 *
			 * @method renderUI
			 * @protected
			 */
			renderUI: function() {
				var instance = this;

				ImageGallery.superclass.renderUI.apply(this, arguments);

				instance._renderPaginator();

				if (instance.get(SHOW_PLAYER)) {
					instance._renderPlayer();
				}
			},

			/**
			 * Bind the events on the ImageGallery UI. Lifecycle.
			 *
			 * @method bindUI
			 * @protected
			 */
			bindUI: function() {
				var instance = this;

				ImageGallery.superclass.bindUI.apply(this, arguments);

				instance._bindToolbarUI();

				instance.on('playingChange', instance._onPlayingChange);
				instance.on('pausedChange', instance._onPausedChange);

				instance.publish('changeRequest', { defaultFn: this._changeRequest });
			},

			/**
			 * Descructor lifecycle implementation for the ImageGallery class.
			 * Purges events attached to the node (and all child nodes).
			 *
			 * @method destroy
			 * @protected
			 */
			destroy: function() {
				var instance = this;

				ImageGallery.superclass.destroy.apply(this, arguments);

				instance.get(PAGINATOR_INSTANCE).destroy();
			},

			/**
			 * Hide the <a href="Paginator.html">Paginator</a> with the thumbnails
		     * list.
			 *
			 * @method hidePaginator
			 */
			hidePaginator: function() {
				var instance = this;

				instance.get(PAGINATOR_EL).addClass(CSS_OVERLAY_HIDDEN);
			},

			/**
			 * Pause the slide show.
			 *
			 * @method pause
			 */
			pause: function() {
				var instance = this;

				instance.set(PAUSED, true);
				instance.set(PLAYING, false);

				instance._syncInfoUI();
			},

			/**
			 * Play the slide show.
			 *
			 * @method play
			 */
			play: function() {
				var instance = this;

				instance.set(PAUSED, false);
				instance.set(PLAYING, true);

				instance._syncInfoUI();
			},

			/**
			 * <p>Show the ImageGallery.</p>
			 *
			 * <p><strong>NOTE:</strong>Overloads the <a
		     * href="ImageViewer.html">ImageViewer</a> show method to not loadImage, the
		     * changeRequest now is responsible to do that if we invoke the superclass
		     * show method its loading the image, and the changeRequest loads again,
		     * avoiding double request.</p>
			 *
			 * @method show
			 */
			show: function() {
				var instance = this;
				var currentLink = instance.getCurrentLink();

				if (currentLink) {
					instance.showMask();

					// invoke the Overlay show method
					// NODE: A.ImageViewer is the parent of ImageGallery, the A.ImageViewer.superclass is the Overlay
					A.ImageViewer.superclass.show.apply(this, arguments);

					// changeRequest on paginatorInstance with the new page set
					var paginatorInstance = instance.get(PAGINATOR_INSTANCE);

					// page start on 1, index on 0, add +1 to the nextIndex to set the correct page on the paginator
					paginatorInstance.set(
						PAGE,
						instance.get(CURRENT_INDEX) + 1
					);

					paginatorInstance.changeRequest();
				}
			},

			/**
			 * Show the <a href="Paginator.html">Paginator</a> with the thumbnails
		     * list.
			 *
			 * @method showPaginator
			 */
			showPaginator: function() {
				var instance = this;

				instance.get(PAGINATOR_EL).removeClass(CSS_OVERLAY_HIDDEN);
			},

			/**
			 * Bind the Toolbar UI for the play and pause buttons.
			 *
			 * @method _bindToolbarUI
			 * @protected
			 */
			_bindToolbarUI: function() {
				var instance = this;

				if (instance.get(SHOW_PLAYER)) {
					var toolbar = instance.toolbar;

					var play = toolbar.item(PLAY);
					var pause = toolbar.item(PAUSE);

					if (play) {
						play.set(HANDLER, A.bind(instance.play, instance));
					}
					if (pause) {
						pause.set(HANDLER, A.bind(instance.pause, instance));
					}
				}
			},

			/**
			 * Cancel the timer between slides.
			 *
			 * @method _cancelTimer
			 * @protected
			 */
			_cancelTimer: function() {
				var instance = this;

				if (instance._timer) {
					instance._timer.cancel();
				}
			},

			/**
			 * Render the <a href="Paginator.html">Paginator</a> with the thumbnails.
			 *
			 * @method _renderPaginator
			 * @protected
			 */
			_renderPaginator: function() {
				var instance = this;
				var paginatorEl = instance.get(PAGINATOR_EL);

				A.one(BODY).append(
					paginatorEl.hide()
				);

				var paginatorInstance = new A.Paginator(
					instance.get(PAGINATOR)
				)
				.render();

				instance.set(PAGINATOR_INSTANCE, paginatorInstance);
			},

			/**
			 * Render the player controls.
			 *
			 * @method _renderPlayer
			 * @protected
			 */
			_renderPlayer: function() {
				var instance = this;
				var paginatorEl = instance.get(PAGINATOR_EL);
				var playerContent = A.Node.create(TPL_PLAYER_CONTENT);

				paginatorEl.append(
					A.Node.create(TPL_PLAYER_CONTAINER).append(playerContent)
				);

				instance.toolbar = new A.Toolbar(
					instance.get(TOOLBAR)
				)
				.render(playerContent);
			},

			/**
			 * Start the timer between slides.
			 *
			 * @method _startTimer
			 * @protected
			 */
			_startTimer: function() {
				var instance = this;
				var delay = instance.get(DELAY);

				instance._cancelTimer();

				instance._timer = A.later(delay, instance, instance._syncSlideShow);
			},

			/**
			 * Sync the controls UI.
			 *
			 * @method _syncControlsUI
			 * @protected
			 */
			_syncControlsUI: function() {
				var instance = this;

				ImageGallery.superclass._syncControlsUI.apply(this, arguments);

				if (instance.get(VISIBLE)) {
					instance._syncSelectedThumbUI();

					instance.showPaginator();
				}
				else {
					instance.hidePaginator();

					instance._cancelTimer();
				}
			},

			/**
			 * Sync the selected thumb UI.
			 *
			 * @method _syncSelectedThumbUI
			 * @protected
			 */
			_syncSelectedThumbUI: function() {
				var instance = this;
				var currentIndex = instance.get(CURRENT_INDEX);
				var paginatorInstance = instance.get(PAGINATOR_INSTANCE);
				var paginatorIndex = paginatorInstance.get(PAGE) - 1;

				// when currentIndex != paginatorIndex we need to load the new image
				if (currentIndex != paginatorIndex) {
					// originally the paginator changeRequest is only invoked when user interaction happens, forcing invoke changeRequest from paginator
					paginatorInstance.set(PAGE, currentIndex + 1);

					paginatorInstance.changeRequest();
				}
			},

			/**
			 * Sync the slide show UI.
			 *
			 * @method _syncSlideShow
			 * @protected
			 */
			_syncSlideShow: function() {
				var instance = this;

				if (!instance.hasNext()) {
					if (instance.get(REPEAT)) {
						instance.set(CURRENT_INDEX, -1);
					}
					else {
						instance._cancelTimer();
					}
				}

				instance.next();
			},

			/**
			 * Change the UI when click on a thumbnail.
			 *
			 * @method _changeRequest
			 * @param {EventFacade} event
			 * @protected
			 */
			_changeRequest: function(event) {
				var instance = this;
				var paginatorInstance = event.state.paginator;
				var newState = event.state;
				var beforeState = newState.before;
				var page = newState.page;

				// only update the paginator UI when the Overlay is visible
				if (!instance.get(VISIBLE)) {
					return false; // NOTE: return
				}

				var currentIndex = instance.get(CURRENT_INDEX);
				var paginatorIndex = page - 1;

				// check if the beforeState page number is different from the newState page number.
				if (!beforeState || (beforeState && beforeState.page != page)) {
					// updating currentIndex
					instance.set(CURRENT_INDEX, paginatorIndex);

					// loading current index image
					instance.loadImage(
						instance.getCurrentLink().attr(HREF)
					);

					// updating the UI of the paginator
					paginatorInstance.setState(newState);

					// restart the timer if the user change the image, respecting the paused state
					var paused = instance.get(PAUSED);
					var playing = instance.get(PLAYING);

					if (playing && !paused) {
						instance._startTimer();
					}
				}
			},

			/**
			 * See <a href="Paginator.html#method_pageLinkContent">pageLinkContent</a>.
			 *
			 * @method _setThumbContent
			 * @param {Node} pageEl
			 * @param {Number} pageNumber
			 * @protected
			 */
			_setThumbContent: function(pageEl, pageNumber) {
				var instance = this;
				var index = pageNumber - 1;
				var link = instance.getLink(index);
				var thumbEl = pageEl.one(DOT+CSS_IMAGE_GALLERY_PAGINATOR_THUMB);
				var thumbSrc = null;

				if (instance.get(USE_ORIGINAL_IMAGE)) {
					thumbSrc = link.attr(HREF);
				}
				else {
					// try to find a inner thumbnail image to show on the paginator
					var innerImage = link.one(IMG);

					if (innerImage) {
						thumbSrc = innerImage.attr(SRC);
					}
				}

				if (thumbSrc && thumbEl.getData('thumbSrc') != thumbSrc) {
					thumbEl.setStyles({
						// use background to show the thumbnails to take advantage of the background-position: 50% 50%
						backgroundImage: 'url(' + thumbSrc + ')'
					});

					thumbEl.setData('thumbSrc', thumbSrc);
				}
			},

			/**
			 * Get the <a href="ImageViewer.html#config_info">info</a> template.
			 *
			 * @method _getInfoTemplate
			 * @param {String} v template
			 * @protected
			 * @return {String} Parsed string.
			 */
			_getInfoTemplate: function(v) {
				var label;
				var instance = this;
				var paused = instance.get(PAUSED);
				var playing = instance.get(PLAYING);

				if (playing) {
					label = instance.get(PLAYING_LABEL);
				}
				else if (paused) {
					label = instance.get(PAUSED_LABEL);
				}

				return concat(
					ImageGallery.superclass._getInfoTemplate.apply(this, arguments),
					label
				);
			},

			/**
			 * Fires after the value of the
			 * <a href="ImageViewer.html#config_visible">visible</a> attribute change.
			 *
			 * @method _afterVisibleChange
			 * @param {EventFacade} event
			 * @protected
			 */
			_afterVisibleChange: function(event) {
				var instance = this;

				// invoke A.ImageViewer _afterVisibleChange method
				ImageGallery.superclass._afterVisibleChange.apply(this, arguments);

				if (event.newVal) {
					// trigger autoPlay when overlay is visible
					if (instance.get(AUTO_PLAY)) {
						instance.play();
					}
				}
			},

			/**
			 * Fires before the value of the
			 * <a href="ImageGallery.html#config_paused">paused</a> attribute change.
			 *
			 * @method _onPausedChange
			 * @param {EventFacade} event
			 * @protected
			 */
			_onPausedChange: function(event) {
				var instance = this;

				if (event.newVal) {
					instance._cancelTimer();
				}
			},

			/**
			 * Fires before the value of the
			 * <a href="ImageGallery.html#config_playing">playing</a> attribute change.
			 *
			 * @method _onPlayingChange
			 * @param {EventFacade} event
			 * @protected
			 */
			_onPlayingChange: function(event) {
				var instance = this;

				if (event.newVal) {
					instance._startTimer();
				}
			}
		}
	}
);

A.ImageGallery = ImageGallery;

}, '@VERSION@' ,{skinnable:true, requires:['aui-image-viewer-base','aui-paginator','aui-toolbar']});