PageSourceSearch

https://www.imist.ma/plugins/jsnimageshow/themeclassic/assets/js/galleria/galleria-1.2.8.js

js imist.ma collected 2026-10-02 04:22:08 UTC 180,996 bytes, 5,958 lines download raw bytes

1/**
2 * Galleria v 1.2.8 2012-08-09
3 * http://galleria.io
4 *
5 * Licensed under the MIT license
6 * https://raw.github.com/aino/galleria/master/LICENSE
7 *
8 */
9
10(function( $ ) {
11
12/*global jQuery, navigator, Galleria:true, Image */
13
14// some references
15var undef,
16    window = this,
17    doc    = window.document,
18    $doc   = $( doc ),
19    $win   = $( window ),
20
21// native prototypes
22    protoArray = Array.prototype,
23
24// internal constants
25    VERSION = 1.28,
26    DEBUG = true,
27    TIMEOUT = 30000,
28    DUMMY = false,
29    NAV = navigator.userAgent.toLowerCase(),
30    HASH = window.location.hash.replace(/#\//, ''),
31    F = function(){},
32    FALSE = function() { return false; },
33    IE = (function() {
34
35        var v = 3,
36            div = doc.createElement( 'div' ),
37            all = div.getElementsByTagName( 'i' );
38
39        do {
40            div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->';
41        } while ( all[0] );
42
43        return v > 4 ? v : undef;
44
45    }() ),
46    DOM = function() {
47        return {
48            html:  doc.documentElement,
49            body:  doc.body,
50            head:  doc.getElementsByTagName('head')[0],
51            title: doc.title
52        };
53    },
54
55    // list of Galleria events
56    _eventlist = 'data ready thumbnail loadstart loadfinish image play pause progress ' +
57                 'fullscreen_enter fullscreen_exit idle_enter idle_exit rescale ' +
58                 'lightbox_open lightbox_close lightbox_image',
59
60    _events = (function() {
61
62        var evs = [];
63
64        $.each( _eventlist.split(' '), function( i, ev ) {
65            evs.push( ev );
66
67            // legacy events
68            if ( /_/.test( ev ) ) {
69                evs.push( ev.replace( /_/g, '' ) );
70            }
71        });
72
73        return evs;
74
75    }()),
76
77    // legacy options
78    // allows the old my_setting syntax and converts it to camel case
79
80    _legacyOptions = function( options ) {
81
82        var n;
83
84        if ( typeof options !== 'object' ) {
85
86            // return whatever it was...
87            return options;
88        }
89
90        $.each( options, function( key, value ) {
91            if ( /^[a-z]+_/.test( key ) ) {
92                n = '';
93                $.each( key.split('_'), function( i, k ) {
94                    n += i > 0 ? k.substr( 0, 1 ).toUpperCase() + k.substr( 1 ) : k;
95                });
96                options[ n ] = value;
97                delete options[ key ];
98            }
99        });
100
101        return options;
102    },
103
104    _patchEvent = function( type ) {
105
106        // allow 'image' instead of Galleria.IMAGE
107        if ( $.inArray( type, _events ) > -1 ) {
108            return Galleria[ type.toUpperCase() ];
109        }
110
111        return type;
112    },
113
114    // video providers
115    _video = {
116        youtube: {
117            reg: /https?:\/\/(?:[a-zA_Z]{2,3}.)?(?:youtube\.com\/watch\?)((?:[\w\d\-\_\=]+&amp;(?:amp;)?)*v(?:&lt;[A-Z]+&gt;)?=([0-9a-zA-Z\-\_]+))/i,
118            embed: function(id) {
119                return 'http://www.youtube.com/embed/'+id;
120            },
121            getThumb: function( id, success, fail ) {
122                fail = fail || F;
123                $.getJSON('http://gdata.youtube.com/feeds/api/videos/' + id + '?v=2&alt=json-in-script&callback=?', function(data) {
124                    try {
125                        success( data.entry.media$group.media$thumbnail[0].url );
126                    } catch(e) {
127                        fail();
128                    }
129                }).error(fail);
130            }
131        },
132        vimeo: {
133            reg: /https?:\/\/(?:www\.)?(vimeo\.com)\/(?:hd#)?([0-9]+)/i,
134            embed: function(id) {
135                return 'http://player.vimeo.com/video/'+id;
136            },
137            getThumb: function( id, success, fail ) {
138                fail = fail || F;
139                $.getJSON('http://vimeo.com/api/v2/video/' + id + '.json?callback=?', function(data) {
140                    try {
141                        success( data[0].thumbnail_medium );
142                    } catch(e) {
143                        fail();
144                    }
145                }).error(fail);
146            }
147        },
148        dailymotion: {
149            reg: /https?:\/\/(?:www\.)?(dailymotion\.com)\/video\/([^_]+)/,
150            embed: function(id) {
151                return 'http://www.dailymotion.com/embed/video/'+id;
152            },
153            getThumb: function( id, success, fail ) {
154                fail = fail || F;
155                $.getJSON('https://api.dailymotion.com/video/'+id+'?fields=thumbnail_medium_url&callback=?', function(data) {
156                    try {
157                        success( data.thumbnail_medium_url );
158                    } catch(e) {
159                        fail();
160                    }
161                }).error(fail);
162            }
163        }
164    },
165
166    // utility for testing the video URL and getting the video ID
167    _videoTest = function( url ) {
168        var match;
169        for ( var v in _video ) {
170            match = url && url.match( _video[v].reg );
171            if( match && match.length ) {
172                return {
173                    id: match[2],
174                    provider: v
175                };
176            }
177        }
178        return false;
179    },
180
181    // native fullscreen handler
182    _nativeFullscreen = {
183
184        support: (function() {
185            var html = DOM().html;
186            return html.requestFullscreen || html.mozRequestFullScreen || html.webkitRequestFullScreen;
187        }()),
188
189        callback: F,
190
191        enter: function( instance, callback ) {
192
193            this.instance = instance;
194
195            this.callback = callback || F;
196
197            var html = DOM().html;
198            if ( html.requestFullscreen ) {
199                html.requestFullscreen();
200            }
201            else if ( html.mozRequestFullScreen ) {
202                html.mozRequestFullScreen();
203            }
204            else if ( html.webkitRequestFullScreen ) {
205                html.webkitRequestFullScreen();
206            }
207        },
208
209        exit: function( callback ) {
210
211            this.callback = callback || F;
212
213            if ( doc.exitFullscreen ) {
214                doc.exitFullscreen();
215            }
216            else if ( doc.mozCancelFullScreen ) {
217                doc.mozCancelFullScreen();
218            }
219            else if ( doc.webkitCancelFullScreen ) {
220                doc.webkitCancelFullScreen();
221            }
222        },
223
224        instance: null,
225
226        listen: function() {
227
228            if ( !this.support ) {
229                return;
230            }
231
232            var handler = function() {
233
234                if ( !_nativeFullscreen.instance ) {
235                    return;
236                }
237                var fs = _nativeFullscreen.instance._fullscreen;
238
239                if ( doc.fullscreen || doc.mozFullScreen || doc.webkitIsFullScreen ) {
240                    fs._enter( _nativeFullscreen.callback );
241                } else {
242                    fs._exit( _nativeFullscreen.callback );
243                }
244            };
245            doc.addEventListener( 'fullscreenchange', handler, false );
246            doc.addEventListener( 'mozfullscreenchange', handler, false );
247            doc.addEventListener( 'webkitfullscreenchange', handler, false );
248        }
249    },
250
251    // the internal gallery holder
252    _galleries = [],
253
254    // the internal instance holder
255    _instances = [],
256
257    // flag for errors
258    _hasError = false,
259
260    // canvas holder
261    _canvas = false,
262
263    // instance pool, holds the galleries until themeLoad is triggered
264    _pool = [],
265
266    // themeLoad trigger
267    _themeLoad = function( theme ) {
268        Galleria.theme = theme;
269
270        // run the instances we have in the pool
271        $.each( _pool, function( i, instance ) {
272            if ( !instance._initialized ) {
273                instance._init.call( instance );
274            }
275        });
276    },
277
278    // the Utils singleton
279    Utils = (function() {
280
281        return {
282
283            // legacy support for clearTimer
284            clearTimer: function( id ) {
285                $.each( Galleria.get(), function() {
286                    this.clearTimer( id );
287                });
288            },
289
290            // legacy support for addTimer
291            addTimer: function( id ) {
292                $.each( Galleria.get(), function() {
293                    this.addTimer( id );
294                });
295            },
296
297            array : function( obj ) {
298                return protoArray.slice.call(obj, 0);
299            },
300
301            create : function( className, nodeName ) {
302                nodeName = nodeName || 'div';
303                var elem = doc.createElement( nodeName );
304                elem.className = className;
305                return elem;
306            },
307
308            getScriptPath : function( src ) {
309
310                // the currently executing script is always the last
311                src = src || $('script:last').attr('src');
312                var slices = src.split('/');
313
314                if (slices.length == 1) {
315                    return '';
316                }
317
318                slices.pop();
319
320                return slices.join('/') + '/';
321            },
322
323            // CSS3 transitions, added in 1.2.4
324            animate : (function() {
325
326                // detect transition
327                var transition = (function( style ) {
328                    var props = 'transition WebkitTransition MozTransition OTransition'.split(' '),
329                        i;
330
331                    // disable css3 animations in opera until stable
332                    if ( window.opera ) {
333                        return false;
vendor: 4,170 bytes, lines 334-439
334                    }
335
336                    for ( i = 0; props[i]; i++ ) {
337                        if ( typeof style[ props[ i ] ] !== 'undefined' ) {
338                            return props[ i ];
339                        }
340                    }
341                    return false;
342                }(( doc.body || doc.documentElement).style ));
343
344                // map transitionend event
345                var endEvent = {
346                    MozTransition: 'transitionend',
347                    OTransition: 'oTransitionEnd',
348                    WebkitTransition: 'webkitTransitionEnd',
349                    transition: 'transitionend'
350                }[ transition ];
351
352                // map bezier easing conversions
353                var easings = {
354                    _default: [0.25, 0.1, 0.25, 1],
355                    galleria: [0.645, 0.045, 0.355, 1],
356                    galleriaIn: [0.55, 0.085, 0.68, 0.53],
357                    galleriaOut: [0.25, 0.46, 0.45, 0.94],
358                    ease: [0.25, 0, 0.25, 1],
359                    linear: [0.25, 0.25, 0.75, 0.75],
360                    'ease-in': [0.42, 0, 1, 1],
361                    'ease-out': [0, 0, 0.58, 1],
362                    'ease-in-out': [0.42, 0, 0.58, 1]
363                };
364
365                // function for setting transition css for all browsers
366                var setStyle = function( elem, value, suffix ) {
367                    var css = {};
368                    suffix = suffix || 'transition';
369                    $.each( 'webkit moz ms o'.split(' '), function() {
370                        css[ '-' + this + '-' + suffix ] = value;
371                    });
372                    elem.css( css );
373                };
374
375                // clear styles
376                var clearStyle = function( elem ) {
377                    setStyle( elem, 'none', 'transition' );
378                    if ( Galleria.WEBKIT && Galleria.TOUCH ) {
379                        setStyle( elem, 'translate3d(0,0,0)', 'transform' );
380                        if ( elem.data('revert') ) {
381                            elem.css( elem.data('revert') );
382                            elem.data('revert', null);
383                        }
384                    }
385                };
386
387                // various variables
388                var change, strings, easing, syntax, revert, form, css;
389
390                // the actual animation method
391                return function( elem, to, options ) {
392
393                    // extend defaults
394                    options = $.extend({
395                        duration: 400,
396                        complete: F,
397                        stop: false
398                    }, options);
399
400                    // cache jQuery instance
401                    elem = $( elem );
402
403                    if ( !options.duration ) {
404                        elem.css( to );
405                        options.complete.call( elem[0] );
406                        return;
407                    }
408
409                    // fallback to jQuery's animate if transition is not supported
410                    if ( !transition ) {
411                        elem.animate(to, options);
412                        return;
413                    }
414
415                    // stop
416                    if ( options.stop ) {
417                        // clear the animation
418                        elem.unbind( endEvent );
419                        clearStyle( elem );
420                    }
421
422                    // see if there is a change
423                    change = false;
424                    $.each( to, function( key, val ) {
425                        css = elem.css( key );
426                        if ( Utils.parseValue( css ) != Utils.parseValue( val ) ) {
427                            change = true;
428                        }
429                        // also add computed styles for FF
430                        elem.css( key, css );
431                    });
432                    if ( !change ) {
433                        window.setTimeout( function() {
434                            options.complete.call( elem[0] );
435                        }, options.duration );
436                        return;
437                    }
438
439                    // the css strings to be applied
440                    strings = [];
441
442                    // the easing bezier
443                    easing = options.easing in easings ? easings[ options.easing ] : easings._default;
444
445                    // the syntax
446                    syntax = ' ' + options.duration + 'ms' + ' cubic-bezier('  + easing.join(',') + ')';
447
448                    // add a tiny timeout so that the browsers catches any css changes before animating
449                    window.setTimeout( (function(elem, endEvent, to, syntax) {
450                        return function() {
451
452                            // attach the end event
453                            elem.one(endEvent, (function( elem ) {
454                                return function() {
455
456                                    // clear the animation
457                                    clearStyle(elem);
458
459                                    // run the complete method
460                                    options.complete.call(elem[0]);
461                                };
462                            }( elem )));
463
464                            // do the webkit translate3d for better performance on iOS
465                            if( Galleria.WEBKIT && Galleria.TOUCH ) {
466
467                                revert = {};
468                                form = [0,0,0];
469
470                                $.each( ['left', 'top'], function(i, m) {
471                                    if ( m in to ) {
472                                        form[ i ] = ( Utils.parseValue( to[ m ] ) - Utils.parseValue(elem.css( m )) ) + 'px';
473                                        revert[ m ] = to[ m ];
474                                        delete to[ m ];
475                                    }
476                                });
477
478                                if ( form[0] || form[1]) {
479
480                                    elem.data('revert', revert);
481
482                                    strings.push('-webkit-transform' + syntax);
483
484                                    // 3d animate
485                                    setStyle( elem, 'translate3d(' + form.join(',') + ')', 'transform');
486                                }
487                            }
488
489                            // push the animation props
490                            $.each(to, function( p, val ) {
491                                strings.push(p + syntax);
492                            });
493
494                            // set the animation styles
495                            setStyle( elem, strings.join(',') );
496
497                            // animate
498                            elem.css( to );
499
500                        };
501                    }(elem, endEvent, to, syntax)), 2);
502                };
503            }()),
504
505            removeAlpha : function( elem ) {
506                if ( IE < 9 && elem ) {
507
508                    var style = elem.style,
509                        currentStyle = elem.currentStyle,
510                        filter = currentStyle && currentStyle.filter || style.filter || "";
511
512                    if ( /alpha/.test( filter ) ) {
513                        style.filter = filter.replace( /alpha\([^)]*\)/i, '' );
514                    }
515                }
516            },
517
518            forceStyles : function( elem, styles ) {
519                elem = $(elem);
520                if ( elem.attr( 'style' ) ) {
521                    elem.data( 'styles', elem.attr( 'style' ) ).removeAttr( 'style' );
522                }
523                elem.css( styles );
524            },
525
526            revertStyles : function() {
527                $.each( Utils.array( arguments ), function( i, elem ) {
528
529                    elem = $( elem );
530                    elem.removeAttr( 'style' );
531
532                    elem.attr('style',''); // "fixes" webkit bug
533
534                    if ( elem.data( 'styles' ) ) {
535                        elem.attr( 'style', elem.data('styles') ).data( 'styles', null );
536                    }
537                });
538            },
539
540            moveOut : function( elem ) {
541                Utils.forceStyles( elem, {
542                    position: 'absolute',
543                    left: -10000
544                });
545            },
546
547            moveIn : function() {
548                Utils.revertStyles.apply( Utils, Utils.array( arguments ) );
549            },
550
551            elem : function( elem ) {
552                if (elem instanceof $) {
553                    return {
554                        $: elem,
555                        dom: elem[0]
556                    };
557                } else {
558                    return {
559                        $: $(elem),
560                        dom: elem
561                    };
562                }
563            },
564
565            hide : function( elem, speed, callback ) {
566
567                callback = callback || F;
568
569                var el = Utils.elem( elem ),
570                    $elem = el.$;
571
572                elem = el.dom;
573
574                // save the value if not exist
575                if (! $elem.data('opacity') ) {
576                    $elem.data('opacity', $elem.css('opacity') );
577                }
578
579                // always hide
580                var style = { opacity: 0 };
581
582                if (speed) {
583
584                    var complete = IE < 9 && elem ? function() {
585                        Utils.removeAlpha( elem );
586                        elem.style.visibility = 'hidden';
587                        callback.call( elem );
588                    } : callback;
589
590                    Utils.animate( elem, style, {
591                        duration: speed,
592                        complete: complete,
593                        stop: true
594                    });
595                } else {
596                    if ( IE < 9 && elem ) {
597                        Utils.removeAlpha( elem );
598                        elem.style.visibility = 'hidden';
599                    } else {
600                        $elem.css( style );
601                    }
602                }
603            },
604
605            show : function( elem, speed, callback ) {
606
607                callback = callback || F;
608
609                var el = Utils.elem( elem ),
610                    $elem = el.$;
611
612                elem = el.dom;
613
614                // bring back saved opacity
615                var saved = parseFloat( $elem.data('opacity') ) || 1,
616                    style = { opacity: saved };
617
618                // animate or toggle
619                if (speed) {
620
621                    if ( IE < 9 ) {
622                        $elem.css('opacity', 0);
623                        elem.style.visibility = 'visible';
624                    }
625
626                    var complete = IE < 9 && elem ? function() {
627                        if ( style.opacity == 1 ) {
628                            Utils.removeAlpha( elem );
629                        }
630                        callback.call( elem );
631                    } : callback;
632
633                    Utils.animate( elem, style, {
634                        duration: speed,
635                        complete: complete,
636                        stop: true
637                    });
638                } else {
639                    if ( IE < 9 && style.opacity == 1 && elem ) {
640                        Utils.removeAlpha( elem );
641                        elem.style.visibility = 'visible';
642                    } else {
643                        $elem.css( style );
644                    }
645                }
646            },
647
648
649            // enhanced click for mobile devices
650            // we bind a touchend and hijack any click event in the bubble
651            // then we execute the click directly and save it in a separate data object for later
652            optimizeTouch: (function() {
653
654                var node,
655                    evs,
656                    fakes,
657                    travel,
658                    evt = {},
659                    handler = function( e ) {
660                        e.preventDefault();
661                        evt = $.extend({}, e, true);
662                    },
663                    attach = function() {
664                        this.evt = evt;
665                    },
666                    fake = function() {
667                        this.handler.call(node, this.evt);
668                    };
669
670                return function( elem ) {
671
672                    $(elem).bind('touchend', function( e ) {
673
674                        node = e.target;
675                        travel = true;
676
677                        while( node.parentNode && node != e.currentTarget && travel ) {
678
679                            evs =   $(node).data('events');
680                            fakes = $(node).data('fakes');
681
682                            if (evs && 'click' in evs) {
683
684                                travel = false;
685                                e.preventDefault();
686
687                                // fake the click and save the event object
688                                $(node).click(handler).click();
689
690                                // remove the faked click
691                                evs.click.pop();
692
693                                // attach the faked event
694                                $.each( evs.click, attach);
695
696                                // save the faked clicks in a new data object
697                                $(node).data('fakes', evs.click);
698
699                                // remove all clicks
700                                delete evs.click;
701
702                            } else if ( fakes ) {
703
704                                travel = false;
705                                e.preventDefault();
706
707                                // fake all clicks
708                                $.each( fakes, fake );
709                            }
710
711                            // bubble
712                            node = node.parentNode;
713                        }
714                    });
715                };
716            }()),
717
718            wait : function(options) {
719                options = $.extend({
720                    until : FALSE,
721                    success : F,
722                    error : function() { Galleria.raise('Could not complete wait function.'); },
723                    timeout: 3000
724                }, options);
725
726                var start = Utils.timestamp(),
727                    elapsed,
728                    now,
729                    fn = function() {
730                        now = Utils.timestamp();
731                        elapsed = now - start;
732                        if ( options.until( elapsed ) ) {
733                            options.success();
734                            return false;
735                        }
736
737                        if (typeof options.timeout == 'number' && now >= start + options.timeout) {
738                            options.error();
739                            return false;
740                        }
741                        window.setTimeout(fn, 10);
742                    };
743
744                window.setTimeout(fn, 10);
745            },
746
747            toggleQuality : function( img, force ) {
748
749                if ( ( IE !== 7 && IE !== 8 ) || !img || img.nodeName.toUpperCase() != 'IMG' ) {
750                    return;
751                }
752
753                if ( typeof force === 'undefined' ) {
754                    force = img.style.msInterpolationMode === 'nearest-neighbor';
755                }
756
757                img.style.msInterpolationMode = force ? 'bicubic' : 'nearest-neighbor';
758            },
759
760            insertStyleTag : function( styles ) {
761                var style = doc.createElement( 'style' );
762                DOM().head.appendChild( style );
763
764                if ( style.styleSheet ) { // IE
765                    style.styleSheet.cssText = styles;
766                } else {
767                    var cssText = doc.createTextNode( styles );
768                    style.appendChild( cssText );
769                }
770            },
771
772            // a loadscript method that works for local scripts
773            loadScript: function( url, callback ) {
774
775                var done = false,
776                    script = $('<scr'+'ipt>').attr({
777                        src: url,
778                        async: true
779                    }).get(0);
780
781               // Attach handlers for all browsers
782               script.onload = script.onreadystatechange = function() {
783                   if ( !done && (!this.readyState ||
784                       this.readyState === 'loaded' || this.readyState === 'complete') ) {
785
786                       done = true;
787
788                       // Handle memory leak in IE
789                       script.onload = script.onreadystatechange = null;
790
791                       if (typeof callback === 'function') {
792                           callback.call( this, this );
793                       }
794                   }
795               };
796
797               DOM().head.appendChild( script );
798            },
799
vendor: 19,778 bytes, lines 800-1453
800            // parse anything into a number
801            parseValue: function( val ) {
802                if (typeof val === 'number') {
803                    return val;
804                } else if (typeof val === 'string') {
805                    var arr = val.match(/\-?\d|\./g);
806                    return arr && arr.constructor === Array ? arr.join('')*1 : 0;
807                } else {
808                    return 0;
809                }
810            },
811
812            // timestamp abstraction
813            timestamp: function() {
814                return new Date().getTime();
815            },
816
817            loadCSS : function( href, id, callback ) {
818
819                var link,
820                    length;
821
822                // look for manual css
823                $('link[rel=stylesheet]').each(function() {
824                    if ( new RegExp( href ).test( this.href ) ) {
825                        link = this;
826                        return false;
827                    }
828                });
829
830                if ( typeof id === 'function' ) {
831                    callback = id;
832                    id = undef;
833                }
834
835                callback = callback || F; // dirty
836
837                // if already present, return
838                if ( link ) {
839                    callback.call( link, link );
840                    return link;
841                }
842
843                // save the length of stylesheets to check against
844                length = doc.styleSheets.length;
845
846                // check for existing id
847                if( $( '#' + id ).length ) {
848
849                    $( '#' + id ).attr( 'href', href );
850                    length--;
851
852                } else {
853                    link = $( '<link>' ).attr({
854                        rel: 'stylesheet',
855                        href: href,
856                        id: id
857                    }).get(0);
858
859                    var styles = $('link[rel="stylesheet"], style');
860                    if ( styles.length ) {
861                        styles.get(0).parentNode.insertBefore( link, styles[0] );
862                    } else {
863                        DOM().head.appendChild( link );
864                    }
865
866                    if ( IE ) {
867
868                        // IE has a limit of 31 stylesheets in one document
869                        if( length >= 31 ) {
870                            Galleria.raise( 'You have reached the browser stylesheet limit (31)', true );
871                            return;
872                        }
873                    }
874                }
875
876                if ( typeof callback === 'function' ) {
877
878                    // First check for dummy element (new in 1.2.8)
879                    var $loader = $('<s>').attr( 'id', 'galleria-loader' ).hide().appendTo( DOM().body );
880
881                    Utils.wait({
882                        until: function() {
883                            return $loader.height() == 1;
884                        },
885                        success: function() {
886                            $loader.remove();
887                            callback.call( link, link );
888                        },
889                        error: function() {
890                            $loader.remove();
891
892                            // If failed, tell the dev to download the latest theme
893                            Galleria.raise( 'Theme CSS could not load after 20 sec. Please download the latest theme at http://galleria.io/customer/', true );
894
895                        },
896                        timeout: 20000
897                    });
898                }
899                return link;
900            }
901        };
902    }()),
903
904    // the transitions holder
905    _transitions = (function() {
906
907        var _slide = function(params, complete, fade, door) {
908
909            var easing = this.getOptions('easing'),
910                distance = this.getStageWidth(),
911                from = { left: distance * ( params.rewind ? -1 : 1 ) },
912                to = { left: 0 };
913
914            if ( fade ) {
915                from.opacity = 0;
916                to.opacity = 1;
917            } else {
918                from.opacity = 1;
919            }
920
921            $(params.next).css(from);
922
923            Utils.animate(params.next, to, {
924                duration: params.speed,
925                complete: (function( elems ) {
926                    return function() {
927                        complete();
928                        elems.css({
929                            left: 0
930                        });
931                    };
932                }( $( params.next ).add( params.prev ) )),
933                queue: false,
934                easing: easing
935            });
936
937            if (door) {
938                params.rewind = !params.rewind;
939            }
940
941            if (params.prev) {
942
943                from = { left: 0 };
944                to = { left: distance * ( params.rewind ? 1 : -1 ) };
945
946                if ( fade ) {
947                    from.opacity = 1;
948                    to.opacity = 0;
949                }
950
951                $(params.prev).css(from);
952                Utils.animate(params.prev, to, {
953                    duration: params.speed,
954                    queue: false,
955                    easing: easing,
956                    complete: function() {
957                        $(this).css('opacity', 0);
958                    }
959                });
960            }
961        };
962
963        return {
964
965            active: false,
966
967            init: function( effect, params, complete ) {
968                if ( _transitions.effects.hasOwnProperty( effect ) ) {
969                    _transitions.effects[ effect ].call( this, params, complete );
970                }
971            },
972
973            effects: {
974
975                fade: function(params, complete) {
976                    $(params.next).css({
977                        opacity: 0,
978                        left: 0
979                    }).show();
980                    Utils.animate(params.next, {
981                        opacity: 1
982                    },{
983                        duration: params.speed,
984                        complete: complete
985                    });
986                    if (params.prev) {
987                        $(params.prev).css('opacity',1).show();
988                        Utils.animate(params.prev, {
989                            opacity: 0
990                        },{
991                            duration: params.speed
992                        });
993                    }
994                },
995
996                flash: function(params, complete) {
997                    $(params.next).css({
998                        opacity: 0,
999                        left: 0
1000                    });
1001                    if (params.prev) {
1002                        Utils.animate( params.prev, {
1003                            opacity: 0
1004                        },{
1005                            duration: params.speed/2,
1006                            complete: function() {
1007                                Utils.animate( params.next, {
1008                                    opacity:1
1009                                },{
1010                                    duration: params.speed,
1011                                    complete: complete
1012                                });
1013                            }
1014                        });
1015                    } else {
1016                        Utils.animate( params.next, {
1017                            opacity: 1
1018                        },{
1019                            duration: params.speed,
1020                            complete: complete
1021                        });
1022                    }
1023                },
1024
1025                pulse: function(params, complete) {
1026                    if (params.prev) {
1027                        $(params.prev).hide();
1028                    }
1029                    $(params.next).css({
1030                        opacity: 0,
1031                        left: 0
1032                    }).show();
1033                    Utils.animate(params.next, {
1034                        opacity:1
1035                    },{
1036                        duration: params.speed,
1037                        complete: complete
1038                    });
1039                },
1040
1041                slide: function(params, complete) {
1042                    _slide.apply( this, Utils.array( arguments ) );
1043                },
1044
1045                fadeslide: function(params, complete) {
1046                    _slide.apply( this, Utils.array( arguments ).concat( [true] ) );
1047                },
1048
1049                doorslide: function(params, complete) {
1050                    _slide.apply( this, Utils.array( arguments ).concat( [false, true] ) );
1051                }
1052            }
1053        };
1054    }());
1055
1056_nativeFullscreen.listen();
1057
1058/**
1059    The main Galleria class
1060
1061    @class
1062    @constructor
1063
1064    @example var gallery = new Galleria();
1065
1066    @author http://aino.se
1067
1068    @requires jQuery
1069
1070*/
1071
1072Galleria = function() {
1073
1074    var self = this;
1075
1076    // internal options
1077    this._options = {};
1078
1079    // flag for controlling play/pause
1080    this._playing = false;
1081
1082    // internal interval for slideshow
1083    this._playtime = 5000;
1084
1085    // internal variable for the currently active image
1086    this._active = null;
1087
1088    // the internal queue, arrayified
1089    this._queue = { length: 0 };
1090
1091    // the internal data array
1092    this._data = [];
1093
1094    // the internal dom collection
1095    this._dom = {};
1096
1097    // the internal thumbnails array
1098    this._thumbnails = [];
1099
1100    // the internal layers array
1101    this._layers = [];
1102
1103    // internal init flag
1104    this._initialized = false;
1105
1106    // internal firstrun flag
1107    this._firstrun = false;
1108
1109    // global stagewidth/height
1110    this._stageWidth = 0;
1111    this._stageHeight = 0;
1112
1113    // target holder
1114    this._target = undef;
1115
1116    // bind hashes
1117    this._binds = [];
1118
1119    // instance id
1120    this._id = parseInt(Math.random()*10000, 10);
1121
1122    // add some elements
1123    var divs =  'container stage images image-nav image-nav-left image-nav-right ' +
1124                'info info-text info-title info-description ' +
1125                'thumbnails thumbnails-list thumbnails-container thumb-nav-left thumb-nav-right ' +
1126                'loader counter tooltip',
1127        spans = 'current total';
1128
1129    $.each( divs.split(' '), function( i, elemId ) {
1130        self._dom[ elemId ] = Utils.create( 'galleria-' + elemId );
1131    });
1132
1133    $.each( spans.split(' '), function( i, elemId ) {
1134        self._dom[ elemId ] = Utils.create( 'galleria-' + elemId, 'span' );
1135    });
1136
1137    // the internal keyboard object
1138    // keeps reference of the keybinds and provides helper methods for binding keys
1139    var keyboard = this._keyboard = {
1140
1141        keys : {
1142            'UP': 38,
1143            'DOWN': 40,
1144            'LEFT': 37,
1145            'RIGHT': 39,
1146            'RETURN': 13,
1147            'ESCAPE': 27,
1148            'BACKSPACE': 8,
1149            'SPACE': 32
1150        },
1151
1152        map : {},
1153
1154        bound: false,
1155
1156        press: function(e) {
1157            var key = e.keyCode || e.which;
1158            if ( key in keyboard.map && typeof keyboard.map[key] === 'function' ) {
1159                keyboard.map[key].call(self, e);
1160            }
1161        },
1162
1163        attach: function(map) {
1164
1165            var key, up;
1166
1167            for( key in map ) {
1168                if ( map.hasOwnProperty( key ) ) {
1169                    up = key.toUpperCase();
1170                    if ( up in keyboard.keys ) {
1171                        keyboard.map[ keyboard.keys[up] ] = map[key];
1172                    } else {
1173                        keyboard.map[ up ] = map[key];
1174                    }
1175                }
1176            }
1177            if ( !keyboard.bound ) {
1178                keyboard.bound = true;
1179                $doc.bind('keydown', keyboard.press);
1180            }
1181        },
1182
1183        detach: function() {
1184            keyboard.bound = false;
1185            keyboard.map = {};
1186            $doc.unbind('keydown', keyboard.press);
1187        }
1188    };
1189
1190    // internal controls for keeping track of active / inactive images
1191    var controls = this._controls = {
1192
1193        0: undef,
1194
1195        1: undef,
1196
1197        active : 0,
1198
1199        swap : function() {
1200            controls.active = controls.active ? 0 : 1;
1201        },
1202
1203        getActive : function() {
1204            return controls[ controls.active ];
1205        },
1206
1207        getNext : function() {
1208            return controls[ 1 - controls.active ];
1209        }
1210    };
1211
1212    // internal carousel object
1213    var carousel = this._carousel = {
1214
1215        // shortcuts
1216        next: self.$('thumb-nav-right'),
1217        prev: self.$('thumb-nav-left'),
1218
1219        // cache the width
1220        width: 0,
1221
1222        // track the current position
1223        current: 0,
1224
1225        // cache max value
1226        max: 0,
1227
1228        // save all hooks for each width in an array
1229        hooks: [],
1230
1231        // update the carousel
1232        // you can run this method anytime, f.ex on window.resize
1233        update: function() {
1234            var w = 0,
1235                h = 0,
1236                hooks = [0];
1237
1238            $.each( self._thumbnails, function( i, thumb ) {
1239                if ( thumb.ready ) {
1240                    w += thumb.outerWidth || $( thumb.container ).outerWidth( true );
1241                    hooks[ i+1 ] = w;
1242                    h = Math.max( h, thumb.outerHeight || $( thumb.container).outerHeight( true ) );
1243                }
1244            });
1245
1246            self.$( 'thumbnails' ).css({
1247                width: w,
1248                height: h
1249            });
1250
1251            carousel.max = w;
1252            carousel.hooks = hooks;
1253            carousel.width = self.$( 'thumbnails-list' ).width();
1254            carousel.setClasses();
1255
1256            self.$( 'thumbnails-container' ).toggleClass( 'galleria-carousel', w > carousel.width );
1257
1258            // one extra calculation
1259            carousel.width = self.$( 'thumbnails-list' ).width();
1260
1261            // todo: fix so the carousel moves to the left
1262        },
1263
1264        bindControls: function() {
1265
1266            var i;
1267
1268            carousel.next.bind( 'click', function(e) {
1269                e.preventDefault();
1270
1271                if ( self._options.carouselSteps === 'auto' ) {
1272
1273                    for ( i = carousel.current; i < carousel.hooks.length; i++ ) {
1274                        if ( carousel.hooks[i] - carousel.hooks[ carousel.current ] > carousel.width ) {
1275                            carousel.set(i - 2);
1276                            break;
1277                        }
1278                    }
1279
1280                } else {
1281                    carousel.set( carousel.current + self._options.carouselSteps);
1282                }
1283            });
1284
1285            carousel.prev.bind( 'click', function(e) {
1286                e.preventDefault();
1287
1288                if ( self._options.carouselSteps === 'auto' ) {
1289
1290                    for ( i = carousel.current; i >= 0; i-- ) {
1291                        if ( carousel.hooks[ carousel.current ] - carousel.hooks[i] > carousel.width ) {
1292                            carousel.set( i + 2 );
1293                            break;
1294                        } else if ( i === 0 ) {
1295                            carousel.set( 0 );
1296                            break;
1297                        }
1298                    }
1299                } else {
1300                    carousel.set( carousel.current - self._options.carouselSteps );
1301                }
1302            });
1303        },
1304
1305        // calculate and set positions
1306        set: function( i ) {
1307            i = Math.max( i, 0 );
1308            while ( carousel.hooks[i - 1] + carousel.width >= carousel.max && i >= 0 ) {
1309                i--;
1310            }
1311            carousel.current = i;
1312            carousel.animate();
1313        },
1314
1315        // get the last position
1316        getLast: function(i) {
1317            return ( i || carousel.current ) - 1;
1318        },
1319
1320        // follow the active image
1321        follow: function(i) {
1322
1323            //don't follow if position fits
1324            if ( i === 0 || i === carousel.hooks.length - 2 ) {
1325                carousel.set( i );
1326                return;
1327            }
1328
1329            // calculate last position
1330            var last = carousel.current;
1331            while( carousel.hooks[last] - carousel.hooks[ carousel.current ] <
1332                   carousel.width && last <= carousel.hooks.length ) {
1333                last ++;
1334            }
1335
1336            // set position
1337            if ( i - 1 < carousel.current ) {
1338                carousel.set( i - 1 );
1339            } else if ( i + 2 > last) {
1340                carousel.set( i - last + carousel.current + 2 );
1341            }
1342        },
1343
1344        // helper for setting disabled classes
1345        setClasses: function() {
1346            carousel.prev.toggleClass( 'disabled', !carousel.current );
1347            carousel.next.toggleClass( 'disabled', carousel.hooks[ carousel.current ] + carousel.width >= carousel.max );
1348        },
1349
1350        // the animation method
1351        animate: function(to) {
1352            carousel.setClasses();
1353            var num = carousel.hooks[ carousel.current ] * -1;
1354
1355            if ( isNaN( num ) ) {
1356                return;
1357            }
1358
1359            Utils.animate(self.get( 'thumbnails' ), {
1360                left: num
1361            },{
1362                duration: self._options.carouselSpeed,
1363                easing: self._options.easing,
1364                queue: false
1365            });
1366        }
1367    };
1368
1369    // tooltip control
1370    // added in 1.2
1371    var tooltip = this._tooltip = {
1372
1373        initialized : false,
1374
1375        open: false,
1376
1377        timer: 'tooltip' + self._id,
1378
1379        swapTimer: 'swap' + self._id,
1380
1381        init: function() {
1382
1383            tooltip.initialized = true;
1384
1385            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;' +
1386                      '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);}';
1387
1388            Utils.insertStyleTag(css);
1389
1390            self.$( 'tooltip' ).css({
1391                opacity: 0.8,
1392                visibility: 'visible',
1393                display: 'none'
1394            });
1395
1396        },
1397
1398        // move handler
1399        move: function( e ) {
1400            var mouseX = self.getMousePosition(e).x,
1401                mouseY = self.getMousePosition(e).y,
1402                $elem = self.$( 'tooltip' ),
1403                x = mouseX,
1404                y = mouseY,
1405                height = $elem.outerHeight( true ) + 1,
1406                width = $elem.outerWidth( true ),
1407                limitY = height + 15;
1408
1409            var maxX = self.$( 'container').width() - width - 2,
1410                maxY = self.$( 'container').height() - height - 2;
1411
1412            if ( !isNaN(x) && !isNaN(y) ) {
1413
1414                x += 10;
1415                y -= 30;
1416
1417                x = Math.max( 0, Math.min( maxX, x ) );
1418                y = Math.max( 0, Math.min( maxY, y ) );
1419
1420                if( mouseY < limitY ) {
1421                    y = limitY;
1422                }
1423
1424                $elem.css({ left: x, top: y });
1425            }
1426        },
1427
1428        // bind elements to the tooltip
1429        // you can bind multiple elementIDs using { elemID : function } or { elemID : string }
1430        // you can also bind single DOM elements using bind(elem, string)
1431        bind: function( elem, value ) {
1432
1433            // todo: revise if alternative tooltip is needed for mobile devices
1434            if (Galleria.TOUCH) {
1435                return;
1436            }
1437
1438            if (! tooltip.initialized ) {
1439                tooltip.init();
1440            }
1441
1442            var mouseout = function() {
1443                self.$( 'container' ).unbind( 'mousemove', tooltip.move );
1444                self.clearTimer( tooltip.timer );
1445
1446                self.$( 'tooltip' ).stop().animate({
1447                    opacity: 0
1448                }, 200, function() {
1449
1450                    self.$( 'tooltip' ).hide();
1451
1452                    self.addTimer( tooltip.swapTimer, function() {
1453                        tooltip.open = false;
1454                    }, 1000);
1455                });
1456            };
1457
1458            var hover = function( elem, value) {
1459
1460                tooltip.define( elem, value );
1461
1462                $( elem ).hover(function() {
1463
1464                    self.clearTimer( tooltip.swapTimer );
1465                    self.$('container').unbind( 'mousemove', tooltip.move ).bind( 'mousemove', tooltip.move ).trigger( 'mousemove' );
1466                    tooltip.show( elem );
1467
1468                    self.addTimer( tooltip.timer, function() {
1469                        self.$( 'tooltip' ).stop().show().animate({
1470                            opacity: 1
1471                        });
1472                        tooltip.open = true;
1473
1474                    }, tooltip.open ? 0 : 500);
1475
1476                }, mouseout).click(mouseout);
1477            };
1478
1479            if ( typeof value === 'string' ) {
1480                hover( ( elem in self._dom ? self.get( elem ) : elem ), value );
1481            } else {
1482                // asume elemID here
1483                $.each( elem, function( elemID, val ) {
1484                    hover( self.get(elemID), val );
1485                });
1486            }
1487        },
1488
1489        show: function( elem ) {
1490
1491            elem = $( elem in self._dom ? self.get(elem) : elem );
1492
1493            var text = elem.data( 'tt' ),
1494                mouseup = function( e ) {
1495
1496                    // attach a tiny settimeout to make sure the new tooltip is filled
1497                    window.setTimeout( (function( ev ) {
1498                        return function() {
1499                            tooltip.move( ev );
1500                        };
1501                    }( e )), 10);
1502
1503                    elem.unbind( 'mouseup', mouseup );
1504
1505                };
1506
1507            text = typeof text === 'function' ? text() : text;
1508
1509            if ( ! text ) {
1510                return;
1511            }
1512
1513            self.$( 'tooltip' ).html( text.replace(/\s/, '&nbsp;') );
1514
1515            // trigger mousemove on mouseup in case of click
1516            elem.bind( 'mouseup', mouseup );
1517        },
1518
1519        define: function( elem, value ) {
1520
1521            // we store functions, not strings
1522            if (typeof value !== 'function') {
1523                var s = value;
1524                value = function() {
1525                    return s;
1526                };
1527            }
1528
1529            elem = $( elem in self._dom ? self.get(elem) : elem ).data('tt', value);
1530
1531            tooltip.show( elem );
1532
1533        }
1534    };
1535
1536    // internal fullscreen control
1537    var fullscreen = this._fullscreen = {
1538
1539        scrolled: 0,
1540
1541        crop: undef,
1542
1543        transition: undef,
1544
1545        active: false,
1546
1547        keymap: self._keyboard.map,
1548
1549        parseCallback: function( callback, enter ) {
1550
1551            return _transitions.active ? function() {
1552                if ( typeof callback == 'function' ) {
1553                    callback();
1554                }
1555                var active = self._controls.getActive(),
1556                    next = self._controls.getNext();
1557
1558                self._scaleImage( next );
1559                self._scaleImage( active );
1560
1561                if ( enter && self._options.trueFullscreen ) {
1562                    // Firefox bug, revise later
1563                    $( active.container ).add( next.container ).trigger( 'transitionend' );
1564                }
1565
1566            } : callback;
1567
1568        },
1569
1570        enter: function( callback ) {
1571
1572            callback = fullscreen.parseCallback( callback, true );
1573
1574            if ( self._options.trueFullscreen && _nativeFullscreen.support ) {
1575                _nativeFullscreen.enter( self, callback );
1576            } else {
1577                fullscreen._enter( callback );
1578            }
1579        },
1580
1581        _enter: function( callback ) {
1582
1583            fullscreen.active = true;
1584
1585            // hide the image until rescale is complete
1586            Utils.hide( self.getActiveImage() );
1587
1588            self.$( 'container' ).addClass( 'fullscreen' );
1589
1590            fullscreen.scrolled = $win.scrollTop();
1591
1592            // begin styleforce
1593            Utils.forceStyles(self.get('container'), {
1594                position: 'fixed',
1595                top: 0,
1596                left: 0,
1597                width: '100%',
1598                height: '100%',
1599                zIndex: 10000
1600            });
1601
1602            var htmlbody = {
1603                    height: '100%',
1604                    overflow: 'hidden',
1605                    margin:0,
1606                    padding:0
1607                },
1608
1609                data = self.getData(),
1610
1611                options = self._options;
1612
1613            Utils.forceStyles( DOM().html, htmlbody );
1614            Utils.forceStyles( DOM().body, htmlbody );
1615
1616            // temporarily attach some keys
1617            // save the old ones first in a cloned object
1618            fullscreen.keymap = $.extend({}, self._keyboard.map);
1619
1620            self.attachKeyboard({
1621                escape: self.exitFullscreen,
1622                right: self.next,
1623                left: self.prev
1624            });
1625
1626            // temporarily save the crop
1627            fullscreen.crop = options.imageCrop;
1628
1629            // set fullscreen options
1630            if ( options.fullscreenCrop != undef ) {
1631                options.imageCrop = options.fullscreenCrop;
1632            }
1633
1634            // swap to big image if it's different from the display image
1635            if ( data && data.big && data.image !== data.big ) {
1636                var big    = new Galleria.Picture(),
1637                    cached = big.isCached( data.big ),
1638                    index  = self.getIndex(),
1639                    thumb  = self._thumbnails[ index ];
1640
1641                self.trigger( {
1642                    type: Galleria.LOADSTART,
1643                    cached: cached,
1644                    rewind: false,
1645                    index: index,
1646                    imageTarget: self.getActiveImage(),
1647                    thumbTarget: thumb,
1648                    galleriaData: data
1649                });
1650
1651                big.load( data.big, function( big ) {
1652                    self._scaleImage( big, {
1653                        complete: function( big ) {
1654                            self.trigger({
1655                                type: Galleria.LOADFINISH,
1656                                cached: cached,
1657                                index: index,
1658                                rewind: false,
1659                                imageTarget: big.image,
1660                                thumbTarget: thumb
1661                            });
1662                            var image = self._controls.getActive().image;
1663                            if ( image ) {
1664                                $( image ).width( big.image.width ).height( big.image.height )
1665                                    .attr( 'style', $( big.image ).attr('style') )
1666                                    .attr( 'src', big.image.src );
1667                            }
1668                        }
1669                    });
1670                });
1671            }
1672
1673            // init the first rescale and attach callbacks
1674            self.rescale(function() {
1675
1676                self.addTimer(false, function() {
1677                    // show the image after 50 ms
1678                    Utils.show( self.getActiveImage() );
1679
1680                    if (typeof callback === 'function') {
1681                        callback.call( self );
1682                    }
1683
1684                }, 100);
1685
1686                self.trigger( Galleria.FULLSCREEN_ENTER );
1687            });
1688
1689            // bind the scaling to the resize event
1690            $win.resize( function() {
1691                fullscreen.scale();
1692            } );
1693        },
1694
1695        scale : function() {
1696            self.rescale();
1697        },
1698
1699        exit: function( callback ) {
1700
1701            callback = fullscreen.parseCallback( callback );
1702
1703            if ( self._options.trueFullscreen && _nativeFullscreen.support ) {
1704                _nativeFullscreen.exit( callback );
1705            } else {
1706                fullscreen._exit( callback );
1707            }
1708        },
1709
1710        _exit: function( callback ) {
1711
1712            fullscreen.active = false;
vendor: 13,641 bytes, lines 1713-2121
1713
1714            Utils.hide( self.getActiveImage() );
1715
1716            self.$('container').removeClass( 'fullscreen' );
1717
1718            // revert all styles
1719            Utils.revertStyles( self.get('container'), DOM().html, DOM().body );
1720
1721            // scroll back
1722            window.scrollTo(0, fullscreen.scrolled);
1723
1724            // detach all keyboard events and apply the old keymap
1725            self.detachKeyboard();
1726            self.attachKeyboard( fullscreen.keymap );
1727
1728            // bring back cached options
1729            self._options.imageCrop = fullscreen.crop;
1730            //self._options.transition = fullscreen.transition;
1731
1732            // return to original image
1733            var big = self.getData().big,
1734                image = self._controls.getActive().image;
1735
1736            if ( !self.getData().iframe && image && big && big == image.src ) {
1737
1738                window.setTimeout(function(src) {
1739                    return function() {
1740                        image.src = src;
1741                    };
1742                }( self.getData().image ), 1 );
1743
1744            }
1745
1746            self.rescale(function() {
1747                self.addTimer(false, function() {
1748
1749                    // show the image after 50 ms
1750                    Utils.show( self.getActiveImage() );
1751
1752                    if ( typeof callback === 'function' ) {
1753                        callback.call( self );
1754                    }
1755
1756                    $win.trigger( 'resize' );
1757
1758                }, 50);
1759                self.trigger( Galleria.FULLSCREEN_EXIT );
1760            });
1761
1762
1763            $win.unbind('resize', fullscreen.scale);
1764        }
1765    };
1766
1767    // the internal idle object for controlling idle states
1768    var idle = this._idle = {
1769
1770        trunk: [],
1771
1772        bound: false,
1773
1774        active: false,
1775
1776        add: function(elem, to, from, hide) {
1777            if (!elem) {
1778                return;
1779            }
1780            if (!idle.bound) {
1781                idle.addEvent();
1782            }
1783            elem = $(elem);
1784
1785            if ( typeof from == 'boolean' ) {
1786                hide = from;
1787                from = {};
1788            }
1789
1790            from = from || {};
1791
1792            var extract = {},
1793                style;
1794
1795            for ( style in to ) {
1796                if ( to.hasOwnProperty( style ) ) {
1797                    extract[ style ] = elem.css( style );
1798                }
1799            }
1800
1801            elem.data('idle', {
1802                from: $.extend( extract, from ),
1803                to: to,
1804                complete: true,
1805                busy: false
1806            });
1807
1808            if ( !hide ) {
1809                idle.addTimer();
1810            } else {
1811                elem.css( to );
1812            }
1813            idle.trunk.push(elem);
1814        },
1815
1816        remove: function(elem) {
1817
1818            elem = $(elem);
1819
1820            $.each(idle.trunk, function(i, el) {
1821                if ( el && el.length && !el.not(elem).length ) {
1822                    elem.css( elem.data( 'idle' ).from );
1823                    idle.trunk.splice(i, 1);
1824                }
1825            });
1826
1827            if (!idle.trunk.length) {
1828                idle.removeEvent();
1829                self.clearTimer( idle.timer );
1830            }
1831        },
1832
1833        addEvent : function() {
1834            idle.bound = true;
1835            self.$('container').bind( 'mousemove click', idle.showAll );
1836            if ( self._options.idleMode == 'hover' ) {
1837                self.$('container').bind( 'mouseleave', idle.hide );
1838            }
1839        },
1840
1841        removeEvent : function() {
1842            idle.bound = false;
1843            self.$('container').bind( 'mousemove click', idle.showAll );
1844            if ( self._options.idleMode == 'hover' ) {
1845                self.$('container').unbind( 'mouseleave', idle.hide );
1846            }
1847        },
1848
1849        addTimer : function() {
1850            if( self._options.idleMode == 'hover' ) {
1851                return;
1852            }
1853            self.addTimer( 'idle', function() {
1854                idle.hide();
1855            }, self._options.idleTime );
1856        },
1857
1858        hide : function() {
1859
1860            if ( !self._options.idleMode || self.getIndex() === false || self.getData().iframe ) {
1861                return;
1862            }
1863
1864            self.trigger( Galleria.IDLE_ENTER );
1865
1866            var len = idle.trunk.length;
1867
1868            $.each( idle.trunk, function(i, elem) {
1869
1870                var data = elem.data('idle');
1871
1872                if (! data) {
1873                    return;
1874                }
1875
1876                elem.data('idle').complete = false;
1877
1878                Utils.animate( elem, data.to, {
1879                    duration: self._options.idleSpeed,
1880                    complete: function() {
1881                        if ( i == len-1 ) {
1882                            idle.active = false;
1883                        }
1884                    }
1885                });
1886            });
1887        },
1888
1889        showAll : function() {
1890
1891            self.clearTimer( 'idle' );
1892
1893            $.each( idle.trunk, function( i, elem ) {
1894                idle.show( elem );
1895            });
1896        },
1897
1898        show: function(elem) {
1899
1900            var data = elem.data('idle');
1901
1902            if ( !idle.active || ( !data.busy && !data.complete ) ) {
1903
1904                data.busy = true;
1905
1906                self.trigger( Galleria.IDLE_EXIT );
1907
1908                self.clearTimer( 'idle' );
1909
1910                Utils.animate( elem, data.from, {
1911                    duration: self._options.idleSpeed/2,
1912                    complete: function() {
1913                        idle.active = true;
1914                        $(elem).data('idle').busy = false;
1915                        $(elem).data('idle').complete = true;
1916                    }
1917                });
1918
1919            }
1920            idle.addTimer();
1921        }
1922    };
1923
1924    // internal lightbox object
1925    // creates a predesigned lightbox for simple popups of images in galleria
1926    var lightbox = this._lightbox = {
1927
1928        width : 0,
1929
1930        height : 0,
1931
1932        initialized : false,
1933
1934        active : null,
1935
1936        image : null,
1937
1938        elems : {},
1939
1940        keymap: false,
1941
1942        init : function() {
1943
1944            // trigger the event
1945            self.trigger( Galleria.LIGHTBOX_OPEN );
1946
1947            if ( lightbox.initialized ) {
1948                return;
1949            }
1950            lightbox.initialized = true;
1951
1952            // create some elements to work with
1953            var elems = 'overlay box content shadow title info close prevholder prev nextholder next counter image',
1954                el = {},
1955                op = self._options,
1956                css = '',
1957                abs = 'position:absolute;',
1958                prefix = 'lightbox-',
1959                cssMap = {
1960                    overlay:    'position:fixed;display:none;opacity:'+op.overlayOpacity+';filter:alpha(opacity='+(op.overlayOpacity*100)+
1961                                ');top:0;left:0;width:100%;height:100%;background:'+op.overlayBackground+';z-index:99990',
1962                    box:        'position:fixed;display:none;width:400px;height:400px;top:50%;left:50%;margin-top:-200px;margin-left:-200px;z-index:99991',
1963                    shadow:     abs+'background:#000;width:100%;height:100%;',
1964                    content:    abs+'background-color:#fff;top:10px;left:10px;right:10px;bottom:10px;overflow:hidden',
1965                    info:       abs+'bottom:10px;left:10px;right:10px;color:#444;font:11px/13px arial,sans-serif;height:13px',
1966                    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',
1967                    image:      abs+'top:10px;left:10px;right:10px;bottom:30px;overflow:hidden;display:block;',
1968                    prevholder: abs+'width:50%;top:0;bottom:40px;cursor:pointer;',
1969                    nextholder: abs+'width:50%;top:0;bottom:40px;right:-1px;cursor:pointer;',
1970                    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',
1971                    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',
1972                    title:      'float:left',
1973                    counter:    'float:right;margin-left:8px;'
1974                },
1975                hover = function(elem) {
1976                    return elem.hover(
1977                        function() { $(this).css( 'color', '#bbb' ); },
1978                        function() { $(this).css( 'color', '#444' ); }
1979                    );
1980                },
1981                appends = {};
1982
1983            // IE8 fix for IE's transparent background event "feature"
1984            if ( IE && IE > 7 ) {
1985                cssMap.nextholder += 'background:#000;filter:alpha(opacity=0);';
1986                cssMap.prevholder += 'background:#000;filter:alpha(opacity=0);';
1987            }
1988
1989            // create and insert CSS
1990            $.each(cssMap, function( key, value ) {
1991                css += '.galleria-'+prefix+key+'{'+value+'}';
1992            });
1993
1994            css += '.galleria-'+prefix+'box.iframe .galleria-'+prefix+'prevholder,'+
1995                   '.galleria-'+prefix+'box.iframe .galleria-'+prefix+'nextholder{'+
1996                   'width:100px;height:100px;top:50%;margin-top:-70px}';
1997
1998            Utils.insertStyleTag( css );
1999
2000            // create the elements
2001            $.each(elems.split(' '), function( i, elemId ) {
2002                self.addElement( 'lightbox-' + elemId );
2003                el[ elemId ] = lightbox.elems[ elemId ] = self.get( 'lightbox-' + elemId );
2004            });
2005
2006            // initiate the image
2007            lightbox.image = new Galleria.Picture();
2008
2009            // append the elements
2010            $.each({
2011                    box: 'shadow content close prevholder nextholder',
2012                    info: 'title counter',
2013                    content: 'info image',
2014                    prevholder: 'prev',
2015                    nextholder: 'next'
2016                }, function( key, val ) {
2017                    var arr = [];
2018                    $.each( val.split(' '), function( i, prop ) {
2019                        arr.push( prefix + prop );
2020                    });
2021                    appends[ prefix+key ] = arr;
2022            });
2023
2024            self.append( appends );
2025
2026            $( el.image ).append( lightbox.image.container );
2027
2028            $( DOM().body ).append( el.overlay, el.box );
2029
2030            Utils.optimizeTouch( el.box );
2031
2032            // add the prev/next nav and bind some controls
2033
2034            hover( $( el.close ).bind( 'click', lightbox.hide ).html('&#215;') );
2035
2036            $.each( ['Prev','Next'], function(i, dir) {
2037
2038                var $d = $( el[ dir.toLowerCase() ] ).html( /v/.test( dir ) ? '&#8249;&nbsp;' : '&nbsp;&#8250;' ),
2039                    $e = $( el[ dir.toLowerCase()+'holder'] );
2040
2041                $e.bind( 'click', function() {
2042                    lightbox[ 'show' + dir ]();
2043                });
2044
2045                // IE7 and touch devices will simply show the nav
2046                if ( IE < 8 || Galleria.TOUCH ) {
2047                    $d.show();
2048                    return;
2049                }
2050
2051                $e.hover( function() {
2052                    $d.show();
2053                }, function(e) {
2054                    $d.stop().fadeOut( 200 );
2055                });
2056
2057            });
2058            $( el.overlay ).bind( 'click', lightbox.hide );
2059
2060            // the lightbox animation is slow on ipad
2061            if ( Galleria.IPAD ) {
2062                self._options.lightboxTransitionSpeed = 0;
2063            }
2064
2065        },
2066
2067        rescale: function(event) {
2068
2069            // calculate
2070             var width = Math.min( $win.width()-40, lightbox.width ),
2071                height = Math.min( $win.height()-60, lightbox.height ),
2072                ratio = Math.min( width / lightbox.width, height / lightbox.height ),
2073                destWidth = Math.round( lightbox.width * ratio ) + 40,
2074                destHeight = Math.round( lightbox.height * ratio ) + 60,
2075                to = {
2076                    width: destWidth,
2077                    height: destHeight,
2078                    'margin-top': Math.ceil( destHeight / 2 ) *- 1,
2079                    'margin-left': Math.ceil( destWidth / 2 ) *- 1
2080                };
2081
2082            // if rescale event, don't animate
2083            if ( event ) {
2084                $( lightbox.elems.box ).css( to );
2085            } else {
2086                $( lightbox.elems.box ).animate( to, {
2087                    duration: self._options.lightboxTransitionSpeed,
2088                    easing: self._options.easing,
2089                    complete: function() {
2090                        var image = lightbox.image,
2091                            speed = self._options.lightboxFadeSpeed;
2092
2093                        self.trigger({
2094                            type: Galleria.LIGHTBOX_IMAGE,
2095                            imageTarget: image.image
2096                        });
2097
2098                        $( image.container ).show();
2099
2100                        $( image.image ).animate({ opacity: 1 }, speed);
2101                        Utils.show( lightbox.elems.info, speed );
2102                    }
2103                });
2104            }
2105        },
2106
2107        hide: function() {
2108
2109            // remove the image
2110            lightbox.image.image = null;
2111
2112            $win.unbind('resize', lightbox.rescale);
2113
2114            $( lightbox.elems.box ).hide();
2115
2116            Utils.hide( lightbox.elems.info );
2117
2118            self.detachKeyboard();
2119            self.attachKeyboard( lightbox.keymap );
2120
2121            lightbox.keymap = false;
2122
2123            Utils.hide( lightbox.elems.overlay, 200, function() {
2124                $( this ).hide().css( 'opacity', self._options.overlayOpacity );
2125                self.trigger( Galleria.LIGHTBOX_CLOSE );
2126            });
2127        },
2128
2129        showNext: function() {
2130            lightbox.show( self.getNext( lightbox.active ) );
2131        },
2132
2133        showPrev: function() {
2134            lightbox.show( self.getPrev( lightbox.active ) );
2135        },
2136
2137        show: function(index) {
2138
2139            lightbox.active = index = typeof index === 'number' ? index : self.getIndex() || 0;
2140
2141            if ( !lightbox.initialized ) {
2142                lightbox.init();
2143            }
2144
2145            // temporarily attach some keys
2146            // save the old ones first in a cloned object
2147            if ( !lightbox.keymap ) {
2148
2149                lightbox.keymap = $.extend({}, self._keyboard.map);
2150
2151                self.attachKeyboard({
2152                    escape: lightbox.hide,
2153                    right: lightbox.showNext,
2154                    left: lightbox.showPrev
2155                });
2156            }
2157
2158            $win.unbind('resize', lightbox.rescale );
2159
2160            var data = self.getData(index),
2161                total = self.getDataLength(),
2162                n = self.getNext( index ),
2163                ndata, p, i;
2164
2165            Utils.hide( lightbox.elems.info );
2166
2167            try {
2168                for ( i = self._options.preload; i > 0; i-- ) {
2169                    p = new Galleria.Picture();
2170                    ndata = self.getData( n );
2171                    p.preload( 'big' in ndata ? ndata.big : ndata.image );
2172                    n = self.getNext( n );
2173                }
2174            } catch(e) {}
2175
2176            lightbox.image.isIframe = !!data.iframe;
2177
2178            $(lightbox.elems.box).toggleClass( 'iframe', !!data.iframe );
2179
2180            lightbox.image.load( data.iframe || data.big || data.image, function( image ) {
2181
2182                lightbox.width = image.isIframe ? $(window).width() : image.original.width;
2183                lightbox.height = image.isIframe ? $(window).height() : image.original.height;
2184
2185                $( image.image ).css({
2186                    width: image.isIframe ? '100%' : '100.1%',
2187                    height: image.isIframe ? '100%' : '100.1%',
2188                    top: 0,
2189                    zIndex: 99998,
2190                    opacity: 0,
2191                    visibility: 'visible'
2192                });
2193
2194                lightbox.elems.title.innerHTML = data.title || '';
2195                lightbox.elems.counter.innerHTML = (index + 1) + ' / ' + total;
2196                $win.resize( lightbox.rescale );
2197                lightbox.rescale();
2198            });
2199
2200            $( lightbox.elems.overlay ).show().css( 'visibility', 'visible' );
2201            $( lightbox.elems.box ).show();
2202        }
2203    };
2204
2205    // the internal timeouts object
2206    // provides helper methods for controlling timeouts
2207
2208    var _timer = this._timer = {
2209
2210        trunk: {},
2211
2212        add: function( id, fn, delay, loop ) {
2213            id = id || new Date().getTime();
2214            loop = loop || false;
2215            this.clear( id );
2216            if ( loop ) {
2217                var old = fn;
2218                fn = function() {
2219                    old();
2220                    _timer.add( id, fn, delay );
2221                };
2222            }
2223            this.trunk[ id ] = window.setTimeout( fn, delay );
2224        },
2225
2226        clear: function( id ) {
2227
2228            var del = function( i ) {
2229                window.clearTimeout( this.trunk[ i ] );
2230                delete this.trunk[ i ];
2231            }, i;
2232
2233            if ( !!id && id in this.trunk ) {
2234                del.call( this, id );
2235
2236            } else if ( typeof id === 'undefined' ) {
2237                for ( i in this.trunk ) {
2238                    if ( this.trunk.hasOwnProperty( i ) ) {
2239                        del.call( this, i );
2240                    }
2241                }
2242            }
2243        }
2244    };
2245
2246    return this;
2247};
2248
2249// end Galleria constructor
2250
2251Galleria.prototype = {
2252
2253    // bring back the constructor reference
2254
2255    constructor: Galleria,
2256
2257    /**
2258        Use this function to initialize the gallery and start loading.
2259        Should only be called once per instance.
2260
2261        @param {HTMLElement} target The target element
2262        @param {Object} options The gallery options
2263
2264        @returns Instance
2265    */
2266
2267    init: function( target, options ) {
2268
2269        var self = this;
2270
2271        options = _legacyOptions( options );
2272
2273        // save the original ingredients
2274        this._original = {
2275            target: target,
2276            options: options,
2277            data: null
2278        };
2279
2280        // save the target here
2281        this._target = this._dom.target = target.nodeName ? target : $( target ).get(0);
2282
2283        // save the original content for destruction
2284        this._original.html = this._target.innerHTML;
2285
2286        // push the instance
2287        _instances.push( this );
2288
2289        // raise error if no target is detected
2290        if ( !this._target ) {
2291             Galleria.raise('Target not found', true);
2292             return;
2293        }
2294
2295        // apply options
2296        this._options = {
2297            autoplay: false,
2298            carousel: true,
2299            carouselFollow: true, // legacy, deprecate at 1.3
2300            carouselSpeed: 400,
2301            carouselSteps: 'auto',
2302            clicknext: false,
2303            dailymotion: {
2304                foreground: '%23EEEEEE',
2305                highlight: '%235BCEC5',
2306                background: '%23222222',
2307                logo: 0,
2308                hideInfos: 1
2309            },
2310            dataConfig : function( elem ) { return {}; },
2311            dataSelector: 'img',
2312            dataSort: false,
2313            dataSource: this._target,
2314            debug: undef,
2315            dummy: undef, // 1.2.5
2316            easing: 'galleria',
2317            extend: function(options) {},
2318            fullscreenCrop: undef, // 1.2.5
2319            fullscreenDoubleTap: true, // 1.2.4 toggles fullscreen on double-tap for touch devices
2320            fullscreenTransition: undef, // 1.2.6
2321            height: 0,
2322            idleMode: true, // 1.2.4 toggles idleMode
2323            idleTime: 3000,
2324            idleSpeed: 200,
2325            imageCrop: false,
2326            imageMargin: 0,
2327            imagePan: false,
2328            imagePanSmoothness: 12,
2329            imagePosition: '50%',
2330            imageTimeout: undef, // 1.2.5
2331            initialTransition: undef, // 1.2.4, replaces transitionInitial
2332            keepSource: false,
2333            layerFollow: true, // 1.2.5
2334            lightbox: false, // 1.2.3
2335            lightboxFadeSpeed: 200,
2336            lightboxTransitionSpeed: 200,
2337            linkSourceImages: true,
2338            maxScaleRatio: undef,
2339            minScaleRatio: undef,
2340            overlayOpacity: 0.85,
2341            overlayBackground: '#0b0b0b',
2342            pauseOnInteraction: true,
2343            popupLinks: false,
2344            preload: 2,
2345            queue: true,
2346            responsive: true,
2347            show: 0,
2348            showInfo: true,
2349            showCounter: true,
2350            showImagenav: true,
2351            swipe: true, // 1.2.4
2352            thumbCrop: true,
2353            thumbEventType: 'click',
2354            thumbFit: true, // legacy, deprecate at 1.3
2355            thumbMargin: 0,
2356            thumbQuality: 'auto',
2357            thumbDisplayOrder: true, // 1.2.8
2358            thumbnails: true,
2359            touchTransition: undef, // 1.2.6
2360            transition: 'fade',
2361            transitionInitial: undef, // legacy, deprecate in 1.3. Use initialTransition instead.
2362            transitionSpeed: 400,
2363            trueFullscreen: true, // 1.2.7
2364            useCanvas: false, // 1.2.4
2365            vimeo: {
2366                title: 0,
2367                byline: 0,
2368                portrait: 0,
2369                color: 'aaaaaa'
2370            },
2371            wait: 5000, // 1.2.7
2372            width: 'auto',
2373            youtube: {
2374                modestbranding: 1,
2375                autohide: 1,
2376                color: 'white',
2377                hd: 1,
2378                rel: 0,
2379                showinfo: 0
2380            }
2381        };
2382
2383        // legacy support for transitionInitial
2384        this._options.initialTransition = this._options.initialTransition || this._options.transitionInitial;
2385
2386        // turn off debug
2387        if ( options && options.debug === false ) {
2388            DEBUG = false;
2389        }
2390
2391        // set timeout
2392        if ( options && typeof options.imageTimeout === 'number' ) {
2393            TIMEOUT = options.imageTimeout;
2394        }
2395
2396        // set dummy
2397        if ( options && typeof options.dummy === 'string' ) {
2398            DUMMY = options.dummy;
2399        }
2400
2401        // hide all content
2402        $( this._target ).children().hide();
2403
2404        // now we just have to wait for the theme...
2405        if ( typeof Galleria.theme === 'object' ) {
2406            this._init();
2407        } else {
2408            // push the instance into the pool and run it when the theme is ready
2409            _pool.push( this );
2410        }
2411
2412        return this;
2413    },
2414
2415    // this method should only be called once per instance
2416    // for manipulation of data, use the .load method
2417
2418    _init: function() {
2419
2420        var self = this,
2421            options = this._options;
2422
2423        if ( this._initialized ) {
2424            Galleria.raise( 'Init failed: Gallery instance already initialized.' );
2425            return this;
2426        }
2427
2428        this._initialized = true;
2429
2430        if ( !Galleria.theme ) {
2431            Galleria.raise( 'Init failed: No theme found.', true );
2432            return this;
2433        }
2434
2435        // merge the theme & caller options
2436        $.extend( true, options, Galleria.theme.defaults, this._original.options, Galleria.configure.options );
2437
2438        // check for canvas support
2439        (function( can ) {
2440
2441            if ( !( 'getContext' in can ) ) {
2442                can = null;
2443                return;
2444            }
2445
2446            _canvas = _canvas || {
2447                elem: can,
2448                context: can.getContext( '2d' ),
2449                cache: {},
2450                length: 0
2451            };
2452
2453        }
vendor: 4,163 bytes, lines 2453-2565
2453( doc.createElement( 'canvas' ) ) );
2454
2455        // bind the gallery to run when data is ready
2456        this.bind( Galleria.DATA, function() {
2457
2458            // Warn for quirks mode
2459            if ( Galleria.QUIRK ) {
2460                Galleria.raise('Your page is in Quirks mode, Galleria may not render correctly. Please validate your HTML.');
2461            }
2462
2463            // save the new data
2464            this._original.data = this._data;
2465
2466            // lets show the counter here
2467            this.get('total').innerHTML = this.getDataLength();
2468
2469            // cache the container
2470            var $container = this.$( 'container' );
2471
2472            // set ratio if height is < 2
2473            if ( self._options.height < 2 ) {
2474                self._ratio = self._options.height;
2475            }
2476
2477            // the gallery is ready, let's just wait for the css
2478            var num = { width: 0, height: 0 };
2479            var testHeight = function() {
2480                return self.$( 'stage' ).height();
2481            };
2482
2483            // check container and thumbnail height
2484            Utils.wait({
2485                until: function() {
2486
2487                    // keep trying to get the value
2488                    num = self._getWH();
2489                    $container.width( num.width ).height( num.height );
2490                    return testHeight() && num.width && num.height > 50;
2491
2492                },
2493                success: function() {
2494
2495                    self._width = num.width;
2496                    self._height = num.height;
2497                    self._ratio = self._ratio || num.height/num.width;
2498
2499                    // for some strange reason, webkit needs a single setTimeout to play ball
2500                    if ( Galleria.WEBKIT ) {
2501                        window.setTimeout( function() {
2502                            self._run();
2503                        }, 1);
2504                    } else {
2505                        self._run();
2506                    }
2507                },
2508                error: function() {
2509
2510                    // Height was probably not set, raise hard errors
2511
2512                    if ( testHeight() ) {
2513                        Galleria.raise('Could not extract sufficient width/height of the gallery container. Traced measures: width:' + num.width + 'px, height: ' + num.height + 'px.', true);
2514                    } else {
2515                        Galleria.raise('Could not extract a stage height from the CSS. Traced height: ' + testHeight() + 'px.', true);
2516                    }
2517                },
2518                timeout: typeof this._options.wait == 'number' ? this._options.wait : false
2519            });
2520        });
2521
2522        // build the gallery frame
2523        this.append({
2524            'info-text' :
2525                ['info-title', 'info-description'],
2526            'info' :
2527                ['info-text'],
2528            'image-nav' :
2529                ['image-nav-right', 'image-nav-left'],
2530            'stage' :
2531                ['images', 'loader', 'counter', 'image-nav'],
2532            'thumbnails-list' :
2533                ['thumbnails'],
2534            'thumbnails-container' :
2535                ['thumb-nav-left', 'thumbnails-list', 'thumb-nav-right'],
2536            'container' :
2537                ['stage', 'thumbnails-container', 'info', 'tooltip']
2538        });
2539
2540        Utils.hide( this.$( 'counter' ).append(
2541            this.get( 'current' ),
2542            doc.createTextNode(' / '),
2543            this.get( 'total' )
2544        ) );
2545
2546        this.setCounter('&#8211;');
2547
2548        Utils.hide( self.get('tooltip') );
2549
2550        // add a notouch class on the container to prevent unwanted :hovers on touch devices
2551        this.$( 'container' ).addClass( Galleria.TOUCH ? 'touch' : 'notouch' );
2552
2553        // add images to the controls
2554        $.each( new Array(2), function( i ) {
2555
2556            // create a new Picture instance
2557            var image = new Galleria.Picture();
2558
2559            // apply some styles, create & prepend overlay
2560            $( image.container ).css({
2561                position: 'absolute',
2562                top: 0,
2563                left: 0
2564            }).prepend( self._layers[i] = $( Utils.create('galleria-layer') ).css({
2565                position: 'absolute',
2566                top:0, left:0, right:0, bottom:0,
2567                zIndex:2
2568            })[0] );
2569
2570            // append the image
2571            self.$( 'images' ).append( image.container );
2572
2573            // reload the controls
2574            self._controls[i] = image;
2575
2576        });
2577
2578        // some forced generic styling
2579        this.$( 'images' ).css({
2580            position: 'relative',
2581            top: 0,
2582            left: 0,
2583            width: '100%',
2584            height: '100%'
2585        });
2586
2587        this.$( 'thumbnails, thumbnails-list' ).css({
2588            overflow: 'hidden',
2589            position: 'relative'
2590        });
2591		//modified by JoomlaShine Team - BEGINNING - Align all images to center
2592		this.$( 'thumbnails' ).css({
2593            margin  : 'auto'
2594        });
2595		//modified by JoomlaShine Team  - END
2596        // bind image navigation arrows
2597        this.$( 'image-nav-right, image-nav-left' ).bind( 'click', function(e) {
2598
2599            // tune the clicknext option
2600            if ( options.clicknext ) {
2601                e.stopPropagation();
2602            }
2603
2604            // pause if options is set
2605            if ( options.pauseOnInteraction ) {
2606                self.pause();
2607            }
2608
2609            // navigate
2610            var fn = /right/.test( this.className ) ? 'next' : 'prev';
2611            self[ fn ]();
2612
2613        });
2614		/******************** Joomlashine dev BEGIN ********************/
2615        // bind information panel
2616        if (self._options.informationPanelClickAction)
2617        {
2618        	var jsntouch = Galleria.TOUCH,
2619            jsnclick = jsntouch ? 'touchstart' : 'click';
2620			this.$('info-text').css({
2621        		cursor: 'pointer'
2622        	}).bind(jsnclick, function(e) {
2623        	var index 	= self.getIndex();	
2624            var link 	= self._data[index].link;
2625         	if (link) 
2626        	{
2627        	  if (self._options.infoPanelPopupLinks) 
2628        	  {
2629        		  win = window.open(link, '_blank');
2630        	  } 
2631        	  else 
2632        	  {
2633        		  window.location.href = link;
2634        	  }
2635        	  return;
2636        	}});
2637        }
2638        /******************** Joomlashine dev END ********************/
2639        // hide controls if chosen to
2640        $.each( ['info','counter','image-nav'], function( i, el ) {
2641            if ( options[ 'show' + el.substr(0,1).toUpperCase() + el.substr(1).replace(/-/,'') ] === false ) {
2642                Utils.moveOut( self.get( el.toLowerCase() ) );
2643            }
2644        });
2645
2646        // load up target content
2647        this.load();
2648
2649        // now it's usually safe to remove the content
2650        // IE will never stop loading if we remove it, so let's keep it hidden for IE (it's usually fast enough anyway)
2651        if ( !options.keepSource && !IE ) {
2652            this._target.innerHTML = '';
2653        }
2654
2655        // re-append the errors, if they happened before clearing
2656        if ( this.get( 'errors' ) ) {
2657            this.appendChild( 'target', 'errors' );
2658        }
2659
2660        // append the gallery frame
2661        this.appendChild( 'target', 'container' );
2662
2663        // parse the carousel on each thumb load
2664        if ( options.carousel ) {
2665            var count = 0,
2666                show = options.show;
2667            this.bind( Galleria.THUMBNAIL, function() {
2668                this.updateCarousel();
2669                if ( ++count == this.getDataLength() && typeof show == 'number' && show > 0 ) {
2670                    this._carousel.follow( show );
2671                }
2672            });
2673        }
2674
2675        // bind window resize for responsiveness
2676        if ( options.responsive ) {
2677            $win.bind( 'resize', function() {
2678                if ( !self.isFullscreen() ) {
2679                    self.resize();
2680                }
2681            });
2682        }
2683
2684        // bind swipe gesture
2685        if ( options.swipe ) {
2686
2687            (function( images ) {
2688
2689                var swipeStart = [0,0],
2690                    swipeStop = [0,0],
2691                    limitX = 30,
2692                    limitY = 100,
2693                    multi = false,
2694                    tid = 0,
2695                    data,
2696                    ev = {
2697                        start: 'touchstart',
2698                        move: 'touchmove',
2699                        stop: 'touchend'
2700                    },
2701                    getData = function(e) {
2702                        return e.originalEvent.touches ? e.originalEvent.touches[0] : e;
2703                    },
2704                    moveHandler = function( e ) {
2705
2706                        if ( e.originalEvent.touches && e.originalEvent.touches.length > 1 ) {
2707                            return;
2708                        }
2709
2710                        data = getData( e );
2711                        swipeStop = [ data.pageX, data.pageY ];
2712
2713                        if ( !swipeStart[0] ) {
2714                            swipeStart = swipeStop;
2715                        }
2716
2717                        if ( Math.abs( swipeStart[0] - swipeStop[0] ) > 10 ) {
2718                            e.preventDefault();
2719                        }
2720                    },
2721                    upHandler = function( e ) {
2722
2723                        images.unbind( ev.move, moveHandler );
2724
2725                        // if multitouch (possibly zooming), abort
2726                        if ( ( e.originalEvent.touches && e.originalEvent.touches.length ) || multi ) {
2727                            multi = !multi;
2728                            return;
2729                        }
2730
2731                        if ( Utils.timestamp() - tid < 1000 &&
2732                             Math.abs( swipeStart[0] - swipeStop[0] ) > limitX &&
2733                             Math.abs( swipeStart[1] - swipeStop[1] ) < limitY ) {
2734
2735                            e.preventDefault();
2736                            self[ swipeStart[0] > swipeStop[0] ? 'next' : 'prev' ]();
2737                        }
2738
2739                        swipeStart = swipeStop = [0,0];
2740                    };
2741
2742                images.bind(ev.start, function(e) {
2743
2744                    if ( e.originalEvent.touches && e.originalEvent.touches.length > 1 ) {
2745                        return;
2746                    }
2747
2748                    data = getData(e);
2749                    tid = Utils.timestamp();
2750                    swipeStart = swipeStop = [ data.pageX, data.pageY ];
2751                    images.bind(ev.move, moveHandler ).one(ev.stop, upHandler);
2752
2753                });
2754
2755            }( self.$( 'images' ) ));
2756
2757            // double-tap/click fullscreen toggle
2758
2759            if ( options.fullscreenDoubleTap ) {
2760
2761                this.$( 'stage' ).bind( 'touchstart', (function() {
2762                    var last, cx, cy, lx, ly, now,
2763                        getData = function(e) {
2764                            return e.originalEvent.touches ? e.originalEvent.touches[0] : e;
2765                        };
2766                    return function(e) {
2767                        now = Galleria.utils.timestamp();
2768                        cx = getData(e).pageX;
2769                        cy = getData(e).pageY;
2770                        if ( ( now - last < 500 ) && ( cx - lx < 20) && ( cy - ly < 20) ) {
2771                            self.toggleFullscreen();
2772                            e.preventDefault();
2773                            self.$( 'stage' ).unbind( 'touchend', arguments.callee );
2774                            return;
2775                        }
2776                        last = now;
2777                        lx = cx;
2778                        ly = cy;
2779                    };
2780                }()));
2781            }
2782
2783        }
2784
2785        // optimize touch for container
2786        Utils.optimizeTouch( this.get( 'container' ) );
2787
2788        // bind the ons
2789        $.each( Galleria.on.binds, function(i, bind) {
2790            // check if already bound
2791            if ( $.inArray( bind.hash, self._binds ) == -1 ) {
2792                self.bind( bind.type, bind.callback );
2793            }
2794        });
2795
2796        return this;
2797    },
2798
2799    addTimer : function() {
2800        this._timer.add.apply( this._timer, Utils.array( arguments ) );
2801        return this;
2802    },
2803
2804    clearTimer : function() {
2805        this._timer.clear.apply( this._timer, Utils.array( arguments ) );
2806        return this;
2807    },
2808
2809    // parse width & height from CSS or options
2810
2811    _getWH : function() {
2812
2813        var $container = this.$( 'container' ),
2814            $target = this.$( 'target' ),
2815            self = this,
2816            num = {},
2817            arr;
2818
2819        $.each(['width', 'height'], function( i, m ) {
2820
2821            // first check if options is set
2822            if ( self._options[ m ] && typeof self._options[ m ] === 'number') {
2823                num[ m ] = self._options[ m ];
2824            } else {
2825
2826                arr = [
2827                    Utils.parseValue( $container.css( m ) ),         // the container css height
2828                    Utils.parseValue( $target.css( m ) ),            // the target css height
2829                    $container[ m ](),                               // the container jQuery method
2830                    $target[ m ]()                                   // the target jQuery method
2831                ];
2832
2833                // if first time, include the min-width & min-height
2834                if ( !self[ '_'+m ] ) {
2835                    arr.splice(arr.length,
2836                        Utils.parseValue( $container.css( 'min-'+m ) ),
2837                        Utils.parseValue( $target.css( 'min-'+m ) )
2838                    );
2839                }
2840
2841                // else extract the measures from different sources and grab the highest value
2842                num[ m ] = Math.max.apply( Math, arr );
2843            }
2844        });
2845
2846        // allow setting a height ratio instead of exact value
2847        // useful when doing responsive galleries
2848
2849        if ( self._ratio ) {
2850            num.height = num.width * self._ratio;
2851        }
2852
2853        return num;
2854    },
2855
2856    // Creates the thumbnails and carousel
vendor: 6,798 bytes, lines 2857-3039
2857    // can be used at any time, f.ex when the data object is manipulated
2858    // push is an optional argument with pushed images
2859
2860    _createThumbnails : function( push ) {
2861
2862        this.get( 'total' ).innerHTML = this.getDataLength();
2863
2864        var src,
2865            thumb,
2866            data,
2867            special,
2868
2869            $container,
2870
2871            self = this,
2872            o = this._options,
2873
2874            i = push ? this._data.length - push.length : 0,
2875            chunk = i,
2876
2877            thumbchunk = [],
2878            loadindex = 0,
2879
2880            gif = IE < 8 ? 'http://upload.wikimedia.org/wikipedia/commons/c/c0/Blank.gif' :
2881                           'data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D',
2882
2883            // get previously active thumbnail, if exists
2884            active = (function() {
2885                var a = self.$('thumbnails').find('.active');
2886                if ( !a.length ) {
2887                    return false;
2888                }
2889                return a.find('img').attr('src');
2890            }()),
2891
2892            // cache the thumbnail option
2893            optval = typeof o.thumbnails === 'string' ? o.thumbnails.toLowerCase() : null,
2894
2895            // move some data into the instance
2896            // for some reason, jQuery cant handle css(property) when zooming in FF, breaking the gallery
2897            // so we resort to getComputedStyle for browsers who support it
2898            getStyle = function( prop ) {
2899                return doc.defaultView && doc.defaultView.getComputedStyle ?
2900                    doc.defaultView.getComputedStyle( thumb.container, null )[ prop ] :
2901                    $container.css( prop );
2902            },
2903
2904            fake = function(image, index, container) {
2905                return function() {
2906                    $( container ).append( image );
2907                    self.trigger({
2908                        type: Galleria.THUMBNAIL,
2909                        thumbTarget: image,
2910                        index: index,
2911                        galleriaData: self.getData( index )
2912                    });
2913                };
2914            },
2915
2916            onThumbEvent = function( e ) {
2917
2918                // pause if option is set
2919                if ( o.pauseOnInteraction ) {
2920                    self.pause();
2921                }
2922
2923                // extract the index from the data
2924                var index = $( e.currentTarget ).data( 'index' );
2925                if ( self.getIndex() !== index ) {
2926                    self.show( index );
2927                }
2928
2929                e.preventDefault();
2930            },
2931
2932            thumbComplete = function( thumb, callback ) {
2933
2934                $( thumb.container ).css( 'visibility', 'visible' );
2935                self.trigger({
2936                    type: Galleria.THUMBNAIL,
2937                    thumbTarget: thumb.image,
2938                    index: thumb.data.order,
2939                    galleriaData: self.getData( thumb.data.order )
2940                });
2941
2942                if ( typeof callback == 'function' ) {
2943                    callback.call( self, thumb );
2944                }
2945            },
2946
2947            onThumbLoad = function( thumb, callback ) {
2948
2949                // scale when ready
2950                thumb.scale({
2951                    width:    thumb.data.width,
2952                    height:   thumb.data.height,
2953                    crop:     o.thumbCrop,
2954                    margin:   o.thumbMargin,
2955                    canvas:   o.useCanvas,
2956                    complete: function( thumb ) {
2957
2958                        // shrink thumbnails to fit
2959                        var top = ['left', 'top'],
2960                            arr = ['Width', 'Height'],
2961                            m,
2962                            css,
2963                            data = self.getData( thumb.index ),
2964                            special = data.thumb.split(':');
2965
2966                        // calculate shrinked positions
2967                        $.each(arr, function( i, measure ) {
2968                            m = measure.toLowerCase();
2969                            if ( (o.thumbCrop !== true || o.thumbCrop === m ) && o.thumbFit ) {
2970                                css = {};
2971                                css[ m ] = thumb[ m ];
2972                                $( thumb.container ).css( css );
2973                                css = {};
2974                                css[ top[ i ] ] = 0;
2975                                $( thumb.image ).css( css );
2976                            }
2977
2978                            // cache outer measures
2979                            thumb[ 'outer' + measure ] = $( thumb.container )[ 'outer' + measure ]( true );
2980                        });
2981
2982                        // set high quality if downscale is moderate
2983                        Utils.toggleQuality( thumb.image,
2984                            o.thumbQuality === true ||
2985                            ( o.thumbQuality === 'auto' && thumb.original.width < thumb.width * 3 )
2986                        );
2987
2988                        // get "special" thumbs from provider
2989                        if( data.iframe && special.length == 2 && special[0] in _video ) {
2990
2991                            _video[ special[0] ].getThumb( special[1], (function(img) {
2992                                return function(src) {
2993                                    img.src = src;
2994                                    thumbComplete( thumb, callback );
2995                                };
2996                            }( thumb.image ) ));
2997
2998                        } else if ( o.thumbDisplayOrder && !thumb.lazy ) {
2999
3000                            $.each( thumbchunk, function( i, th ) {
3001                                if ( i === loadindex && th.ready && !th.displayed ) {
3002
3003                                    loadindex++;
3004                                    th.displayed = true;
3005
3006                                    thumbComplete( th, callback );
3007
3008                                    return;
3009                                }
3010                            });
3011                        } else {
3012                            thumbComplete( thumb, callback );
3013                        }
3014                    }
3015                });
3016            };
3017
3018        if ( !push ) {
3019            this._thumbnails = [];
3020            this.$( 'thumbnails' ).empty();
3021        }
3022
3023        // loop through data and create thumbnails
3024        for( ; this._data[ i ]; i++ ) {
3025
3026            data = this._data[ i ];
3027
3028            // get source from thumb or image
3029            src = data.thumb || data.image;
3030
3031            if ( ( o.thumbnails === true || optval == 'lazy' ) && ( data.thumb || data.image ) ) {
3032
3033                // add a new Picture instance
3034                thumb = new Galleria.Picture(i);
3035
3036                // save the index
3037                thumb.index = i;
3038
3039                // flag displayed
3040                thumb.displayed = false;
3041
3042                // flag lazy
3043                thumb.lazy = false;
3044
3045                // flag video
3046                thumb.video = false;
3047
3048                // append the thumbnail
3049                this.$( 'thumbnails' ).append( thumb.container );
3050
3051                // cache the container
3052                $container = $( thumb.container );
3053
3054                // hide it
3055                $container.css( 'visibility', 'hidden' );
3056
3057                thumb.data = {
3058                    width  : Utils.parseValue( getStyle( 'width' ) ),
3059                    height : Utils.parseValue( getStyle( 'height' ) ),
3060                    order  : i,
3061                    src    : src
3062                };
3063
3064                // grab & reset size for smoother thumbnail loads
3065                if ( o.thumbFit && o.thumbCrop !== true ) {
3066                    $container.css( { width: 'auto', height: 'auto' } );
3067                } else {
3068                    $container.css( { width: thumb.data.width, height: thumb.data.height } );
3069                }
3070
3071                // load the thumbnail
3072                special = src.split(':');
3073
3074                if ( special.length == 2 && special[0] in _video ) {
3075
3076                    thumb.video = true;
3077                    thumb.ready = true;
3078
3079                    thumb.load( gif, {
3080                        height: thumb.data.height,
3081                        width: thumb.data.height*1.25
3082                    }, onThumbLoad);
3083
3084                } else if ( optval == 'lazy' ) {
3085
3086                    $container.addClass( 'lazy' );
3087
3088                    thumb.lazy = true;
3089
3090                    thumb.load( gif, {
3091                        height: thumb.data.height,
3092                        width: thumb.data.width
3093                    });
3094
3095                } else {
3096                    thumb.load( src, onThumbLoad );
3097                }
3098
3099                // preload all images here
3100                if ( o.preload === 'all' ) {
3101                    thumb.preload( data.image );
3102                }
3103
3104            // create empty spans if thumbnails is set to 'empty'
3105            } else if ( data.iframe || optval === 'empty' || optval === 'numbers' ) {
3106
3107                thumb = {
3108                    container:  Utils.create( 'galleria-image' ),
3109                    image: Utils.create( 'img', 'span' ),
3110                    ready: true
3111                };
3112
3113                // create numbered thumbnails
3114                if ( optval === 'numbers' ) {
3115                    $( thumb.image ).text( i + 1 );
3116                }
3117
3118                if ( data.iframe ) {
3119                    $( thumb.image ).addClass( 'iframe' );
3120                }
3121
3122                this.$( 'thumbnails' ).append( thumb.container );
3123
3124                // we need to "fake" a loading delay before we append and trigger
3125                // 50+ should be enough
3126
3127                window.setTimeout( ( fake )( thumb.image, i, thumb.container ), 50 + ( i*20 ) );
3128
3129            // create null object to silent errors
3130            } else {
3131                thumb = {
3132                    container: null,
3133                    image: null
3134                };
3135            }
3136
3137            // add events for thumbnails
3138            // you can control the event type using thumb_event_type
3139            // we'll add the same event to the source if it's kept
3140
3141            $( thumb.container ).add( o.keepSource && o.linkSourceImages ? data.original : null )
3142                .data('index', i).bind( o.thumbEventType, onThumbEvent )
3143                .data('thumbload', onThumbLoad);
3144
3145            if (active === src) {
3146                $( thumb.container ).addClass( 'active' );
3147            }
3148
3149            this._thumbnails.push( thumb );
3150        }
3151
3152        thumbchunk = this._thumbnails.slice( chunk );
3153
3154        return this;
3155    },
3156
3157    /**
3158        Lazy-loads thumbnails.
3159        You can call this method to load lazy thumbnails at run time
3160
3161        @param {Array|Number} index Index or array of indexes of thumbnails to be loaded
3162        @param {Function} complete Callback that is called when all lazy thumbnails have been loaded
3163
3164        @returns Instance
3165    */
3166
3167    lazyLoad: function( index, complete ) {
3168
3169        var arr = index.constructor == Array ? index : [ index ],
3170            self = this,
3171            thumbnails = this.$( 'thumbnails' ).children().filter(function() {
3172                return $(this).data('lazy-src');
3173            }),
3174            loaded = 0;
3175
3176        $.each( arr, function(i, ind) {
3177
3178            if ( ind > self._thumbnails.length - 1 ) {
3179                return;
3180            }
3181
3182            var thumb = self._thumbnails[ ind ],
3183                data = thumb.data,
3184                special = data.src.split(':'),
3185                callback = function() {
3186                    if ( ++loaded == arr.length && typeof complete == 'function' ) {
3187                        complete.call( self );
3188                    }
3189                },
3190                thumbload = $( thumb.container ).data( 'thumbload' );
3191            if ( thumb.video ) {
3192                thumbload.call( self, thumb, callback );
3193            } else {
3194                thumb.load( data.src , function( thumb ) {
3195                    thumbload.call( self, thumb, callback );
3196                });
3197            }
3198        });
3199
3200        return this;
3201
3202    },
3203
3204    /**
3205        Lazy-loads thumbnails in chunks.
3206        This method automatcally chops up the loading process of many thumbnails into chunks
3207
3208        @param {Number} size Size of each chunk to be loaded
3209        @param {Number} [delay] Delay between each loads
3210
3211        @returns Instance
3212    */
3213
3214    lazyLoadChunks: function( size, delay ) {
3215
3216        var len = this.getDataLength(),
3217            i = 0,
3218            n = 0,
3219            arr = [],
3220            temp = [],
3221            self = this;
3222
vendor: 6,369 bytes, lines 3223-3428
3223        delay = delay || 0;
3224
3225        for( ; i<len; i++ ) {
3226            temp.push(i);
3227            if ( ++n == size || i == len-1 ) {
3228                arr.push( temp );
3229                n = 0;
3230                temp = [];
3231            }
3232        }
3233
3234        var init = function( wait ) {
3235            var a = arr.shift();
3236            if ( a ) {
3237                window.setTimeout(function() {
3238                    self.lazyLoad(a, function() {
3239                        init( true );
3240                    });
3241                }, ( delay && wait ) ? delay : 0 );
3242            }
3243        };
3244
3245        init( false );
3246
3247        return this;
3248
3249    },
3250
3251    // the internal _run method should be called after loading data into galleria
3252    // makes sure the gallery has proper measurements before postrun & ready
3253    _run : function() {
3254
3255        var self = this;
3256
3257        self._createThumbnails();
3258
3259        // make sure we have a stageHeight && stageWidth
3260
3261        Utils.wait({
3262
3263            timeout: 10000,
3264
3265            until: function() {
3266
3267                // Opera crap
3268                if ( Galleria.OPERA ) {
3269                    self.$( 'stage' ).css( 'display', 'inline-block' );
3270                }
3271
3272                self._stageWidth  = self.$( 'stage' ).width();
3273                self._stageHeight = self.$( 'stage' ).height();
3274
3275                return( self._stageWidth &&
3276                        self._stageHeight > 50 ); // what is an acceptable height?
3277            },
3278
3279            success: function() {
3280
3281                // save the instance
3282                _galleries.push( self );
3283
3284                // postrun some stuff after the gallery is ready
3285
3286                // show counter
3287                Utils.show( self.get('counter') );
3288
3289                // bind carousel nav
3290                if ( self._options.carousel ) {
3291                    self._carousel.bindControls();
3292                }
3293
3294                // start autoplay
3295                if ( self._options.autoplay ) {
3296
3297                    self.pause();
3298
3299                    if ( typeof self._options.autoplay === 'number' ) {
3300                        self._playtime = self._options.autoplay;
3301                    }
3302
3303                    self._playing = true;
3304                }
3305                // if second load, just do the show and return
3306                if ( self._firstrun ) {
3307
3308                    if ( self._options.autoplay ) {
3309                        self.trigger( Galleria.PLAY );
3310                    }
3311
3312                    if ( typeof self._options.show === 'number' ) {
3313                        self.show( self._options.show );
3314                    }
3315                    return;
3316                }
3317
3318                self._firstrun = true;
3319
3320                // initialize the History plugin
3321                if ( Galleria.History ) {
3322
3323                    // bind the show method
3324                    Galleria.History.change(function( value ) {
3325
3326                        // if ID is NaN, the user pressed back from the first image
3327                        // return to previous address
3328                        if ( isNaN( value ) ) {
3329                            window.history.go(-1);
3330
3331                        // else show the image
3332                        } else {
3333                            self.show( value, undef, true );
3334                        }
3335                    });
3336                }
3337
3338                self.trigger( Galleria.READY );
3339
3340                // call the theme init method
3341                Galleria.theme.init.call( self, self._options );
3342
3343                // Trigger Galleria.ready
3344                $.each( Galleria.ready.callbacks, function(i ,fn) {
3345                    if ( typeof fn == 'function' ) {
3346                        fn.call( self, self._options );
3347                    }
3348                });
3349
3350                // call the extend option
3351                self._options.extend.call( self, self._options );
3352
3353                // show the initial image
3354                // first test for permalinks in history
3355                if ( /^[0-9]{1,4}$/.test( HASH ) && Galleria.History ) {
3356                    self.show( HASH, undef, true );
3357
3358                } else if( self._data[ self._options.show ] ) {
3359                    self.show( self._options.show );
3360                }
3361
3362                // play trigger
3363                if ( self._options.autoplay ) {
3364                    self.trigger( Galleria.PLAY );
3365                }
3366            },
3367
3368            error: function() {
3369                Galleria.raise('Stage width or height is too small to show the gallery. Traced measures: width:' + self._stageWidth + 'px, height: ' + self._stageHeight + 'px.', true);
3370            }
3371
3372        });
3373    },
3374
3375    /**
3376        Loads data into the gallery.
3377        You can call this method on an existing gallery to reload the gallery with new data.
3378
3379        @param {Array|string} [source] Optional JSON array of data or selector of where to find data in the document.
3380        Defaults to the Galleria target or dataSource option.
3381
3382        @param {string} [selector] Optional element selector of what elements to parse.
3383        Defaults to 'img'.
3384
3385        @param {Function} [config] Optional function to modify the data extraction proceedure from the selector.
3386        See the dataConfig option for more information.
3387
3388        @returns Instance
3389    */
3390
3391    load : function( source, selector, config ) {
3392
3393        var self = this,
3394            o = this._options;
3395
3396        // empty the data array
3397        this._data = [];
3398
3399        // empty the thumbnails
3400        this._thumbnails = [];
3401        this.$('thumbnails').empty();
3402
3403        // shorten the arguments
3404        if ( typeof selector === 'function' ) {
3405            config = selector;
3406            selector = null;
3407        }
3408
3409        // use the source set by target
3410        source = source || o.dataSource;
3411
3412        // use selector set by option
3413        selector = selector || o.dataSelector;
3414
3415        // use the dataConfig set by option
3416        config = config || o.dataConfig;
3417
3418        // if source is a true object, make it into an array
3419        if( /^function Object/.test( source.constructor ) ) {
3420            source = [source];
3421        }
3422
3423        // check if the data is an array already
3424        if ( source.constructor === Array ) {
3425            if ( this.validate( source ) ) {
3426                this._data = source;
3427            } else {
3428                Galleria.raise( 'Load failed: JSON Array not val
3428id.' );
3429            }
3430        } else {
3431
3432            // add .video and .iframe to the selector (1.2.7)
3433            selector += ',.video,.iframe';
3434
3435            // loop through images and set data
3436            $( source ).find( selector ).each( function( i, elem ) {
3437
3438                elem = $( elem );
3439                var data = {},
3440                    parent = elem.parent(),
3441                    href = parent.attr( 'href' ),
3442                    rel  = parent.attr( 'rel' );
3443
3444                if( href && ( elem[0].nodeName == 'IMG' || elem.hasClass('video') ) && _videoTest( href ) ) {
3445                    data.video = href;
3446                } else if( href && elem.hasClass('iframe') ) {
3447                    data.iframe = href;
3448                } else {
3449                    data.image = data.big = href;
3450                }
3451
3452                if ( rel ) {
3453                    data.big = rel;
3454                }
3455
3456                // alternative extraction from HTML5 data attribute, added in 1.2.7
3457                $.each( 'big title description link layer'.split(' '), function( i, val ) {
3458                    if ( elem.data(val) ) {
3459                        data[ val ] = elem.data(val);
3460                    }
3461                });
3462
3463                // mix default extractions with the hrefs and config
3464                // and push it into the data array
3465                self._data.push( $.extend({
3466
3467                    title:       elem.attr('title') || '',
3468                    thumb:       elem.attr('src'),
3469                    image:       elem.attr('src'),
3470                    big:         elem.attr('src'),
3471                    description: elem.attr('alt') || '',
3472                    link:        elem.attr('longdesc'),
3473                    original:    elem.get(0) // saved as a reference
3474
3475                }, data, config( elem ) ) );
3476
3477            });
3478        }
3479
3480        if ( typeof o.dataSort == 'function' ) {
3481            protoArray.sort.call( this._data, o.dataSort );
3482        } else if ( o.dataSort == 'random' ) {
3483            this._data.sort( function() {
3484                return Math.round(Math.random())-0.5;
3485            });
3486        }
3487
3488
3489
3490        // trigger the DATA event and return
3491        if ( this.getDataLength() ) {
3492            this._parseData().trigger( Galleria.DATA );
3493        }
3494        return this;
3495
3496    },
3497
3498    // make sure the data works properly
3499    _parseData : function() {
3500
3501        var self = this,
3502            current;
3503
3504        $.each( this._data, function( i, data ) {
3505
3506            current = self._data[ i ];
3507
3508            // copy image as thumb if no thumb exists
3509            if ( 'thumb' in data === false ) {
3510                current.thumb = data.image;
3511            }
3512            // copy image as big image if no biggie exists
3513            if ( !'big' in data ) {
3514                current.big = data.image;
3515            }
3516            // parse video
3517            if ( 'video' in data ) {
3518                var result = _videoTest( data.video );
3519
3520                if ( result ) {
3521                    current.iframe = _video[ result.provider ].embed( result.id ) + (function() {
3522
3523                        // add options
3524                        if ( typeof self._options[ result.provider ] == 'object' ) {
3525                            var str = '?', arr = [];
3526                            $.each( self._options[ result.provider ], function( key, val ) {
3527                                arr.push( key + '=' + val );
3528                            });
3529
3530                            // small youtube specifics, perhaps move to _video later
3531                            if ( result.provider == 'youtube' ) {
3532                                arr = ['wmode=opaque'].concat(arr);
3533                            }
3534                            return str + arr.join('&');
3535                        }
3536                        return '';
3537                    }());
3538                    delete current.video;
3539                    if( !('thumb' in current) || !current.thumb ) {
3540                        current.thumb = result.provider+':'+result.id;
3541                    }
3542                }
3543            }
3544        });
3545
3546        return this;
3547    },
3548
3549    /**
3550        Destroy the Galleria instance and recover the original content
3551
3552        @example this.destroy();
3553
3554        @returns Instance
3555    */
3556
3557    destroy : function() {
3558        this.get('target').innerHTML = this._original.html;
3559        this.clearTimer();
3560        return this;
3561    },
3562
3563    /**
vendor: 17,686 bytes, lines 3564-4208
3564        Adds and/or removes images from the gallery
3565        Works just like Array.splice
3566        https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice
3567
3568        @example this.splice( 2, 4 ); // removes 4 images after the second image
3569
3570        @returns Instance
3571    */
3572
3573    splice : function() {
3574        var self = this,
3575            args = Utils.array( arguments );
3576        window.setTimeout(function() {
3577            protoArray.splice.apply( self._data, args );
3578            self._parseData()._createThumbnails();
3579        },2);
3580        return self;
3581    },
3582
3583    /**
3584        Append images to the gallery
3585        Works just like Array.push
3586        https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push
3587
3588        @example this.push({ image: 'image1.jpg' }); // appends the image to the gallery
3589
3590        @returns Instance
3591    */
3592
3593    push : function() {
3594        var self = this,
3595            args = Utils.array( arguments );
3596
3597        if ( args.length == 1 && args[0].constructor == Array ) {
3598            args = args[0];
3599        }
3600
3601        window.setTimeout(function() {
3602            protoArray.push.apply( self._data, args );
3603            self._parseData()._createThumbnails( args );
3604        },2);
3605        return self;
3606    },
3607
3608    _getActive: function() {
3609        return this._controls.getActive();
3610    },
3611
3612    validate : function( data ) {
3613        // todo: validate a custom data array
3614        return true;
3615    },
3616
3617    /**
3618        Bind any event to Galleria
3619
3620        @param {string} type The Event type to listen for
3621        @param {Function} fn The function to execute when the event is triggered
3622
3623        @example this.bind( 'image', function() { Galleria.log('image shown') });
3624
3625        @returns Instance
3626    */
3627
3628    bind : function(type, fn) {
3629
3630        // allow 'image' instead of Galleria.IMAGE
3631        type = _patchEvent( type );
3632
3633        this.$( 'container' ).bind( type, this.proxy(fn) );
3634        return this;
3635    },
3636
3637    /**
3638        Unbind any event to Galleria
3639
3640        @param {string} type The Event type to forget
3641
3642        @returns Instance
3643    */
3644
3645    unbind : function(type) {
3646
3647        type = _patchEvent( type );
3648
3649        this.$( 'container' ).unbind( type );
3650        return this;
3651    },
3652
3653    /**
3654        Manually trigger a Galleria event
3655
3656        @param {string} type The Event to trigger
3657
3658        @returns Instance
3659    */
3660
3661    trigger : function( type ) {
3662
3663        type = typeof type === 'object' ?
3664            $.extend( type, { scope: this } ) :
3665            { type: _patchEvent( type ), scope: this };
3666
3667        this.$( 'container' ).trigger( type );
3668
3669        return this;
3670    },
3671
3672    /**
3673        Assign an "idle state" to any element.
3674        The idle state will be applied after a certain amount of idle time
3675        Useful to hide f.ex navigation when the gallery is inactive
3676
3677        @param {HTMLElement|string} elem The Dom node or selector to apply the idle state to
3678        @param {Object} styles the CSS styles to apply when in idle mode
3679        @param {Object} [from] the CSS styles to apply when in normal
3680        @param {Boolean} [hide] set to true if you want to hide it first
3681
3682        @example addIdleState( this.get('image-nav'), { opacity: 0 });
3683        @example addIdleState( '.galleria-image-nav', { top: -200 }, true);
3684
3685        @returns Instance
3686    */
3687
3688    addIdleState: function( elem, styles, from, hide ) {
3689        this._idle.add.apply( this._idle, Utils.array( arguments ) );
3690        return this;
3691    },
3692
3693    /**
3694        Removes any idle state previously set using addIdleState()
3695
3696        @param {HTMLElement|string} elem The Dom node or selector to remove the idle state from.
3697
3698        @returns Instance
3699    */
3700
3701    removeIdleState: function( elem ) {
3702        this._idle.remove.apply( this._idle, Utils.array( arguments ) );
3703        return this;
3704    },
3705
3706    /**
3707        Force Galleria to enter idle mode.
3708
3709        @returns Instance
3710    */
3711
3712    enterIdleMode: function() {
3713        this._idle.hide();
3714        return this;
3715    },
3716
3717    /**
3718        Force Galleria to exit idle mode.
3719
3720        @returns Instance
3721    */
3722
3723    exitIdleMode: function() {
3724        this._idle.showAll();
3725        return this;
3726    },
3727
3728    /**
3729        Enter FullScreen mode
3730
3731        @param {Function} callback the function to be executed when the fullscreen mode is fully applied.
3732
3733        @returns Instance
3734    */
3735
3736    enterFullscreen: function( callback ) {
3737        this._fullscreen.enter.apply( this, Utils.array( arguments ) );
3738        return this;
3739    },
3740
3741    /**
3742        Exits FullScreen mode
3743
3744        @param {Function} callback the function to be executed when the fullscreen mode is fully applied.
3745
3746        @returns Instance
3747    */
3748
3749    exitFullscreen: function( callback ) {
3750        this._fullscreen.exit.apply( this, Utils.array( arguments ) );
3751        return this;
3752    },
3753
3754    /**
3755        Toggle FullScreen mode
3756
3757        @param {Function} callback the function to be executed when the fullscreen mode is fully applied or removed.
3758
3759        @returns Instance
3760    */
3761
3762    toggleFullscreen: function( callback ) {
3763        this._fullscreen[ this.isFullscreen() ? 'exit' : 'enter'].apply( this, Utils.array( arguments ) );
3764        return this;
3765    },
3766
3767    /**
3768        Adds a tooltip to any element.
3769        You can also call this method with an object as argument with elemID:value pairs to apply tooltips to (see examples)
3770
3771        @param {HTMLElement} elem The DOM Node to attach the event to
3772        @param {string|Function} value The tooltip message. Can also be a function that returns a string.
3773
3774        @example this.bindTooltip( this.get('thumbnails'), 'My thumbnails');
3775        @example this.bindTooltip( this.get('thumbnails'), function() { return 'My thumbs' });
3776        @example this.bindTooltip( { image_nav: 'Navigation' });
3777
3778        @returns Instance
3779    */
3780
3781    bindTooltip: function( elem, value ) {
3782        this._tooltip.bind.apply( this._tooltip, Utils.array(arguments) );
3783        return this;
3784    },
3785
3786    /**
3787        Note: this method is deprecated. Use refreshTooltip() instead.
3788
3789        Redefine a tooltip.
3790        Use this if you want to re-apply a tooltip value to an already bound tooltip element.
3791
3792        @param {HTMLElement} elem The DOM Node to attach the event to
3793        @param {string|Function} value The tooltip message. Can also be a function that returns a string.
3794
3795        @returns Instance
3796    */
3797
3798    defineTooltip: function( elem, value ) {
3799        this._tooltip.define.apply( this._tooltip, Utils.array(arguments) );
3800        return this;
3801    },
3802
3803    /**
3804        Refresh a tooltip value.
3805        Use this if you want to change the tooltip value at runtime, f.ex if you have a play/pause toggle.
3806
3807        @param {HTMLElement} elem The DOM Node that has a tooltip that should be refreshed
3808
3809        @returns Instance
3810    */
3811
3812    refreshTooltip: function( elem ) {
3813        this._tooltip.show.apply( this._tooltip, Utils.array(arguments) );
3814        return this;
3815    },
3816
3817    /**
3818        Open a pre-designed lightbox with the currently active image.
3819        You can control some visuals using gallery options.
3820
3821        @returns Instance
3822    */
3823
3824    openLightbox: function() {
3825        this._lightbox.show.apply( this._lightbox, Utils.array( arguments ) );
3826        return this;
3827    },
3828
3829    /**
3830        Close the lightbox.
3831
3832        @returns Instance
3833    */
3834
3835    closeLightbox: function() {
3836        this._lightbox.hide.apply( this._lightbox, Utils.array( arguments ) );
3837        return this;
3838    },
3839
3840    /**
3841        Get the currently active image element.
3842
3843        @returns {HTMLElement} The image element
3844    */
3845
3846    getActiveImage: function() {
3847        return this._getActive().image || undef;
3848    },
3849
3850    /**
3851        Get the currently active thumbnail element.
3852
3853        @returns {HTMLElement} The thumbnail element
3854    */
3855
3856    getActiveThumb: function() {
3857        return this._thumbnails[ this._active ].image || undef;
3858    },
3859
3860    /**
3861        Get the mouse position relative to the gallery container
3862
3863        @param e The mouse event
3864
3865        @example
3866
3867var gallery = this;
3868$(document).mousemove(function(e) {
3869    console.log( gallery.getMousePosition(e).x );
3870});
3871
3872        @returns {Object} Object with x & y of the relative mouse postion
3873    */
3874
3875    getMousePosition : function(e) {
3876        return {
3877            x: e.pageX - this.$( 'container' ).offset().left,
3878            y: e.pageY - this.$( 'container' ).offset().top
3879        };
3880    },
3881
3882    /**
3883        Adds a panning effect to the image
3884
3885        @param [img] The optional image element. If not specified it takes the currently active image
3886
3887        @returns Instance
3888    */
3889
3890    addPan : function( img ) {
3891
3892        if ( this._options.imageCrop === false ) {
3893            return;
3894        }
3895
3896        img = $( img || this.getActiveImage() );
3897
3898        // define some variables and methods
3899        var self   = this,
3900            x      = img.width() / 2,
3901            y      = img.height() / 2,
3902            destX  = parseInt( img.css( 'left' ), 10 ),
3903            destY  = parseInt( img.css( 'top' ), 10 ),
3904            curX   = destX || 0,
3905            curY   = destY || 0,
3906            distX  = 0,
3907            distY  = 0,
3908            active = false,
3909            ts     = Utils.timestamp(),
3910            cache  = 0,
3911            move   = 0,
3912
3913            // positions the image
3914            position = function( dist, cur, pos ) {
3915                if ( dist > 0 ) {
3916                    move = Math.round( Math.max( dist * -1, Math.min( 0, cur ) ) );
3917                    if ( cache !== move ) {
3918
3919                        cache = move;
3920
3921                        if ( IE === 8 ) { // scroll is faster for IE
3922                            img.parent()[ 'scroll' + pos ]( move * -1 );
3923                        } else {
3924                            var css = {};
3925                            css[ pos.toLowerCase() ] = move;
3926                            img.css(css);
3927                        }
3928                    }
3929                }
3930            },
3931
3932            // calculates mouse position after 50ms
3933            calculate = function(e) {
3934                if (Utils.timestamp() - ts < 50) {
3935                    return;
3936                }
3937                active = true;
3938                x = self.getMousePosition(e).x;
3939                y = self.getMousePosition(e).y;
3940            },
3941
3942            // the main loop to check
3943            loop = function(e) {
3944
3945                if (!active) {
3946                    return;
3947                }
3948
3949                distX = img.width() - self._stageWidth;
3950                distY = img.height() - self._stageHeight;
3951                destX = x / self._stageWidth * distX * -1;
3952                destY = y / self._stageHeight * distY * -1;
3953                curX += ( destX - curX ) / self._options.imagePanSmoothness;
3954                curY += ( destY - curY ) / self._options.imagePanSmoothness;
3955
3956                position( distY, curY, 'Top' );
3957                position( distX, curX, 'Left' );
3958
3959            };
3960
3961        // we need to use scroll in IE8 to speed things up
3962        if ( IE === 8 ) {
3963
3964            img.parent().scrollTop( curY * -1 ).scrollLeft( curX * -1 );
3965            img.css({
3966                top: 0,
3967                left: 0
3968            });
3969
3970        }
3971
3972        // unbind and bind event
3973        this.$( 'stage' ).unbind( 'mousemove', calculate ).bind( 'mousemove', calculate );
3974
3975        // loop the loop
3976        this.addTimer( 'pan' + self._id, loop, 50, true);
3977
3978        return this;
3979    },
3980
3981    /**
3982        Brings the scope into any callback
3983
3984        @param fn The callback to bring the scope into
3985        @param [scope] Optional scope to bring
3986
3987        @example $('#fullscreen').click( this.proxy(function() { this.enterFullscreen(); }) )
3988
3989        @returns {Function} Return the callback with the gallery scope
3990    */
3991
3992    proxy : function( fn, scope ) {
3993        if ( typeof fn !== 'function' ) {
3994            return F;
3995        }
3996        scope = scope || this;
3997        return function() {
3998            return fn.apply( scope, Utils.array( arguments ) );
3999        };
4000    },
4001
4002    /**
4003        Removes the panning effect set by addPan()
4004
4005        @returns Instance
4006    */
4007
4008    removePan: function() {
4009
4010        // todo: doublecheck IE8
4011
4012        this.$( 'stage' ).unbind( 'mousemove' );
4013
4014        this.clearTimer( 'pan' + this._id );
4015
4016        return this;
4017    },
4018
4019    /**
4020        Adds an element to the Galleria DOM array.
4021        When you add an element here, you can access it using element ID in many API calls
4022
4023        @param {string} id The element ID you wish to use. You can add many elements by adding more arguments.
4024
4025        @example addElement('mybutton');
4026        @example addElement('mybutton','mylink');
4027
4028        @returns Instance
4029    */
4030
4031    addElement : function( id ) {
4032
4033        var dom = this._dom;
4034
4035        $.each( Utils.array(arguments), function( i, blueprint ) {
4036           dom[ blueprint ] = Utils.create( 'galleria-' + blueprint );
4037        });
4038
4039        return this;
4040    },
4041
4042    /**
4043        Attach keyboard events to Galleria
4044
4045        @param {Object} map The map object of events.
4046        Possible keys are 'UP', 'DOWN', 'LEFT', 'RIGHT', 'RETURN', 'ESCAPE', 'BACKSPACE', and 'SPACE'.
4047
4048        @example
4049
4050this.attachKeyboard({
4051    right: this.next,
4052    left: this.prev,
4053    up: function() {
4054        console.log( 'up key pressed' )
4055    }
4056});
4057
4058        @returns Instance
4059    */
4060
4061    attachKeyboard : function( map ) {
4062        this._keyboard.attach.apply( this._keyboard, Utils.array( arguments ) );
4063        return this;
4064    },
4065
4066    /**
4067        Detach all keyboard events to Galleria
4068
4069        @returns Instance
4070    */
4071
4072    detachKeyboard : function() {
4073        this._keyboard.detach.apply( this._keyboard, Utils.array( arguments ) );
4074        return this;
4075    },
4076
4077    /**
4078        Fast helper for appending galleria elements that you added using addElement()
4079
4080        @param {string} parentID The parent element ID where the element will be appended
4081        @param {string} childID the element ID that should be appended
4082
4083        @example this.addElement('myElement');
4084        this.appendChild( 'info', 'myElement' );
4085
4086        @returns Instance
4087    */
4088
4089    appendChild : function( parentID, childID ) {
4090        this.$( parentID ).append( this.get( childID ) || childID );
4091        return this;
4092    },
4093
4094    /**
4095        Fast helper for prepending galleria elements that you added using addElement()
4096
4097        @param {string} parentID The parent element ID where the element will be prepended
4098        @param {string} childID the element ID that should be prepended
4099
4100        @example
4101
4102this.addElement('myElement');
4103this.prependChild( 'info', 'myElement' );
4104
4105        @returns Instance
4106    */
4107
4108    prependChild : function( parentID, childID ) {
4109        this.$( parentID ).prepend( this.get( childID ) || childID );
4110        return this;
4111    },
4112
4113    /**
4114        Remove an element by blueprint
4115
4116        @param {string} elemID The element to be removed.
4117        You can remove multiple elements by adding arguments.
4118
4119        @returns Instance
4120    */
4121
4122    remove : function( elemID ) {
4123        this.$( Utils.array( arguments ).join(',') ).remove();
4124        return this;
4125    },
4126
4127    // a fast helper for building dom structures
4128    // leave this out of the API for now
4129
4130    append : function( data ) {
4131        var i, j;
4132        for( i in data ) {
4133            if ( data.hasOwnProperty( i ) ) {
4134                if ( data[i].constructor === Array ) {
4135                    for( j = 0; data[i][j]; j++ ) {
4136                        this.appendChild( i, data[i][j] );
4137                    }
4138                } else {
4139                    this.appendChild( i, data[i] );
4140                }
4141            }
4142        }
4143        return this;
4144    },
4145
4146    // an internal helper for scaling according to options
4147    _scaleImage : function( image, options ) {
4148
4149        image = image || this._controls.getActive();
4150
4151        // janpub (JH) fix:
4152        // image might be unselected yet
4153        // e.g. when external logics rescales the gallery on window resize events
4154        if( !image ) {
4155            return;
4156        }
4157
4158        var self = this,
4159
4160            complete,
4161
4162            scaleLayer = function( img ) {
4163                $( img.container ).children(':first').css({
4164                    top: Math.max(0, Utils.parseValue( img.image.style.top )),
4165                    left: Math.max(0, Utils.parseValue( img.image.style.left )),
4166                    width: Utils.parseValue( img.image.width ),
4167                    height: Utils.parseValue( img.image.height )
4168                });
4169            };
4170
4171        options = $.extend({
4172            width:    this._stageWidth,
4173            height:   this._stageHeight,
4174            crop:     this._options.imageCrop,
4175            max:      this._options.maxScaleRatio,
4176            min:      this._options.minScaleRatio,
4177            margin:   this._options.imageMargin,
4178            position: this._options.imagePosition
4179        }, options );
4180
4181        if ( this._options.layerFollow && this._options.imageCrop !== true ) {
4182
4183            if ( typeof options.complete == 'function' ) {
4184                complete = options.complete;
4185                options.complete = function() {
4186                    complete.call( image, image );
4187                    scaleLayer( image );
4188                };
4189            } else {
4190                options.complete = scaleLayer;
4191            }
4192
4193        } else {
4194            $( image.container ).children(':first').css({ top: 0, left: 0 });
4195        }
4196
4197        image.scale( options );
4198        return this;
4199    },
4200
4201    /**
4202        Updates the carousel,
4203        useful if you resize the gallery and want to re-check if the carousel nav is needed.
4204
4205        @returns Instance
4206    */
4207
4208    updateCarousel : function() {
4209        this._carousel.update();
4210        return this;
4211    },
4212
4213    /**
4214        Resize the entire gallery container
4215
4216        @param {Object} [measures] Optional object with width/height specified
4217        @param {Function} [complete] The callback to be called when the scaling is complete
4218
4219        @returns Instance
4220    */
4221
4222    resize : function( measures, complete ) {
4223
4224        if ( typeof measures == 'function' ) {
4225            complete = measures;
4226            measures = undef;
4227        }
4228
4229        measures = $.extend( { width:0, height:0 }, measures );
4230
4231        var self = this,
4232            $container = this.$( 'container' );
4233
4234        $.each( measures, function( m, val ) {
4235            if ( !val ) {
4236                $container[ m ]( 'auto' );
4237                measures[ m ] = self._getWH()[ m ];
4238            }
4239        });
4240
4241        $.each( measures, function( m, val ) {
4242            $container[ m ]( val );
4243        });
4244
4245        return this.rescale( complete );
4246
4247    },
4248
4249    /**
4250        Rescales the gallery
4251
4252        @param {number} width The target width
4253        @param {number} height The target height
4254        @param {Function} complete The callback to be called when the scaling is complete
4255
4256        @returns Instance
4257    */
4258
4259    rescale : function( width, height, complete ) {
4260
4261        var self = this;
4262
4263        // allow rescale(fn)
4264        if ( typeof width === 'function' ) {
4265            complete = width;
4266            width = undef;
4267        }
4268
4269        var scale = function() {
4270
4271            // set stagewidth
4272            self._stageWidth = width || self.$( 'stage' ).width();
4273            self._stageHeight = height || self.$( 'stage' ).height();
4274
4275            // scale the active image
4276            self._scaleImage();
4277
4278            if ( self._options.carousel ) {
4279                self.updateCarousel();
4280            }
4281
4282            self.trigger( Galleria.RESCALE );
4283
4284            if ( typeof complete === 'function' ) {
4285                complete.call( self );
4286            }
4287        };
4288
4289        scale.call( self );
4290
4291        return this;
4292    },
4293
4294    /**
4295        Refreshes the gallery.
4296        Useful if you change image options at runtime and want to apply the changes to the active image.
4297
4298        @returns Instance
4299    */
4300
4301    refreshImage : function() {
4302        this._scaleImage();
4303        if ( this._options.imagePan ) {
4304            this.addPan();
4305        }
4306        return this;
4307    },
4308
4309    /**
4310        Shows an image by index
4311
4312        @param {number|boolean} index The index to show
4313        @param {Boolean} rewind A boolean that should be true if you want the transition to go back
4314
4315        @returns Instance
4316    */
4317
4318    show : function( index, rewind, _history ) {
4319
4320        // do nothing if index is false or queue is false and transition is in progress
4321        if ( index === false || ( !this._options.queue && this._queue.stalled ) ) {
4322            return;
4323        }
4324
4325        index = Math.max( 0, Math.min( parseInt( index, 10 ), this.getDataLength() - 1 ) );
4326
4327        rewind = typeof rewind !== 'undefined' ? !!rewind : index < this.getIndex();
4328
4329        _history = _history || false;
4330
4331        // do the history thing and return
4332        if ( !_history && Galleria.History ) {
4333            Galleria.History.set( index.toString() );
4334            return;
4335        }
4336
4337        this._active = index;
4338
4339        protoArray.push.call( this._queue, {
4340            index : index,
4341            rewind : rewind
4342        });
4343        if ( !this._queue.stalled ) {
4344            this._show();
4345        }
4346
4347        return this;
4348    },
4349
4350    // the internal _show method does the actual showing
4351    _show : function() {
4352
4353        // shortcuts
4354        var self = this,
4355            queue = this._queue[ 0 ],
4356            data = this.getData( queue.index );
4357
4358        if ( !data ) {
4359            return;
4360        }
4361
4362        var src = data.iframe || ( this.isFullscreen() && 'big' in data ? data.big : data.image ), // use big image if fullscreen mode
4363            active = this._controls.getActive(),
4364            next = this._controls.getNext(),
4365            cached = next.isCached( src ),
4366            thumb = this._thumbnails[ queue.index ],
4367            mousetrigger = function() {
4368                $( next.image ).trigger( 'mouseup' );
4369            };
4370
4371        // to be fired when loading & transition is complete:
4372        var complete = (function( data, next, active, queue, thumb ) {
4373
4374            return function() {
4375
4376                var win;
4377
4378                _transitions.active = false;
4379
4380                // remove stalled
4381                self._queue.stalled = false;
4382
4383                // optimize quality
4384                Utils.toggleQuality( next.image, self._options.imageQuality );
4385
4386                // remove old layer
4387                self._layers[ self._controls.active ].innerHTML = '';
4388
4389                // swap
4390                $( active.container ).css({
4391                    zIndex: 0,
4392                    opacity: 0
4393                }).show();
4394
4395                if( active.isIframe ) {
4396                    $( active.container ).find( 'iframe' ).remove();
4397                }
4398
4399                self.$('container').toggleClass('iframe', !!data.iframe);
4400
4401                $( next.container ).css({
4402                    zIndex: 1,
4403                    left: 0,
4404                    top: 0
4405                }).show();
4406
4407                self._controls.swap();
4408
4409                // add pan according to option
4410                if ( self._options.imagePan ) {
4411                    self.addPan( next.image );
4412                }
4413
4414                // make the image link or add lightbox
4415                // link takes precedence over lightbox if both are detected
4416				if(self._options.imageClickAction)
4417                {
4418					if ( data.link || self._options.lightbox || self._options.clicknext ) {
4419
4420						$( next.image ).css({
4421							cursor: 'pointer'
4422						}).bind( 'mouseup', function() {
4423
4424							// clicknext
4425							if ( self._options.clicknext && !Galleria.TOUCH ) {
4426								if ( self._options.pauseOnInteraction ) {
4427									self.pause();
4428								}
4429								self.next();
4430								return;
4431							}
4432
4433							// popup link
4434							if ( data.link ) {
4435								if ( self._options.popupLinks ) {
4436									win = window.open( data.link, '_blank' );
4437								} else {
4438									window.location.href = data.link;
4439								}
4440								return;
4441							}
4442
4443							if ( self._options.lightbox ) {
4444								self.openLightbox();
4445							}
4446
4447						});
4448					}
4449				}
4450
4451                // remove the queued image
4452                protoArray.shift.call( self._queue );
4453
4454                // if we still have images in the queue, show it
4455                if ( self._queue.length ) {
4456                    self._show();
4457                }
4458
4459                // check if we are playing
4460                self._playCheck();
4461
4462                // trigger IMAGE event
4463                self.trigger({
4464                    type: Galleria.IMAGE,
4465                    index: queue.index,
4466                    imageTarget: next.image,
4467                    thumbTarget: thumb.image,
4468                    galleriaData: data
4469                });
4470            };
4471        }( data, next, active, queue, thumb ));
4472
4473        // let the carousel follow
4474        if ( this._options.carousel && this._options.carouselFollow ) {
4475            this._carousel.follow( queue.index );
4476        }
4477
4478        // preload images
4479        if ( this._options.preload ) {
4480
4481            var p, i,
4482                n = this.getNext(),
4483                ndata;
4484
4485            try {
4486                for ( i = this._options.preload; i > 0; i-- ) {
4487                    p = new Galleria.Picture();
4488                    ndata = self.getData( n );
4489                    p.preload( this.isFullscreen() && 'big' in ndata ? ndata.big : ndata.image );
4490                    n = self.getNext( n );
4491                }
4492            } catch(e) {}
4493        }
4494
4495        // show the next image, just in case
4496        Utils.show( next.container );
4497
4498        next.isIframe = !!data.iframe;
4499
4500        // add active classes
4501        $( self._thumbnails[ queue.index ].container )
4502            .addClass( 'active' )
4503            .siblings( '.active' )
4504            .removeClass( 'active' );
4505
4506        // trigger the LOADSTART event
4507        self.trigger( {
4508            type: Galleria.LOADSTART,
4509            cached: cached,
4510            index: queue.index,
4511            rewind: queue.rewind,
4512            imageTarget: next.image,
4513            thumbTarget: thumb.image,
4514            galleriaData: data
4515        });
4516
4517        // begin loading the next image
4518        next.load( src, function( next ) {
4519
4520            // add layer HTML
4521            var layer = $( self._layers[ 1-self._controls.active ] ).html( data.layer || '' ).hide();
4522
4523            self._scaleImage( next, {
4524
4525                complete: function( next ) {
4526
4527                    // toggle low quality for IE
4528                    if ( 'image' in active ) {
4529                        Utils.toggleQuality( active.image, false );
4530                    }
4531                    Utils.toggleQuality( next.image, false );
4532
4533                    // stall the queue
4534                    self._queue.stalled = true;
4535
4536                    // remove the image panning, if applied
4537                    // TODO: rethink if this is necessary
4538                    self.removePan();
4539
4540                    // set the captions and counter
vendor: 10,705 bytes, lines 4541-4956
4541                    self.setInfo( queue.index );
4542                    self.setCounter( queue.index );
4543
4544                    // show the layer now
4545                    if ( data.layer ) {
4546                        layer.show();
4547                        // inherit click events set on image
4548                        if ( data.link || self._options.lightbox || self._options.clicknext ) {
4549                            layer.css( 'cursor', 'pointer' ).unbind( 'mouseup' ).mouseup( mousetrigger );
4550                        }
4551                    }
4552
4553                    // trigger the LOADFINISH event
4554                    self.trigger({
4555                        type: Galleria.LOADFINISH,
4556                        cached: cached,
4557                        index: queue.index,
4558                        rewind: queue.rewind,
4559                        imageTarget: next.image,
4560                        thumbTarget: self._thumbnails[ queue.index ].image,
4561                        galleriaData: self.getData( queue.index )
4562                    });
4563
4564                    var transition = self._options.transition;
4565
4566                    // can JavaScript loop through objects in order? yes.
4567                    $.each({
4568                        initial: active.image === null,
4569                        touch: Galleria.TOUCH,
4570                        fullscreen: self.isFullscreen()
4571                    }, function( type, arg ) {
4572                        if ( arg && self._options[ type + 'Transition' ] !== undef ) {
4573                            transition = self._options[ type + 'Transition' ];
4574                            return false;
4575                        }
4576                    });
4577
4578                    // validate the transition
4579                    if ( transition in _transitions.effects === false ) {
4580                        complete();
4581                    } else {
4582                        var params = {
4583                            prev: active.container,
4584                            next: next.container,
4585                            rewind: queue.rewind,
4586                            speed: self._options.transitionSpeed || 400
4587                        };
4588
4589                        _transitions.active = true;
4590
4591                        // call the transition function and send some stuff
4592                        _transitions.init.call( self, transition, params, complete );
4593
4594                    }
4595                }
4596            });
4597        });
4598    },
4599
4600    /**
4601        Gets the next index
4602
4603        @param {number} [base] Optional starting point
4604
4605        @returns {number} the next index, or the first if you are at the first (looping)
4606    */
4607
4608    getNext : function( base ) {
4609        base = typeof base === 'number' ? base : this.getIndex();
4610        return base === this.getDataLength() - 1 ? 0 : base + 1;
4611    },
4612
4613    /**
4614        Gets the previous index
4615
4616        @param {number} [base] Optional starting point
4617
4618        @returns {number} the previous index, or the last if you are at the first (looping)
4619    */
4620
4621    getPrev : function( base ) {
4622        base = typeof base === 'number' ? base : this.getIndex();
4623        return base === 0 ? this.getDataLength() - 1 : base - 1;
4624    },
4625
4626    /**
4627        Shows the next image in line
4628
4629        @returns Instance
4630    */
4631
4632    next : function() {
4633        if ( this.getDataLength() > 1 ) {
4634            this.show( this.getNext(), false );
4635        }
4636        return this;
4637    },
4638
4639    /**
4640        Shows the previous image in line
4641
4642        @returns Instance
4643    */
4644
4645    prev : function() {
4646        if ( this.getDataLength() > 1 ) {
4647            this.show( this.getPrev(), true );
4648        }
4649        return this;
4650    },
4651
4652    /**
4653        Retrieve a DOM element by element ID
4654
4655        @param {string} elemId The delement ID to fetch
4656
4657        @returns {HTMLElement} The elements DOM node or null if not found.
4658    */
4659
4660    get : function( elemId ) {
4661        return elemId in this._dom ? this._dom[ elemId ] : null;
4662    },
4663
4664    /**
4665        Retrieve a data object
4666
4667        @param {number} index The data index to retrieve.
4668        If no index specified it will take the currently active image
4669
4670        @returns {Object} The data object
4671    */
4672
4673    getData : function( index ) {
4674        return index in this._data ?
4675            this._data[ index ] : this._data[ this._active ];
4676    },
4677
4678    /**
4679        Retrieve the number of data items
4680
4681        @returns {number} The data length
4682    */
4683    getDataLength : function() {
4684        return this._data.length;
4685    },
4686
4687    /**
4688        Retrieve the currently active index
4689
4690        @returns {number|boolean} The active index or false if none found
4691    */
4692
4693    getIndex : function() {
4694        return typeof this._active === 'number' ? this._active : false;
4695    },
4696
4697    /**
4698        Retrieve the stage height
4699
4700        @returns {number} The stage height
4701    */
4702
4703    getStageHeight : function() {
4704        return this._stageHeight;
4705    },
4706
4707    /**
4708        Retrieve the stage width
4709
4710        @returns {number} The stage width
4711    */
4712
4713    getStageWidth : function() {
4714        return this._stageWidth;
4715    },
4716
4717    /**
4718        Retrieve the option
4719
4720        @param {string} key The option key to retrieve. If no key specified it will return all options in an object.
4721
4722        @returns option or options
4723    */
4724
4725    getOptions : function( key ) {
4726        return typeof key === 'undefined' ? this._options : this._options[ key ];
4727    },
4728
4729    /**
4730        Set options to the instance.
4731        You can set options using a key & value argument or a single object argument (see examples)
4732
4733        @param {string} key The option key
4734        @param {string} value the the options value
4735
4736        @example setOptions( 'autoplay', true )
4737        @example setOptions({ autoplay: true });
4738
4739        @returns Instance
4740    */
4741
4742    setOptions : function( key, value ) {
4743        if ( typeof key === 'object' ) {
4744            $.extend( this._options, key );
4745        } else {
4746            this._options[ key ] = value;
4747        }
4748        return this;
4749    },
4750
4751    /**
4752        Starts playing the slideshow
4753
4754        @param {number} delay Sets the slideshow interval in milliseconds.
4755        If you set it once, you can just call play() and get the same interval the next time.
4756
4757        @returns Instance
4758    */
4759
4760    play : function( delay ) {
4761
4762        this._playing = true;
4763
4764        this._playtime = delay || this._playtime;
4765
4766        this._playCheck();
4767
4768        this.trigger( Galleria.PLAY );
4769
4770        return this;
4771    },
4772
4773    /**
4774        Stops the slideshow if currently playing
4775
4776        @returns Instance
4777    */
4778
4779    pause : function() {
4780
4781        this._playing = false;
4782
4783        this.trigger( Galleria.PAUSE );
4784
4785        return this;
4786    },
4787
4788    /**
4789        Toggle between play and pause events.
4790
4791        @param {number} delay Sets the slideshow interval in milliseconds.
4792
4793        @returns Instance
4794    */
4795
4796    playToggle : function( delay ) {
4797        return ( this._playing ) ? this.pause() : this.play( delay );
4798    },
4799
4800    /**
4801        Checks if the gallery is currently playing
4802
4803        @returns {Boolean}
4804    */
4805
4806    isPlaying : function() {
4807        return this._playing;
4808    },
4809
4810    /**
4811        Checks if the gallery is currently in fullscreen mode
4812
4813        @returns {Boolean}
4814    */
4815
4816    isFullscreen : function() {
4817        return this._fullscreen.active;
4818    },
4819
4820    _playCheck : function() {
4821        var self = this,
4822            played = 0,
4823            interval = 20,
4824            now = Utils.timestamp(),
4825            timer_id = 'play' + this._id;
4826
4827        if ( this._playing ) {
4828
4829            this.clearTimer( timer_id );
4830
4831            var fn = function() {
4832
4833                played = Utils.timestamp() - now;
4834                if ( played >= self._playtime && self._playing ) {
4835                    self.clearTimer( timer_id );
4836                    self.next();
4837                    return;
4838                }
4839                if ( self._playing ) {
4840
4841                    // trigger the PROGRESS event
4842                    self.trigger({
4843                        type:         Galleria.PROGRESS,
4844                        percent:      Math.ceil( played / self._playtime * 100 ),
4845                        seconds:      Math.floor( played / 1000 ),
4846                        milliseconds: played
4847                    });
4848
4849                    self.addTimer( timer_id, fn, interval );
4850                }
4851            };
4852            self.addTimer( timer_id, fn, interval );
4853        }
4854    },
4855
4856    /**
4857        Modify the slideshow delay
4858
4859        @param {number} delay the number of milliseconds between slides,
4860
4861        @returns Instance
4862    */
4863
4864    setPlaytime: function( delay ) {
4865        this._playtime = delay;
4866        return this;
4867    },
4868
4869    setIndex: function( val ) {
4870        this._active = val;
4871        return this;
4872    },
4873
4874    /**
4875        Manually modify the counter
4876
4877        @param {number} [index] Optional data index to fectch,
4878        if no index found it assumes the currently active index
4879
4880        @returns Instance
4881    */
4882
4883    setCounter: function( index ) {
4884
4885        if ( typeof index === 'number' ) {
4886            index++;
4887        } else if ( typeof index === 'undefined' ) {
4888            index = this.getIndex()+1;
4889        }
4890
4891        this.get( 'current' ).innerHTML = index;
4892
4893        if ( IE ) { // weird IE bug
4894
4895            var count = this.$( 'counter' ),
4896                opacity = count.css( 'opacity' );
4897
4898            if ( parseInt( opacity, 10 ) === 1) {
4899                Utils.removeAlpha( count[0] );
4900            } else {
4901                this.$( 'counter' ).css( 'opacity', opacity );
4902            }
4903
4904        }
4905
4906        return this;
4907    },
4908
4909    /**
4910        Manually set captions
4911
4912        @param {number} [index] Optional data index to fectch and apply as caption,
4913        if no index found it assumes the currently active index
4914
4915        @returns Instance
4916    */
4917
4918    setInfo : function( index ) {
4919
4920        var self = this,
4921            data = this.getData( index );
4922
4923        $.each( ['title','description'], function( i, type ) {
4924
4925            var elem = self.$( 'info-' + type );
4926
4927            if ( !!data[type] ) {
4928                elem[ data[ type ].length ? 'show' : 'hide' ]().html( data[ type ] );
4929            } else {
4930               elem.empty().hide();
4931            }
4932        });
4933
4934        return this;
4935    },
4936
4937    /**
4938        Checks if the data contains any captions
4939
4940        @param {number} [index] Optional data index to fectch,
4941        if no index found it assumes the currently active index.
4942
4943        @returns {boolean}
4944    */
4945
4946    hasInfo : function( index ) {
4947
4948        var check = 'title description'.split(' '),
4949            i;
4950
4951        for ( i = 0; check[i]; i++ ) {
4952            if ( !!this.getData( index )[ check[i] ] ) {
4953                return true;
4954            }
4955        }
4956        return false;
4957
4958    },
4959
4960    jQuery : function( str ) {
4961
4962        var self = this,
4963            ret = [];
4964
4965        $.each( str.split(','), function( i, elemId ) {
4966            elemId = $.trim( elemId );
4967
4968            if ( self.get( elemId ) ) {
4969                ret.push( elemId );
4970            }
4971        });
4972
4973        var jQ = $( self.get( ret.shift() ) );
4974
4975        $.each( ret, function( i, elemId ) {
4976            jQ = jQ.add( self.get( elemId ) );
4977        });
4978
4979        return jQ;
4980
4981    },
4982
4983    /**
4984        Converts element IDs into a jQuery collection
4985        You can call for multiple IDs separated with commas.
4986
4987        @param {string} str One or more element IDs (comma-separated)
4988
4989        @returns jQuery
4990
4991        @example this.$('info,container').hide();
4992    */
4993
4994    $ : function( str ) {
4995        return this.jQuery.apply( this, Utils.array( arguments ) );
4996    }
4997
4998};
4999
5000// End of Galleria prototype
5001
5002// Add events as static variables
5003$.each( _events, function( i, ev ) {
5004
5005    // legacy events
5006    var type = /_/.test( ev ) ? ev.replace( /_/g, '' ) : ev;
5007
5008    Galleria[ ev.toUpperCase() ] = 'galleria.'+type;
5009
5010} );
5011
5012$.extend( Galleria, {
5013
5014    // Browser helpers
5015    IE9:     IE === 9,
5016    IE8:     IE === 8,
5017    IE7:     IE === 7,
5018    IE6:     IE === 6,
5019    IE:      IE,
5020    WEBKIT:  /webkit/.test( NAV ),
5021    CHROME:  /chrome/.test( NAV ),
5022    SAFARI:  /safari/.test( NAV ) && !(/chrome/.test( NAV )),
5023    QUIRK:   ( IE && doc.compatMode && doc.compatMode === "BackCompat" ),
5024    MAC:     /mac/.test( navigator.platform.toLowerCase() ),
5025    OPERA:   !!window.opera,
5026    IPHONE:  /iphone/.test( NAV ),
5027    IPAD:    /ipad/.test( NAV ),
5028    ANDROID: /android/.test( NAV ),
5029    TOUCH:   ('ontouchstart' in doc)
5030
5031});
5032
5033// Galleria static methods
5034
5035/**
5036    Adds a theme that you can use for your Gallery
5037
5038    @param {Object} theme Object that should contain all your theme settings.
5039    <ul>
5040        <li>name - name of the theme</li>
5041        <li>author - name of the author</li>
5042        <li>css - css file name (not path)</li>
5043        <li>defaults - default options to apply, including theme-specific options</li>
5044        <li>init - the init function</li>
5045    </ul>
5046
5047    @returns {Object} theme
5048*/
5049
5050Galleria.addTheme = function( theme ) {
5051
5052    // make sure we have a name
5053    if ( !theme.name ) {
5054        Galleria.raise('No theme name specified');
5055    }
5056
5057    if ( typeof theme.defaults !== 'object' ) {
5058        theme.defaults = {};
5059    } else {
5060        theme.defaults = _legacyOptions( theme.defaults );
5061    }
5062
5063    var css = false,
5064        reg;
5065
5066    if ( typeof theme.css === 'string' ) {
5067
5068        // look for manually added CSS
5069        $('link').each(function( i, link ) {
5070            reg = new RegExp( theme.css );
5071            if ( reg.test( link.href ) ) {
5072
5073                // we found the css
5074                css = true;
5075
5076                // the themeload trigger
5077                _themeLoad( theme );
5078
5079                return false;
5080            }
5081        });
5082
5083        // else look for the absolute path and load the CSS dynamic
5084        if ( !css ) {
5085
5086            $('script').each(function( i, script ) {
5087
5088                // look for the theme script
5089                reg = new RegExp( 'galleria\\.' + theme.name.toLowerCase() + '\\.' );
5090                if( reg.test( script.src )) {
5091
5092                    // we have a match
5093                    css = script.src.replace(/[^\/]*$/, '') + theme.css;
5094
5095                    window.setTimeout(function() {
5096                        Utils.loadCSS( css, 'galleria-theme', function() {
5097
5098                            // the themeload trigger
5099                            _themeLoad( theme );
5100
5101                        });
5102                    }, 1);
5103
5104                }
5105            });
5106        }
5107
5108        if ( !css ) {
5109            Galleria.raise('No theme CSS loaded');
5110        }
5111    } else {
5112
5113        // pass
5114        _themeLoad( theme );
5115    }
5116    return theme;
5117};
5118
5119/**
5120    loadTheme loads a theme js file and attaches a load event to Galleria
5121
5122    @param {string} src The relative path to the theme source file
5123
5124    @param {Object} [options] Optional options you want to apply
5125
5126    @returns Galleria
5127*/
5128
5129Galleria.loadTheme = function( src, options ) {
5130
5131    var loaded = false,
5132        length = _galleries.length,
5133        err = window.setTimeout( function() {
5134            Galleria.raise( "Theme at " + src + " could not load, check theme path.", true );
5135        }, 5000 );
5136
5137    // first clear the current theme, if exists
5138    Galleria.theme = undef;
5139
5140    // load the theme
5141    Utils.loadScript( src, function() {
5142
5143        window.clearTimeout( err );
5144
5145        // check for existing galleries and reload them with the new theme
5146        if ( length ) {
5147
5148            // temporary save the new galleries
5149            var refreshed = [];
5150
5151            // refresh all instances
5152            // when adding a new theme to an existing gallery, all options will be resetted but the data will be kept
5153            // you can apply new options as a second argument
5154            $.each( Galleria.get(), function(i, instance) {
5155
5156                // mix the old data and options into the new instance
5157                var op = $.extend( instance._original.options, {
5158                    data_source: instance._data
5159                }, options);
5160
5161                // remove the old container
5162                instance.$('container').remove();
5163
5164                // create a new instance
5165                var g = new Galleria();
5166
5167                // move the id
5168                g._id = instance._id;
5169
5170                // initialize the new instance
5171                g.init( instance._original.target, op );
5172
5173                // push the new instance
5174                refreshed.push( g );
5175            });
5176
5177            // now overwrite the old holder with the new instances
5178            _galleries = refreshed;
5179        }
5180
5181    });
5182
5183    return Galleria;
5184};
5185
5186/**
5187    Retrieves a Galleria instance.
5188
5189    @param {number} [index] Optional index to retrieve.
5190    If no index is supplied, the method will return all instances in an array.
vendor: 5,523 bytes, lines 5191-5428
5191
5192    @returns Instance or Array of instances
5193*/
5194
5195Galleria.get = function( index ) {
5196    if ( !!_instances[ index ] ) {
5197        return _instances[ index ];
5198    } else if ( typeof index !== 'number' ) {
5199        return _instances;
5200    } else {
5201        Galleria.raise('Gallery index ' + index + ' not found');
5202    }
5203};
5204
5205/**
5206
5207    Configure Galleria options via a static function.
5208    The options will be applied to all instances
5209
5210    @param {string|object} key The options to apply or a key
5211
5212    @param [value] If key is a string, this is the value
5213
5214    @returns Galleria
5215
5216*/
5217
5218Galleria.configure = function( key, value ) {
5219
5220    var opts = {};
5221
5222    if( typeof key == 'string' && value ) {
5223        opts[key] = value;
5224        key = opts;
5225    } else {
5226        $.extend( opts, key );
5227    }
5228
5229    Galleria.configure.options = opts;
5230
5231    $.each( Galleria.get(), function(i, instance) {
5232        instance.setOptions( opts );
5233    });
5234
5235    return Galleria;
5236};
5237
5238Galleria.configure.options = {};
5239
5240/**
5241
5242    Bind a Galleria event to the gallery
5243
5244    @param {string} type A string representing the galleria event
5245
5246    @param {function} callback The function that should run when the event is triggered
5247
5248    @returns Galleria
5249
5250*/
5251
5252Galleria.on = function( type, callback ) {
5253    if ( !type ) {
5254        return;
5255    }
5256
5257    callback = callback || F;
5258
5259    // hash the bind
5260    var hash = type + callback.toString().replace(/\s/g,'') + Utils.timestamp();
5261
5262    // for existing instances
5263    $.each( Galleria.get(), function(i, instance) {
5264        instance._binds.push( hash );
5265        instance.bind( type, callback );
5266    });
5267
5268    // for future instances
5269    Galleria.on.binds.push({
5270        type: type,
5271        callback: callback,
5272        hash: hash
5273    });
5274
5275    return Galleria;
5276};
5277
5278Galleria.on.binds = [];
5279
5280/**
5281
5282    Run Galleria
5283    Alias for $(selector).galleria(options)
5284
5285    @param {string} selector A selector of element(s) to intialize galleria to
5286
5287    @param {object} options The options to apply
5288
5289    @returns Galleria
5290
5291*/
5292
5293Galleria.run = function( selector, options ) {
5294    $( selector || '#galleria' ).galleria( options );
5295    return Galleria;
5296};
5297
5298/**
5299    Creates a transition to be used in your gallery
5300
5301    @param {string} name The name of the transition that you will use as an option
5302
5303    @param {Function} fn The function to be executed in the transition.
5304    The function contains two arguments, params and complete.
5305    Use the params Object to integrate the transition, and then call complete when you are done.
5306
5307    @returns Galleria
5308
5309*/
5310
5311Galleria.addTransition = function( name, fn ) {
5312    _transitions.effects[name] = fn;
5313    return Galleria;
5314};
5315
5316/**
5317    The Galleria utilites
5318*/
5319
5320Galleria.utils = Utils;
5321
5322/**
5323    A helper metod for cross-browser logging.
5324    It uses the console log if available otherwise it falls back to alert
5325
5326    @example Galleria.log("hello", document.body, [1,2,3]);
5327*/
5328
5329Galleria.log = function() {
5330    var args = Utils.array( arguments );
5331    if( 'console' in window && 'log' in window.console ) {
5332        try {
5333            return window.console.log.apply( window.console, args );
5334        } catch( e ) {
5335            $.each( args, function() {
5336                window.console.log(this);
5337            });
5338        }
5339    } else {
5340        return window.alert( args.join('<br>') );
5341    }
5342};
5343
5344/**
5345    A ready method for adding callbacks when a gallery is ready
5346    Each method is call before the extend option for every instance
5347
5348    @param {function} callback The function to call
5349
5350    @returns Galleria
5351*/
5352
5353Galleria.ready = function( fn ) {
5354    if ( typeof fn != 'function' ) {
5355        return Galleria;
5356    }
5357    $.each( _galleries, function( i, gallery ) {
5358        fn.call( gallery, gallery._options );
5359    });
5360    Galleria.ready.callbacks.push( fn );
5361    return Galleria;
5362};
5363
5364Galleria.ready.callbacks = [];
5365
5366/**
5367    Method for raising errors
5368
5369    @param {string} msg The message to throw
5370
5371    @param {boolean} [fatal] Set this to true to override debug settings and display a fatal error
5372*/
5373
5374Galleria.raise = function( msg, fatal ) {
5375
5376    var type = fatal ? 'Fatal error' : 'Error',
5377
5378        self = this,
5379
5380        css = {
5381            color: '#fff',
5382            position: 'absolute',
5383            top: 0,
5384            left: 0,
5385            zIndex: 100000
5386        },
5387
5388        echo = function( msg ) {
5389
5390            var html = '<div style="padding:4px;margin:0 0 2px;background:#' +
5391                ( fatal ? '811' : '222' ) + '";>' +
5392                ( fatal ? '<strong>' + type + ': </strong>' : '' ) +
5393                msg + '</div>';
5394
5395            $.each( _instances, function() {
5396
5397                var cont = this.$( 'errors' ),
5398                    target = this.$( 'target' );
5399
5400                if ( !cont.length ) {
5401
5402                    target.css( 'position', 'relative' );
5403
5404                    cont = this.addElement( 'errors' ).appendChild( 'target', 'errors' ).$( 'errors' ).css(css);
5405                }
5406                cont.append( html );
5407
5408            });
5409
5410            if ( !_instances.length ) {
5411                $('<div>').css( $.extend( css, { position: 'fixed' } ) ).append( html ).appendTo( DOM().body );
5412            }
5413        };
5414
5415    // if debug is on, display errors and throw exception if fatal
5416    if ( DEBUG ) {
5417        echo( msg );
5418        if ( fatal ) {
5419            throw new Error(type + ': ' + msg);
5420        }
5421
5422    // else just echo a silent generic error if fatal
5423    } else if ( fatal ) {
5424        if ( _hasError ) {
5425            return;
5426        }
5427        _hasError = true;
5428        fatal = false;
5429        echo( 'Gallery could not load.' );
5430    }
5431};
5432
5433// Add the version
5434Galleria.version = VERSION;
5435
5436/**
5437    A method for checking what version of Galleria the user has installed and throws a readable error if the user needs to upgrade.
5438    Useful when building plugins that requires a certain version to function.
5439
5440    @param {number} version The minimum version required
5441
5442    @param {string} [msg] Optional message to display. If not specified, Galleria will throw a generic error.
5443
5444    @returns Galleria
5445*/
5446
5447Galleria.requires = function( version, msg ) {
5448    msg = msg || 'You need to upgrade Galleria to version ' + version + ' to use one or more components.';
5449    if ( Galleria.version < version ) {
5450        Galleria.raise(msg, true);
5451    }
5452    return Galleria;
5453};
5454
5455/**
5456    Adds preload, cache, scale and crop functionality
5457
5458    @constructor
5459
5460    @requires jQuery
5461
5462    @param {number} [id] Optional id to keep track of instances
5463*/
5464
5465Galleria.Picture = function( id ) {
5466
5467    // save the id
5468    this.id = id || null;
5469
5470    // the image should be null until loaded
5471    this.image = null;
5472
5473    // Create a new container
5474    this.container = Utils.create('galleria-image');
5475
5476    // add container styles
5477    $( this.container ).css({
5478        overflow: 'hidden',
5479        position: 'relative' // for IE Standards mode
5480    });
5481
5482    // saves the original measurements
5483    this.original = {
5484        width: 0,
5485        height: 0
5486    };
5487
5488    // flag when the image is ready
5489    this.ready = false;
5490
5491    // flag for iframe Picture
5492    this.isIframe = false;
5493
5494};
5495
5496Galleria.Picture.prototype = {
5497
5498    // the inherited cache object
5499    cache: {},
5500
5501    // show the image on stage
5502    show: function() {
5503        Utils.show( this.image );
5504    },
5505
5506    // hide the image
5507    hide: function() {
5508        Utils.moveOut( this.image );
5509    },
5510
5511    clear: function() {
5512        this.image = null;
5513    },
5514
5515    /**
5516        Checks if an image is in cache
5517
5518        @param {string} src The image source path, ex '/path/to/img.jpg'
5519
5520        @returns {boolean}
5521    */
5522
5523    isCached: function( src ) {
5524        return !!this.cache[src];
5525    },
5526
5527    /**
5528        Preloads an image into the cache
5529
5530        @param {string} src The image source path, ex '/path/to/img.jpg'
5531
5532        @returns Galleria.Picture
5533    */
5534
5535    preload: function( src ) {
5536        $( new Image() ).load((function(src, cache) {
5537            return function() {
5538                cache[ src ] = src;
5539            };
5540        }( src, this.cache ))).attr( 'src', src );
5541    },
5542
5543    /**
5544        Loads an image and call the callback when ready.
5545        Will also add the image to cache.
5546
5547        @param {string} src The image source path, ex '/path/to/img.jpg'
5548        @param {Object} [size] The forced size of the image, defined as an object { width: xx, height:xx }
5549        @param {Function} callback The function to be executed when the image is loaded & scaled
5550
5551        @returns The image container (jQuery object)
5552    */
5553
5554    load: function(src, size, callback) {
5555
5556        if ( typeof size == 'function' ) {
5557            callback = size;
5558            size = null;
5559        }
5560
5561        if( this.isIframe ) {
5562            var id = 'if'+new Date().getTime();
5563
5564            this.image = $('<iframe>', {
5565                src: src,
5566                frameborder: 0,
5567                id: id,
5568                allowfullscreen: true,
5569                css: { visibility: 'hidden' }
5570            })[0];
5571
5572            $( this.container ).find( 'iframe,img' ).remove();
5573
5574            this.container.appendChild( this.image );
5575
5576            $('#'+id).load( (function( self, callback ) {
5577                return function() {
5578                    window.setTimeout(function() {
5579                        $( self.image ).css( 'visibility', 'visible' );
5580                        if( typeof callback == 'function' ) {
5581                            callback.call( self, self );
5582                        }
5583                    }, 10);
5584                };
5585            }( this, callback )));
5586
5587            return this.container;
5588        }
5589
5590        this.image = new Image();
5591
5592        var i = 0,
5593            reload = false,
5594            resort = false,
5595
5596            // some jquery cache
5597            $container = $( this.container ),
5598            $image = $( this.image ),
5599
5600            onerror = function() {
5601                if ( !reload ) {
5602                    reload = true;
5603                    // reload the image with a timestamp
5604                    window.setTimeout((function(image, src) {
5605                        return function() {
5606                            image.attr('src', src + '?' + Utils.timestamp() );
5607                        };
5608                    }( $(this), src )), 50);
5609                } else {
5610                    // apply the dummy image if it exists
5611                    if ( DUMMY ) {
5612                        $( this ).attr( 'src', DUMMY );
5613                    } else {
5614                        Galleria.raise('Image not found: ' + src);
5615                    }
5616                }
5617            },
5618
5619            // the onload method
5620            onload = (function( self, callback, src ) {
5621
5622                return function() {
5623
5624                    var complete = function() {
5625
5626                        $( this ).unbind( 'load' );
5627
5628                        // save the original size
5629                        self.original = size || {
5630                            height: this.height,
5631                            width: this.width
5632                        };
5633
5634                        //self.container.appendChild( this );
5635
5636                        self.cache[ src ] = src; // will override old cache
5637
5638                        if (typeof callback == 'function' ) {
5639                            window.setTimeout(function() {
5640                                callback.call( self, self );
5641                            },1);
5642                        }
5643                    };
5644
5645                    // Delay the callback to "fix" the Adblock Bug
5646                    // http://code.google.com/p/adblockforchrome/issues/detail?id=3701
5647                    if ( ( !this.width || !this.height ) ) {
5648                        window.setTimeout( (function( img ) {
5649                            return function() {
5650                                if ( img.width && img.height ) {
5651                                    complete.call( img );
5652                                } else {
5653                                    // last resort, this should never happen but just in case it does...
5654                                    if ( !resort ) {
5655                                        $(new Image()).load( onload ).attr( 'src', img.src );
5656                                        resort = true;
5657                                    } else {
5658                                        Galleria.raise('Could not extract width/height from image: ' + img.src +
5659                                            '. Traced measures: width:' + img.width + 'px, height: ' + img.height + 'px.');
5660                                    }
5661                                }
5662                            };
5663                        }( this )), 2);
5664                    } else {
5665                        complete.call( this );
5666                    }
5667                };
5668            }( this, callback, src ));
5669
5670        // remove any previous images
5671        $container.find( 'iframe,img' ).remove();
5672
5673        // append the image
5674        $image.css( 'display', 'block');
5675
5676        // hide it for now
5677        Utils.hide( this.image );
5678
5679        // remove any max/min scaling
5680        $.each('minWidth minHeight maxWidth maxHeight'.split(' '), function(i, prop) {
5681            $image.css(prop, (/min/.test(prop) ? '0' : 'none'));
5682        });
5683
5684		$container.append( $image );
5685        // begin load and insert in cache when done
5686        $image.load( onload ).error( onerror ).attr( 'src', src );
5687
5688        // return the container
5689        return this.container;
5690    },
5691
5692    /**
5693        Scales and crops the image
5694
5695        @param {Object} options The method takes an object with a number of options:
5696
5697        <ul>
5698            <li>width - width of the container</li>
5699            <li>height - height of the container</li>
5700            <li>min - minimum scale ratio</li>
5701            <li>max - maximum scale ratio</li>
5702            <li>margin - distance in pixels from the image border to the container</li>
5703            <li>complete - a callback that fires when scaling is complete</li>
5704            <li>position - positions the image, works like the css background-image property.</li>
5705            <li>crop - defines how to crop. Can be true, false, 'width' or 'height'</li>
5706            <li>canvas - set to true to try a canvas-based rescale</li>
5707        </ul>
5708
5709        @returns The image container object (jQuery)
5710    */
5711
5712    scale: function( options ) {
5713
5714        var self = this;
5715
5716        // extend some defaults
5717        options = $.extend({
5718            width: 0,
5719            height: 0,
5720            min: undef,
5721            max: undef,
5722            margin: 0,
5723            complete: F,
5724            position: 'center',
5725            crop: false,
5726            canvas: false
5727        }, options);
5728
5729        if( this.isIframe ) {
5730            $( this.image ).width( options.width ).height( options.height ).removeAttr( 'width' ).removeAttr( 'height' );
5731            $( this.container ).width( options.width ).height( options.height) ;
5732            options.complete.call(self, self);
5733            try {
5734                if( this.image.contentWindow ) {
5735                    $( this.image.contentWindow ).trigger('resize');
5736                }
5737            } catch(e) {}
5738            return this.container;
5739        }
5740
5741        // return the element if no image found
5742        if (!this.image) {
5743            return this.container;
5744        }
5745
5746        // store locale variables
5747        var width,
5748            height,
5749            $container = $( self.container ),
5750            data;
5751
5752        // wait for the width/height
5753        Utils.wait({
5754            until: function() {
5755                width  = options.width ||
5756                         $container.width() ||
5757                         Utils.parseValue( $container.css('width') );
5758
5759                height = options.height ||
5760                         $container.height() ||
5761                         Utils.parseValue( $container.css('height') );
5762
5763                return width && height;
5764            },
5765            success: function() {
5766
5767                // calculate some cropping
5768                var newWidth = ( width - options.margin * 2 ) / self.original.width,
5769                    newHeight = ( height - options.margin * 2 ) / self.original.height,
5770                    min = Math.min( newWidth, newHeight ),
5771                    max = Math.max( newWidth, newHeight ),
5772                    cropMap = {
5773                        'true'  : max,
5774                        'width' : newWidth,
5775                        'height': newHeight,
5776                        'false' : min,
5777                        'landscape': self.original.width > self.original.height ? max : min,
5778                        'portrait': self.original.width < self.original.height ? max : min
5779                    },
5780                    ratio = cropMap[ options.crop.toString() ],
vendor: 5,434 bytes, lines 5781-5958
5781                    canvasKey = '';
5782
5783                // allow maxScaleRatio
5784                if ( options.max ) {
5785                    ratio = Math.min( options.max, ratio );
5786                }
5787
5788                // allow minScaleRatio
5789                if ( options.min ) {
5790                    ratio = Math.max( options.min, ratio );
5791                }
5792
5793                $.each( ['width','height'], function( i, m ) {
5794                    $( self.image )[ m ]( self[ m ] = self.image[ m ] = Math.round( self.original[ m ] * ratio ) );
5795                });
5796
5797                $( self.container ).width( width ).height( height );
5798
5799                if ( options.canvas && _canvas ) {
5800
5801                    _canvas.elem.width = self.width;
5802                    _canvas.elem.height = self.height;
5803
5804                    canvasKey = self.image.src + ':' + self.width + 'x' + self.height;
5805
5806                    self.image.src = _canvas.cache[ canvasKey ] || (function( key ) {
5807
5808                        _canvas.context.drawImage(self.image, 0, 0, self.original.width*ratio, self.original.height*ratio);
5809
5810                        try {
5811
5812                            data = _canvas.elem.toDataURL();
5813                            _canvas.length += data.length;
5814                            _canvas.cache[ key ] = data;
5815                            return data;
5816
5817                        } catch( e ) {
5818                            return self.image.src;
5819                        }
5820
5821                    }( canvasKey ) );
5822
5823                }
5824
5825                // calculate image_position
5826                var pos = {},
5827                    mix = {},
5828                    getPosition = function(value, measure, margin) {
5829                        var result = 0;
5830                        if (/\%/.test(value)) {
5831                            var flt = parseInt( value, 10 ) / 100,
5832                                m = self.image[ measure ] || $( self.image )[ measure ]();
5833
5834                            result = Math.ceil( m * -1 * flt + margin * flt );
5835                        } else {
5836                            result = Utils.parseValue( value );
5837                        }
5838                        return result;
5839                    },
5840                    positionMap = {
5841                        'top': { top: 0 },
5842                        'left': { left: 0 },
5843                        'right': { left: '100%' },
5844                        'bottom': { top: '100%' }
5845                    };
5846
5847                $.each( options.position.toLowerCase().split(' '), function( i, value ) {
5848                    if ( value === 'center' ) {
5849                        value = '50%';
5850                    }
5851                    pos[i ? 'top' : 'left'] = value;
5852                });
5853
5854                $.each( pos, function( i, value ) {
5855                    if ( positionMap.hasOwnProperty( value ) ) {
5856                        $.extend( mix, positionMap[ value ] );
5857                    }
5858                });
5859
5860                pos = pos.top ? $.extend( pos, mix ) : mix;
5861
5862                pos = $.extend({
5863                    top: '50%',
5864                    left: '50%'
5865                }, pos);
5866
5867                // apply position
5868                $( self.image ).css({
5869                    position : 'absolute',
5870                    top :  getPosition(pos.top, 'height', height),
5871                    left : getPosition(pos.left, 'width', width)
5872                });
5873
5874                // show the image
5875                self.show();
5876
5877                // flag ready and call the callback
5878                self.ready = true;
5879                options.complete.call( self, self );
5880
5881            },
5882            error: function() {
5883                Galleria.raise('Could not scale image: '+self.image.src);
5884            },
5885            timeout: 1000
5886        });
5887        return this;
5888    }
5889};
5890
5891// our own easings
5892$.extend( $.easing, {
5893
5894    galleria: function (_, t, b, c, d) {
5895        if ((t/=d/2) < 1) {
5896            return c/2*t*t*t + b;
5897        }
5898        return c/2*((t-=2)*t*t + 2) + b;
5899    },
5900
5901    galleriaIn: function (_, t, b, c, d) {
5902        return c*(t/=d)*t + b;
5903    },
5904
5905    galleriaOut: function (_, t, b, c, d) {
5906        return -c *(t/=d)*(t-2) + b;
5907    }
5908
5909});
5910
5911// the plugin initializer
5912$.fn.galleria = function( options ) {
5913
5914    var selector = this.selector;
5915
5916    // try domReady if element not found
5917    if ( !$(this).length ) {
5918
5919        $(function() {
5920            if ( $( selector ).length ) {
5921
5922                // if found on domReady, go ahead
5923                $( selector ).galleria( options );
5924
5925            } else {
5926
5927                // if not, try fetching the element for 5 secs, then raise a warning.
5928                Galleria.utils.wait({
5929                    until: function() {
5930                        return $( selector ).length;
5931                    },
5932                    success: function() {
5933                        $( selector ).galleria( options );
5934                    },
5935                    error: function() {
5936                        Galleria.raise('Init failed: Galleria could not find the element "'+selector+'".');
5937                    },
5938                    timeout: 5000
5939                });
5940
5941            }
5942        });
5943        return this;
5944    }
5945
5946    return this.each(function() {
5947
5948        // fail silent if already run
5949        if ( !$.data(this, 'galleria') ) {
5950            $.data( this, 'galleria', new Galleria().init( this, options ) );
5951        }
5952    });
5953
5954};
5955
5956// phew
5957
5958}( jsnThemeClassicjQuery ) );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.