diff --git a/rhein/page/default_anfrage/default_anfrage.tpl b/rhein/page/default_anfrage/default_anfrage.tpl
index c77f675..8be7ebf 100644
--- a/rhein/page/default_anfrage/default_anfrage.tpl
+++ b/rhein/page/default_anfrage/default_anfrage.tpl
@@ -1,9 +1,5 @@
diff --git a/rhein/page/default_galerie/default_galerie.php b/rhein/page/default_galerie/default_galerie.php
index 6f12015..0da8886 100644
--- a/rhein/page/default_galerie/default_galerie.php
+++ b/rhein/page/default_galerie/default_galerie.php
@@ -1,6 +1,19 @@
gallery();
+ //return $vars['rhein_wohnung'].''.saimod_webcraft_gallery::getGallery(2);
+ return SYSTEM\PAGE\replace::replaceFile(\SYSTEM\SERVERPATH(new PPAGE(),'default_galerie/default_gallery.tpl'), $vars);
+
+ }
+
+ private function gallery(){
+ $pics = \SYSTEM\FILES\files::get('gallery');
+ $result = '';
+ foreach($pics as $pic){
+ $result .= '

';
+ }
+ return $result;
}
}
\ No newline at end of file
diff --git a/rhein/page/default_galerie/default_gallery.tpl b/rhein/page/default_galerie/default_gallery.tpl
new file mode 100644
index 0000000..c72c6d9
--- /dev/null
+++ b/rhein/page/default_galerie/default_gallery.tpl
@@ -0,0 +1,9 @@
+
+
+ ${default_gallery}
+
+
+
\ No newline at end of file
diff --git a/rhein/page/default_page/default_page.php b/rhein/page/default_page/default_page.php
index e9273be..2f4b6be 100644
--- a/rhein/page/default_page/default_page.php
+++ b/rhein/page/default_page/default_page.php
@@ -7,6 +7,7 @@ class default_page extends SYSTEM\PAGE\Page {
''.
''.
''.
+ ''.
'';
}
public static function css(){
diff --git a/rhein/page/default_page/js/galleria/LICENSE b/rhein/page/default_page/js/galleria/LICENSE
new file mode 100644
index 0000000..4f6237e
--- /dev/null
+++ b/rhein/page/default_page/js/galleria/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2012 Aino http://aino.se
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/rhein/page/default_page/js/galleria/galleria-1.2.9.js b/rhein/page/default_page/js/galleria/galleria-1.2.9.js
new file mode 100644
index 0000000..a29919e
--- /dev/null
+++ b/rhein/page/default_page/js/galleria/galleria-1.2.9.js
@@ -0,0 +1,6121 @@
+/**
+ * Galleria v 1.2.9 2013-02-14
+ * http://galleria.io
+ *
+ * Licensed under the MIT license
+ * https://raw.github.com/aino/galleria/master/LICENSE
+ *
+ */
+
+(function( $ ) {
+
+/*global jQuery, navigator, Galleria:true, Image */
+
+// some references
+var undef,
+ window = this,
+ doc = window.document,
+ $doc = $( doc ),
+ $win = $( window ),
+
+// native prototypes
+ protoArray = Array.prototype,
+
+// internal constants
+ VERSION = 1.29,
+ DEBUG = true,
+ TIMEOUT = 30000,
+ DUMMY = false,
+ NAV = navigator.userAgent.toLowerCase(),
+ HASH = window.location.hash.replace(/#\//, ''),
+ F = function(){},
+ FALSE = function() { return false; },
+ IE = (function() {
+
+ var v = 3,
+ div = doc.createElement( 'div' ),
+ all = div.getElementsByTagName( 'i' );
+
+ do {
+ div.innerHTML = '';
+ } while ( all[0] );
+
+ return v > 4 ? v : undef;
+
+ }() ),
+ DOM = function() {
+ return {
+ html: doc.documentElement,
+ body: doc.body,
+ head: doc.getElementsByTagName('head')[0],
+ title: doc.title
+ };
+ },
+ IFRAME = window.parent !== window.self,
+
+ // list of Galleria events
+ _eventlist = 'data ready thumbnail loadstart loadfinish image play pause progress ' +
+ 'fullscreen_enter fullscreen_exit idle_enter idle_exit rescale ' +
+ 'lightbox_open lightbox_close lightbox_image',
+
+ _events = (function() {
+
+ var evs = [];
+
+ $.each( _eventlist.split(' '), function( i, ev ) {
+ evs.push( ev );
+
+ // legacy events
+ if ( /_/.test( ev ) ) {
+ evs.push( ev.replace( /_/g, '' ) );
+ }
+ });
+
+ return evs;
+
+ }()),
+
+ // legacy options
+ // allows the old my_setting syntax and converts it to camel case
+
+ _legacyOptions = function( options ) {
+
+ var n;
+
+ if ( typeof options !== 'object' ) {
+
+ // return whatever it was...
+ return options;
+ }
+
+ $.each( options, function( key, value ) {
+ if ( /^[a-z]+_/.test( key ) ) {
+ n = '';
+ $.each( key.split('_'), function( i, k ) {
+ n += i > 0 ? k.substr( 0, 1 ).toUpperCase() + k.substr( 1 ) : k;
+ });
+ options[ n ] = value;
+ delete options[ key ];
+ }
+ });
+
+ return options;
+ },
+
+ _patchEvent = function( type ) {
+
+ // allow 'image' instead of Galleria.IMAGE
+ if ( $.inArray( type, _events ) > -1 ) {
+ return Galleria[ type.toUpperCase() ];
+ }
+
+ return type;
+ },
+
+ // video providers
+ _video = {
+ youtube: {
+ reg: /https?:\/\/(?:[a-zA_Z]{2,3}.)?(?:youtube\.com\/watch\?)((?:[\w\d\-\_\=]+&(?:amp;)?)*v(?:<[A-Z]+>)?=([0-9a-zA-Z\-\_]+))/i,
+ embed: function(id) {
+ return 'http://www.youtube.com/embed/'+id;
+ },
+ getThumb: function( id, success, fail ) {
+ fail = fail || F;
+ $.getJSON(window.location.protocol+'//gdata.youtube.com/feeds/api/videos/' + id + '?v=2&alt=json-in-script&callback=?', function(data) {
+ try {
+ success( data.entry.media$group.media$thumbnail[0].url );
+ } catch(e) {
+ fail();
+ }
+ }).error(fail);
+ }
+ },
+ vimeo: {
+ reg: /https?:\/\/(?:www\.)?(vimeo\.com)\/(?:hd#)?([0-9]+)/i,
+ embed: function(id) {
+ return 'http://player.vimeo.com/video/'+id;
+ },
+ getThumb: function( id, success, fail ) {
+ fail = fail || F;
+ $.getJSON('http://vimeo.com/api/v2/video/' + id + '.json?callback=?', function(data) {
+ try {
+ success( data[0].thumbnail_medium );
+ } catch(e) {
+ fail();
+ }
+ }).error(fail);
+ }
+ },
+ dailymotion: {
+ reg: /https?:\/\/(?:www\.)?(dailymotion\.com)\/video\/([^_]+)/,
+ embed: function(id) {
+ return 'http://www.dailymotion.com/embed/video/'+id;
+ },
+ getThumb: function( id, success, fail ) {
+ fail = fail || F;
+ $.getJSON('https://api.dailymotion.com/video/'+id+'?fields=thumbnail_medium_url&callback=?', function(data) {
+ try {
+ success( data.thumbnail_medium_url );
+ } catch(e) {
+ fail();
+ }
+ }).error(fail);
+ }
+ }
+ },
+
+ // utility for testing the video URL and getting the video ID
+ _videoTest = function( url ) {
+ var match;
+ for ( var v in _video ) {
+ match = url && url.match( _video[v].reg );
+ if( match && match.length ) {
+ return {
+ id: match[2],
+ provider: v
+ };
+ }
+ }
+ return false;
+ },
+
+ // native fullscreen handler
+ _nativeFullscreen = {
+
+ support: (function() {
+ var html = DOM().html;
+ return !IFRAME && ( html.requestFullscreen || html.mozRequestFullScreen || html.webkitRequestFullScreen );
+ }()),
+
+ callback: F,
+
+ enter: function( instance, callback, elem ) {
+
+ this.instance = instance;
+
+ this.callback = callback || F;
+
+ elem = elem || DOM().html;
+ if ( elem.requestFullscreen ) {
+ elem.requestFullscreen();
+ }
+ else if ( elem.mozRequestFullScreen ) {
+ elem.mozRequestFullScreen();
+ }
+ else if ( elem.webkitRequestFullScreen ) {
+ elem.webkitRequestFullScreen();
+ }
+ },
+
+ exit: function( callback ) {
+
+ this.callback = callback || F;
+
+ if ( doc.exitFullscreen ) {
+ doc.exitFullscreen();
+ }
+ else if ( doc.mozCancelFullScreen ) {
+ doc.mozCancelFullScreen();
+ }
+ else if ( doc.webkitCancelFullScreen ) {
+ doc.webkitCancelFullScreen();
+ }
+ },
+
+ instance: null,
+
+ listen: function() {
+
+ if ( !this.support ) {
+ return;
+ }
+
+ var handler = function() {
+
+ if ( !_nativeFullscreen.instance ) {
+ return;
+ }
+ var fs = _nativeFullscreen.instance._fullscreen;
+
+ if ( doc.fullscreen || doc.mozFullScreen || doc.webkitIsFullScreen ) {
+ fs._enter( _nativeFullscreen.callback );
+ } else {
+ fs._exit( _nativeFullscreen.callback );
+ }
+ };
+ doc.addEventListener( 'fullscreenchange', handler, false );
+ doc.addEventListener( 'mozfullscreenchange', handler, false );
+ doc.addEventListener( 'webkitfullscreenchange', handler, false );
+ }
+ },
+
+ // the internal gallery holder
+ _galleries = [],
+
+ // the internal instance holder
+ _instances = [],
+
+ // flag for errors
+ _hasError = false,
+
+ // canvas holder
+ _canvas = false,
+
+ // instance pool, holds the galleries until themeLoad is triggered
+ _pool = [],
+
+ // themeLoad trigger
+ _themeLoad = function( theme ) {
+
+ Galleria.theme = theme;
+
+ // run the instances we have in the pool
+ $.each( _pool, function( i, instance ) {
+ if ( !instance._initialized ) {
+ instance._init.call( instance );
+ }
+ });
+
+ _pool = [];
+ },
+
+ // the Utils singleton
+ Utils = (function() {
+
+ return {
+
+ // legacy support for clearTimer
+ clearTimer: function( id ) {
+ $.each( Galleria.get(), function() {
+ this.clearTimer( id );
+ });
+ },
+
+ // legacy support for addTimer
+ addTimer: function( id ) {
+ $.each( Galleria.get(), function() {
+ this.addTimer( id );
+ });
+ },
+
+ array : function( obj ) {
+ return protoArray.slice.call(obj, 0);
+ },
+
+ create : function( className, nodeName ) {
+ nodeName = nodeName || 'div';
+ var elem = doc.createElement( nodeName );
+ elem.className = className;
+ return elem;
+ },
+
+ removeFromArray : function( arr, elem ) {
+ $.each(arr, function(i, el) {
+ if ( el == elem ) {
+ arr.splice(i, 1);
+ return false;
+ }
+ });
+ return arr;
+ },
+
+ getScriptPath : function( src ) {
+
+ // the currently executing script is always the last
+ src = src || $('script:last').attr('src');
+ var slices = src.split('/');
+
+ if (slices.length == 1) {
+ return '';
+ }
+
+ slices.pop();
+
+ return slices.join('/') + '/';
+ },
+
+ // CSS3 transitions, added in 1.2.4
+ animate : (function() {
+
+ // detect transition
+ var transition = (function( style ) {
+ var props = 'transition WebkitTransition MozTransition OTransition'.split(' '),
+ i;
+
+ // disable css3 animations in opera until stable
+ if ( window.opera ) {
+ return false;
+ }
+
+ for ( i = 0; props[i]; i++ ) {
+ if ( typeof style[ props[ i ] ] !== 'undefined' ) {
+ return props[ i ];
+ }
+ }
+ return false;
+ }(( doc.body || doc.documentElement).style ));
+
+ // map transitionend event
+ var endEvent = {
+ MozTransition: 'transitionend',
+ OTransition: 'oTransitionEnd',
+ WebkitTransition: 'webkitTransitionEnd',
+ transition: 'transitionend'
+ }[ transition ];
+
+ // map bezier easing conversions
+ var easings = {
+ _default: [0.25, 0.1, 0.25, 1],
+ galleria: [0.645, 0.045, 0.355, 1],
+ galleriaIn: [0.55, 0.085, 0.68, 0.53],
+ galleriaOut: [0.25, 0.46, 0.45, 0.94],
+ ease: [0.25, 0, 0.25, 1],
+ linear: [0.25, 0.25, 0.75, 0.75],
+ 'ease-in': [0.42, 0, 1, 1],
+ 'ease-out': [0, 0, 0.58, 1],
+ 'ease-in-out': [0.42, 0, 0.58, 1]
+ };
+
+ // function for setting transition css for all browsers
+ var setStyle = function( elem, value, suffix ) {
+ var css = {};
+ suffix = suffix || 'transition';
+ $.each( 'webkit moz ms o'.split(' '), function() {
+ css[ '-' + this + '-' + suffix ] = value;
+ });
+ elem.css( css );
+ };
+
+ // clear styles
+ var clearStyle = function( elem ) {
+ setStyle( elem, 'none', 'transition' );
+ if ( Galleria.WEBKIT && Galleria.TOUCH ) {
+ setStyle( elem, 'translate3d(0,0,0)', 'transform' );
+ if ( elem.data('revert') ) {
+ elem.css( elem.data('revert') );
+ elem.data('revert', null);
+ }
+ }
+ };
+
+ // various variables
+ var change, strings, easing, syntax, revert, form, css;
+
+ // the actual animation method
+ return function( elem, to, options ) {
+
+ // extend defaults
+ options = $.extend({
+ duration: 400,
+ complete: F,
+ stop: false
+ }, options);
+
+ // cache jQuery instance
+ elem = $( elem );
+
+ if ( !options.duration ) {
+ elem.css( to );
+ options.complete.call( elem[0] );
+ return;
+ }
+
+ // fallback to jQuery's animate if transition is not supported
+ if ( !transition ) {
+ elem.animate(to, options);
+ return;
+ }
+
+ // stop
+ if ( options.stop ) {
+ // clear the animation
+ elem.unbind( endEvent );
+ clearStyle( elem );
+ }
+
+ // see if there is a change
+ change = false;
+ $.each( to, function( key, val ) {
+ css = elem.css( key );
+ if ( Utils.parseValue( css ) != Utils.parseValue( val ) ) {
+ change = true;
+ }
+ // also add computed styles for FF
+ elem.css( key, css );
+ });
+ if ( !change ) {
+ window.setTimeout( function() {
+ options.complete.call( elem[0] );
+ }, options.duration );
+ return;
+ }
+
+ // the css strings to be applied
+ strings = [];
+
+ // the easing bezier
+ easing = options.easing in easings ? easings[ options.easing ] : easings._default;
+
+ // the syntax
+ syntax = ' ' + options.duration + 'ms' + ' cubic-bezier(' + easing.join(',') + ')';
+
+ // add a tiny timeout so that the browsers catches any css changes before animating
+ window.setTimeout( (function(elem, endEvent, to, syntax) {
+ return function() {
+
+ // attach the end event
+ elem.one(endEvent, (function( elem ) {
+ return function() {
+
+ // clear the animation
+ clearStyle(elem);
+
+ // run the complete method
+ options.complete.call(elem[0]);
+ };
+ }( elem )));
+
+ // do the webkit translate3d for better performance on iOS
+ if( Galleria.WEBKIT && Galleria.TOUCH ) {
+
+ revert = {};
+ form = [0,0,0];
+
+ $.each( ['left', 'top'], function(i, m) {
+ if ( m in to ) {
+ form[ i ] = ( Utils.parseValue( to[ m ] ) - Utils.parseValue(elem.css( m )) ) + 'px';
+ revert[ m ] = to[ m ];
+ delete to[ m ];
+ }
+ });
+
+ if ( form[0] || form[1]) {
+
+ elem.data('revert', revert);
+
+ strings.push('-webkit-transform' + syntax);
+
+ // 3d animate
+ setStyle( elem, 'translate3d(' + form.join(',') + ')', 'transform');
+ }
+ }
+
+ // push the animation props
+ $.each(to, function( p, val ) {
+ strings.push(p + syntax);
+ });
+
+ // set the animation styles
+ setStyle( elem, strings.join(',') );
+
+ // animate
+ elem.css( to );
+
+ };
+ }(elem, endEvent, to, syntax)), 2);
+ };
+ }()),
+
+ removeAlpha : function( elem ) {
+ if ( IE < 9 && elem ) {
+
+ var style = elem.style,
+ currentStyle = elem.currentStyle,
+ filter = currentStyle && currentStyle.filter || style.filter || "";
+
+ if ( /alpha/.test( filter ) ) {
+ style.filter = filter.replace( /alpha\([^)]*\)/i, '' );
+ }
+ }
+ },
+
+ forceStyles : function( elem, styles ) {
+ elem = $(elem);
+ if ( elem.attr( 'style' ) ) {
+ elem.data( 'styles', elem.attr( 'style' ) ).removeAttr( 'style' );
+ }
+ elem.css( styles );
+ },
+
+ revertStyles : function() {
+ $.each( Utils.array( arguments ), function( i, elem ) {
+
+ elem = $( elem );
+ elem.removeAttr( 'style' );
+
+ elem.attr('style',''); // "fixes" webkit bug
+
+ if ( elem.data( 'styles' ) ) {
+ elem.attr( 'style', elem.data('styles') ).data( 'styles', null );
+ }
+ });
+ },
+
+ moveOut : function( elem ) {
+ Utils.forceStyles( elem, {
+ position: 'absolute',
+ left: -10000
+ });
+ },
+
+ moveIn : function() {
+ Utils.revertStyles.apply( Utils, Utils.array( arguments ) );
+ },
+
+ elem : function( elem ) {
+ if (elem instanceof $) {
+ return {
+ $: elem,
+ dom: elem[0]
+ };
+ } else {
+ return {
+ $: $(elem),
+ dom: elem
+ };
+ }
+ },
+
+ hide : function( elem, speed, callback ) {
+
+ callback = callback || F;
+
+ var el = Utils.elem( elem ),
+ $elem = el.$;
+
+ elem = el.dom;
+
+ // save the value if not exist
+ if (! $elem.data('opacity') ) {
+ $elem.data('opacity', $elem.css('opacity') );
+ }
+
+ // always hide
+ var style = { opacity: 0 };
+
+ if (speed) {
+
+ var complete = IE < 9 && elem ? function() {
+ Utils.removeAlpha( elem );
+ elem.style.visibility = 'hidden';
+ callback.call( elem );
+ } : callback;
+
+ Utils.animate( elem, style, {
+ duration: speed,
+ complete: complete,
+ stop: true
+ });
+ } else {
+ if ( IE < 9 && elem ) {
+ Utils.removeAlpha( elem );
+ elem.style.visibility = 'hidden';
+ } else {
+ $elem.css( style );
+ }
+ }
+ },
+
+ show : function( elem, speed, callback ) {
+
+ callback = callback || F;
+
+ var el = Utils.elem( elem ),
+ $elem = el.$;
+
+ elem = el.dom;
+
+ // bring back saved opacity
+ var saved = parseFloat( $elem.data('opacity') ) || 1,
+ style = { opacity: saved };
+
+ // animate or toggle
+ if (speed) {
+
+ if ( IE < 9 ) {
+ $elem.css('opacity', 0);
+ elem.style.visibility = 'visible';
+ }
+
+ var complete = IE < 9 && elem ? function() {
+ if ( style.opacity == 1 ) {
+ Utils.removeAlpha( elem );
+ }
+ callback.call( elem );
+ } : callback;
+
+ Utils.animate( elem, style, {
+ duration: speed,
+ complete: complete,
+ stop: true
+ });
+ } else {
+ if ( IE < 9 && style.opacity == 1 && elem ) {
+ Utils.removeAlpha( elem );
+ elem.style.visibility = 'visible';
+ } else {
+ $elem.css( style );
+ }
+ }
+ },
+
+
+ // enhanced click for mobile devices
+ // we bind a touchend and hijack any click event in the bubble
+ // then we execute the click directly and save it in a separate data object for later
+ optimizeTouch: (function() {
+
+ var node,
+ evs,
+ fakes,
+ travel,
+ evt = {},
+ handler = function( e ) {
+ e.preventDefault();
+ evt = $.extend({}, e, true);
+ },
+ attach = function() {
+ this.evt = evt;
+ },
+ fake = function() {
+ this.handler.call(node, this.evt);
+ };
+
+ return function( elem ) {
+
+ $(elem).bind('touchend', function( e ) {
+
+ node = e.target;
+ travel = true;
+
+ while( node.parentNode && node != e.currentTarget && travel ) {
+
+ evs = $(node).data('events');
+ fakes = $(node).data('fakes');
+
+ if (evs && 'click' in evs) {
+
+ travel = false;
+ e.preventDefault();
+
+ // fake the click and save the event object
+ $(node).click(handler).click();
+
+ // remove the faked click
+ evs.click.pop();
+
+ // attach the faked event
+ $.each( evs.click, attach);
+
+ // save the faked clicks in a new data object
+ $(node).data('fakes', evs.click);
+
+ // remove all clicks
+ delete evs.click;
+
+ } else if ( fakes ) {
+
+ travel = false;
+ e.preventDefault();
+
+ // fake all clicks
+ $.each( fakes, fake );
+ }
+
+ // bubble
+ node = node.parentNode;
+ }
+ });
+ };
+ }()),
+
+ wait : function(options) {
+ options = $.extend({
+ until : FALSE,
+ success : F,
+ error : function() { Galleria.raise('Could not complete wait function.'); },
+ timeout: 3000
+ }, options);
+
+ var start = Utils.timestamp(),
+ elapsed,
+ now,
+ fn = function() {
+ now = Utils.timestamp();
+ elapsed = now - start;
+ if ( options.until( elapsed ) ) {
+ options.success();
+ return false;
+ }
+
+ if (typeof options.timeout == 'number' && now >= start + options.timeout) {
+ options.error();
+ return false;
+ }
+ window.setTimeout(fn, 10);
+ };
+
+ window.setTimeout(fn, 10);
+ },
+
+ toggleQuality : function( img, force ) {
+
+ if ( ( IE !== 7 && IE !== 8 ) || !img || img.nodeName.toUpperCase() != 'IMG' ) {
+ return;
+ }
+
+ if ( typeof force === 'undefined' ) {
+ force = img.style.msInterpolationMode === 'nearest-neighbor';
+ }
+
+ img.style.msInterpolationMode = force ? 'bicubic' : 'nearest-neighbor';
+ },
+
+ insertStyleTag : function( styles, id ) {
+
+ if ( id && $( '#'+id ).length ) {
+ return;
+ }
+
+ var style = doc.createElement( 'style' );
+ if ( id ) {
+ style.id = id;
+ }
+
+ DOM().head.appendChild( style );
+
+ if ( style.styleSheet ) { // IE
+ style.styleSheet.cssText = styles;
+ } else {
+ var cssText = doc.createTextNode( styles );
+ style.appendChild( cssText );
+ }
+ },
+
+ // a loadscript method that works for local scripts
+ loadScript: function( url, callback ) {
+
+ var done = false,
+ script = $('
').attr({
+ src: url,
+ async: true
+ }).get(0);
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function() {
+ if ( !done && (!this.readyState ||
+ this.readyState === 'loaded' || this.readyState === 'complete') ) {
+
+ done = true;
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ if (typeof callback === 'function') {
+ callback.call( this, this );
+ }
+ }
+ };
+
+ DOM().head.appendChild( script );
+ },
+
+ // parse anything into a number
+ parseValue: function( val ) {
+ if (typeof val === 'number') {
+ return val;
+ } else if (typeof val === 'string') {
+ var arr = val.match(/\-?\d|\./g);
+ return arr && arr.constructor === Array ? arr.join('')*1 : 0;
+ } else {
+ return 0;
+ }
+ },
+
+ // timestamp abstraction
+ timestamp: function() {
+ return new Date().getTime();
+ },
+
+ loadCSS : function( href, id, callback ) {
+
+ var link,
+ length;
+
+ // look for manual css
+ $('link[rel=stylesheet]').each(function() {
+ if ( new RegExp( href ).test( this.href ) ) {
+ link = this;
+ return false;
+ }
+ });
+
+ if ( typeof id === 'function' ) {
+ callback = id;
+ id = undef;
+ }
+
+ callback = callback || F; // dirty
+
+ // if already present, return
+ if ( link ) {
+ callback.call( link, link );
+ return link;
+ }
+
+ // save the length of stylesheets to check against
+ length = doc.styleSheets.length;
+
+ // check for existing id
+ if( $( '#' + id ).length ) {
+
+ $( '#' + id ).attr( 'href', href );
+ length--;
+
+ } else {
+ link = $( '' ).attr({
+ rel: 'stylesheet',
+ href: href,
+ id: id
+ }).get(0);
+
+ var styles = $('link[rel="stylesheet"], style');
+ if ( styles.length ) {
+ styles.get(0).parentNode.insertBefore( link, styles[0] );
+ } else {
+ DOM().head.appendChild( link );
+ }
+
+ if ( IE ) {
+
+ // IE has a limit of 31 stylesheets in one document
+ if( length >= 31 ) {
+ Galleria.raise( 'You have reached the browser stylesheet limit (31)', true );
+ return;
+ }
+ }
+ }
+
+ if ( typeof callback === 'function' ) {
+
+ // First check for dummy element (new in 1.2.8)
+ var $loader = $('').attr( 'id', 'galleria-loader' ).hide().appendTo( DOM().body );
+
+ Utils.wait({
+ until: function() {
+ return $loader.height() == 1;
+ },
+ success: function() {
+ $loader.remove();
+ callback.call( link, link );
+ },
+ error: function() {
+ $loader.remove();
+
+ // If failed, tell the dev to download the latest theme
+ Galleria.raise( 'Theme CSS could not load after 20 sec. ' + ( Galleria.QUIRK ?
+ 'Your browser is in Quirks Mode, please add a correct doctype.' :
+ 'Please download the latest theme at http://galleria.io/customer/.' ), true );
+ },
+ timeout: 5000
+ });
+ }
+ return link;
+ }
+ };
+ }()),
+
+ // the transitions holder
+ _transitions = (function() {
+
+ var _slide = function(params, complete, fade, door) {
+
+ var easing = this.getOptions('easing'),
+ distance = this.getStageWidth(),
+ from = { left: distance * ( params.rewind ? -1 : 1 ) },
+ to = { left: 0 };
+
+ if ( fade ) {
+ from.opacity = 0;
+ to.opacity = 1;
+ } else {
+ from.opacity = 1;
+ }
+
+ $(params.next).css(from);
+
+ Utils.animate(params.next, to, {
+ duration: params.speed,
+ complete: (function( elems ) {
+ return function() {
+ complete();
+ elems.css({
+ left: 0
+ });
+ };
+ }( $( params.next ).add( params.prev ) )),
+ queue: false,
+ easing: easing
+ });
+
+ if (door) {
+ params.rewind = !params.rewind;
+ }
+
+ if (params.prev) {
+
+ from = { left: 0 };
+ to = { left: distance * ( params.rewind ? 1 : -1 ) };
+
+ if ( fade ) {
+ from.opacity = 1;
+ to.opacity = 0;
+ }
+
+ $(params.prev).css(from);
+ Utils.animate(params.prev, to, {
+ duration: params.speed,
+ queue: false,
+ easing: easing,
+ complete: function() {
+ $(this).css('opacity', 0);
+ }
+ });
+ }
+ };
+
+ return {
+
+ active: false,
+
+ init: function( effect, params, complete ) {
+ if ( _transitions.effects.hasOwnProperty( effect ) ) {
+ _transitions.effects[ effect ].call( this, params, complete );
+ }
+ },
+
+ effects: {
+
+ fade: function(params, complete) {
+ $(params.next).css({
+ opacity: 0,
+ left: 0
+ });
+ Utils.animate(params.next, {
+ opacity: 1
+ },{
+ duration: params.speed,
+ complete: complete
+ });
+ if (params.prev) {
+ $(params.prev).css('opacity',1).show();
+ Utils.animate(params.prev, {
+ opacity: 0
+ },{
+ duration: params.speed
+ });
+ }
+ },
+
+ flash: function(params, complete) {
+ $(params.next).css({
+ opacity: 0,
+ left: 0
+ });
+ if (params.prev) {
+ Utils.animate( params.prev, {
+ opacity: 0
+ },{
+ duration: params.speed/2,
+ complete: function() {
+ Utils.animate( params.next, {
+ opacity:1
+ },{
+ duration: params.speed,
+ complete: complete
+ });
+ }
+ });
+ } else {
+ Utils.animate( params.next, {
+ opacity: 1
+ },{
+ duration: params.speed,
+ complete: complete
+ });
+ }
+ },
+
+ pulse: function(params, complete) {
+ if (params.prev) {
+ $(params.prev).hide();
+ }
+ $(params.next).css({
+ opacity: 0,
+ left: 0
+ }).show();
+ Utils.animate(params.next, {
+ opacity:1
+ },{
+ duration: params.speed,
+ complete: complete
+ });
+ },
+
+ slide: function(params, complete) {
+ _slide.apply( this, Utils.array( arguments ) );
+ },
+
+ fadeslide: function(params, complete) {
+ _slide.apply( this, Utils.array( arguments ).concat( [true] ) );
+ },
+
+ doorslide: function(params, complete) {
+ _slide.apply( this, Utils.array( arguments ).concat( [false, true] ) );
+ }
+ }
+ };
+ }());
+
+_nativeFullscreen.listen();
+
+/**
+ The main Galleria class
+
+ @class
+ @constructor
+
+ @example var gallery = new Galleria();
+
+ @author http://aino.se
+
+ @requires jQuery
+
+*/
+
+Galleria = function() {
+
+ var self = this;
+
+ // internal options
+ this._options = {};
+
+ // flag for controlling play/pause
+ this._playing = false;
+
+ // internal interval for slideshow
+ this._playtime = 5000;
+
+ // internal variable for the currently active image
+ this._active = null;
+
+ // the internal queue, arrayified
+ this._queue = { length: 0 };
+
+ // the internal data array
+ this._data = [];
+
+ // the internal dom collection
+ this._dom = {};
+
+ // the internal thumbnails array
+ this._thumbnails = [];
+
+ // the internal layers array
+ this._layers = [];
+
+ // internal init flag
+ this._initialized = false;
+
+ // internal firstrun flag
+ this._firstrun = false;
+
+ // global stagewidth/height
+ this._stageWidth = 0;
+ this._stageHeight = 0;
+
+ // target holder
+ this._target = undef;
+
+ // bind hashes
+ this._binds = [];
+
+ // instance id
+ this._id = parseInt(Math.random()*10000, 10);
+
+ // add some elements
+ var divs = 'container stage images image-nav image-nav-left image-nav-right ' +
+ 'info info-text info-title info-description ' +
+ 'thumbnails thumbnails-list thumbnails-container thumb-nav-left thumb-nav-right ' +
+ 'loader counter tooltip',
+ spans = 'current total';
+
+ $.each( divs.split(' '), function( i, elemId ) {
+ self._dom[ elemId ] = Utils.create( 'galleria-' + elemId );
+ });
+
+ $.each( spans.split(' '), function( i, elemId ) {
+ self._dom[ elemId ] = Utils.create( 'galleria-' + elemId, 'span' );
+ });
+
+ // the internal keyboard object
+ // keeps reference of the keybinds and provides helper methods for binding keys
+ var keyboard = this._keyboard = {
+
+ keys : {
+ 'UP': 38,
+ 'DOWN': 40,
+ 'LEFT': 37,
+ 'RIGHT': 39,
+ 'RETURN': 13,
+ 'ESCAPE': 27,
+ 'BACKSPACE': 8,
+ 'SPACE': 32
+ },
+
+ map : {},
+
+ bound: false,
+
+ press: function(e) {
+ var key = e.keyCode || e.which;
+ if ( key in keyboard.map && typeof keyboard.map[key] === 'function' ) {
+ keyboard.map[key].call(self, e);
+ }
+ },
+
+ attach: function(map) {
+
+ var key, up;
+
+ for( key in map ) {
+ if ( map.hasOwnProperty( key ) ) {
+ up = key.toUpperCase();
+ if ( up in keyboard.keys ) {
+ keyboard.map[ keyboard.keys[up] ] = map[key];
+ } else {
+ keyboard.map[ up ] = map[key];
+ }
+ }
+ }
+ if ( !keyboard.bound ) {
+ keyboard.bound = true;
+ $doc.bind('keydown', keyboard.press);
+ }
+ },
+
+ detach: function() {
+ keyboard.bound = false;
+ keyboard.map = {};
+ $doc.unbind('keydown', keyboard.press);
+ }
+ };
+
+ // internal controls for keeping track of active / inactive images
+ var controls = this._controls = {
+
+ 0: undef,
+
+ 1: undef,
+
+ active : 0,
+
+ swap : function() {
+ controls.active = controls.active ? 0 : 1;
+ },
+
+ getActive : function() {
+ return controls[ controls.active ];
+ },
+
+ getNext : function() {
+ return controls[ 1 - controls.active ];
+ }
+ };
+
+ // internal carousel object
+ var carousel = this._carousel = {
+
+ // shortcuts
+ next: self.$('thumb-nav-right'),
+ prev: self.$('thumb-nav-left'),
+
+ // cache the width
+ width: 0,
+
+ // track the current position
+ current: 0,
+
+ // cache max value
+ max: 0,
+
+ // save all hooks for each width in an array
+ hooks: [],
+
+ // update the carousel
+ // you can run this method anytime, f.ex on window.resize
+ update: function() {
+ var w = 0,
+ h = 0,
+ hooks = [0];
+
+ $.each( self._thumbnails, function( i, thumb ) {
+ if ( thumb.ready ) {
+ w += thumb.outerWidth || $( thumb.container ).outerWidth( true );
+ hooks[ i+1 ] = w;
+ h = Math.max( h, thumb.outerHeight || $( thumb.container).outerHeight( true ) );
+ }
+ });
+
+ self.$( 'thumbnails' ).css({
+ width: w,
+ height: h
+ });
+
+ carousel.max = w;
+ carousel.hooks = hooks;
+ carousel.width = self.$( 'thumbnails-list' ).width();
+ carousel.setClasses();
+
+ self.$( 'thumbnails-container' ).toggleClass( 'galleria-carousel', w > carousel.width );
+
+ // one extra calculation
+ carousel.width = self.$( 'thumbnails-list' ).width();
+
+ // todo: fix so the carousel moves to the left
+ },
+
+ bindControls: function() {
+
+ var i;
+
+ carousel.next.bind( 'click', function(e) {
+ e.preventDefault();
+
+ if ( self._options.carouselSteps === 'auto' ) {
+
+ for ( i = carousel.current; i < carousel.hooks.length; i++ ) {
+ if ( carousel.hooks[i] - carousel.hooks[ carousel.current ] > carousel.width ) {
+ carousel.set(i - 2);
+ break;
+ }
+ }
+
+ } else {
+ carousel.set( carousel.current + self._options.carouselSteps);
+ }
+ });
+
+ carousel.prev.bind( 'click', function(e) {
+ e.preventDefault();
+
+ if ( self._options.carouselSteps === 'auto' ) {
+
+ for ( i = carousel.current; i >= 0; i-- ) {
+ if ( carousel.hooks[ carousel.current ] - carousel.hooks[i] > carousel.width ) {
+ carousel.set( i + 2 );
+ break;
+ } else if ( i === 0 ) {
+ carousel.set( 0 );
+ break;
+ }
+ }
+ } else {
+ carousel.set( carousel.current - self._options.carouselSteps );
+ }
+ });
+ },
+
+ // calculate and set positions
+ set: function( i ) {
+ i = Math.max( i, 0 );
+ while ( carousel.hooks[i - 1] + carousel.width >= carousel.max && i >= 0 ) {
+ i--;
+ }
+ carousel.current = i;
+ carousel.animate();
+ },
+
+ // get the last position
+ getLast: function(i) {
+ return ( i || carousel.current ) - 1;
+ },
+
+ // follow the active image
+ follow: function(i) {
+
+ //don't follow if position fits
+ if ( i === 0 || i === carousel.hooks.length - 2 ) {
+ carousel.set( i );
+ return;
+ }
+
+ // calculate last position
+ var last = carousel.current;
+ while( carousel.hooks[last] - carousel.hooks[ carousel.current ] <
+ carousel.width && last <= carousel.hooks.length ) {
+ last ++;
+ }
+
+ // set position
+ if ( i - 1 < carousel.current ) {
+ carousel.set( i - 1 );
+ } else if ( i + 2 > last) {
+ carousel.set( i - last + carousel.current + 2 );
+ }
+ },
+
+ // helper for setting disabled classes
+ setClasses: function() {
+ carousel.prev.toggleClass( 'disabled', !carousel.current );
+ carousel.next.toggleClass( 'disabled', carousel.hooks[ carousel.current ] + carousel.width >= carousel.max );
+ },
+
+ // the animation method
+ animate: function(to) {
+ carousel.setClasses();
+ var num = carousel.hooks[ carousel.current ] * -1;
+
+ if ( isNaN( num ) ) {
+ return;
+ }
+
+ Utils.animate(self.get( 'thumbnails' ), {
+ left: num
+ },{
+ duration: self._options.carouselSpeed,
+ easing: self._options.easing,
+ queue: false
+ });
+ }
+ };
+
+ // tooltip control
+ // added in 1.2
+ var tooltip = this._tooltip = {
+
+ initialized : false,
+
+ open: false,
+
+ timer: 'tooltip' + self._id,
+
+ swapTimer: 'swap' + self._id,
+
+ init: function() {
+
+ tooltip.initialized = true;
+
+ var css = '.galleria-tooltip{padding:3px 8px;max-width:50%;background:#ffe;color:#000;z-index:3;position:absolute;font-size:11px;line-height:1.3;' +
+ 'opacity:0;box-shadow:0 0 2px rgba(0,0,0,.4);-moz-box-shadow:0 0 2px rgba(0,0,0,.4);-webkit-box-shadow:0 0 2px rgba(0,0,0,.4);}';
+
+ Utils.insertStyleTag( css, 'galleria-tooltip' );
+
+ self.$( 'tooltip' ).css({
+ opacity: 0.8,
+ visibility: 'visible',
+ display: 'none'
+ });
+
+ },
+
+ // move handler
+ move: function( e ) {
+ var mouseX = self.getMousePosition(e).x,
+ mouseY = self.getMousePosition(e).y,
+ $elem = self.$( 'tooltip' ),
+ x = mouseX,
+ y = mouseY,
+ height = $elem.outerHeight( true ) + 1,
+ width = $elem.outerWidth( true ),
+ limitY = height + 15;
+
+ var maxX = self.$( 'container' ).width() - width - 2,
+ maxY = self.$( 'container' ).height() - height - 2;
+
+ if ( !isNaN(x) && !isNaN(y) ) {
+
+ x += 10;
+ y -= ( height+8 );
+
+ x = Math.max( 0, Math.min( maxX, x ) );
+ y = Math.max( 0, Math.min( maxY, y ) );
+
+ if( mouseY < limitY ) {
+ y = limitY;
+ }
+
+ $elem.css({ left: x, top: y });
+ }
+ },
+
+ // bind elements to the tooltip
+ // you can bind multiple elementIDs using { elemID : function } or { elemID : string }
+ // you can also bind single DOM elements using bind(elem, string)
+ bind: function( elem, value ) {
+
+ // todo: revise if alternative tooltip is needed for mobile devices
+ if (Galleria.TOUCH) {
+ return;
+ }
+
+ if (! tooltip.initialized ) {
+ tooltip.init();
+ }
+
+ var mouseout = function() {
+ self.$( 'container' ).unbind( 'mousemove', tooltip.move );
+ self.clearTimer( tooltip.timer );
+
+ self.$( 'tooltip' ).stop().animate({
+ opacity: 0
+ }, 200, function() {
+
+ self.$( 'tooltip' ).hide();
+
+ self.addTimer( tooltip.swapTimer, function() {
+ tooltip.open = false;
+ }, 1000);
+ });
+ };
+
+ var hover = function( elem, value) {
+
+ tooltip.define( elem, value );
+
+ $( elem ).hover(function() {
+
+ self.clearTimer( tooltip.swapTimer );
+ self.$('container').unbind( 'mousemove', tooltip.move ).bind( 'mousemove', tooltip.move ).trigger( 'mousemove' );
+ tooltip.show( elem );
+
+ self.addTimer( tooltip.timer, function() {
+ self.$( 'tooltip' ).stop().show().animate({
+ opacity: 1
+ });
+ tooltip.open = true;
+
+ }, tooltip.open ? 0 : 500);
+
+ }, mouseout).click(mouseout);
+ };
+
+ if ( typeof value === 'string' ) {
+ hover( ( elem in self._dom ? self.get( elem ) : elem ), value );
+ } else {
+ // asume elemID here
+ $.each( elem, function( elemID, val ) {
+ hover( self.get(elemID), val );
+ });
+ }
+ },
+
+ show: function( elem ) {
+
+ elem = $( elem in self._dom ? self.get(elem) : elem );
+
+ var text = elem.data( 'tt' ),
+ mouseup = function( e ) {
+
+ // attach a tiny settimeout to make sure the new tooltip is filled
+ window.setTimeout( (function( ev ) {
+ return function() {
+ tooltip.move( ev );
+ };
+ }( e )), 10);
+
+ elem.unbind( 'mouseup', mouseup );
+
+ };
+
+ text = typeof text === 'function' ? text() : text;
+
+ if ( ! text ) {
+ return;
+ }
+
+ self.$( 'tooltip' ).html( text.replace(/\s/, ' ') );
+
+ // trigger mousemove on mouseup in case of click
+ elem.bind( 'mouseup', mouseup );
+ },
+
+ define: function( elem, value ) {
+
+ // we store functions, not strings
+ if (typeof value !== 'function') {
+ var s = value;
+ value = function() {
+ return s;
+ };
+ }
+
+ elem = $( elem in self._dom ? self.get(elem) : elem ).data('tt', value);
+
+ tooltip.show( elem );
+
+ }
+ };
+
+ // internal fullscreen control
+ var fullscreen = this._fullscreen = {
+
+ scrolled: 0,
+
+ crop: undef,
+
+ active: false,
+
+ keymap: self._keyboard.map,
+
+ parseCallback: function( callback, enter ) {
+
+ return _transitions.active ? function() {
+ if ( typeof callback == 'function' ) {
+ callback.call(self);
+ }
+ var active = self._controls.getActive(),
+ next = self._controls.getNext();
+
+ self._scaleImage( next );
+ self._scaleImage( active );
+
+ if ( enter && self._options.trueFullscreen ) {
+ // Firefox bug, revise later
+ $( active.container ).add( next.container ).trigger( 'transitionend' );
+ }
+
+ } : callback;
+
+ },
+
+ enter: function( callback ) {
+
+ callback = fullscreen.parseCallback( callback, true );
+
+ if ( self._options.trueFullscreen && _nativeFullscreen.support ) {
+
+ // do some stuff prior animation for wmoother transitions
+
+ fullscreen.active = true;
+
+ Utils.forceStyles( self.get('container'), {
+ width: '100%',
+ height: '100%'
+ });
+
+ self.rescale();
+
+ if ( Galleria.MAC ) {
+ if ( Galleria.WEBKIT && !( Galleria.SAFARI && /version\/[1-5]/.test(NAV)) ) {
+ self.$('container').css('opacity', 0).addClass('fullscreen');
+ window.setTimeout(function() {
+ fullscreen.scale();
+ self.$('container').css('opacity', 1);
+ }, 50);
+ } else {
+ self.$('stage').css('opacity', 0);
+ window.setTimeout(function() {
+ fullscreen.scale();
+ self.$('stage').css('opacity', 1);
+ },4);
+ }
+ } else {
+ self.$('container').addClass('fullscreen');
+ }
+
+ $win.resize( fullscreen.scale );
+
+ _nativeFullscreen.enter( self, callback, self.get('container') );
+
+ } else {
+
+ fullscreen.scrolled = $win.scrollTop();
+ window.scrollTo(0, 0);
+
+ fullscreen._enter( callback );
+ }
+
+ },
+
+ _enter: function( callback ) {
+
+ fullscreen.active = true;
+
+ if ( IFRAME ) {
+
+ fullscreen.iframe = (function() {
+
+ var elem,
+ refer = doc.referrer,
+ test = doc.createElement('a'),
+ loc = window.location;
+
+ test.href = refer;
+
+ if( test.protocol != loc.protocol ||
+ test.hostname != loc.hostname ||
+ test.port != loc.port ) {
+ Galleria.raise('Parent fullscreen not available. Iframe protocol, domains and ports must match.');
+ return false;
+ }
+
+ fullscreen.pd = window.parent.document;
+
+ $( fullscreen.pd ).find('iframe').each(function() {
+ var idoc = this.contentDocument || this.contentWindow.document;
+ if ( idoc === doc ) {
+ elem = this;
+ return false;
+ }
+ });
+
+ return elem;
+ }());
+
+ }
+
+ // hide the image until rescale is complete
+ Utils.hide( self.getActiveImage() );
+
+ if ( IFRAME && fullscreen.iframe ) {
+ fullscreen.iframe.scrolled = $( window.parent ).scrollTop();
+ window.parent.scrollTo(0, 0);
+ }
+
+ var data = self.getData(),
+ options = self._options,
+ inBrowser = !self._options.trueFullscreen || !_nativeFullscreen.support,
+ htmlbody = {
+ height: '100%',
+ overflow: 'hidden',
+ margin:0,
+ padding:0
+ };
+
+ if (inBrowser) {
+
+ self.$('container').addClass('fullscreen');
+
+ // begin styleforce
+
+ Utils.forceStyles(self.get('container'), {
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ zIndex: 10000
+ });
+ Utils.forceStyles( DOM().html, htmlbody );
+ Utils.forceStyles( DOM().body, htmlbody );
+ }
+
+ if ( IFRAME && fullscreen.iframe ) {
+ Utils.forceStyles( fullscreen.pd.documentElement, htmlbody );
+ Utils.forceStyles( fullscreen.pd.body, htmlbody );
+ Utils.forceStyles( fullscreen.iframe, $.extend( htmlbody, {
+ width: '100%',
+ height: '100%',
+ top: 0,
+ left: 0,
+ position: 'fixed',
+ zIndex: 10000,
+ border: 'none'
+ }));
+ }
+
+ // temporarily attach some keys
+ // save the old ones first in a cloned object
+ fullscreen.keymap = $.extend({}, self._keyboard.map);
+
+ self.attachKeyboard({
+ escape: self.exitFullscreen,
+ right: self.next,
+ left: self.prev
+ });
+
+ // temporarily save the crop
+ fullscreen.crop = options.imageCrop;
+
+ // set fullscreen options
+ if ( options.fullscreenCrop != undef ) {
+ options.imageCrop = options.fullscreenCrop;
+ }
+
+ // swap to big image if it's different from the display image
+ if ( data && data.big && data.image !== data.big ) {
+ var big = new Galleria.Picture(),
+ cached = big.isCached( data.big ),
+ index = self.getIndex(),
+ thumb = self._thumbnails[ index ];
+
+ self.trigger( {
+ type: Galleria.LOADSTART,
+ cached: cached,
+ rewind: false,
+ index: index,
+ imageTarget: self.getActiveImage(),
+ thumbTarget: thumb,
+ galleriaData: data
+ });
+
+ big.load( data.big, function( big ) {
+ self._scaleImage( big, {
+ complete: function( big ) {
+ self.trigger({
+ type: Galleria.LOADFINISH,
+ cached: cached,
+ index: index,
+ rewind: false,
+ imageTarget: big.image,
+ thumbTarget: thumb
+ });
+ var image = self._controls.getActive().image;
+ if ( image ) {
+ $( image ).width( big.image.width ).height( big.image.height )
+ .attr( 'style', $( big.image ).attr('style') )
+ .attr( 'src', big.image.src );
+ }
+ }
+ });
+ });
+ }
+
+ // init the first rescale and attach callbacks
+
+ self.rescale(function() {
+
+ self.addTimer(false, function() {
+ // show the image after 50 ms
+ if ( inBrowser ) {
+ Utils.show( self.getActiveImage() );
+ }
+
+ if (typeof callback === 'function') {
+ callback.call( self );
+ }
+
+ }, 100);
+
+ self.trigger( Galleria.FULLSCREEN_ENTER );
+ });
+
+ if ( !inBrowser ) {
+ Utils.show( self.getActiveImage() );
+ } else {
+ $win.resize( fullscreen.scale );
+ }
+
+ },
+
+ scale : function() {
+ self.rescale();
+ },
+
+ exit: function( callback ) {
+
+ callback = fullscreen.parseCallback( callback );
+
+ if ( self._options.trueFullscreen && _nativeFullscreen.support ) {
+ _nativeFullscreen.exit( callback );
+ } else {
+ fullscreen._exit( callback );
+ }
+ },
+
+ _exit: function( callback ) {
+
+ fullscreen.active = false;
+
+ var inBrowser = !self._options.trueFullscreen || !_nativeFullscreen.support;
+
+ self.$('container').removeClass( 'fullscreen' );
+
+ if ( inBrowser ) {
+ Utils.hide( self.getActiveImage() );
+
+ // revert all styles
+ Utils.revertStyles( self.get('container'), DOM().html, DOM().body );
+
+ // scroll back
+ window.scrollTo(0, fullscreen.scrolled);
+ }
+
+ if ( IFRAME && fullscreen.iframe ) {
+ Utils.revertStyles( fullscreen.pd.documentElement, fullscreen.pd.body, fullscreen.iframe );
+ if ( fullscreen.iframe.scrolled ) {
+ window.parent.scrollTo(0, fullscreen.iframe.scrolled );
+ }
+ }
+
+ // detach all keyboard events and apply the old keymap
+ self.detachKeyboard();
+ self.attachKeyboard( fullscreen.keymap );
+
+ // bring back cached options
+ self._options.imageCrop = fullscreen.crop;
+
+ // return to original image
+ var big = self.getData().big,
+ image = self._controls.getActive().image;
+
+ if ( !self.getData().iframe && image && big && big == image.src ) {
+
+ window.setTimeout(function(src) {
+ return function() {
+ image.src = src;
+ };
+ }( self.getData().image ), 1 );
+
+ }
+
+ self.rescale(function() {
+ self.addTimer(false, function() {
+
+ // show the image after 50 ms
+ if ( inBrowser ) {
+ Utils.show( self.getActiveImage() );
+ }
+
+ if ( typeof callback === 'function' ) {
+ callback.call( self );
+ }
+
+ $win.trigger( 'resize' );
+
+ }, 50);
+ self.trigger( Galleria.FULLSCREEN_EXIT );
+ });
+
+ $win.unbind('resize', fullscreen.scale);
+ }
+ };
+
+ // the internal idle object for controlling idle states
+ var idle = this._idle = {
+
+ trunk: [],
+
+ bound: false,
+
+ active: false,
+
+ add: function(elem, to, from, hide) {
+ if (!elem) {
+ return;
+ }
+ if (!idle.bound) {
+ idle.addEvent();
+ }
+ elem = $(elem);
+
+ if ( typeof from == 'boolean' ) {
+ hide = from;
+ from = {};
+ }
+
+ from = from || {};
+
+ var extract = {},
+ style;
+
+ for ( style in to ) {
+ if ( to.hasOwnProperty( style ) ) {
+ extract[ style ] = elem.css( style );
+ }
+ }
+
+ elem.data('idle', {
+ from: $.extend( extract, from ),
+ to: to,
+ complete: true,
+ busy: false
+ });
+
+ if ( !hide ) {
+ idle.addTimer();
+ } else {
+ elem.css( to );
+ }
+ idle.trunk.push(elem);
+ },
+
+ remove: function(elem) {
+
+ elem = $(elem);
+
+ $.each(idle.trunk, function(i, el) {
+ if ( el && el.length && !el.not(elem).length ) {
+ elem.css( elem.data( 'idle' ).from );
+ idle.trunk.splice(i, 1);
+ }
+ });
+
+ if (!idle.trunk.length) {
+ idle.removeEvent();
+ self.clearTimer( idle.timer );
+ }
+ },
+
+ addEvent : function() {
+ idle.bound = true;
+ self.$('container').bind( 'mousemove click', idle.showAll );
+ if ( self._options.idleMode == 'hover' ) {
+ self.$('container').bind( 'mouseleave', idle.hide );
+ }
+ },
+
+ removeEvent : function() {
+ idle.bound = false;
+ self.$('container').bind( 'mousemove click', idle.showAll );
+ if ( self._options.idleMode == 'hover' ) {
+ self.$('container').unbind( 'mouseleave', idle.hide );
+ }
+ },
+
+ addTimer : function() {
+ if( self._options.idleMode == 'hover' ) {
+ return;
+ }
+ self.addTimer( 'idle', function() {
+ idle.hide();
+ }, self._options.idleTime );
+ },
+
+ hide : function() {
+
+ if ( !self._options.idleMode || self.getIndex() === false || self.getData().iframe ) {
+ return;
+ }
+
+ self.trigger( Galleria.IDLE_ENTER );
+
+ var len = idle.trunk.length;
+
+ $.each( idle.trunk, function(i, elem) {
+
+ var data = elem.data('idle');
+
+ if (! data) {
+ return;
+ }
+
+ elem.data('idle').complete = false;
+
+ Utils.animate( elem, data.to, {
+ duration: self._options.idleSpeed,
+ complete: function() {
+ if ( i == len-1 ) {
+ idle.active = false;
+ }
+ }
+ });
+ });
+ },
+
+ showAll : function() {
+
+ self.clearTimer( 'idle' );
+
+ $.each( idle.trunk, function( i, elem ) {
+ idle.show( elem );
+ });
+ },
+
+ show: function(elem) {
+
+ var data = elem.data('idle');
+
+ if ( !idle.active || ( !data.busy && !data.complete ) ) {
+
+ data.busy = true;
+
+ self.trigger( Galleria.IDLE_EXIT );
+
+ self.clearTimer( 'idle' );
+
+ Utils.animate( elem, data.from, {
+ duration: self._options.idleSpeed/2,
+ complete: function() {
+ idle.active = true;
+ $(elem).data('idle').busy = false;
+ $(elem).data('idle').complete = true;
+ }
+ });
+
+ }
+ idle.addTimer();
+ }
+ };
+
+ // internal lightbox object
+ // creates a predesigned lightbox for simple popups of images in galleria
+ var lightbox = this._lightbox = {
+
+ width : 0,
+
+ height : 0,
+
+ initialized : false,
+
+ active : null,
+
+ image : null,
+
+ elems : {},
+
+ keymap: false,
+
+ init : function() {
+
+ // trigger the event
+ self.trigger( Galleria.LIGHTBOX_OPEN );
+
+ if ( lightbox.initialized ) {
+ return;
+ }
+ lightbox.initialized = true;
+
+ // create some elements to work with
+ var elems = 'overlay box content shadow title info close prevholder prev nextholder next counter image',
+ el = {},
+ op = self._options,
+ css = '',
+ abs = 'position:absolute;',
+ prefix = 'lightbox-',
+ cssMap = {
+ overlay: 'position:fixed;display:none;opacity:'+op.overlayOpacity+';filter:alpha(opacity='+(op.overlayOpacity*100)+
+ ');top:0;left:0;width:100%;height:100%;background:'+op.overlayBackground+';z-index:99990',
+ box: 'position:fixed;display:none;width:400px;height:400px;top:50%;left:50%;margin-top:-200px;margin-left:-200px;z-index:99991',
+ shadow: abs+'background:#000;width:100%;height:100%;',
+ content: abs+'background-color:#fff;top:10px;left:10px;right:10px;bottom:10px;overflow:hidden',
+ info: abs+'bottom:10px;left:10px;right:10px;color:#444;font:11px/13px arial,sans-serif;height:13px',
+ close: abs+'top:10px;right:10px;height:20px;width:20px;background:#fff;text-align:center;cursor:pointer;color:#444;font:16px/22px arial,sans-serif;z-index:99999',
+ image: abs+'top:10px;left:10px;right:10px;bottom:30px;overflow:hidden;display:block;',
+ prevholder: abs+'width:50%;top:0;bottom:40px;cursor:pointer;',
+ nextholder: abs+'width:50%;top:0;bottom:40px;right:-1px;cursor:pointer;',
+ prev: abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;left:20px;display:none;text-align:center;color:#000;font:bold 16px/36px arial,sans-serif',
+ next: abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;right:20px;left:auto;display:none;font:bold 16px/36px arial,sans-serif;text-align:center;color:#000',
+ title: 'float:left',
+ counter: 'float:right;margin-left:8px;'
+ },
+ hover = function(elem) {
+ return elem.hover(
+ function() { $(this).css( 'color', '#bbb' ); },
+ function() { $(this).css( 'color', '#444' ); }
+ );
+ },
+ appends = {};
+
+ // IE8 fix for IE's transparent background event "feature"
+ if ( IE && IE > 7 ) {
+ cssMap.nextholder += 'background:#000;filter:alpha(opacity=0);';
+ cssMap.prevholder += 'background:#000;filter:alpha(opacity=0);';
+ }
+
+ // create and insert CSS
+ $.each(cssMap, function( key, value ) {
+ css += '.galleria-'+prefix+key+'{'+value+'}';
+ });
+
+ css += '.galleria-'+prefix+'box.iframe .galleria-'+prefix+'prevholder,'+
+ '.galleria-'+prefix+'box.iframe .galleria-'+prefix+'nextholder{'+
+ 'width:100px;height:100px;top:50%;margin-top:-70px}';
+
+ Utils.insertStyleTag( css, 'galleria-lightbox' );
+
+ // create the elements
+ $.each(elems.split(' '), function( i, elemId ) {
+ self.addElement( 'lightbox-' + elemId );
+ el[ elemId ] = lightbox.elems[ elemId ] = self.get( 'lightbox-' + elemId );
+ });
+
+ // initiate the image
+ lightbox.image = new Galleria.Picture();
+
+ // append the elements
+ $.each({
+ box: 'shadow content close prevholder nextholder',
+ info: 'title counter',
+ content: 'info image',
+ prevholder: 'prev',
+ nextholder: 'next'
+ }, function( key, val ) {
+ var arr = [];
+ $.each( val.split(' '), function( i, prop ) {
+ arr.push( prefix + prop );
+ });
+ appends[ prefix+key ] = arr;
+ });
+
+ self.append( appends );
+
+ $( el.image ).append( lightbox.image.container );
+
+ $( DOM().body ).append( el.overlay, el.box );
+
+ Utils.optimizeTouch( el.box );
+
+ // add the prev/next nav and bind some controls
+
+ hover( $( el.close ).bind( 'click', lightbox.hide ).html('×') );
+
+ $.each( ['Prev','Next'], function(i, dir) {
+
+ var $d = $( el[ dir.toLowerCase() ] ).html( /v/.test( dir ) ? '‹ ' : ' ›' ),
+ $e = $( el[ dir.toLowerCase()+'holder'] );
+
+ $e.bind( 'click', function() {
+ lightbox[ 'show' + dir ]();
+ });
+
+ // IE7 and touch devices will simply show the nav
+ if ( IE < 8 || Galleria.TOUCH ) {
+ $d.show();
+ return;
+ }
+
+ $e.hover( function() {
+ $d.show();
+ }, function(e) {
+ $d.stop().fadeOut( 200 );
+ });
+
+ });
+ $( el.overlay ).bind( 'click', lightbox.hide );
+
+ // the lightbox animation is slow on ipad
+ if ( Galleria.IPAD ) {
+ self._options.lightboxTransitionSpeed = 0;
+ }
+
+ },
+
+ rescale: function(event) {
+
+ // calculate
+ var width = Math.min( $win.width()-40, lightbox.width ),
+ height = Math.min( $win.height()-60, lightbox.height ),
+ ratio = Math.min( width / lightbox.width, height / lightbox.height ),
+ destWidth = Math.round( lightbox.width * ratio ) + 40,
+ destHeight = Math.round( lightbox.height * ratio ) + 60,
+ to = {
+ width: destWidth,
+ height: destHeight,
+ 'margin-top': Math.ceil( destHeight / 2 ) *- 1,
+ 'margin-left': Math.ceil( destWidth / 2 ) *- 1
+ };
+
+ // if rescale event, don't animate
+ if ( event ) {
+ $( lightbox.elems.box ).css( to );
+ } else {
+ $( lightbox.elems.box ).animate( to, {
+ duration: self._options.lightboxTransitionSpeed,
+ easing: self._options.easing,
+ complete: function() {
+ var image = lightbox.image,
+ speed = self._options.lightboxFadeSpeed;
+
+ self.trigger({
+ type: Galleria.LIGHTBOX_IMAGE,
+ imageTarget: image.image
+ });
+
+ $( image.container ).show();
+
+ $( image.image ).animate({ opacity: 1 }, speed);
+ Utils.show( lightbox.elems.info, speed );
+ }
+ });
+ }
+ },
+
+ hide: function() {
+
+ // remove the image
+ lightbox.image.image = null;
+
+ $win.unbind('resize', lightbox.rescale);
+
+ $( lightbox.elems.box ).hide();
+
+ Utils.hide( lightbox.elems.info );
+
+ self.detachKeyboard();
+ self.attachKeyboard( lightbox.keymap );
+
+ lightbox.keymap = false;
+
+ Utils.hide( lightbox.elems.overlay, 200, function() {
+ $( this ).hide().css( 'opacity', self._options.overlayOpacity );
+ self.trigger( Galleria.LIGHTBOX_CLOSE );
+ });
+ },
+
+ showNext: function() {
+ lightbox.show( self.getNext( lightbox.active ) );
+ },
+
+ showPrev: function() {
+ lightbox.show( self.getPrev( lightbox.active ) );
+ },
+
+ show: function(index) {
+
+ lightbox.active = index = typeof index === 'number' ? index : self.getIndex() || 0;
+
+ if ( !lightbox.initialized ) {
+ lightbox.init();
+ }
+
+ // temporarily attach some keys
+ // save the old ones first in a cloned object
+ if ( !lightbox.keymap ) {
+
+ lightbox.keymap = $.extend({}, self._keyboard.map);
+
+ self.attachKeyboard({
+ escape: lightbox.hide,
+ right: lightbox.showNext,
+ left: lightbox.showPrev
+ });
+ }
+
+ $win.unbind('resize', lightbox.rescale );
+
+ var data = self.getData(index),
+ total = self.getDataLength(),
+ n = self.getNext( index ),
+ ndata, p, i;
+
+ Utils.hide( lightbox.elems.info );
+
+ try {
+ for ( i = self._options.preload; i > 0; i-- ) {
+ p = new Galleria.Picture();
+ ndata = self.getData( n );
+ p.preload( 'big' in ndata ? ndata.big : ndata.image );
+ n = self.getNext( n );
+ }
+ } catch(e) {}
+
+ lightbox.image.isIframe = !!data.iframe;
+
+ $(lightbox.elems.box).toggleClass( 'iframe', !!data.iframe );
+
+ lightbox.image.load( data.iframe || data.big || data.image, function( image ) {
+
+ if ( image.isIframe ) {
+
+ var cw = $(window).width(),
+ ch = $(window).height();
+
+ if ( self._options.maxVideoSize ) {
+ var r = Math.min( self._options.maxVideoSize/cw, self._options.maxVideoSize/ch );
+ if ( r < 1 ) {
+ cw *= r;
+ ch *= r;
+ }
+ }
+ lightbox.width = cw;
+ lightbox.height = ch;
+
+ } else {
+ lightbox.width = image.original.width;
+ lightbox.height = image.original.height;
+ }
+
+ $( image.image ).css({
+ width: image.isIframe ? '100%' : '100.1%',
+ height: image.isIframe ? '100%' : '100.1%',
+ top: 0,
+ zIndex: 99998,
+ opacity: 0,
+ visibility: 'visible'
+ });
+
+ lightbox.elems.title.innerHTML = data.title || '';
+ lightbox.elems.counter.innerHTML = (index + 1) + ' / ' + total;
+ $win.resize( lightbox.rescale );
+ lightbox.rescale();
+ });
+
+ $( lightbox.elems.overlay ).show().css( 'visibility', 'visible' );
+ $( lightbox.elems.box ).show();
+ }
+ };
+
+ // the internal timeouts object
+ // provides helper methods for controlling timeouts
+
+ var _timer = this._timer = {
+
+ trunk: {},
+
+ add: function( id, fn, delay, loop ) {
+ id = id || new Date().getTime();
+ loop = loop || false;
+ this.clear( id );
+ if ( loop ) {
+ var old = fn;
+ fn = function() {
+ old();
+ _timer.add( id, fn, delay );
+ };
+ }
+ this.trunk[ id ] = window.setTimeout( fn, delay );
+ },
+
+ clear: function( id ) {
+
+ var del = function( i ) {
+ window.clearTimeout( this.trunk[ i ] );
+ delete this.trunk[ i ];
+ }, i;
+
+ if ( !!id && id in this.trunk ) {
+ del.call( this, id );
+
+ } else if ( typeof id === 'undefined' ) {
+ for ( i in this.trunk ) {
+ if ( this.trunk.hasOwnProperty( i ) ) {
+ del.call( this, i );
+ }
+ }
+ }
+ }
+ };
+
+ return this;
+};
+
+// end Galleria constructor
+
+Galleria.prototype = {
+
+ // bring back the constructor reference
+
+ constructor: Galleria,
+
+ /**
+ Use this function to initialize the gallery and start loading.
+ Should only be called once per instance.
+
+ @param {HTMLElement} target The target element
+ @param {Object} options The gallery options
+
+ @returns Instance
+ */
+
+ init: function( target, options ) {
+
+ var self = this;
+
+ options = _legacyOptions( options );
+
+ // save the original ingredients
+ this._original = {
+ target: target,
+ options: options,
+ data: null
+ };
+
+ // save the target here
+ this._target = this._dom.target = target.nodeName ? target : $( target ).get(0);
+
+ // save the original content for destruction
+ this._original.html = this._target.innerHTML;
+
+ // push the instance
+ _instances.push( this );
+
+ // raise error if no target is detected
+ if ( !this._target ) {
+ Galleria.raise('Target not found', true);
+ return;
+ }
+
+ // apply options
+ this._options = {
+ autoplay: false,
+ carousel: true,
+ carouselFollow: true, // legacy, deprecate at 1.3
+ carouselSpeed: 400,
+ carouselSteps: 'auto',
+ clicknext: false,
+ dailymotion: {
+ foreground: '%23EEEEEE',
+ highlight: '%235BCEC5',
+ background: '%23222222',
+ logo: 0,
+ hideInfos: 1
+ },
+ dataConfig : function( elem ) { return {}; },
+ dataSelector: 'img',
+ dataSort: false,
+ dataSource: this._target,
+ debug: undef,
+ dummy: undef, // 1.2.5
+ easing: 'galleria',
+ extend: function(options) {},
+ fullscreenCrop: undef, // 1.2.5
+ fullscreenDoubleTap: true, // 1.2.4 toggles fullscreen on double-tap for touch devices
+ fullscreenTransition: undef, // 1.2.6
+ height: 0,
+ idleMode: true, // 1.2.4 toggles idleMode
+ idleTime: 3000,
+ idleSpeed: 200,
+ imageCrop: false,
+ imageMargin: 0,
+ imagePan: false,
+ imagePanSmoothness: 12,
+ imagePosition: '50%',
+ imageTimeout: undef, // 1.2.5
+ initialTransition: undef, // 1.2.4, replaces transitionInitial
+ keepSource: false,
+ layerFollow: true, // 1.2.5
+ lightbox: false, // 1.2.3
+ lightboxFadeSpeed: 200,
+ lightboxTransitionSpeed: 200,
+ linkSourceImages: true,
+ maxScaleRatio: undef,
+ maxVideoSize: undef, // 1.2.9
+ minScaleRatio: undef, // deprecated in 1.2.9
+ overlayOpacity: 0.85,
+ overlayBackground: '#0b0b0b',
+ pauseOnInteraction: true,
+ popupLinks: false,
+ preload: 2,
+ queue: true,
+ responsive: true,
+ show: 0,
+ showInfo: true,
+ showCounter: true,
+ showImagenav: true,
+ swipe: true, // 1.2.4
+ thumbCrop: true,
+ thumbEventType: 'click',
+ thumbFit: true, // legacy, deprecate at 1.3
+ thumbMargin: 0,
+ thumbQuality: 'auto',
+ thumbDisplayOrder: true, // 1.2.8
+ thumbnails: true,
+ touchTransition: undef, // 1.2.6
+ transition: 'fade',
+ transitionInitial: undef, // legacy, deprecate in 1.3. Use initialTransition instead.
+ transitionSpeed: 400,
+ trueFullscreen: true, // 1.2.7
+ useCanvas: false, // 1.2.4
+ vimeo: {
+ title: 0,
+ byline: 0,
+ portrait: 0,
+ color: 'aaaaaa'
+ },
+ wait: 5000, // 1.2.7
+ width: 'auto',
+ youtube: {
+ modestbranding: 1,
+ autohide: 1,
+ color: 'white',
+ hd: 1,
+ rel: 0,
+ showinfo: 0
+ }
+ };
+
+ // legacy support for transitionInitial
+ this._options.initialTransition = this._options.initialTransition || this._options.transitionInitial;
+
+ // turn off debug
+ if ( options && options.debug === false ) {
+ DEBUG = false;
+ }
+
+ // set timeout
+ if ( options && typeof options.imageTimeout === 'number' ) {
+ TIMEOUT = options.imageTimeout;
+ }
+
+ // set dummy
+ if ( options && typeof options.dummy === 'string' ) {
+ DUMMY = options.dummy;
+ }
+
+ // hide all content
+ $( this._target ).children().hide();
+
+ // Warn for quirks mode
+ if ( Galleria.QUIRK ) {
+ Galleria.raise('Your page is in Quirks mode, Galleria may not render correctly. Please validate your HTML and add a correct doctype.');
+ }
+
+ // now we just have to wait for the theme...
+ if ( typeof Galleria.theme === 'object' ) {
+ this._init();
+ } else {
+ // push the instance into the pool and run it when the theme is ready
+ _pool.push( this );
+ }
+
+ return this;
+ },
+
+ // this method should only be called once per instance
+ // for manipulation of data, use the .load method
+
+ _init: function() {
+
+ var self = this,
+ options = this._options;
+
+ if ( this._initialized ) {
+ Galleria.raise( 'Init failed: Gallery instance already initialized.' );
+ return this;
+ }
+
+ this._initialized = true;
+
+ if ( !Galleria.theme ) {
+ Galleria.raise( 'Init failed: No theme found.', true );
+ return this;
+ }
+
+ // merge the theme & caller options
+ $.extend( true, options, Galleria.theme.defaults, this._original.options, Galleria.configure.options );
+
+ // check for canvas support
+ (function( can ) {
+
+ if ( !( 'getContext' in can ) ) {
+ can = null;
+ return;
+ }
+
+ _canvas = _canvas || {
+ elem: can,
+ context: can.getContext( '2d' ),
+ cache: {},
+ length: 0
+ };
+
+ }( doc.createElement( 'canvas' ) ) );
+
+ // bind the gallery to run when data is ready
+ this.bind( Galleria.DATA, function() {
+
+ // save the new data
+ this._original.data = this._data;
+
+ // lets show the counter here
+ this.get('total').innerHTML = this.getDataLength();
+
+ // cache the container
+ var $container = this.$( 'container' );
+
+ // set ratio if height is < 2
+ if ( self._options.height < 2 ) {
+ self._userRatio = self._ratio = self._options.height;
+ }
+
+ // the gallery is ready, let's just wait for the css
+ var num = { width: 0, height: 0 };
+ var testHeight = function() {
+ return self.$( 'stage' ).height();
+ };
+
+ // check container and thumbnail height
+ Utils.wait({
+ until: function() {
+
+ // keep trying to get the value
+ num = self._getWH();
+ $container.width( num.width ).height( num.height );
+ return testHeight() && num.width && num.height > 50;
+
+ },
+ success: function() {
+
+ self._width = num.width;
+ self._height = num.height;
+ self._ratio = self._ratio || num.height/num.width;
+
+ // for some strange reason, webkit needs a single setTimeout to play ball
+ if ( Galleria.WEBKIT ) {
+ window.setTimeout( function() {
+ self._run();
+ }, 1);
+ } else {
+ self._run();
+ }
+ },
+ error: function() {
+
+ // Height was probably not set, raise hard errors
+
+ if ( testHeight() ) {
+ Galleria.raise('Could not extract sufficient width/height of the gallery container. Traced measures: width:' + num.width + 'px, height: ' + num.height + 'px.', true);
+ } else {
+ Galleria.raise('Could not extract a stage height from the CSS. Traced height: ' + testHeight() + 'px.', true);
+ }
+ },
+ timeout: typeof this._options.wait == 'number' ? this._options.wait : false
+ });
+ });
+
+ // build the gallery frame
+ this.append({
+ 'info-text' :
+ ['info-title', 'info-description'],
+ 'info' :
+ ['info-text'],
+ 'image-nav' :
+ ['image-nav-right', 'image-nav-left'],
+ 'stage' :
+ ['images', 'loader', 'counter', 'image-nav'],
+ 'thumbnails-list' :
+ ['thumbnails'],
+ 'thumbnails-container' :
+ ['thumb-nav-left', 'thumbnails-list', 'thumb-nav-right'],
+ 'container' :
+ ['stage', 'thumbnails-container', 'info', 'tooltip']
+ });
+
+ Utils.hide( this.$( 'counter' ).append(
+ this.get( 'current' ),
+ doc.createTextNode(' / '),
+ this.get( 'total' )
+ ) );
+
+ this.setCounter('–');
+
+ Utils.hide( self.get('tooltip') );
+
+ // add a notouch class on the container to prevent unwanted :hovers on touch devices
+ this.$( 'container' ).addClass( Galleria.TOUCH ? 'touch' : 'notouch' );
+
+ // add images to the controls
+ $.each( new Array(2), function( i ) {
+
+ // create a new Picture instance
+ var image = new Galleria.Picture();
+
+ // apply some styles, create & prepend overlay
+ $( image.container ).css({
+ position: 'absolute',
+ top: 0,
+ left: 0
+ }).prepend( self._layers[i] = $( Utils.create('galleria-layer') ).css({
+ position: 'absolute',
+ top:0, left:0, right:0, bottom:0,
+ zIndex:2
+ })[0] );
+
+ // append the image
+ self.$( 'images' ).append( image.container );
+
+ // reload the controls
+ self._controls[i] = image;
+
+ });
+
+ // some forced generic styling
+ this.$( 'images' ).css({
+ position: 'relative',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%'
+ });
+
+ this.$( 'thumbnails, thumbnails-list' ).css({
+ overflow: 'hidden',
+ position: 'relative'
+ });
+
+ // bind image navigation arrows
+ this.$( 'image-nav-right, image-nav-left' ).bind( 'click', function(e) {
+
+ // tune the clicknext option
+ if ( options.clicknext ) {
+ e.stopPropagation();
+ }
+
+ // pause if options is set
+ if ( options.pauseOnInteraction ) {
+ self.pause();
+ }
+
+ // navigate
+ var fn = /right/.test( this.className ) ? 'next' : 'prev';
+ self[ fn ]();
+
+ });
+
+ // hide controls if chosen to
+ $.each( ['info','counter','image-nav'], function( i, el ) {
+ if ( options[ 'show' + el.substr(0,1).toUpperCase() + el.substr(1).replace(/-/,'') ] === false ) {
+ Utils.moveOut( self.get( el.toLowerCase() ) );
+ }
+ });
+
+ // load up target content
+ this.load();
+
+ // now it's usually safe to remove the content
+ // IE will never stop loading if we remove it, so let's keep it hidden for IE (it's usually fast enough anyway)
+ if ( !options.keepSource && !IE ) {
+ this._target.innerHTML = '';
+ }
+
+ // re-append the errors, if they happened before clearing
+ if ( this.get( 'errors' ) ) {
+ this.appendChild( 'target', 'errors' );
+ }
+
+ // append the gallery frame
+ this.appendChild( 'target', 'container' );
+
+ // parse the carousel on each thumb load
+ if ( options.carousel ) {
+ var count = 0,
+ show = options.show;
+ this.bind( Galleria.THUMBNAIL, function() {
+ this.updateCarousel();
+ if ( ++count == this.getDataLength() && typeof show == 'number' && show > 0 ) {
+ this._carousel.follow( show );
+ }
+ });
+ }
+
+ // bind window resize for responsiveness
+ if ( options.responsive ) {
+ $win.bind( 'resize', function() {
+ if ( !self.isFullscreen() ) {
+ self.resize();
+ }
+ });
+ }
+
+ // bind swipe gesture
+ if ( options.swipe ) {
+
+ (function( images ) {
+
+ var swipeStart = [0,0],
+ swipeStop = [0,0],
+ limitX = 30,
+ limitY = 100,
+ multi = false,
+ tid = 0,
+ data,
+ ev = {
+ start: 'touchstart',
+ move: 'touchmove',
+ stop: 'touchend'
+ },
+ getData = function(e) {
+ return e.originalEvent.touches ? e.originalEvent.touches[0] : e;
+ },
+ moveHandler = function( e ) {
+
+ if ( e.originalEvent.touches && e.originalEvent.touches.length > 1 ) {
+ return;
+ }
+
+ data = getData( e );
+ swipeStop = [ data.pageX, data.pageY ];
+
+ if ( !swipeStart[0] ) {
+ swipeStart = swipeStop;
+ }
+
+ if ( Math.abs( swipeStart[0] - swipeStop[0] ) > 10 ) {
+ e.preventDefault();
+ }
+ },
+ upHandler = function( e ) {
+
+ images.unbind( ev.move, moveHandler );
+
+ // if multitouch (possibly zooming), abort
+ if ( ( e.originalEvent.touches && e.originalEvent.touches.length ) || multi ) {
+ multi = !multi;
+ return;
+ }
+
+ if ( Utils.timestamp() - tid < 1000 &&
+ Math.abs( swipeStart[0] - swipeStop[0] ) > limitX &&
+ Math.abs( swipeStart[1] - swipeStop[1] ) < limitY ) {
+
+ e.preventDefault();
+ self[ swipeStart[0] > swipeStop[0] ? 'next' : 'prev' ]();
+ }
+
+ swipeStart = swipeStop = [0,0];
+ };
+
+ images.bind(ev.start, function(e) {
+
+ if ( e.originalEvent.touches && e.originalEvent.touches.length > 1 ) {
+ return;
+ }
+
+ data = getData(e);
+ tid = Utils.timestamp();
+ swipeStart = swipeStop = [ data.pageX, data.pageY ];
+ images.bind(ev.move, moveHandler ).one(ev.stop, upHandler);
+
+ });
+
+ }( self.$( 'images' ) ));
+
+ // double-tap/click fullscreen toggle
+
+ if ( options.fullscreenDoubleTap ) {
+
+ this.$( 'stage' ).bind( 'touchstart', (function() {
+ var last, cx, cy, lx, ly, now,
+ getData = function(e) {
+ return e.originalEvent.touches ? e.originalEvent.touches[0] : e;
+ };
+ return function(e) {
+ now = Galleria.utils.timestamp();
+ cx = getData(e).pageX;
+ cy = getData(e).pageY;
+ if ( ( now - last < 500 ) && ( cx - lx < 20) && ( cy - ly < 20) ) {
+ self.toggleFullscreen();
+ e.preventDefault();
+ self.$( 'stage' ).unbind( 'touchend', arguments.callee );
+ return;
+ }
+ last = now;
+ lx = cx;
+ ly = cy;
+ };
+ }()));
+ }
+
+ }
+
+ // optimize touch for container
+ Utils.optimizeTouch( this.get( 'container' ) );
+
+ // bind the ons
+ $.each( Galleria.on.binds, function(i, bind) {
+ // check if already bound
+ if ( $.inArray( bind.hash, self._binds ) == -1 ) {
+ self.bind( bind.type, bind.callback );
+ }
+ });
+
+ return this;
+ },
+
+ addTimer : function() {
+ this._timer.add.apply( this._timer, Utils.array( arguments ) );
+ return this;
+ },
+
+ clearTimer : function() {
+ this._timer.clear.apply( this._timer, Utils.array( arguments ) );
+ return this;
+ },
+
+ // parse width & height from CSS or options
+
+ _getWH : function() {
+
+ var $container = this.$( 'container' ),
+ $target = this.$( 'target' ),
+ self = this,
+ num = {},
+ arr;
+
+ $.each(['width', 'height'], function( i, m ) {
+
+ // first check if options is set
+ if ( self._options[ m ] && typeof self._options[ m ] === 'number') {
+ num[ m ] = self._options[ m ];
+ } else {
+
+ arr = [
+ Utils.parseValue( $container.css( m ) ), // the container css height
+ Utils.parseValue( $target.css( m ) ), // the target css height
+ $container[ m ](), // the container jQuery method
+ $target[ m ]() // the target jQuery method
+ ];
+
+ // if first time, include the min-width & min-height
+ if ( !self[ '_'+m ] ) {
+ arr.splice(arr.length,
+ Utils.parseValue( $container.css( 'min-'+m ) ),
+ Utils.parseValue( $target.css( 'min-'+m ) )
+ );
+ }
+
+ // else extract the measures from different sources and grab the highest value
+ num[ m ] = Math.max.apply( Math, arr );
+ }
+ });
+
+ // allow setting a height ratio instead of exact value
+ // useful when doing responsive galleries
+
+ if ( self._userRatio ) {
+ num.height = num.width * self._userRatio;
+ }
+
+ return num;
+ },
+
+ // Creates the thumbnails and carousel
+ // can be used at any time, f.ex when the data object is manipulated
+ // push is an optional argument with pushed images
+
+ _createThumbnails : function( push ) {
+
+ this.get( 'total' ).innerHTML = this.getDataLength();
+
+ var src,
+ thumb,
+ data,
+ special,
+
+ $container,
+
+ self = this,
+ o = this._options,
+
+ i = push ? this._data.length - push.length : 0,
+ chunk = i,
+
+ thumbchunk = [],
+ loadindex = 0,
+
+ gif = IE < 8 ? 'http://upload.wikimedia.org/wikipedia/commons/c/c0/Blank.gif' :
+ 'data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D',
+
+ // get previously active thumbnail, if exists
+ active = (function() {
+ var a = self.$('thumbnails').find('.active');
+ if ( !a.length ) {
+ return false;
+ }
+ return a.find('img').attr('src');
+ }()),
+
+ // cache the thumbnail option
+ optval = typeof o.thumbnails === 'string' ? o.thumbnails.toLowerCase() : null,
+
+ // move some data into the instance
+ // for some reason, jQuery cant handle css(property) when zooming in FF, breaking the gallery
+ // so we resort to getComputedStyle for browsers who support it
+ getStyle = function( prop ) {
+ return doc.defaultView && doc.defaultView.getComputedStyle ?
+ doc.defaultView.getComputedStyle( thumb.container, null )[ prop ] :
+ $container.css( prop );
+ },
+
+ fake = function(image, index, container) {
+ return function() {
+ $( container ).append( image );
+ self.trigger({
+ type: Galleria.THUMBNAIL,
+ thumbTarget: image,
+ index: index,
+ galleriaData: self.getData( index )
+ });
+ };
+ },
+
+ onThumbEvent = function( e ) {
+
+ // pause if option is set
+ if ( o.pauseOnInteraction ) {
+ self.pause();
+ }
+
+ // extract the index from the data
+ var index = $( e.currentTarget ).data( 'index' );
+ if ( self.getIndex() !== index ) {
+ self.show( index );
+ }
+
+ e.preventDefault();
+ },
+
+ thumbComplete = function( thumb, callback ) {
+
+ $( thumb.container ).css( 'visibility', 'visible' );
+ self.trigger({
+ type: Galleria.THUMBNAIL,
+ thumbTarget: thumb.image,
+ index: thumb.data.order,
+ galleriaData: self.getData( thumb.data.order )
+ });
+
+ if ( typeof callback == 'function' ) {
+ callback.call( self, thumb );
+ }
+ },
+
+ onThumbLoad = function( thumb, callback ) {
+
+ // scale when ready
+ thumb.scale({
+ width: thumb.data.width,
+ height: thumb.data.height,
+ crop: o.thumbCrop,
+ margin: o.thumbMargin,
+ canvas: o.useCanvas,
+ complete: function( thumb ) {
+
+ // shrink thumbnails to fit
+ var top = ['left', 'top'],
+ arr = ['Width', 'Height'],
+ m,
+ css,
+ data = self.getData( thumb.index ),
+ special = data.thumb.split(':');
+
+ // calculate shrinked positions
+ $.each(arr, function( i, measure ) {
+ m = measure.toLowerCase();
+ if ( (o.thumbCrop !== true || o.thumbCrop === m ) && o.thumbFit ) {
+ css = {};
+ css[ m ] = thumb[ m ];
+ $( thumb.container ).css( css );
+ css = {};
+ css[ top[ i ] ] = 0;
+ $( thumb.image ).css( css );
+ }
+
+ // cache outer measures
+ thumb[ 'outer' + measure ] = $( thumb.container )[ 'outer' + measure ]( true );
+ });
+
+ // set high quality if downscale is moderate
+ Utils.toggleQuality( thumb.image,
+ o.thumbQuality === true ||
+ ( o.thumbQuality === 'auto' && thumb.original.width < thumb.width * 3 )
+ );
+
+ // get "special" thumbs from provider
+ if( data.iframe && special.length == 2 && special[0] in _video ) {
+
+ _video[ special[0] ].getThumb( special[1], (function(img) {
+ return function(src) {
+ img.src = src;
+ thumbComplete( thumb, callback );
+ };
+ }( thumb.image ) ));
+
+ } else if ( o.thumbDisplayOrder && !thumb.lazy ) {
+
+ $.each( thumbchunk, function( i, th ) {
+ if ( i === loadindex && th.ready && !th.displayed ) {
+
+ loadindex++;
+ th.displayed = true;
+
+ thumbComplete( th, callback );
+
+ return;
+ }
+ });
+ } else {
+ thumbComplete( thumb, callback );
+ }
+ }
+ });
+ };
+
+ if ( !push ) {
+ this._thumbnails = [];
+ this.$( 'thumbnails' ).empty();
+ }
+
+ // loop through data and create thumbnails
+ for( ; this._data[ i ]; i++ ) {
+
+ data = this._data[ i ];
+
+ // get source from thumb or image
+ src = data.thumb || data.image;
+
+ if ( ( o.thumbnails === true || optval == 'lazy' ) && ( data.thumb || data.image ) ) {
+
+ // add a new Picture instance
+ thumb = new Galleria.Picture(i);
+
+ // save the index
+ thumb.index = i;
+
+ // flag displayed
+ thumb.displayed = false;
+
+ // flag lazy
+ thumb.lazy = false;
+
+ // flag video
+ thumb.video = false;
+
+ // append the thumbnail
+ this.$( 'thumbnails' ).append( thumb.container );
+
+ // cache the container
+ $container = $( thumb.container );
+
+ // hide it
+ $container.css( 'visibility', 'hidden' );
+
+ thumb.data = {
+ width : Utils.parseValue( getStyle( 'width' ) ),
+ height : Utils.parseValue( getStyle( 'height' ) ),
+ order : i,
+ src : src
+ };
+
+ // grab & reset size for smoother thumbnail loads
+ if ( o.thumbFit && o.thumbCrop !== true ) {
+ $container.css( { width: 'auto', height: 'auto' } );
+ } else {
+ $container.css( { width: thumb.data.width, height: thumb.data.height } );
+ }
+
+ // load the thumbnail
+ special = src.split(':');
+
+ if ( special.length == 2 && special[0] in _video ) {
+
+ thumb.video = true;
+ thumb.ready = true;
+
+ thumb.load( gif, {
+ height: thumb.data.height,
+ width: thumb.data.height*1.25
+ }, onThumbLoad);
+
+ } else if ( optval == 'lazy' ) {
+
+ $container.addClass( 'lazy' );
+
+ thumb.lazy = true;
+
+ thumb.load( gif, {
+ height: thumb.data.height,
+ width: thumb.data.width
+ });
+
+ } else {
+ thumb.load( src, onThumbLoad );
+ }
+
+ // preload all images here
+ if ( o.preload === 'all' ) {
+ thumb.preload( data.image );
+ }
+
+ // create empty spans if thumbnails is set to 'empty'
+ } else if ( data.iframe || optval === 'empty' || optval === 'numbers' ) {
+
+ thumb = {
+ container: Utils.create( 'galleria-image' ),
+ image: Utils.create( 'img', 'span' ),
+ ready: true
+ };
+
+ // create numbered thumbnails
+ if ( optval === 'numbers' ) {
+ $( thumb.image ).text( i + 1 );
+ }
+
+ if ( data.iframe ) {
+ $( thumb.image ).addClass( 'iframe' );
+ }
+
+ this.$( 'thumbnails' ).append( thumb.container );
+
+ // we need to "fake" a loading delay before we append and trigger
+ // 50+ should be enough
+
+ window.setTimeout( ( fake )( thumb.image, i, thumb.container ), 50 + ( i*20 ) );
+
+ // create null object to silent errors
+ } else {
+ thumb = {
+ container: null,
+ image: null
+ };
+ }
+
+ // add events for thumbnails
+ // you can control the event type using thumb_event_type
+ // we'll add the same event to the source if it's kept
+
+ $( thumb.container ).add( o.keepSource && o.linkSourceImages ? data.original : null )
+ .data('index', i).bind( o.thumbEventType, onThumbEvent )
+ .data('thumbload', onThumbLoad);
+
+ if (active === src) {
+ $( thumb.container ).addClass( 'active' );
+ }
+
+ this._thumbnails.push( thumb );
+ }
+
+ thumbchunk = this._thumbnails.slice( chunk );
+
+ return this;
+ },
+
+ /**
+ Lazy-loads thumbnails.
+ You can call this method to load lazy thumbnails at run time
+
+ @param {Array|Number} index Index or array of indexes of thumbnails to be loaded
+ @param {Function} complete Callback that is called when all lazy thumbnails have been loaded
+
+ @returns Instance
+ */
+
+ lazyLoad: function( index, complete ) {
+
+ var arr = index.constructor == Array ? index : [ index ],
+ self = this,
+ thumbnails = this.$( 'thumbnails' ).children().filter(function() {
+ return $(this).data('lazy-src');
+ }),
+ loaded = 0;
+
+ $.each( arr, function(i, ind) {
+
+ if ( ind > self._thumbnails.length - 1 ) {
+ return;
+ }
+
+ var thumb = self._thumbnails[ ind ],
+ data = thumb.data,
+ special = data.src.split(':'),
+ callback = function() {
+ if ( ++loaded == arr.length && typeof complete == 'function' ) {
+ complete.call( self );
+ }
+ },
+ thumbload = $( thumb.container ).data( 'thumbload' );
+ if ( thumb.video ) {
+ thumbload.call( self, thumb, callback );
+ } else {
+ thumb.load( data.src , function( thumb ) {
+ thumbload.call( self, thumb, callback );
+ });
+ }
+ });
+
+ return this;
+
+ },
+
+ /**
+ Lazy-loads thumbnails in chunks.
+ This method automatcally chops up the loading process of many thumbnails into chunks
+
+ @param {Number} size Size of each chunk to be loaded
+ @param {Number} [delay] Delay between each loads
+
+ @returns Instance
+ */
+
+ lazyLoadChunks: function( size, delay ) {
+
+ var len = this.getDataLength(),
+ i = 0,
+ n = 0,
+ arr = [],
+ temp = [],
+ self = this;
+
+ delay = delay || 0;
+
+ for( ; i 50 ); // what is an acceptable height?
+ },
+
+ success: function() {
+
+ // save the instance
+ _galleries.push( self );
+
+ // postrun some stuff after the gallery is ready
+
+ // show counter
+ Utils.show( self.get('counter') );
+
+ // bind carousel nav
+ if ( self._options.carousel ) {
+ self._carousel.bindControls();
+ }
+
+ // start autoplay
+ if ( self._options.autoplay ) {
+
+ self.pause();
+
+ if ( typeof self._options.autoplay === 'number' ) {
+ self._playtime = self._options.autoplay;
+ }
+
+ self._playing = true;
+ }
+ // if second load, just do the show and return
+ if ( self._firstrun ) {
+
+ if ( self._options.autoplay ) {
+ self.trigger( Galleria.PLAY );
+ }
+
+ if ( typeof self._options.show === 'number' ) {
+ self.show( self._options.show );
+ }
+ return;
+ }
+
+ self._firstrun = true;
+
+ // initialize the History plugin
+ if ( Galleria.History ) {
+
+ // bind the show method
+ Galleria.History.change(function( value ) {
+
+ // if ID is NaN, the user pressed back from the first image
+ // return to previous address
+ if ( isNaN( value ) ) {
+ window.history.go(-1);
+
+ // else show the image
+ } else {
+ self.show( value, undef, true );
+ }
+ });
+ }
+
+ self.trigger( Galleria.READY );
+
+ // call the theme init method
+ Galleria.theme.init.call( self, self._options );
+
+ // Trigger Galleria.ready
+ $.each( Galleria.ready.callbacks, function(i ,fn) {
+ if ( typeof fn == 'function' ) {
+ fn.call( self, self._options );
+ }
+ });
+
+ // call the extend option
+ self._options.extend.call( self, self._options );
+
+ // show the initial image
+ // first test for permalinks in history
+ if ( /^[0-9]{1,4}$/.test( HASH ) && Galleria.History ) {
+ self.show( HASH, undef, true );
+
+ } else if( self._data[ self._options.show ] ) {
+ self.show( self._options.show );
+ }
+
+ // play trigger
+ if ( self._options.autoplay ) {
+ self.trigger( Galleria.PLAY );
+ }
+ },
+
+ error: function() {
+ Galleria.raise('Stage width or height is too small to show the gallery. Traced measures: width:' + self._stageWidth + 'px, height: ' + self._stageHeight + 'px.', true);
+ }
+
+ });
+ },
+
+ /**
+ Loads data into the gallery.
+ You can call this method on an existing gallery to reload the gallery with new data.
+
+ @param {Array|string} [source] Optional JSON array of data or selector of where to find data in the document.
+ Defaults to the Galleria target or dataSource option.
+
+ @param {string} [selector] Optional element selector of what elements to parse.
+ Defaults to 'img'.
+
+ @param {Function} [config] Optional function to modify the data extraction proceedure from the selector.
+ See the dataConfig option for more information.
+
+ @returns Instance
+ */
+
+ load : function( source, selector, config ) {
+
+ var self = this,
+ o = this._options;
+
+ // empty the data array
+ this._data = [];
+
+ // empty the thumbnails
+ this._thumbnails = [];
+ this.$('thumbnails').empty();
+
+ // shorten the arguments
+ if ( typeof selector === 'function' ) {
+ config = selector;
+ selector = null;
+ }
+
+ // use the source set by target
+ source = source || o.dataSource;
+
+ // use selector set by option
+ selector = selector || o.dataSelector;
+
+ // use the dataConfig set by option
+ config = config || o.dataConfig;
+
+ // if source is a true object, make it into an array
+ if( /^function Object/.test( source.constructor ) ) {
+ source = [source];
+ }
+
+ // check if the data is an array already
+ if ( source.constructor === Array ) {
+ if ( this.validate( source ) ) {
+ this._data = source;
+ } else {
+ Galleria.raise( 'Load failed: JSON Array not valid.' );
+ }
+ } else {
+
+ // add .video and .iframe to the selector (1.2.7)
+ selector += ',.video,.iframe';
+
+ // loop through images and set data
+ $( source ).find( selector ).each( function( i, elem ) {
+
+ elem = $( elem );
+ var data = {},
+ parent = elem.parent(),
+ href = parent.attr( 'href' ),
+ rel = parent.attr( 'rel' );
+
+ if( href && ( elem[0].nodeName == 'IMG' || elem.hasClass('video') ) && _videoTest( href ) ) {
+ data.video = href;
+ } else if( href && elem.hasClass('iframe') ) {
+ data.iframe = href;
+ } else {
+ data.image = data.big = href;
+ }
+
+ if ( rel ) {
+ data.big = rel;
+ }
+
+ // alternative extraction from HTML5 data attribute, added in 1.2.7
+ $.each( 'big title description link layer'.split(' '), function( i, val ) {
+ if ( elem.data(val) ) {
+ data[ val ] = elem.data(val);
+ }
+ });
+
+ // mix default extractions with the hrefs and config
+ // and push it into the data array
+ self._data.push( $.extend({
+
+ title: elem.attr('title') || '',
+ thumb: elem.attr('src'),
+ image: elem.attr('src'),
+ big: elem.attr('src'),
+ description: elem.attr('alt') || '',
+ link: elem.attr('longdesc'),
+ original: elem.get(0) // saved as a reference
+
+ }, data, config( elem ) ) );
+
+ });
+ }
+
+ if ( typeof o.dataSort == 'function' ) {
+ protoArray.sort.call( this._data, o.dataSort );
+ } else if ( o.dataSort == 'random' ) {
+ this._data.sort( function() {
+ return Math.round(Math.random())-0.5;
+ });
+ }
+
+
+
+ // trigger the DATA event and return
+ if ( this.getDataLength() ) {
+ this._parseData().trigger( Galleria.DATA );
+ }
+ return this;
+
+ },
+
+ // make sure the data works properly
+ _parseData : function() {
+
+ var self = this,
+ current;
+
+ $.each( this._data, function( i, data ) {
+
+ current = self._data[ i ];
+
+ // copy image as thumb if no thumb exists
+ if ( 'thumb' in data === false ) {
+ current.thumb = data.image;
+ }
+ // copy image as big image if no biggie exists
+ if ( !'big' in data ) {
+ current.big = data.image;
+ }
+ // parse video
+ if ( 'video' in data ) {
+ var result = _videoTest( data.video );
+
+ if ( result ) {
+ current.iframe = _video[ result.provider ].embed( result.id ) + (function() {
+
+ // add options
+ if ( typeof self._options[ result.provider ] == 'object' ) {
+ var str = '?', arr = [];
+ $.each( self._options[ result.provider ], function( key, val ) {
+ arr.push( key + '=' + val );
+ });
+
+ // small youtube specifics, perhaps move to _video later
+ if ( result.provider == 'youtube' ) {
+ arr = ['wmode=opaque'].concat(arr);
+ }
+ return str + arr.join('&');
+ }
+ return '';
+ }());
+ delete current.video;
+ if( !('thumb' in current) || !current.thumb ) {
+ current.thumb = result.provider+':'+result.id;
+ }
+ }
+ }
+ });
+
+ return this;
+ },
+
+ /**
+ Destroy the Galleria instance and recover the original content
+
+ @example this.destroy();
+
+ @returns Instance
+ */
+
+ destroy : function() {
+ this.$( 'target' ).data( 'galleria', null );
+ this.$( 'container' ).unbind( 'galleria' );
+ this.get( 'target' ).innerHTML = this._original.html;
+ this.clearTimer();
+ Utils.removeFromArray( _instances, this );
+ Utils.removeFromArray( _galleries, this );
+ return this;
+ },
+
+ /**
+ Adds and/or removes images from the gallery
+ Works just like Array.splice
+ https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice
+
+ @example this.splice( 2, 4 ); // removes 4 images after the second image
+
+ @returns Instance
+ */
+
+ splice : function() {
+ var self = this,
+ args = Utils.array( arguments );
+ window.setTimeout(function() {
+ protoArray.splice.apply( self._data, args );
+ self._parseData()._createThumbnails();
+ },2);
+ return self;
+ },
+
+ /**
+ Append images to the gallery
+ Works just like Array.push
+ https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push
+
+ @example this.push({ image: 'image1.jpg' }); // appends the image to the gallery
+
+ @returns Instance
+ */
+
+ push : function() {
+ var self = this,
+ args = Utils.array( arguments );
+
+ if ( args.length == 1 && args[0].constructor == Array ) {
+ args = args[0];
+ }
+
+ window.setTimeout(function() {
+ protoArray.push.apply( self._data, args );
+ self._parseData()._createThumbnails( args );
+ },2);
+ return self;
+ },
+
+ _getActive: function() {
+ return this._controls.getActive();
+ },
+
+ validate : function( data ) {
+ // todo: validate a custom data array
+ return true;
+ },
+
+ /**
+ Bind any event to Galleria
+
+ @param {string} type The Event type to listen for
+ @param {Function} fn The function to execute when the event is triggered
+
+ @example this.bind( 'image', function() { Galleria.log('image shown') });
+
+ @returns Instance
+ */
+
+ bind : function(type, fn) {
+
+ // allow 'image' instead of Galleria.IMAGE
+ type = _patchEvent( type );
+
+ this.$( 'container' ).bind( type, this.proxy(fn) );
+ return this;
+ },
+
+ /**
+ Unbind any event to Galleria
+
+ @param {string} type The Event type to forget
+
+ @returns Instance
+ */
+
+ unbind : function(type) {
+
+ type = _patchEvent( type );
+
+ this.$( 'container' ).unbind( type );
+ return this;
+ },
+
+ /**
+ Manually trigger a Galleria event
+
+ @param {string} type The Event to trigger
+
+ @returns Instance
+ */
+
+ trigger : function( type ) {
+
+ type = typeof type === 'object' ?
+ $.extend( type, { scope: this } ) :
+ { type: _patchEvent( type ), scope: this };
+
+ this.$( 'container' ).trigger( type );
+
+ return this;
+ },
+
+ /**
+ Assign an "idle state" to any element.
+ The idle state will be applied after a certain amount of idle time
+ Useful to hide f.ex navigation when the gallery is inactive
+
+ @param {HTMLElement|string} elem The Dom node or selector to apply the idle state to
+ @param {Object} styles the CSS styles to apply when in idle mode
+ @param {Object} [from] the CSS styles to apply when in normal
+ @param {Boolean} [hide] set to true if you want to hide it first
+
+ @example addIdleState( this.get('image-nav'), { opacity: 0 });
+ @example addIdleState( '.galleria-image-nav', { top: -200 }, true);
+
+ @returns Instance
+ */
+
+ addIdleState: function( elem, styles, from, hide ) {
+ this._idle.add.apply( this._idle, Utils.array( arguments ) );
+ return this;
+ },
+
+ /**
+ Removes any idle state previously set using addIdleState()
+
+ @param {HTMLElement|string} elem The Dom node or selector to remove the idle state from.
+
+ @returns Instance
+ */
+
+ removeIdleState: function( elem ) {
+ this._idle.remove.apply( this._idle, Utils.array( arguments ) );
+ return this;
+ },
+
+ /**
+ Force Galleria to enter idle mode.
+
+ @returns Instance
+ */
+
+ enterIdleMode: function() {
+ this._idle.hide();
+ return this;
+ },
+
+ /**
+ Force Galleria to exit idle mode.
+
+ @returns Instance
+ */
+
+ exitIdleMode: function() {
+ this._idle.showAll();
+ return this;
+ },
+
+ /**
+ Enter FullScreen mode
+
+ @param {Function} callback the function to be executed when the fullscreen mode is fully applied.
+
+ @returns Instance
+ */
+
+ enterFullscreen: function( callback ) {
+ this._fullscreen.enter.apply( this, Utils.array( arguments ) );
+ return this;
+ },
+
+ /**
+ Exits FullScreen mode
+
+ @param {Function} callback the function to be executed when the fullscreen mode is fully applied.
+
+ @returns Instance
+ */
+
+ exitFullscreen: function( callback ) {
+ this._fullscreen.exit.apply( this, Utils.array( arguments ) );
+ return this;
+ },
+
+ /**
+ Toggle FullScreen mode
+
+ @param {Function} callback the function to be executed when the fullscreen mode is fully applied or removed.
+
+ @returns Instance
+ */
+
+ toggleFullscreen: function( callback ) {
+ this._fullscreen[ this.isFullscreen() ? 'exit' : 'enter'].apply( this, Utils.array( arguments ) );
+ return this;
+ },
+
+ /**
+ Adds a tooltip to any element.
+ You can also call this method with an object as argument with elemID:value pairs to apply tooltips to (see examples)
+
+ @param {HTMLElement} elem The DOM Node to attach the event to
+ @param {string|Function} value The tooltip message. Can also be a function that returns a string.
+
+ @example this.bindTooltip( this.get('thumbnails'), 'My thumbnails');
+ @example this.bindTooltip( this.get('thumbnails'), function() { return 'My thumbs' });
+ @example this.bindTooltip( { image_nav: 'Navigation' });
+
+ @returns Instance
+ */
+
+ bindTooltip: function( elem, value ) {
+ this._tooltip.bind.apply( this._tooltip, Utils.array(arguments) );
+ return this;
+ },
+
+ /**
+ Note: this method is deprecated. Use refreshTooltip() instead.
+
+ Redefine a tooltip.
+ Use this if you want to re-apply a tooltip value to an already bound tooltip element.
+
+ @param {HTMLElement} elem The DOM Node to attach the event to
+ @param {string|Function} value The tooltip message. Can also be a function that returns a string.
+
+ @returns Instance
+ */
+
+ defineTooltip: function( elem, value ) {
+ this._tooltip.define.apply( this._tooltip, Utils.array(arguments) );
+ return this;
+ },
+
+ /**
+ Refresh a tooltip value.
+ Use this if you want to change the tooltip value at runtime, f.ex if you have a play/pause toggle.
+
+ @param {HTMLElement} elem The DOM Node that has a tooltip that should be refreshed
+
+ @returns Instance
+ */
+
+ refreshTooltip: function( elem ) {
+ this._tooltip.show.apply( this._tooltip, Utils.array(arguments) );
+ return this;
+ },
+
+ /**
+ Open a pre-designed lightbox with the currently active image.
+ You can control some visuals using gallery options.
+
+ @returns Instance
+ */
+
+ openLightbox: function() {
+ this._lightbox.show.apply( this._lightbox, Utils.array( arguments ) );
+ return this;
+ },
+
+ /**
+ Close the lightbox.
+
+ @returns Instance
+ */
+
+ closeLightbox: function() {
+ this._lightbox.hide.apply( this._lightbox, Utils.array( arguments ) );
+ return this;
+ },
+
+ /**
+ Get the currently active image element.
+
+ @returns {HTMLElement} The image element
+ */
+
+ getActiveImage: function() {
+ return this._getActive().image || undef;
+ },
+
+ /**
+ Get the currently active thumbnail element.
+
+ @returns {HTMLElement} The thumbnail element
+ */
+
+ getActiveThumb: function() {
+ return this._thumbnails[ this._active ].image || undef;
+ },
+
+ /**
+ Get the mouse position relative to the gallery container
+
+ @param e The mouse event
+
+ @example
+
+var gallery = this;
+$(document).mousemove(function(e) {
+ console.log( gallery.getMousePosition(e).x );
+});
+
+ @returns {Object} Object with x & y of the relative mouse postion
+ */
+
+ getMousePosition : function(e) {
+ return {
+ x: e.pageX - this.$( 'container' ).offset().left,
+ y: e.pageY - this.$( 'container' ).offset().top
+ };
+ },
+
+ /**
+ Adds a panning effect to the image
+
+ @param [img] The optional image element. If not specified it takes the currently active image
+
+ @returns Instance
+ */
+
+ addPan : function( img ) {
+
+ if ( this._options.imageCrop === false ) {
+ return;
+ }
+
+ img = $( img || this.getActiveImage() );
+
+ // define some variables and methods
+ var self = this,
+ x = img.width() / 2,
+ y = img.height() / 2,
+ destX = parseInt( img.css( 'left' ), 10 ),
+ destY = parseInt( img.css( 'top' ), 10 ),
+ curX = destX || 0,
+ curY = destY || 0,
+ distX = 0,
+ distY = 0,
+ active = false,
+ ts = Utils.timestamp(),
+ cache = 0,
+ move = 0,
+
+ // positions the image
+ position = function( dist, cur, pos ) {
+ if ( dist > 0 ) {
+ move = Math.round( Math.max( dist * -1, Math.min( 0, cur ) ) );
+ if ( cache !== move ) {
+
+ cache = move;
+
+ if ( IE === 8 ) { // scroll is faster for IE
+ img.parent()[ 'scroll' + pos ]( move * -1 );
+ } else {
+ var css = {};
+ css[ pos.toLowerCase() ] = move;
+ img.css(css);
+ }
+ }
+ }
+ },
+
+ // calculates mouse position after 50ms
+ calculate = function(e) {
+ if (Utils.timestamp() - ts < 50) {
+ return;
+ }
+ active = true;
+ x = self.getMousePosition(e).x;
+ y = self.getMousePosition(e).y;
+ },
+
+ // the main loop to check
+ loop = function(e) {
+
+ if (!active) {
+ return;
+ }
+
+ distX = img.width() - self._stageWidth;
+ distY = img.height() - self._stageHeight;
+ destX = x / self._stageWidth * distX * -1;
+ destY = y / self._stageHeight * distY * -1;
+ curX += ( destX - curX ) / self._options.imagePanSmoothness;
+ curY += ( destY - curY ) / self._options.imagePanSmoothness;
+
+ position( distY, curY, 'Top' );
+ position( distX, curX, 'Left' );
+
+ };
+
+ // we need to use scroll in IE8 to speed things up
+ if ( IE === 8 ) {
+
+ img.parent().scrollTop( curY * -1 ).scrollLeft( curX * -1 );
+ img.css({
+ top: 0,
+ left: 0
+ });
+
+ }
+
+ // unbind and bind event
+ this.$( 'stage' ).unbind( 'mousemove', calculate ).bind( 'mousemove', calculate );
+
+ // loop the loop
+ this.addTimer( 'pan' + self._id, loop, 50, true);
+
+ return this;
+ },
+
+ /**
+ Brings the scope into any callback
+
+ @param fn The callback to bring the scope into
+ @param [scope] Optional scope to bring
+
+ @example $('#fullscreen').click( this.proxy(function() { this.enterFullscreen(); }) )
+
+ @returns {Function} Return the callback with the gallery scope
+ */
+
+ proxy : function( fn, scope ) {
+ if ( typeof fn !== 'function' ) {
+ return F;
+ }
+ scope = scope || this;
+ return function() {
+ return fn.apply( scope, Utils.array( arguments ) );
+ };
+ },
+
+ /**
+ Removes the panning effect set by addPan()
+
+ @returns Instance
+ */
+
+ removePan: function() {
+
+ // todo: doublecheck IE8
+
+ this.$( 'stage' ).unbind( 'mousemove' );
+
+ this.clearTimer( 'pan' + this._id );
+
+ return this;
+ },
+
+ /**
+ Adds an element to the Galleria DOM array.
+ When you add an element here, you can access it using element ID in many API calls
+
+ @param {string} id The element ID you wish to use. You can add many elements by adding more arguments.
+
+ @example addElement('mybutton');
+ @example addElement('mybutton','mylink');
+
+ @returns Instance
+ */
+
+ addElement : function( id ) {
+
+ var dom = this._dom;
+
+ $.each( Utils.array(arguments), function( i, blueprint ) {
+ dom[ blueprint ] = Utils.create( 'galleria-' + blueprint );
+ });
+
+ return this;
+ },
+
+ /**
+ Attach keyboard events to Galleria
+
+ @param {Object} map The map object of events.
+ Possible keys are 'UP', 'DOWN', 'LEFT', 'RIGHT', 'RETURN', 'ESCAPE', 'BACKSPACE', and 'SPACE'.
+
+ @example
+
+this.attachKeyboard({
+ right: this.next,
+ left: this.prev,
+ up: function() {
+ console.log( 'up key pressed' )
+ }
+});
+
+ @returns Instance
+ */
+
+ attachKeyboard : function( map ) {
+ this._keyboard.attach.apply( this._keyboard, Utils.array( arguments ) );
+ return this;
+ },
+
+ /**
+ Detach all keyboard events to Galleria
+
+ @returns Instance
+ */
+
+ detachKeyboard : function() {
+ this._keyboard.detach.apply( this._keyboard, Utils.array( arguments ) );
+ return this;
+ },
+
+ /**
+ Fast helper for appending galleria elements that you added using addElement()
+
+ @param {string} parentID The parent element ID where the element will be appended
+ @param {string} childID the element ID that should be appended
+
+ @example this.addElement('myElement');
+ this.appendChild( 'info', 'myElement' );
+
+ @returns Instance
+ */
+
+ appendChild : function( parentID, childID ) {
+ this.$( parentID ).append( this.get( childID ) || childID );
+ return this;
+ },
+
+ /**
+ Fast helper for prepending galleria elements that you added using addElement()
+
+ @param {string} parentID The parent element ID where the element will be prepended
+ @param {string} childID the element ID that should be prepended
+
+ @example
+
+this.addElement('myElement');
+this.prependChild( 'info', 'myElement' );
+
+ @returns Instance
+ */
+
+ prependChild : function( parentID, childID ) {
+ this.$( parentID ).prepend( this.get( childID ) || childID );
+ return this;
+ },
+
+ /**
+ Remove an element by blueprint
+
+ @param {string} elemID The element to be removed.
+ You can remove multiple elements by adding arguments.
+
+ @returns Instance
+ */
+
+ remove : function( elemID ) {
+ this.$( Utils.array( arguments ).join(',') ).remove();
+ return this;
+ },
+
+ // a fast helper for building dom structures
+ // leave this out of the API for now
+
+ append : function( data ) {
+ var i, j;
+ for( i in data ) {
+ if ( data.hasOwnProperty( i ) ) {
+ if ( data[i].constructor === Array ) {
+ for( j = 0; data[i][j]; j++ ) {
+ this.appendChild( i, data[i][j] );
+ }
+ } else {
+ this.appendChild( i, data[i] );
+ }
+ }
+ }
+ return this;
+ },
+
+ // an internal helper for scaling according to options
+ _scaleImage : function( image, options ) {
+
+ image = image || this._controls.getActive();
+
+ // janpub (JH) fix:
+ // image might be unselected yet
+ // e.g. when external logics rescales the gallery on window resize events
+ if( !image ) {
+ return;
+ }
+
+ var self = this,
+
+ complete,
+
+ scaleLayer = function( img ) {
+ $( img.container ).children(':first').css({
+ top: Math.max(0, Utils.parseValue( img.image.style.top )),
+ left: Math.max(0, Utils.parseValue( img.image.style.left )),
+ width: Utils.parseValue( img.image.width ),
+ height: Utils.parseValue( img.image.height )
+ });
+ };
+
+ options = $.extend({
+ width: this._stageWidth,
+ height: this._stageHeight,
+ crop: this._options.imageCrop,
+ max: this._options.maxScaleRatio,
+ min: this._options.minScaleRatio,
+ margin: this._options.imageMargin,
+ position: this._options.imagePosition,
+ iframelimit: this._options.maxVideoSize
+ }, options );
+
+ if ( this._options.layerFollow && this._options.imageCrop !== true ) {
+
+ if ( typeof options.complete == 'function' ) {
+ complete = options.complete;
+ options.complete = function() {
+ complete.call( image, image );
+ scaleLayer( image );
+ };
+ } else {
+ options.complete = scaleLayer;
+ }
+
+ } else {
+ $( image.container ).children(':first').css({ top: 0, left: 0 });
+ }
+
+ image.scale( options );
+ return this;
+ },
+
+ /**
+ Updates the carousel,
+ useful if you resize the gallery and want to re-check if the carousel nav is needed.
+
+ @returns Instance
+ */
+
+ updateCarousel : function() {
+ this._carousel.update();
+ return this;
+ },
+
+ /**
+ Resize the entire gallery container
+
+ @param {Object} [measures] Optional object with width/height specified
+ @param {Function} [complete] The callback to be called when the scaling is complete
+
+ @returns Instance
+ */
+
+ resize : function( measures, complete ) {
+
+ if ( typeof measures == 'function' ) {
+ complete = measures;
+ measures = undef;
+ }
+
+ measures = $.extend( { width:0, height:0 }, measures );
+
+ var self = this,
+ $container = this.$( 'container' );
+
+ $.each( measures, function( m, val ) {
+ if ( !val ) {
+ $container[ m ]( 'auto' );
+ measures[ m ] = self._getWH()[ m ];
+ }
+ });
+
+ $.each( measures, function( m, val ) {
+ $container[ m ]( val );
+ });
+
+ return this.rescale( complete );
+
+ },
+
+ /**
+ Rescales the gallery
+
+ @param {number} width The target width
+ @param {number} height The target height
+ @param {Function} complete The callback to be called when the scaling is complete
+
+ @returns Instance
+ */
+
+ rescale : function( width, height, complete ) {
+
+ var self = this;
+
+ // allow rescale(fn)
+ if ( typeof width === 'function' ) {
+ complete = width;
+ width = undef;
+ }
+
+ var scale = function() {
+
+ // set stagewidth
+ self._stageWidth = width || self.$( 'stage' ).width();
+ self._stageHeight = height || self.$( 'stage' ).height();
+
+ // scale the active image
+ self._scaleImage();
+
+ if ( self._options.carousel ) {
+ self.updateCarousel();
+ }
+
+ self.trigger( Galleria.RESCALE );
+
+ if ( typeof complete === 'function' ) {
+ complete.call( self );
+ }
+ };
+
+ scale.call( self );
+
+ return this;
+ },
+
+ /**
+ Refreshes the gallery.
+ Useful if you change image options at runtime and want to apply the changes to the active image.
+
+ @returns Instance
+ */
+
+ refreshImage : function() {
+ this._scaleImage();
+ if ( this._options.imagePan ) {
+ this.addPan();
+ }
+ return this;
+ },
+
+ /**
+ Shows an image by index
+
+ @param {number|boolean} index The index to show
+ @param {Boolean} rewind A boolean that should be true if you want the transition to go back
+
+ @returns Instance
+ */
+
+ show : function( index, rewind, _history ) {
+
+ // do nothing queue is long || index is false || queue is false and transition is in progress
+ if ( this._queue.length > 3 || index === false || ( !this._options.queue && this._queue.stalled ) ) {
+ return;
+ }
+
+ index = Math.max( 0, Math.min( parseInt( index, 10 ), this.getDataLength() - 1 ) );
+
+ rewind = typeof rewind !== 'undefined' ? !!rewind : index < this.getIndex();
+
+ _history = _history || false;
+
+ // do the history thing and return
+ if ( !_history && Galleria.History ) {
+ Galleria.History.set( index.toString() );
+ return;
+ }
+
+ this._active = index;
+
+ protoArray.push.call( this._queue, {
+ index : index,
+ rewind : rewind
+ });
+ if ( !this._queue.stalled ) {
+ this._show();
+ }
+
+ return this;
+ },
+
+ // the internal _show method does the actual showing
+ _show : function() {
+
+ // shortcuts
+ var self = this,
+ queue = this._queue[ 0 ],
+ data = this.getData( queue.index );
+
+ if ( !data ) {
+ return;
+ }
+
+ var src = data.iframe || ( this.isFullscreen() && 'big' in data ? data.big : data.image ), // use big image if fullscreen mode
+ active = this._controls.getActive(),
+ next = this._controls.getNext(),
+ cached = next.isCached( src ),
+ thumb = this._thumbnails[ queue.index ],
+ mousetrigger = function() {
+ $( next.image ).trigger( 'mouseup' );
+ };
+
+ // to be fired when loading & transition is complete:
+ var complete = (function( data, next, active, queue, thumb ) {
+
+ return function() {
+
+ var win;
+
+ _transitions.active = false;
+
+ // optimize quality
+ Utils.toggleQuality( next.image, self._options.imageQuality );
+
+ // remove old layer
+ self._layers[ self._controls.active ].innerHTML = '';
+
+ // swap
+ $( active.container ).css({
+ zIndex: 0,
+ opacity: 0
+ }).show();
+
+ if( active.isIframe ) {
+ $( active.container ).find( 'iframe' ).remove();
+ }
+
+ self.$('container').toggleClass('iframe', !!data.iframe);
+
+ $( next.container ).css({
+ zIndex: 1,
+ left: 0,
+ top: 0
+ }).show();
+
+ self._controls.swap();
+
+ // add pan according to option
+ if ( self._options.imagePan ) {
+ self.addPan( next.image );
+ }
+
+ // make the image link or add lightbox
+ // link takes precedence over lightbox if both are detected
+ if ( data.link || self._options.lightbox || self._options.clicknext ) {
+
+ $( next.image ).css({
+ cursor: 'pointer'
+ }).bind( 'mouseup', function( e ) {
+
+ // non-left click
+ if ( typeof e.which == 'number' && e.which > 1 ) {
+ return;
+ }
+
+ // clicknext
+ if ( self._options.clicknext && !Galleria.TOUCH ) {
+ if ( self._options.pauseOnInteraction ) {
+ self.pause();
+ }
+ self.next();
+ return;
+ }
+
+ // popup link
+ if ( data.link ) {
+ if ( self._options.popupLinks ) {
+ win = window.open( data.link, '_blank' );
+ } else {
+ window.location.href = data.link;
+ }
+ return;
+ }
+
+ if ( self._options.lightbox ) {
+ self.openLightbox();
+ }
+
+ });
+ }
+
+ // check if we are playing
+ self._playCheck();
+
+ // trigger IMAGE event
+ self.trigger({
+ type: Galleria.IMAGE,
+ index: queue.index,
+ imageTarget: next.image,
+ thumbTarget: thumb.image,
+ galleriaData: data
+ });
+
+ // remove the queued image
+ protoArray.shift.call( self._queue );
+
+ // remove stalled
+ self._queue.stalled = false;
+
+ // if we still have images in the queue, show it
+ if ( self._queue.length ) {
+ self._show();
+ }
+
+ };
+ }( data, next, active, queue, thumb ));
+
+ // let the carousel follow
+ if ( this._options.carousel && this._options.carouselFollow ) {
+ this._carousel.follow( queue.index );
+ }
+
+ // preload images
+ if ( this._options.preload ) {
+
+ var p, i,
+ n = this.getNext(),
+ ndata;
+
+ try {
+ for ( i = this._options.preload; i > 0; i-- ) {
+ p = new Galleria.Picture();
+ ndata = self.getData( n );
+ p.preload( this.isFullscreen() && 'big' in ndata ? ndata.big : ndata.image );
+ n = self.getNext( n );
+ }
+ } catch(e) {}
+ }
+
+ // show the next image, just in case
+ Utils.show( next.container );
+
+ next.isIframe = !!data.iframe;
+
+ // add active classes
+ $( self._thumbnails[ queue.index ].container )
+ .addClass( 'active' )
+ .siblings( '.active' )
+ .removeClass( 'active' );
+
+ // trigger the LOADSTART event
+ self.trigger( {
+ type: Galleria.LOADSTART,
+ cached: cached,
+ index: queue.index,
+ rewind: queue.rewind,
+ imageTarget: next.image,
+ thumbTarget: thumb.image,
+ galleriaData: data
+ });
+
+ // stall the queue
+ self._queue.stalled = true;
+
+ // begin loading the next image
+ next.load( src, function( next ) {
+
+ // add layer HTML
+ var layer = $( self._layers[ 1-self._controls.active ] ).html( data.layer || '' ).hide();
+
+ self._scaleImage( next, {
+
+ complete: function( next ) {
+
+ // toggle low quality for IE
+ if ( 'image' in active ) {
+ Utils.toggleQuality( active.image, false );
+ }
+ Utils.toggleQuality( next.image, false );
+
+ // remove the image panning, if applied
+ // TODO: rethink if this is necessary
+ self.removePan();
+
+ // set the captions and counter
+ self.setInfo( queue.index );
+ self.setCounter( queue.index );
+
+ // show the layer now
+ if ( data.layer ) {
+ layer.show();
+ // inherit click events set on image
+ if ( data.link || self._options.lightbox || self._options.clicknext ) {
+ layer.css( 'cursor', 'pointer' ).unbind( 'mouseup' ).mouseup( mousetrigger );
+ }
+ }
+
+ var transition = self._options.transition;
+
+ // can JavaScript loop through objects in order? yes.
+ $.each({
+ initial: active.image === null,
+ touch: Galleria.TOUCH,
+ fullscreen: self.isFullscreen()
+ }, function( type, arg ) {
+ if ( arg && self._options[ type + 'Transition' ] !== undef ) {
+ transition = self._options[ type + 'Transition' ];
+ return false;
+ }
+ });
+
+ // validate the transition
+ if ( transition in _transitions.effects === false ) {
+ complete();
+ } else {
+ var params = {
+ prev: active.container,
+ next: next.container,
+ rewind: queue.rewind,
+ speed: self._options.transitionSpeed || 400
+ };
+
+ _transitions.active = true;
+
+ // call the transition function and send some stuff
+ _transitions.init.call( self, transition, params, complete );
+
+ }
+
+ // trigger the LOADFINISH event
+ self.trigger({
+ type: Galleria.LOADFINISH,
+ cached: cached,
+ index: queue.index,
+ rewind: queue.rewind,
+ imageTarget: next.image,
+ thumbTarget: self._thumbnails[ queue.index ].image,
+ galleriaData: self.getData( queue.index )
+ });
+
+
+ }
+ });
+ });
+ },
+
+ /**
+ Gets the next index
+
+ @param {number} [base] Optional starting point
+
+ @returns {number} the next index, or the first if you are at the first (looping)
+ */
+
+ getNext : function( base ) {
+ base = typeof base === 'number' ? base : this.getIndex();
+ return base === this.getDataLength() - 1 ? 0 : base + 1;
+ },
+
+ /**
+ Gets the previous index
+
+ @param {number} [base] Optional starting point
+
+ @returns {number} the previous index, or the last if you are at the first (looping)
+ */
+
+ getPrev : function( base ) {
+ base = typeof base === 'number' ? base : this.getIndex();
+ return base === 0 ? this.getDataLength() - 1 : base - 1;
+ },
+
+ /**
+ Shows the next image in line
+
+ @returns Instance
+ */
+
+ next : function() {
+ if ( this.getDataLength() > 1 ) {
+ this.show( this.getNext(), false );
+ }
+ return this;
+ },
+
+ /**
+ Shows the previous image in line
+
+ @returns Instance
+ */
+
+ prev : function() {
+ if ( this.getDataLength() > 1 ) {
+ this.show( this.getPrev(), true );
+ }
+ return this;
+ },
+
+ /**
+ Retrieve a DOM element by element ID
+
+ @param {string} elemId The delement ID to fetch
+
+ @returns {HTMLElement} The elements DOM node or null if not found.
+ */
+
+ get : function( elemId ) {
+ return elemId in this._dom ? this._dom[ elemId ] : null;
+ },
+
+ /**
+ Retrieve a data object
+
+ @param {number} index The data index to retrieve.
+ If no index specified it will take the currently active image
+
+ @returns {Object} The data object
+ */
+
+ getData : function( index ) {
+ return index in this._data ?
+ this._data[ index ] : this._data[ this._active ];
+ },
+
+ /**
+ Retrieve the number of data items
+
+ @returns {number} The data length
+ */
+ getDataLength : function() {
+ return this._data.length;
+ },
+
+ /**
+ Retrieve the currently active index
+
+ @returns {number|boolean} The active index or false if none found
+ */
+
+ getIndex : function() {
+ return typeof this._active === 'number' ? this._active : false;
+ },
+
+ /**
+ Retrieve the stage height
+
+ @returns {number} The stage height
+ */
+
+ getStageHeight : function() {
+ return this._stageHeight;
+ },
+
+ /**
+ Retrieve the stage width
+
+ @returns {number} The stage width
+ */
+
+ getStageWidth : function() {
+ return this._stageWidth;
+ },
+
+ /**
+ Retrieve the option
+
+ @param {string} key The option key to retrieve. If no key specified it will return all options in an object.
+
+ @returns option or options
+ */
+
+ getOptions : function( key ) {
+ return typeof key === 'undefined' ? this._options : this._options[ key ];
+ },
+
+ /**
+ Set options to the instance.
+ You can set options using a key & value argument or a single object argument (see examples)
+
+ @param {string} key The option key
+ @param {string} value the the options value
+
+ @example setOptions( 'autoplay', true )
+ @example setOptions({ autoplay: true });
+
+ @returns Instance
+ */
+
+ setOptions : function( key, value ) {
+ if ( typeof key === 'object' ) {
+ $.extend( this._options, key );
+ } else {
+ this._options[ key ] = value;
+ }
+ return this;
+ },
+
+ /**
+ Starts playing the slideshow
+
+ @param {number} delay Sets the slideshow interval in milliseconds.
+ If you set it once, you can just call play() and get the same interval the next time.
+
+ @returns Instance
+ */
+
+ play : function( delay ) {
+
+ this._playing = true;
+
+ this._playtime = delay || this._playtime;
+
+ this._playCheck();
+
+ this.trigger( Galleria.PLAY );
+
+ return this;
+ },
+
+ /**
+ Stops the slideshow if currently playing
+
+ @returns Instance
+ */
+
+ pause : function() {
+
+ this._playing = false;
+
+ this.trigger( Galleria.PAUSE );
+
+ return this;
+ },
+
+ /**
+ Toggle between play and pause events.
+
+ @param {number} delay Sets the slideshow interval in milliseconds.
+
+ @returns Instance
+ */
+
+ playToggle : function( delay ) {
+ return ( this._playing ) ? this.pause() : this.play( delay );
+ },
+
+ /**
+ Checks if the gallery is currently playing
+
+ @returns {Boolean}
+ */
+
+ isPlaying : function() {
+ return this._playing;
+ },
+
+ /**
+ Checks if the gallery is currently in fullscreen mode
+
+ @returns {Boolean}
+ */
+
+ isFullscreen : function() {
+ return this._fullscreen.active;
+ },
+
+ _playCheck : function() {
+ var self = this,
+ played = 0,
+ interval = 20,
+ now = Utils.timestamp(),
+ timer_id = 'play' + this._id;
+
+ if ( this._playing ) {
+
+ this.clearTimer( timer_id );
+
+ var fn = function() {
+
+ played = Utils.timestamp() - now;
+ if ( played >= self._playtime && self._playing ) {
+ self.clearTimer( timer_id );
+ self.next();
+ return;
+ }
+ if ( self._playing ) {
+
+ // trigger the PROGRESS event
+ self.trigger({
+ type: Galleria.PROGRESS,
+ percent: Math.ceil( played / self._playtime * 100 ),
+ seconds: Math.floor( played / 1000 ),
+ milliseconds: played
+ });
+
+ self.addTimer( timer_id, fn, interval );
+ }
+ };
+ self.addTimer( timer_id, fn, interval );
+ }
+ },
+
+ /**
+ Modify the slideshow delay
+
+ @param {number} delay the number of milliseconds between slides,
+
+ @returns Instance
+ */
+
+ setPlaytime: function( delay ) {
+ this._playtime = delay;
+ return this;
+ },
+
+ setIndex: function( val ) {
+ this._active = val;
+ return this;
+ },
+
+ /**
+ Manually modify the counter
+
+ @param {number} [index] Optional data index to fectch,
+ if no index found it assumes the currently active index
+
+ @returns Instance
+ */
+
+ setCounter: function( index ) {
+
+ if ( typeof index === 'number' ) {
+ index++;
+ } else if ( typeof index === 'undefined' ) {
+ index = this.getIndex()+1;
+ }
+
+ this.get( 'current' ).innerHTML = index;
+
+ if ( IE ) { // weird IE bug
+
+ var count = this.$( 'counter' ),
+ opacity = count.css( 'opacity' );
+
+ if ( parseInt( opacity, 10 ) === 1) {
+ Utils.removeAlpha( count[0] );
+ } else {
+ this.$( 'counter' ).css( 'opacity', opacity );
+ }
+
+ }
+
+ return this;
+ },
+
+ /**
+ Manually set captions
+
+ @param {number} [index] Optional data index to fectch and apply as caption,
+ if no index found it assumes the currently active index
+
+ @returns Instance
+ */
+
+ setInfo : function( index ) {
+
+ var self = this,
+ data = this.getData( index );
+
+ $.each( ['title','description'], function( i, type ) {
+
+ var elem = self.$( 'info-' + type );
+
+ if ( !!data[type] ) {
+ elem[ data[ type ].length ? 'show' : 'hide' ]().html( data[ type ] );
+ } else {
+ elem.empty().hide();
+ }
+ });
+
+ return this;
+ },
+
+ /**
+ Checks if the data contains any captions
+
+ @param {number} [index] Optional data index to fectch,
+ if no index found it assumes the currently active index.
+
+ @returns {boolean}
+ */
+
+ hasInfo : function( index ) {
+
+ var check = 'title description'.split(' '),
+ i;
+
+ for ( i = 0; check[i]; i++ ) {
+ if ( !!this.getData( index )[ check[i] ] ) {
+ return true;
+ }
+ }
+ return false;
+
+ },
+
+ jQuery : function( str ) {
+
+ var self = this,
+ ret = [];
+
+ $.each( str.split(','), function( i, elemId ) {
+ elemId = $.trim( elemId );
+
+ if ( self.get( elemId ) ) {
+ ret.push( elemId );
+ }
+ });
+
+ var jQ = $( self.get( ret.shift() ) );
+
+ $.each( ret, function( i, elemId ) {
+ jQ = jQ.add( self.get( elemId ) );
+ });
+
+ return jQ;
+
+ },
+
+ /**
+ Converts element IDs into a jQuery collection
+ You can call for multiple IDs separated with commas.
+
+ @param {string} str One or more element IDs (comma-separated)
+
+ @returns jQuery
+
+ @example this.$('info,container').hide();
+ */
+
+ $ : function( str ) {
+ return this.jQuery.apply( this, Utils.array( arguments ) );
+ }
+
+};
+
+// End of Galleria prototype
+
+// Add events as static variables
+$.each( _events, function( i, ev ) {
+
+ // legacy events
+ var type = /_/.test( ev ) ? ev.replace( /_/g, '' ) : ev;
+
+ Galleria[ ev.toUpperCase() ] = 'galleria.'+type;
+
+} );
+
+$.extend( Galleria, {
+
+ // Browser helpers
+ IE9: IE === 9,
+ IE8: IE === 8,
+ IE7: IE === 7,
+ IE6: IE === 6,
+ IE: IE,
+ WEBKIT: /webkit/.test( NAV ),
+ CHROME: /chrome/.test( NAV ),
+ SAFARI: /safari/.test( NAV ) && !(/chrome/.test( NAV )),
+ QUIRK: ( IE && doc.compatMode && doc.compatMode === "BackCompat" ),
+ MAC: /mac/.test( navigator.platform.toLowerCase() ),
+ OPERA: !!window.opera,
+ IPHONE: /iphone/.test( NAV ),
+ IPAD: /ipad/.test( NAV ),
+ ANDROID: /android/.test( NAV ),
+ TOUCH: ('ontouchstart' in doc)
+
+});
+
+// Galleria static methods
+
+/**
+ Adds a theme that you can use for your Gallery
+
+ @param {Object} theme Object that should contain all your theme settings.
+
+ - name - name of the theme
+ - author - name of the author
+ - css - css file name (not path)
+ - defaults - default options to apply, including theme-specific options
+ - init - the init function
+
+
+ @returns {Object} theme
+*/
+
+Galleria.addTheme = function( theme ) {
+
+ // make sure we have a name
+ if ( !theme.name ) {
+ Galleria.raise('No theme name specified');
+ }
+
+ if ( typeof theme.defaults !== 'object' ) {
+ theme.defaults = {};
+ } else {
+ theme.defaults = _legacyOptions( theme.defaults );
+ }
+
+ var css = false,
+ reg;
+
+ if ( typeof theme.css === 'string' ) {
+
+ // look for manually added CSS
+ $('link').each(function( i, link ) {
+ reg = new RegExp( theme.css );
+ if ( reg.test( link.href ) ) {
+
+ // we found the css
+ css = true;
+
+ // the themeload trigger
+ _themeLoad( theme );
+
+ return false;
+ }
+ });
+
+ // else look for the absolute path and load the CSS dynamic
+ if ( !css ) {
+
+ $('script').each(function( i, script ) {
+
+ // look for the theme script
+ reg = new RegExp( 'galleria\\.' + theme.name.toLowerCase() + '\\.' );
+ if( reg.test( script.src )) {
+
+ // we have a match
+ css = script.src.replace(/[^\/]*$/, '') + theme.css;
+
+ window.setTimeout(function() {
+ Utils.loadCSS( css, 'galleria-theme', function() {
+
+ // the themeload trigger
+ _themeLoad( theme );
+
+ });
+ }, 1);
+
+ }
+ });
+ }
+
+ if ( !css ) {
+ Galleria.raise('No theme CSS loaded');
+ }
+ } else {
+
+ // pass
+ _themeLoad( theme );
+ }
+ return theme;
+};
+
+/**
+ loadTheme loads a theme js file and attaches a load event to Galleria
+
+ @param {string} src The relative path to the theme source file
+
+ @param {Object} [options] Optional options you want to apply
+
+ @returns Galleria
+*/
+
+Galleria.loadTheme = function( src, options ) {
+
+ // Don't load if theme is already loaded
+ if( $('script').filter(function() { return $(this).attr('src') == src; }).length ) {
+ return;
+ }
+
+ var loaded = false,
+ err;
+
+ // start listening for the timeout onload
+ $( window ).load( function() {
+ if ( !loaded ) {
+ // give it another 20 seconds
+ err = window.setTimeout(function() {
+ if ( !loaded && !Galleria.theme ) {
+ Galleria.raise( "Galleria had problems loading theme at " + src + ". Please check theme path or load manually.", true );
+ }
+ }, 20000);
+ }
+ });
+
+ // first clear the current theme, if exists
+ Galleria.unloadTheme();
+
+ // load the theme
+ Utils.loadScript( src, function() {
+ loaded = true;
+ window.clearTimeout( err );
+ });
+
+ return Galleria;
+};
+
+/**
+ unloadTheme unloads the Galleria theme and prepares for a new theme
+
+ @returns Galleria
+*/
+
+Galleria.unloadTheme = function() {
+
+ if ( typeof Galleria.theme == 'object' ) {
+
+ $('script').each(function( i, script ) {
+
+ if( new RegExp( 'galleria\\.' + Galleria.theme.name + '\\.' ).test( script.src ) ) {
+ $( script ).remove();
+ }
+ });
+
+ Galleria.theme = undef;
+ }
+
+ return Galleria;
+};
+
+/**
+ Retrieves a Galleria instance.
+
+ @param {number} [index] Optional index to retrieve.
+ If no index is supplied, the method will return all instances in an array.
+
+ @returns Instance or Array of instances
+*/
+
+Galleria.get = function( index ) {
+ if ( !!_instances[ index ] ) {
+ return _instances[ index ];
+ } else if ( typeof index !== 'number' ) {
+ return _instances;
+ } else {
+ Galleria.raise('Gallery index ' + index + ' not found');
+ }
+};
+
+/**
+
+ Configure Galleria options via a static function.
+ The options will be applied to all instances
+
+ @param {string|object} key The options to apply or a key
+
+ @param [value] If key is a string, this is the value
+
+ @returns Galleria
+
+*/
+
+Galleria.configure = function( key, value ) {
+
+ var opts = {};
+
+ if( typeof key == 'string' && value ) {
+ opts[key] = value;
+ key = opts;
+ } else {
+ $.extend( opts, key );
+ }
+
+ Galleria.configure.options = opts;
+
+ $.each( Galleria.get(), function(i, instance) {
+ instance.setOptions( opts );
+ });
+
+ return Galleria;
+};
+
+Galleria.configure.options = {};
+
+/**
+
+ Bind a Galleria event to the gallery
+
+ @param {string} type A string representing the galleria event
+
+ @param {function} callback The function that should run when the event is triggered
+
+ @returns Galleria
+
+*/
+
+Galleria.on = function( type, callback ) {
+ if ( !type ) {
+ return;
+ }
+
+ callback = callback || F;
+
+ // hash the bind
+ var hash = type + callback.toString().replace(/\s/g,'') + Utils.timestamp();
+
+ // for existing instances
+ $.each( Galleria.get(), function(i, instance) {
+ instance._binds.push( hash );
+ instance.bind( type, callback );
+ });
+
+ // for future instances
+ Galleria.on.binds.push({
+ type: type,
+ callback: callback,
+ hash: hash
+ });
+
+ return Galleria;
+};
+
+Galleria.on.binds = [];
+
+/**
+
+ Run Galleria
+ Alias for $(selector).galleria(options)
+
+ @param {string} selector A selector of element(s) to intialize galleria to
+
+ @param {object} options The options to apply
+
+ @returns Galleria
+
+*/
+
+Galleria.run = function( selector, options ) {
+ if ( $.isFunction( options ) ) {
+ options = { extend: options };
+ }
+ $( selector || '#galleria' ).galleria( options );
+ return Galleria;
+};
+
+/**
+ Creates a transition to be used in your gallery
+
+ @param {string} name The name of the transition that you will use as an option
+
+ @param {Function} fn The function to be executed in the transition.
+ The function contains two arguments, params and complete.
+ Use the params Object to integrate the transition, and then call complete when you are done.
+
+ @returns Galleria
+
+*/
+
+Galleria.addTransition = function( name, fn ) {
+ _transitions.effects[name] = fn;
+ return Galleria;
+};
+
+/**
+ The Galleria utilites
+*/
+
+Galleria.utils = Utils;
+
+/**
+ A helper metod for cross-browser logging.
+ It uses the console log if available otherwise it falls back to alert
+
+ @example Galleria.log("hello", document.body, [1,2,3]);
+*/
+
+Galleria.log = function() {
+ var args = Utils.array( arguments );
+ if( 'console' in window && 'log' in window.console ) {
+ try {
+ return window.console.log.apply( window.console, args );
+ } catch( e ) {
+ $.each( args, function() {
+ window.console.log(this);
+ });
+ }
+ } else {
+ return window.alert( args.join('
') );
+ }
+};
+
+/**
+ A ready method for adding callbacks when a gallery is ready
+ Each method is call before the extend option for every instance
+
+ @param {function} callback The function to call
+
+ @returns Galleria
+*/
+
+Galleria.ready = function( fn ) {
+ if ( typeof fn != 'function' ) {
+ return Galleria;
+ }
+ $.each( _galleries, function( i, gallery ) {
+ fn.call( gallery, gallery._options );
+ });
+ Galleria.ready.callbacks.push( fn );
+ return Galleria;
+};
+
+Galleria.ready.callbacks = [];
+
+/**
+ Method for raising errors
+
+ @param {string} msg The message to throw
+
+ @param {boolean} [fatal] Set this to true to override debug settings and display a fatal error
+*/
+
+Galleria.raise = function( msg, fatal ) {
+
+ var type = fatal ? 'Fatal error' : 'Error',
+
+ self = this,
+
+ css = {
+ color: '#fff',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ zIndex: 100000
+ },
+
+ echo = function( msg ) {
+
+ var html = '' +
+ ( fatal ? '' + type + ': ' : '' ) +
+ msg + '
';
+
+ $.each( _instances, function() {
+
+ var cont = this.$( 'errors' ),
+ target = this.$( 'target' );
+
+ if ( !cont.length ) {
+
+ target.css( 'position', 'relative' );
+
+ cont = this.addElement( 'errors' ).appendChild( 'target', 'errors' ).$( 'errors' ).css(css);
+ }
+ cont.append( html );
+
+ });
+
+ if ( !_instances.length ) {
+ $('').css( $.extend( css, { position: 'fixed' } ) ).append( html ).appendTo( DOM().body );
+ }
+ };
+
+ // if debug is on, display errors and throw exception if fatal
+ if ( DEBUG ) {
+ echo( msg );
+ if ( fatal ) {
+ throw new Error(type + ': ' + msg);
+ }
+
+ // else just echo a silent generic error if fatal
+ } else if ( fatal ) {
+ if ( _hasError ) {
+ return;
+ }
+ _hasError = true;
+ fatal = false;
+ echo( 'Gallery could not load.' );
+ }
+};
+
+// Add the version
+Galleria.version = VERSION;
+
+/**
+ A method for checking what version of Galleria the user has installed and throws a readable error if the user needs to upgrade.
+ Useful when building plugins that requires a certain version to function.
+
+ @param {number} version The minimum version required
+
+ @param {string} [msg] Optional message to display. If not specified, Galleria will throw a generic error.
+
+ @returns Galleria
+*/
+
+Galleria.requires = function( version, msg ) {
+ msg = msg || 'You need to upgrade Galleria to version ' + version + ' to use one or more components.';
+ if ( Galleria.version < version ) {
+ Galleria.raise(msg, true);
+ }
+ return Galleria;
+};
+
+/**
+ Adds preload, cache, scale and crop functionality
+
+ @constructor
+
+ @requires jQuery
+
+ @param {number} [id] Optional id to keep track of instances
+*/
+
+Galleria.Picture = function( id ) {
+
+ // save the id
+ this.id = id || null;
+
+ // the image should be null until loaded
+ this.image = null;
+
+ // Create a new container
+ this.container = Utils.create('galleria-image');
+
+ // add container styles
+ $( this.container ).css({
+ overflow: 'hidden',
+ position: 'relative' // for IE Standards mode
+ });
+
+ // saves the original measurements
+ this.original = {
+ width: 0,
+ height: 0
+ };
+
+ // flag when the image is ready
+ this.ready = false;
+
+ // flag for iframe Picture
+ this.isIframe = false;
+
+};
+
+Galleria.Picture.prototype = {
+
+ // the inherited cache object
+ cache: {},
+
+ // show the image on stage
+ show: function() {
+ Utils.show( this.image );
+ },
+
+ // hide the image
+ hide: function() {
+ Utils.moveOut( this.image );
+ },
+
+ clear: function() {
+ this.image = null;
+ },
+
+ /**
+ Checks if an image is in cache
+
+ @param {string} src The image source path, ex '/path/to/img.jpg'
+
+ @returns {boolean}
+ */
+
+ isCached: function( src ) {
+ return !!this.cache[src];
+ },
+
+ /**
+ Preloads an image into the cache
+
+ @param {string} src The image source path, ex '/path/to/img.jpg'
+
+ @returns Galleria.Picture
+ */
+
+ preload: function( src ) {
+ $( new Image() ).load((function(src, cache) {
+ return function() {
+ cache[ src ] = src;
+ };
+ }( src, this.cache ))).attr( 'src', src );
+ },
+
+ /**
+ Loads an image and call the callback when ready.
+ Will also add the image to cache.
+
+ @param {string} src The image source path, ex '/path/to/img.jpg'
+ @param {Object} [size] The forced size of the image, defined as an object { width: xx, height:xx }
+ @param {Function} callback The function to be executed when the image is loaded & scaled
+
+ @returns The image container (jQuery object)
+ */
+
+ load: function(src, size, callback) {
+
+ if ( typeof size == 'function' ) {
+ callback = size;
+ size = null;
+ }
+
+ if( this.isIframe ) {
+ var id = 'if'+new Date().getTime();
+
+ this.image = $('