Alloy UI

aui-image-viewer  1.0.1

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

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

	NodeFx = A.Plugin.NodeFX,

	ANIM = 'anim',
	ARROW = 'arrow',
	ARROW_LEFT_EL = 'arrowLeftEl',
	ARROW_RIGHT_EL = 'arrowRightEl',
	BD = 'bd',
	BLANK = 'blank',
	BODY = 'body',
	BOUNDING_BOX = 'boundingBox',
	CAPTION = 'caption',
	CAPTION_EL = 'captionEl',
	CAPTION_FROM_TITLE = 'captionFromTitle',
	CENTERED = 'centered',
	CLOSE = 'close',
	CLOSE_EL = 'closeEl',
	CREATE_DOCUMENT_FRAGMENT = 'createDocumentFragment',
	CURRENT_INDEX = 'currentIndex',
	EASE_BOTH_STRONG = 'easeBothStrong',
	FOOTER = 'footer',
	HELPER = 'helper',
	HIDDEN = 'hidden',
	HIDE = 'hide',
	HREF = 'href',
	ICON = 'icon',
	IMAGE = 'image',
	IMAGE_ANIM = 'imageAnim',
	IMAGE_VIEWER = 'image-viewer',
	INFO = 'info',
	INFO_EL = 'infoEl',
	INFO_TEMPLATE = 'infoTemplate',
	LEFT = 'left',
	LINK = 'link',
	LINKS = 'links',
	LOADER = 'loader',
	LOADING = 'loading',
	LOADING_EL = 'loadingEl',
	LOCK = 'lock',
	MAX_HEIGHT = 'maxHeight',
	MAX_WIDTH = 'maxWidth',
	MODAL = 'modal',
	OFFSET_HEIGHT = 'offsetHeight',
	OFFSET_WIDTH = 'offsetWidth',
	OPACITY = 'opacity',
	OVERLAY = 'overlay',
	PRELOAD_ALL_IMAGES = 'preloadAllImages',
	PRELOAD_NEIGHBOR_IMAGES = 'preloadNeighborImages',
	PX = 'px',
	REGION = 'region',
	RIGHT = 'right',
	SCROLL = 'scroll',
	SHOW = 'show',
	SHOW_ARROWS = 'showArrows',
	SHOW_CLOSE = 'showClose',
	SPACE = ' ',
	SRC = 'src',
	TITLE = 'title',
	TOP = 'top',
	TOTAL_LINKS = 'totalLinks',
	VIEWPORT_REGION = 'viewportRegion',
	VISIBLE = 'visible',
    OWNER_DOCUMENT = 'ownerDocument',

	isNodeList = function(v) {
		return (v instanceof A.NodeList);
	},

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

	KEY_ESC = 27,
	KEY_RIGHT = 39,
	KEY_LEFT = 37,

	getCN = A.ClassNameManager.getClassName,

	CSS_HELPER_SCROLL_LOCK = getCN(HELPER, SCROLL, LOCK),
	CSS_ICON_LOADING = getCN(ICON, LOADING),
	CSS_IMAGE_VIEWER_ARROW = getCN(IMAGE_VIEWER, ARROW),
	CSS_IMAGE_VIEWER_ARROW_LEFT = getCN(IMAGE_VIEWER, ARROW, LEFT),
	CSS_IMAGE_VIEWER_ARROW_RIGHT = getCN(IMAGE_VIEWER, ARROW, RIGHT),
	CSS_IMAGE_VIEWER_BD = getCN(IMAGE_VIEWER, BD),
	CSS_IMAGE_VIEWER_CAPTION = getCN(IMAGE_VIEWER, CAPTION),
	CSS_IMAGE_VIEWER_CLOSE = getCN(IMAGE_VIEWER, CLOSE),
	CSS_IMAGE_VIEWER_IMAGE = getCN(IMAGE_VIEWER, IMAGE),
	CSS_IMAGE_VIEWER_INFO = getCN(IMAGE_VIEWER, INFO),
	CSS_IMAGE_VIEWER_LINK = getCN(IMAGE_VIEWER, LINK),
	CSS_IMAGE_VIEWER_LOADING = getCN(IMAGE_VIEWER, LOADING),
	CSS_OVERLAY_HIDDEN = getCN(OVERLAY, HIDDEN),

	NODE_BLANK_TEXT = document.createTextNode(''),

	INFO_LABEL_TEMPLATE = 'Image {current} of {total}',

	TPL_ARROW_LEFT = '<a href="#" class="'+concat(CSS_IMAGE_VIEWER_ARROW, CSS_IMAGE_VIEWER_ARROW_LEFT)+'"></a>',
	TPL_ARROW_RIGHT = '<a href="#" class="'+concat(CSS_IMAGE_VIEWER_ARROW, CSS_IMAGE_VIEWER_ARROW_RIGHT)+'"></a>',
	TPL_CAPTION = '<div class="' + CSS_IMAGE_VIEWER_CAPTION + '"></div>',
	TPL_CLOSE = '<a href="#" class="'+ CSS_IMAGE_VIEWER_CLOSE +'"></a>',
	TPL_IMAGE = '<img class="' + CSS_IMAGE_VIEWER_IMAGE + '" />',
	TPL_INFO = '<div class="' + CSS_IMAGE_VIEWER_INFO + '"></div>',
	TPL_LOADER = '<div class="' + CSS_OVERLAY_HIDDEN + '"></div>',
	TPL_LOADING = '<div class="' + CSS_ICON_LOADING + '"></div>';

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

		/**
		 * Static property used to define the default attribute
		 * configuration for the ImageViewer.
		 *
		 * @property ImageViewer.ATTRS
		 * @type Object
		 * @static
		 */
		ATTRS: {
			/**
			 * If <code>true</code> the navigation is animated.
			 *
			 * @attribute anim
			 * @default true
			 * @type boolean
			 */
			anim: {
				value: true,
				validator: isBoolean
			},

			bodyContent: {
				value: NODE_BLANK_TEXT
			},

			/**
			 * The caption of the displayed image.
			 *
			 * @attribute caption
			 * @default ''
			 * @type String
			 */
			caption: {
				value: BLANK,
				validator: isString
			},

			/**
			 * If <code>true</code> the <a
	         * href="ImageViewer.html#config_caption">caption</a> will be pulled
	         * from the title DOM attribute.
			 *
			 * @attribute captionFromTitle
			 * @default true
			 * @type boolean
			 */
			captionFromTitle: {
				value: true,
				validator: isBoolean
			},

			/**
			 * If <code>true</code> the Overlay with the image will be positioned
	         * on the center of the viewport.
			 *
			 * @attribute centered
			 * @default true
			 * @type boolean
			 */
			centered: {
				value: true
			},

			/**
			 * Index of the current image.
			 *
			 * @attribute currentIndex
			 * @default 0
			 * @type Number
			 */
			currentIndex: {
				value: 0,
				validator: isNumber
			},

			/**
			 * Image node element used to load the images.
			 *
			 * @attribute image
			 * @default Generated img element.
			 * @readOnly
			 * @type Node
			 */
			image: {
				readOnly: true,
				valueFn: function() {
					return A.Node.create(TPL_IMAGE);
				}
			},

			/**
			 * Configuration attributes passed to the <a href="Anim.html">Anim</a>
	         * class.
			 *
			 * @attribute imageAnim
			 * @default Predefined <a href="Anim.html">Anim</a> configuration.
			 * @type Object
			 */
			imageAnim: {
				value: {},
				setter: function(value) {
					return A.merge(
						{
							to: {
								opacity: 1
							},
							easing: EASE_BOTH_STRONG,
							duration: .8
						},
						value
					);
				},
				validator: isObject
			},

			/**
			 * String template used to display the information.
			 *
			 * @attribute infoTemplate
			 * @default 'Image {current} of {total}'
			 * @type String
			 */
			infoTemplate: {
				getter: function(v) {
					return this._getInfoTemplate(v);
				},
				value: INFO_LABEL_TEMPLATE,
				validator: isString
			},

			/**
			 * Selector or NodeList containing the links where the ImageViewer
	         * extracts the information to generate the thumbnails.
			 *
			 * @attribute links
			 * @type String | NodeList
			 */
			links: {
				setter: function(v) {
					var instance = this;

					if (isNodeList(v)) {
						return v;
					}
					else if (isString(v)) {
						return A.all(v);
					}

					return new A.NodeList([v]);
				}
			},

			/**
			 * Whether the image is during a loading state.
			 *
			 * @attribute loading
			 * @default false
			 * @type boolean
			 */
			loading: {
				value: false,
				validator: isBoolean
			},

			/**
			 * Displays the modal <a href="OverlayMask.html">OverlayMask</a> on
	         * the viewport. Set to <code>false</code> to disable.
			 *
			 * @attribute modal
			 * @default { opacity: .8, background: '#000' }
			 * @type boolean | Object
			 */
			modal: {
				value: {
					opacity: .8,
					background: '#000'
				}
			},

			/**
			 * Preload all images grabbed from the <a
	         * href="ImageViewer.html#config_links">links</a> attribute.
			 *
			 * @attribute preloadAllImages
			 * @default false
			 * @type boolean
			 */
			preloadAllImages: {
				value: false,
				validator: isBoolean
			},

			/**
			 * Preload the neighbor image (i.e., the previous and next image based
	         * on the current load one).
			 *
			 * @attribute preloadAllImages
			 * @default false
			 * @type boolean
			 */
			preloadNeighborImages: {
				value: true,
				validator: isBoolean
			},

			/**
			 * Show close icon control.
			 *
			 * @attribute showClose
			 * @default true
			 * @type boolean
			 */
			showClose: {
				value: true,
				validator: isBoolean
			},

			/**
			 * Show the arrow controls.
			 *
			 * @attribute showArrows
			 * @default true
			 * @type boolean
			 */
			showArrows: {
				value: true,
				validator: isBoolean
			},

			/**
			 * Helper attribute to get the <code>size</code> of the <a
	         * href="ImageViewer.html#config_links">links</a> NodeList.
			 *
			 * @attribute totalLinks
			 * @default true
			 * @readOnly
			 * @type boolean
			 */
			totalLinks: {
				readOnly: true,
				getter: function(v) {
					return this.get(LINKS).size();
				}
			},

			visible: {
				value: false
			},

			zIndex: {
				value: 3000,
				validator: isNumber
			},

			/**
			 * The element to be used as arrow left.
			 *
			 * @attribute arrowLeftEl
			 * @default Generated HTML div element.
			 * @readOnly
			 * @type Node
			 */
			arrowLeftEl: {
				readOnly: true,
				valueFn: function() {
					return A.Node.create(TPL_ARROW_LEFT);
				}
			},

			/**
			 * The element to be used as arrow right.
			 *
			 * @attribute arrowRightEl
			 * @default Generated HTML div element.
			 * @readOnly
			 * @type Node
			 */
			arrowRightEl: {
				readOnly: true,
				valueFn: function() {
					return A.Node.create(TPL_ARROW_RIGHT);
				}
			},

			/**
			 * The element to be used as caption.
			 *
			 * @attribute captionEl
			 * @default Generated HTML div element.
			 * @readOnly
			 * @type Node
			 */
			captionEl: {
				readOnly: true,
				valueFn: function() {
					return A.Node.create(TPL_CAPTION);
				}
			},

			/**
			 * The element to be used as close.
			 *
			 * @attribute closeEl
			 * @default Generated HTML div element.
			 * @readOnly
			 * @type Node
			 */
			closeEl: {
				readOnly: true,
				valueFn: function() {
					return A.Node.create(TPL_CLOSE);
				}
			},

			/**
			 * The element to be used as info.
			 *
			 * @attribute infoEl
			 * @default Generated HTML div element.
			 * @readOnly
			 * @type Node
			 */
			infoEl: {
				readOnly: true,
				valueFn: function() {
					return A.Node.create(TPL_INFO);
				}
			},

			/**
			 * HTML element to house the <code>img</code> which is being loaded.
			 *
			 * @attribute loader
			 * @default Generated HTML div element.
			 * @type Node
			 */
			loader: {
				readOnly: true,
				valueFn: function() {
					return A.Node.create(TPL_LOADER).appendTo(document.body);
				}
			},

			/**
			 * The element to be used as loading.
			 *
			 * @attribute loadingEl
			 * @default Generated HTML div element.
			 * @type Node
			 */
			loadingEl: {
				valueFn: function() {
					return A.Node.create(TPL_LOADING);
				}
			},

	        /**
	         * The maximum height of the element
	         *
	         * @attribute maxHeight
	         * @default Infinity
	         * @type Number
	         */
			maxHeight: {
				value: Infinity,
				validator: isNumber
			},

	        /**
	         * The maximum width of the element
	         *
	         * @attribute maxWidth
	         * @default Infinity
	         * @type Number
	         */
			maxWidth: {
				value: Infinity,
				validator: isNumber
			}
		},

		EXTENDS: A.OverlayBase,

		prototype: {
			/**
			 * The index of the active image.
			 *
			 * @property activeImage
			 * @type Number
			 * @protected
			 */
			activeImage: 0,

			/**
			 * Handler for the key events.
			 *
			 * @property _keyHandler
			 * @type EventHandler
			 * @protected
			 */
			_keyHandler: null,

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

				instance._renderControls();
				instance._renderFooter();

				instance.get(LINKS).addClass(CSS_IMAGE_VIEWER_LINK);
			},

			/**
			 * Bind the events on the ImageViewer UI. Lifecycle.
			 *
			 * @method bindUI
			 * @protected
			 */
			bindUI: function() {
				var instance = this;
				var links = instance.get(LINKS);
				var arrowLeftEl = instance.get(ARROW_LEFT_EL);
				var arrowRightEl = instance.get(ARROW_RIGHT_EL);
				var closeEl = instance.get(CLOSE_EL);

				arrowLeftEl.on('click', A.bind(instance._onClickLeftArrow, instance));
				arrowRightEl.on('click', A.bind(instance._onClickRightArrow, instance));
				closeEl.on('click', A.bind(instance._onClickCloseEl, instance));
				links.on('click', A.bind(instance._onClickLinks, instance));

				instance._keyHandler = A.bind(instance._onKeyInteraction, instance);

				// NOTE: using keydown to avoid keyCode bug on IE
				A.getDoc().on('keydown', instance._keyHandler);

				instance.after('render', instance._afterRender);
				instance.after('loadingChange', instance._afterLoadingChange);
				instance.after('visibleChange', instance._afterVisibleChange);

				/**
				 * Handles the load event. Fired when a image is laoded.
				 *
				 * @event load
				 * @param {Event.Facade} event The load event.
				 * @type {Event.Custom}
				 */

				/**
				 * Handles the request event. Fired when a image is requested.
				 *
				 * @event request
				 * @param {Event.Facade} event The load event.
				 * @type {Event.Custom}
				 */

				/**
				 * Handles the anim event. Fired when the image anim ends.
				 *
				 * @event anim
				 * @param {Event.Facade} event The load event.
				 * @type {Event.Custom}
				 */
			},

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

				var links = instance.get(LINKS);

				instance.close();

				links.detach('click');
				links.removeClass(CSS_IMAGE_VIEWER_LINK);

				// detach key global listener from the document
				A.getDoc().detach('keydown', instance._keyHandler);

				instance.get(ARROW_LEFT_EL).remove(true);
				instance.get(ARROW_RIGHT_EL).remove(true);
				instance.get(CLOSE_EL).remove(true);
				instance.get(LOADER).remove(true);
			},

			/**
			 * Close the ImageViewer.
			 *
			 * @method close
			 */
			close: function() {
				var instance = this;

				instance.hide();
				instance.hideMask();
			},

			/**
			 * Get the Node reference to the <code>currentIndex</code> element from
		     * the <a href="ImageViewer.html#config_links">links</a>.
			 *
			 * @method getLink
			 * @param {Number} currentIndex
			 * @return {Node}
			 */
			getLink: function(currentIndex) {
				var instance = this;

				return instance.get(LINKS).item(currentIndex);
			},

			/**
			 * Get the current loaded node link reference.
			 *
			 * @method getCurrentLink
			 * @return {Node}
			 */
			getCurrentLink: function() {
				var instance = this;

				return instance.getLink(
					instance.get(CURRENT_INDEX)
				);
			},

			/**
			 * Load an image <code>src</code> on the ImageViewer.
			 *
			 * @method loadImage
			 * @param {String} src Image src.
			 */
			loadImage: function(src) {
				var instance = this;
				var bodyNode = instance.bodyNode;
				var loader = instance.get(LOADER);

				instance.set(LOADING, true);

				// the user could navigate to the next/prev image before the current image onLoad trigger
				// detach load event from the activeImage before create the new image placeholder
				if (instance.activeImage) {
					instance.activeImage.detach('load');
				}

				// creating the placeholder image
				instance.activeImage = instance.get(IMAGE).clone();

				var image = instance.activeImage;

				// append the placeholder image to the loader div
				loader.empty();
				loader.append(image);

				// bind the onLoad handler to the image, this handler should append the loaded image
				// to the overlay and take care of all animations
				image.on('load', A.bind(instance._onLoadImage, instance));

				// set the src of the image to be loaded on the placeholder image
				image.attr(SRC, src);

				instance.fire('request', { image: image });
			},

			/**
			 * Check if there is a node reference for the <code>currentIndex</code>.
			 *
			 * @method hasLink
			 * @param {Number} currentIndex
			 * @return {boolean}
			 */
			hasLink: function(currentIndex) {
				var instance = this;

				return instance.getLink(currentIndex);
			},

			/**
			 * Check if there is a next element to navigate.
			 *
			 * @method hasNext
			 * @return {boolean}
			 */
			hasNext: function() {
				var instance = this;

				return instance.hasLink(
					instance.get(CURRENT_INDEX) + 1
				);
			},

			/**
			 * Check if there is a previous element to navigate.
			 *
			 * @method hasPrev
			 * @return {boolean}
			 */
			hasPrev: function() {
				var instance = this;

				return instance.hasLink(
					instance.get(CURRENT_INDEX) - 1
				);
			},

			/**
			 * Hide all UI controls (i.e., arrows, close icon etc).
			 *
			 * @method hideControls
			 */
			hideControls: function() {
				var instance = this;

				instance.get(ARROW_LEFT_EL).hide();
				instance.get(ARROW_RIGHT_EL).hide();
				instance.get(CLOSE_EL).hide();
			},

			/**
			 * Hide the <a href="OverlayMask.html">OverlayMask</a> used when <a
		     * href="ImageViewer.html#config_modal">modal</a> is <code>true</code>.
			 *
			 * @method hideMask
			 */
			hideMask: function() {
				A.ImageViewerMask.hide();
			},

			/**
			 * Load the next image.
			 *
			 * @method next
			 */
			next: function() {
				var instance = this;

				if (instance.hasNext()) {
					instance.set(
						CURRENT_INDEX,
						instance.get(CURRENT_INDEX) + 1
					);

					instance.show();
				}
			},

			/**
			 * Preload all images.
			 *
			 * @method preloadAllImages
			 */
			preloadAllImages: function() {
				var instance = this;

				instance.get(LINKS).each(function(link, index) {
					instance.preloadImage(index);
				});
			},

			/**
			 * Preload an image based on its <code>index</code>.
			 *
			 * @method preloadImage
			 * @param {Number} currentIndex
			 */
			preloadImage: function(currentIndex) {
				var instance = this;
				var link = instance.getLink(currentIndex);

				if (link) {
					var src = link.attr(HREF);

					instance.get(IMAGE).clone().attr(SRC, src);
				}
			},

			/**
			 * Load the previous image.
			 *
			 * @method next
			 */
			prev: function() {
				var instance = this;

				if (instance.hasPrev()) {
					instance.set(
						CURRENT_INDEX,
						instance.get(CURRENT_INDEX) - 1
					);

					instance.show();
				}
			},

			/**
			 * Show the loading icon.
			 *
			 * @method showLoading
			 */
			showLoading: function() {
				var instance = this;
				var bodyNode = instance.bodyNode;

				instance.setStdModContent(
					BODY,
					instance.get(LOADING_EL)
				);
			},

			/**
			 * Show the the OverlayMask used on the <a
		     * href="ImageViewer.html#config_modal">modal</a>.
			 *
			 * @method showMask
			 */
			showMask: function() {
				var instance = this;
				var modal = instance.get(MODAL);

				if (isObject(modal)) {
					A.each(modal, function(value, key) {
						A.ImageViewerMask.set(key, value);
					});
				}

				if (modal) {
					A.ImageViewerMask.show();
				}
			},

			/**
			 * Show the ImageViewer UI.
			 *
			 * @method show
			 */
			show: function() {
				var instance = this;
				var currentLink = instance.getCurrentLink();

				if (currentLink) {
					instance.showMask();

					ImageViewer.superclass.show.apply(this, arguments);

					instance.loadImage(
						currentLink.attr(HREF)
					);
				}
			},

			/**
			 * Render the controls UI.
			 *
			 * @method _renderControls
			 * @protected
			 */
			_renderControls: function() {
				var instance = this;
				var body = A.one(BODY);

				body.append(
					instance.get(ARROW_LEFT_EL).hide()
				);

				body.append(
					instance.get(ARROW_RIGHT_EL).hide()
				);

				body.append(
					instance.get(CLOSE_EL).hide()
				);
			},

			/**
			 * Render the footer UI.
			 *
			 * @method _renderFooter
			 * @protected
			 */
			_renderFooter: function() {
				var instance = this;
				var boundingBox = instance.get(BOUNDING_BOX);
				var docFrag = boundingBox.get(OWNER_DOCUMENT).invoke(CREATE_DOCUMENT_FRAGMENT);

				docFrag.append(
					instance.get(CAPTION_EL)
				);
				docFrag.append(
					instance.get(INFO_EL)
				);

				instance.setStdModContent(
					FOOTER,
					docFrag
				);
			},

			/**
			 * Sync the caption UI.
			 *
			 * @method _syncCaptionUI
			 * @protected
			 */
			_syncCaptionUI: function() {
				var instance = this;
				var caption = instance.get(CAPTION);
				var captionEl = instance.get(CAPTION_EL);
				var captionFromTitle = instance.get(CAPTION_FROM_TITLE);

				if (captionFromTitle) {
					var currentLink = instance.getCurrentLink();

					if (currentLink) {
						var title = currentLink.attr(TITLE);

						if (title) {
							caption = currentLink.attr(TITLE);
						}
					}
				}

				captionEl.html(caption);
			},

			/**
			 * Sync the controls UI.
			 *
			 * @method _syncControlsUI
			 * @protected
			 */
			_syncControlsUI: function() {
				var instance = this;
				var boundingBox = instance.get(BOUNDING_BOX);
				var arrowLeftEl = instance.get(ARROW_LEFT_EL);
				var arrowRightEl = instance.get(ARROW_RIGHT_EL);
				var closeEl = instance.get(CLOSE_EL);

				if (instance.get(VISIBLE)) {
					if (instance.get(SHOW_ARROWS)) {
						// get the viewportRegion to centralize the arrows on the middle of the window viewport
						var viewportRegion = boundingBox.get(VIEWPORT_REGION);
						var heightRegion = Math.floor(viewportRegion.height/2) + viewportRegion.top;

						// show or hide arrows based on the hasPrev/hasNext information
						arrowLeftEl[ instance.hasPrev() ? SHOW : HIDE ]();
						arrowRightEl[ instance.hasNext() ? SHOW : HIDE ]();

						// set style top of the arrows in the middle of the window viewport
						arrowLeftEl.setStyle(TOP, heightRegion - arrowLeftEl.get(OFFSET_HEIGHT) + PX);
						arrowRightEl.setStyle(TOP, heightRegion - arrowRightEl.get(OFFSET_HEIGHT) + PX);
					}

					// if SHOW_CLOSE is enables, show close icon
					if (instance.get(SHOW_CLOSE)) {
						closeEl.show();
					}
				}
				else {
					// if the overlay is not visible hide all controls
					instance.hideControls();
				}
			},

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

				instance._syncControlsUI();
				instance._syncCaptionUI();
				instance._syncInfoUI();
			},

			/**
			 * Sync the info UI.
			 *
			 * @method _syncInfoUI
			 * @protected
			 */
			_syncInfoUI: function() {
				var instance = this;
				var infoEl = instance.get(INFO_EL);

				infoEl.html(
					instance.get(INFO_TEMPLATE)
				);
			},

			/**
			 * Calculate the resize ratio for the loaded image.
			 *
			 * @method _getRatio
			 * @param {Number} width Image width
			 * @param {Number} height Image height
			 * @protected
			 * @return {Number}
			 */
			_getRatio: function(width, height) {
				var instance = this;

				var ratio = 1;
				var maxHeight = instance.get(MAX_HEIGHT);
				var maxWidth = instance.get(MAX_WIDTH);

				if ((height > maxHeight) || (width > maxWidth)) {
					var hRatio = (height / maxHeight);
					var wRatio = (width / maxWidth);

					ratio = Math.max(hRatio, wRatio);
				}

				return ratio;
			},

			/**
			 * 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 instance = this;
				var total = instance.get(TOTAL_LINKS);
				var current = instance.get(CURRENT_INDEX) + 1;

				return A.substitute(v, {
					current: current,
					total: total
				});
			},

			/**
			 * Fires after the ImageViewer render phase.
			 *
			 * @method _afterRender
			 * @param {EventFacade} event
			 * @protected
			 */
			_afterRender: function() {
				var instance = this;
				var bodyNode = instance.bodyNode;

				bodyNode.addClass(CSS_IMAGE_VIEWER_BD);

				if (instance.get(PRELOAD_ALL_IMAGES)) {
					instance.preloadAllImages();
				}
			},

			/**
			 * Fires after the value of the
			 * <a href="ImageViewer.html#config_loading">loading</a> attribute change.
			 *
			 * @method _afterLoadingChange
			 * @param {EventFacade} event
			 * @protected
			 */
			_afterLoadingChange: function(event) {
				var instance = this;
				var boundingBox = instance.get(BOUNDING_BOX);

				if (event.newVal) {
					boundingBox.addClass(CSS_IMAGE_VIEWER_LOADING);

					instance.showLoading();
				}
				else{
					boundingBox.removeClass(CSS_IMAGE_VIEWER_LOADING);
				}
			},

			/**
			 * 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;

				instance._syncControlsUI();
			},

			/**
			 * Fires the click event on the close icon.
			 *
			 * @method _onClickCloseEl
			 * @param {EventFacade} event click event facade
			 * @protected
			 */
			_onClickCloseEl: function(event) {
				var instance = this;

				instance.close();

				event.halt()
			},

			/**
			 * Fires the click event on the left arrow icon.
			 *
			 * @method _onClickLeftArrow
			 * @param {EventFacade} event click event facade
			 * @protected
			 */
			_onClickLeftArrow: function(event) {
				var instance = this;

				instance.prev();

				event.halt()
			},

			/**
			 * Fires the click event on the right arrow icon.
			 *
			 * @method _onClickRightArrow
			 * @param {EventFacade} event click event facade
			 * @protected
			 */
			_onClickRightArrow: function(event) {
				var instance = this;

				instance.next();

				event.halt()
			},

			/**
			 * Fires the click event on the links.
			 *
			 * @method _onClickLinks
			 * @param {EventFacade} event click event facade
			 * @protected
			 */
			_onClickLinks: function(event) {
				var instance = this;
				var target = event.currentTarget;

				// set the current currentIndex of the clicked image
				instance.set(
					CURRENT_INDEX,
					instance.get(LINKS).indexOf(target)
				);

				instance.show();

				event.preventDefault();
			},

			/**
			 * Handles the key interaction (i.e., next, prev etc).
			 *
			 * @method _onKeyInteraction
			 * @param {EventFacade} event click event facade
			 * @protected
			 */
			_onKeyInteraction: function(event) {
				var instance = this;
				var keyCode = event.keyCode;

				if (!instance.get(VISIBLE)) {
					return false; // NOTE: return
				}

				if (keyCode == KEY_LEFT) {
					instance.prev();
				}
				else if (keyCode == KEY_RIGHT) {
					instance.next();
				}
				else if (keyCode == KEY_ESC) {
					instance.close();
				}
			},

			/**
			 * Fires on a image load.
			 *
			 * @method _onLoadImage
			 * @param {EventFacade} event
			 * @protected
			 */
			_onLoadImage: function(event) {
				var instance = this;
				var image = event.currentTarget;

				var imageAnim = instance.get(IMAGE_ANIM);

				if (instance.get(ANIM)) {
					image.setStyle(OPACITY, 0);

					// preparing node to the animation, pluging the NodeFX
					image.unplug(NodeFx).plug(NodeFx);

					image.fx.on('end', function(info) {
						instance.fire('anim', { anim: info, image: image });
					});

					image.fx.setAttrs(imageAnim);
					image.fx.stop().run();
				}

				instance.setStdModContent(BODY, image);

				instance._uiSetImageSize(image);

				instance._syncImageViewerUI();

				// invoke WidgetPosition _setAlignCenter to force center alignment
				instance._setAlignCenter(true);

				instance.set(LOADING, false);

				instance.fire('load', { image: image });

				if (instance.get(PRELOAD_NEIGHBOR_IMAGES)) {
					// preload neighbor images
					var currentIndex = instance.get(CURRENT_INDEX);

					instance.preloadImage(currentIndex + 1);
					instance.preloadImage(currentIndex - 1);
				}
			},

			/**
			 * Set the size of the image and the overlay respecting the
             * maxHeight/maxWidth ratio.
			 *
			 * @method _uiSetImageSize
			 * @param {HTMLImage} image Image
			 * @protected
			 */
			_uiSetImageSize: function(image) {
				var instance = this;
				var bodyNode = instance.bodyNode;
				var imageRegion = image.get(REGION);

				var ratio = instance._getRatio(
					imageRegion.width,
					imageRegion.height
				);

				var height = (imageRegion.height / ratio);
				var width = (imageRegion.width / ratio);

				image.set(OFFSET_HEIGHT, height);
				image.set(OFFSET_WIDTH, width);

				bodyNode.setStyles({
					height: height + PX,
					width: width + PX
				});
			}
		}
	}
);

A.ImageViewer = ImageViewer;

/**
 * A base class for ImageViewerMask - Controls the <a
 * href="ImageViewer.html#config_modal">modal</a> attribute.
 *
 * @class ImageViewerMask
 * @constructor
 * @extends OverlayMask
 * @static
 */
A.ImageViewerMask = new A.OverlayMask().render();

}, '@VERSION@' ,{skinnable:true, requires:['anim','aui-overlay-mask','substitute']});
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']});


AUI.add('aui-image-viewer', function(A){}, '@VERSION@' ,{use:['aui-image-viewer-base','aui-image-viewer-gallery'], skinnable:true});