Source: src/ui/moogallery.js

// load the css stylesheet
ajs.css('moogallery.css');

// look at initialize method for class description
ajs.ui.moogallery = new Class({

	Implements: [Options, Events],
	options: {
		show_bullets: true,
		onComplete: function() {}
	},
	/**
	 * @summary Images gallery user interface.
	 * @classdesc <p>The class creates an interactive gallery of images, videos and audios.</p>
	 *            <p>Given the thumbs' paths and some information the thumbs are loaded sequentially and inserted in a table structure automatically sized according to the size of the container,
	 *            then events are added in order to manage tips, lightbox widget (and navigation through media) and show media's meta information.</p>
   *            <p>Videos can be hosted on youtube or vimeo, audio files are inserted following html5 specifications. If used with cpoyer's mootools-mobile (https://github.com/cpojer/mootools-mobile) supports swipe gestures on mobile (android, ios) to change media in the lightbox view (activate the support_mobile option).</p>
	 * @constructs ajs.ui.moogallery
	 * @param {String|Element} container the gallery container element or its id attribute
	 * @param {Array} items_opt the array of objects containing the images properties. Each object has the following properties:
	 *                           <ul> 
	 *                             <li>thumb: path to the thumb image</li> 
	 *                             <li>img: path to the image</li> 
	 *                             <li>youtube: code of the youtube video</li> 
	 *                             <li>vimeo: code of the vimeo video</li> 
	 *                             <li>video_width: video width</li> 
	 *                             <li>video_height: video height</li> 
	 *                             <li>mpeg: path to mpeg file</li> 
	 *                             <li>ogg: path to ogg file</li> 
	 *                             <li>title: media title</li> 
	 *                             <li>description: media description</li> 
	 *                             <li>credits: media credits</li> 
	 *                           </ul> 
	 * @param {Object} options The class options object
	 * @param {Function} [options.onComplete=function(){}] A callback to be called when the rendering of the thumb ended
	 * @param {Boolean} [options.show_bullets=true] Whether or not to show bullets in lightbox widget
	 * @example
	 * 	var mt = new ajs.ui.moogallery('mycontainer', [
	 *          {
	 *              thumb: 'http://my/thumb/path', 
	 *              img: 'http://my/img/path', 
	 *              title: 'image title', 
	 *              description: 'image description'
	 *              credits: 'image credits'
	 *          },
	 *          {
	 *              thumb: 'http://my/thumb/path2', 
	 *              img: 'http://my/img/path2', 
	 *              title: 'image title2', 
	 *              description: 'image description2'
	 *              credits: 'image credits2'
	 *          }
	 *     ]);
	 */
	initialize: function(container, items_opt, options) {
		
		this.container = typeOf(container)=='element' ? container : $(container);
		this.container.setStyle('padding', '0');
		this.items_opt = items_opt;
		this.setOptions(options);

		this.mobile = this.options.support_mobile && ( Browser.Platform.ios || Browser.Platform.android) ? true : false;

		this.video_base_link = {
			youtube: 'http://www.youtube.com/embed/',
			vimeo: 'http://player.vimeo.com/video/'
		}

		this.items = [];
		this.thumbs = [];

		this.max_z_index = ajs.shared.getMaxZindex();

		this.table = new Element('table', {'class': 'moogallery'}).inject(this.container);
		this.tr = new Element('tr').inject(this.table);
    this.first_row = true;
    this.cols = 0;
    this.actual_col = 1;
		this.tr_width = 0;
		this.container_width = this.container.getCoordinates().width;

		this.addEvent('item_rendered', function(item_opt_index) {
			if(typeOf(this.items_opt[item_opt_index]) != 'null') {
				this.renderItem(this.items_opt[item_opt_index]);
			}
			else {
				this.fireEvent('complete');
			}
		});

		this.renderItem(this.items_opt[0]);
	},
	/**
	 * @summary Inserts an image in the table creating a new cell and changing row if necessary
	 * @memberof ajs.ui.moogallery.prototype
	 * @method
	 * @param {Object} item_opt the image options object to show
	 * @protected
	 * @return void
	 */		 
	renderItem: function(item_opt) {

		var thumb = new Element('img');

		thumb.onload = function() {
			var td = new Element('td');
			thumb.inject(td);
      if(!this.first_row) {
        if(this.actual_col >= this.cols) {
          this.tr = new Element('tr').inject(this.table);
          this.actual_col = 1;
        }
        else {
          this.actual_col++;
        }
        td.inject(this.tr);
      }
      else {
			  td.inject(this.tr);
        if(this.table.getCoordinates().width >= this.container_width) {
          td.dispose();
          this.tr = new Element('tr').inject(this.table);
          td.inject(this.tr);
          this.first_row = false;
        }
        else {
          this.cols++;
        }
      }
      this.fireEvent('item_rendered', this.items_opt.indexOf(item_opt)+1)
		}.bind(this);

		thumb.src = item_opt.thumb;

		this.setTip(thumb, item_opt);
		this.setLightbox(thumb, item_opt);

		this.thumbs.push(thumb);

		if(typeOf(item_opt.img) != 'null') {
			var item = new Element('img');
			item.src = item_opt.img;
		}
		else if(typeOf(item_opt.youtube) != 'null' || typeOf(item_opt.vimeo) != 'null') {
			var site = typeOf(item_opt.youtube) != 'null' ? 'youtube' : 'vimeo';
			var item = new Element('iframe');
			item.src = this.video_base_link[site] + item_opt[site];
			item.setProperty('frameborder', '0');
			item.setProperty('allowfullscreen', '');
			item.setProperty('width', item_opt.video_width);
			item.setProperty('height', item_opt.video_height);
		}
		else if(typeOf(item_opt.mpeg) != 'null' || typeOf(item_opt.ogg) != null) {
			var item = new Element('audio', {text: "Your browser does not support the audio element"}).addEvent('click', function(e) { e.stopPropagation(); });
			item.setProperty('controls', 'controls');
			['mpeg', 'ogg'].each(function(type) {
				if(typeOf(item_opt[type]) != 'null') {
					var src = new Element('source', {src: item_opt[type], type: 'audio/' + type});
					src.inject(item, 'top');
				}
			})
		}

		this.items.push(item);
		
	}.protect(),
	/**
	 * @summary Sets a tooltip tied to the thumb and displayed on mouseover
	 * @memberof ajs.ui.moogallery.prototype
	 * @method
	 * @param {Element} thumb the thumb image element
	 * @param {Object} item_opt the image options object to show
	 * @protected
	 * @return void
	 */		 
	setTip: function(thumb, item_opt) {

		var tip_container = new Element('div', {'class': 'moogallery_tip'});
		tip_container.set('html', '<b>' + item_opt.title + '</b>');
		thumb.addEvents({
			'mousemove': function(e) {
				tip_container.setStyles({
					position: 'absolute',
					top: (e.page.y + 10) + 'px',
					left: (e.page.x + 10) + 'px',
					'z-index': this.max_z_index++
				});
				tip_container.inject(document.body);
	
			}.bind(this),
			'mouseout': function(e) {
				tip_container.dispose();
			}
		});

	}.protect(),
	/**
	 * @summary Sets the thumb click event to render the lightbox navigation
	 * @memberof ajs.ui.moogallery.prototype
	 * @method
	 * @param {Element} thumb the thumb image element
	 * @param {Object} item_opt the image options object to show
	 * @protected
	 * @return void
	 */		 
	setLightbox: function(thumb, item_opt) {

		thumb.addEvent('click', function() {
			this.renderOverlay(this.renderLightbox.bind(this, item_opt));
		}.bind(this));

	}.protect(),
	/**
	 * @summary Renders the overlay and calls the function to execute after
	 * @memberof ajs.ui.moogallery.prototype
	 * @method
	 * @param {Function} chain_callback the function to call when overlay opacity animation ends
	 * @protected
	 * @return void
	 */		 
	renderOverlay: function(chain_callback) {

		var docDim = document.getScrollSize();

		this.overlay = new Element('div', {'class': 'moogallery_overlay'});
		this.overlay.setStyles({
			position: 'absolute',
			top: 0,
			left: 0,
			'z-index': this.max_z_index++,
			width: docDim.x,
			height: docDim.y,
			opacity: 0
		});
		this.overlay.inject(document.body);

		var myfx = new Fx.Tween(this.overlay, {'property': 'opacity'});
		myfx.start(0, 0.9).chain(chain_callback);
	},
	/**
	 * @summary Renders the lightbox widget
	 * @description This methos is public since has to be called in a chain process, but it's not necessary to call it directly
	 * @memberof ajs.ui.moogallery.prototype
	 * @method
	 * @param {Object} item_opt the image options object to show
	 * @return void
	 */		 
	renderLightbox: function(item_opt) {

		this.lightbox_container = new Element('div.moogallery_lightbox_container').setStyles({
			'visibility': 'hidden',
			'position': 'absolute',
			'overflow': 'hidden',
			'margin': '0' // no margin please!!
		});

		this.lightbox_container.inject(document.body);

		// click event
		this.lightbox_container.addEvent('click', function(e) {
			if(e.target.get('tag') == 'a') return true;
			var cont_dim = this.lightbox_container.getCoordinates();
			if(e.page.x < cont_dim.left + cont_dim.width/2) {
				if(this.index==0) return false;
				this.changeItem(this.index - 1);
			}
			else {
				if(this.index==this.items.length-1) return false;
				this.changeItem(this.index + 1);
			}
		}.bind(this));	

		// mouseover shows next prev arrows
		this.lightbox_container.addEvent('mouseover', function(e) {
			var cont_dim = this.lightbox_container.getCoordinates();
			if(e.page.x < cont_dim.left + cont_dim.width/2) {
				if(this.arrow_next.getStyle('opacity') != '0') {
					this.arrow_next.fade('hide');
				}
				if(this.index==0) return false;
				this.arrow_prev.fade('in');
			}
			else {
				if(this.arrow_prev.getStyle('opacity') != '0') {
					this.arrow_prev.fade('hide');
				}
				if(this.index==this.items.length-1) return false;
				this.arrow_next.fade('in');
			}
		}.bind(this));

		this.lightbox_container.addEvent('mouseleave', function(e) {
			this.arrow_next.fade('hide');
			this.arrow_prev.fade('hide');
		}.bind(this));

		this.index = this.items_opt.indexOf(item_opt);
		this.renderLightboxContainer();

		// swipe event
		if(this.mobile && Browser.Features.Touch) {
			document.body.addEvent('swipe', function(e) {
				Element.disableCustomEvents();
				(function(){
					Element.enableCustomEvents();
				}).delay(1000);
				if(e.direction == 'left') {
					if(this.index==this.items.length-1) return false;
					this.changeItem(this.index + 1);
				}
				else {
					if(this.index==0) return false;
					this.changeItem(this.index - 1);
				}
			}.bind(this));
		}

	},
	/**
	 * @summary Renders the lightbox widget container (image, title, description, credits, navigation)
	 * @description This methos is public since has to be called in a chain process, but it's not necessary to call it directly
	 * @memberof ajs.ui.moogallery.prototype
	 * @method
	 * @param {Object} item_opt the image options object to show
	 * @return void
	 */		 
	renderLightboxContainer: function() {

		// image to show
		item_opt = this.items_opt[this.index];
		var item = this.items[this.index];

		var item_info = new Element('div.moogallery_lightbox_info');
		var item_info_title = new Element('p.moogallery_lightbox_info_title').set('html', item_opt.title);

		var item_info_description_text = typeOf(item_opt.description) === 'null' ? '' : item_opt.description;
		var item_info_description = new Element('div.moogallery_lightbox_info_description').set('html', item_info_description_text);

		var item_info_credits_text = typeOf(item_opt.credits) === 'null' ? '' : item_opt.credits;
		var item_info_credits = new Element('p.moogallery_lightbox_info_credits').set('html', item_info_credits_text);

		item_info.adopt(item_info_title, item_info_description, item_info_credits);

		var navigation = this.renderNavigation(item_opt);

		// contents hidden with opacity
		var lightbox_subcontainer = new Element('div').setStyle('opacity', '0').inject(this.lightbox_container);

		lightbox_subcontainer.adopt(item, navigation, item_info);

		// dimensions
		// padding and borders increase the width of the element
		var plus_dim = this.lightbox_container.getStyle('padding').toInt()*2 + this.lightbox_container.getStyle('border-width').toInt() * 2;
		var final_width = item.getCoordinates().width;
		
		// first image opened
		if(this.lightbox_container.getStyle('visibility') == 'hidden') {
			var init_dim = 20;
			var init_real_dim = 20 + plus_dim;
			var vp = ajs.shared.getViewport();
			this.lightbox_container.setStyles({
				'width': init_dim + 'px',
				'height': init_dim + 'px',
				'top': (vp.cY - init_real_dim/2) + 'px',
				'left': (vp.cX - init_real_dim/2) + 'px',
				'visibility': 'visible',
				'z-index': this.max_z_index++
			});
		}

		var current_width = this.lightbox_container.getCoordinates().width;
		var current_height = this.lightbox_container.getCoordinates().height;
		var lc_animation = new Fx.Morph(this.lightbox_container, {duration: 'short'});
		lc_animation.start({
			'width': final_width, 
			'left': this.lightbox_container.getStyle('left').toInt() - (final_width + plus_dim - current_width)/2 
		}).chain(function() {
			lightbox_subcontainer.adopt(navigation, item_info);
			var final_height = lightbox_subcontainer.getCoordinates().height;
			lc_animation.start({
				'height': final_height,
				'top': this.lightbox_container.getStyle('top').toInt() - (final_height + plus_dim - current_height)/2, 
			}).chain(function() {
				lightbox_subcontainer.fade('in');	
			});
		}.bind(this));	

		// click outside to close
		this.overlay.addEvent('click', function(e) {
			var event = new DOMEvent(e);
			if(event.target != this.container) {
				this.lightbox_container.dispose();
				var myfx = new Fx.Tween(this.overlay, {'property': 'opacity'});
				myfx.start(0.9, 0).chain(function() {
					this.overlay.dispose();
				}.bind(this));
				if(this.mobile && Browser.Features.Touch) {
					document.body.removeEvent('swipe');
				}
			}
		}.bind(this));

	},
	/**
	 * @summary Renders the navigation controllers to surf through images in the lightbox widget
	 * @memberof ajs.ui.moogallery.prototype
	 * @method
	 * @param {Object} item_opt the image options object to show 
	 * @protected
	 * @return the lightbox navigation controllers
	 */		 
	renderNavigation: function(item_opt) {

		var index = this.items_opt.indexOf(item_opt);
		var nav = new Element('div.moogallery_nav');

		this.arrow_next = new Element('div', {'class': 'arrow arrow_next'}).setStyle('opacity', '0');
		this.arrow_prev = new Element('div', {'class': 'arrow arrow_prev'}).setStyle('opacity', '0');
		var arrows = new Element('div.nav_arrows').adopt(this.arrow_next, this.arrow_prev);

		var nav_info_text = (index + 1) + '/' + this.items_opt.length;
		var nav_info = new Element('div.moogallery_nav_info').set('text', nav_info_text);

		nav.grab(arrows);

		// bullets
		if(this.options.show_bullets) {
			var nav_table = new Element('table.moogallery_nav_table');
			var tr = new Element('tr').inject(nav_table);
			this.items.each(function(item, i) {
				var td = new Element('td').inject(tr);
				var bullet = new Element('div.moogallery_nav_bullet').inject(td);
				if(i == index) bullet.addClass('bullet_selected');
				else {
					bullet.addEvent('click', function(e) {
						e.stopPropagation();
						this.changeItem(i);
					}.bind(this))
				}
			}.bind(this));

			nav_table.inject(nav);	
		}

		nav.grab(nav_info);

		return nav;

	}.protect(),
	/**
	 * @summary Changes the image displayed in the lightbox widget
	 * @memberof ajs.ui.moogallery.prototype
	 * @method
	 * @param {Number} index the index of the image to show
	 * @return void
	 */		 
	changeItem: function(index) {
		this.index = index;
		var myfx = new Fx.Tween(this.lightbox_container.getChildren('div')[0], {property: 'opacity', duration: 'short'});
		myfx.start(1, 0).chain(function() {
			this.lightbox_container.empty();
			this.renderLightboxContainer();	
		}.bind(this));
	}

});