You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by er...@apache.org on 2010/11/14 10:54:06 UTC

svn commit: r1034970 [2/3] - in /james/project/trunk/project/src/site: resources/icon/ resources/js/ resources/js/galleria/ resources/js/galleria/plugins/ resources/js/galleria/themes/ resources/js/galleria/themes/classic/ resources/js/galleria/themes/...

Added: james/project/trunk/project/src/site/resources/js/galleria/galleria.js
URL: http://svn.apache.org/viewvc/james/project/trunk/project/src/site/resources/js/galleria/galleria.js?rev=1034970&view=auto
==============================================================================
--- james/project/trunk/project/src/site/resources/js/galleria/galleria.js (added)
+++ james/project/trunk/project/src/site/resources/js/galleria/galleria.js Sun Nov 14 09:54:04 2010
@@ -0,0 +1,3765 @@
+/*
+ * Galleria v 1.2 prerelease 1.1 2010-11-04
+ * http://galleria.aino.se
+ *
+ * Copyright (c) 2010, Aino
+ * Licensed under the MIT license.
+ */
+
+(function($) {
+
+// some references
+var undef,
+    window = this,
+    doc    = document,
+    $doc   = $( doc );
+
+// internal constants
+var DEBUG = false,
+    NAV   = navigator.userAgent.toLowerCase(),
+    HASH  = window.location.hash.replace(/#\//, ''),
+    CLICK = function() {
+        // use this to make touch devices snappier
+        return Galleria.TOUCH ? 'touchstart' : 'click';
+    },
+    IE    = (function() {
+        var v = 3,
+            div = doc.createElement( 'div' );
+        while (
+            div.innerHTML = '<!--[if gt IE '+(++v)+']><i></i><![endif]-->',
+            div.getElementsByTagName('i')[0]
+        );
+        return v > 4 ? v : undef;
+    }() ),
+    DOM   = function() {
+        return {
+            html:  doc.documentElement,
+            body:  doc.body,
+            head:  doc.getElementsByTagName('head')[0],
+            title: doc.title
+        };
+    },
+
+    // the internal timeouts object
+    // provides helper methods for controlling timeouts
+    _timeouts = {
+
+        trunk: {},
+
+        add: function( id, fn, delay, loop ) {
+            loop = loop || false;
+            this.clear( id );
+            if ( loop ) {
+                var old = fn;
+                fn = function() {
+                    old();
+                    _timeouts.add( id, fn, delay );
+                };
+            }
+            this.trunk[ id ] = window.setTimeout( fn, delay );
+        },
+
+        clear: function( id ) {
+
+            var del = function( i ) {
+                window.clearTimeout( this.trunk[ i ] );
+                delete this.trunk[ i ];
+            };
+
+            if ( !!id && id in this.trunk ) {
+                del.call( _timeouts, id );
+
+            } else if ( typeof id == 'undefined' ) {
+                for ( var i in this.trunk ) {
+                    del.call( _timeouts, i );
+                }
+            }
+        }
+    },
+
+    // the internal gallery holder
+    _galleries = [],
+
+    // the transitions holder
+    _transitions = {
+
+        fade: function(params, complete) {
+            $(params.next).css('opacity', 0).show().animate({
+                opacity: 1
+            }, params.speed, complete);
+
+            if (params.prev) {
+                $(params.prev).css('opacity', 1).show().animate({
+                    opacity: 0
+                }, params.speed);
+            }
+        },
+
+        flash: function(params, complete) {
+            $(params.next).css('opacity', 0);
+            if (params.prev) {
+                $(params.prev).animate({
+                    opacity: 0
+                }, (params.speed / 2), function() {
+                    $(params.next).animate({
+                        opacity: 1
+                    }, params.speed, complete);
+                });
+            } else {
+                $(params.next).animate({
+                    opacity: 1
+                }, params.speed, complete);
+            }
+        },
+
+        pulse: function(params, complete) {
+            if (params.prev) {
+                $(params.prev).hide();
+            }
+            $(params.next).css('opacity', 0).animate({
+                opacity:1
+            }, params.speed, complete);
+        },
+
+        slide: function(params, complete) {
+            var image  = $(params.next).parent(),
+                images = this.$('images'), // ??
+                width  = this._stageWidth,
+                easing = this.getOptions( 'easing' );
+
+            image.css({
+                left: width * ( params.rewind ? -1 : 1 )
+            });
+            images.animate({
+                left: width * ( params.rewind ? 1 : -1 )
+            }, {
+                duration: params.speed,
+                queue: false,
+                easing: easing,
+                complete: function() {
+                    images.css('left', 0);
+                    image.css('left', 0);
+                    complete();
+                }
+            });
+        },
+
+        fadeslide: function(params, complete) {
+
+            var x = 0,
+                easing = this.getOptions('easing'),
+                distance = this.getStageWidth();
+
+            if (params.prev) {
+                x = Utils.parseValue( $(params.prev).css('left') );
+                $(params.prev).css({
+                    opacity: 1,
+                    left: x
+                }).animate({
+                    opacity: 0,
+                    left: x + ( distance * ( params.rewind ? 1 : -1 ) )
+                },{
+                    duration: params.speed,
+                    queue: false,
+                    easing: easing
+                });
+            }
+
+            x = Utils.parseValue( $(params.next).css('left') );
+
+            $(params.next).css({
+                left: x + ( distance * ( params.rewind ? -1 : 1 ) ),
+                opacity: 0
+            }).animate({
+                opacity: 1,
+                left: x
+            }, {
+                duration: params.speed,
+                complete: complete,
+                queue: false,
+                easing: easing
+            });
+        }
+    },
+
+    // the Utils singleton
+    Utils = (function() {
+
+        return {
+
+            array : function( obj ) {
+                return Array.prototype.slice.call(obj);
+            },
+
+            create : function( className, nodeName ) {
+                nodeName = nodeName || 'div';
+                var elem = doc.createElement( nodeName );
+                elem.className = className;
+                return elem;
+            },
+
+            forceStyles : function( elem, styles ) {
+                elem = $(elem);
+                if ( elem.attr( 'style' ) ) {
+                    elem.data( 'styles', elem.attr( 'style' ) ).removeAttr( 'style' );
+                }
+                elem.css( styles );
+            },
+
+            revertStyles : function() {
+                $.each( Utils.array( arguments ), function( i, elem ) {
+
+                    elem = $( elem ).removeAttr( 'style' );
+
+                    if ( elem.data( 'styles' ) ) {
+                        elem.attr( 'style', elem.data('styles') ).data( 'styles', null );
+                    }
+                });
+            },
+
+            moveOut : function( elem ) {
+                Utils.forceStyles( elem, {
+                    position: 'absolute',
+                    left: -10000
+                });
+            },
+
+            moveIn : function() {
+                Utils.revertStyles.apply( Utils, Utils.array( arguments ) );
+            },
+
+            hide : function( elem, speed, callback ) {
+                elem = $(elem);
+
+                // save the value if not exist
+                if (! elem.data('opacity') ) {
+                    elem.data('opacity', elem.css('opacity') );
+                }
+
+                // always hide
+                var style = { opacity: 0 };
+
+                if (speed) {
+                    elem.stop().animate( style, speed, callback );
+                } else {
+                    elem.css( style );
+                };
+            },
+
+            show : function( elem, speed, callback ) {
+                elem = $(elem);
+
+                // bring back saved opacity
+                var saved = parseFloat( elem.data('opacity') ) || 1,
+                    style = { opacity: saved };
+
+                // reset save if opacity == 1
+                if (saved == 1) {
+                    elem.data('opacity', null);
+                }
+
+                // animate or toggle
+                if (speed) {
+                    elem.stop().animate( style, speed, callback );
+                } else {
+                    elem.css( style );
+                };
+            },
+
+            addTimer : function() {
+                _timeouts.add.apply( _timeouts, Utils.array( arguments ) );
+                return this;
+            },
+
+            clearTimer : function() {
+                _timeouts.clear.apply( _timeouts, Utils.array( arguments ) );
+                return this;
+            },
+
+            wait : function(options) {
+                options = $.extend({
+                    until : function() { return false; },
+                    success : function() {},
+                    error : function() { Galleria.raise('Could not complete wait function.'); },
+                    timeout: 3000
+                }, options);
+
+                var start = Utils.timestamp(),
+                    elapsed,
+                    now;
+
+                window.setTimeout(function() {
+                    now = Utils.timestamp();
+                    elapsed = now - start;
+                    if ( options.until( elapsed ) ) {
+                        options.success();
+                        return false;
+                    }
+
+                    if (now >= start + options.timeout) {
+                        options.error();
+                        return false;
+                    }
+                    window.setTimeout(arguments.callee, 2);
+                }, 2);
+            },
+
+            toggleQuality : function( img, force ) {
+
+                if ( !( IE == 7 || IE == 8 ) || !!img === false ) {
+                    return;
+                }
+
+                if ( typeof force === 'undefined' ) {
+                    force = img.style.msInterpolationMode == 'nearest-neighbor';
+                }
+
+                img.style.msInterpolationMode = force ? 'bicubic' : 'nearest-neighbor';
+            },
+
+            insertStyleTag : function( styles ) {
+                var style = doc.createElement( 'style' );
+                DOM().head.appendChild( style );
+
+                if ( style.styleSheet ) { // IE
+                    style.styleSheet.cssText = styles;
+                } else {
+                    var cssText = doc.createTextNode( styles );
+                    style.appendChild( cssText );
+                }
+            },
+
+            // a loadscript method that works for local scripts
+            loadScript: function( url, callback ) {
+                var done = false,
+                    script = $('<scr'+'ipt>').attr({
+                        src: url,
+                        async: true
+                    }).get(0);
+
+               // Attach handlers for all browsers
+               script.onload = script.onreadystatechange = function() {
+                   if ( !done && (!this.readyState ||
+                       this.readyState == 'loaded' || this.readyState == 'complete') ) {
+                       done = true;
+
+                       if (typeof callback == 'function') {
+                           callback.call( this, this );
+                       }
+
+                       // Handle memory leak in IE
+                       script.onload = script.onreadystatechange = null;
+                   }
+               };
+
+               var s = doc.getElementsByTagName( 'script' )[0];
+               s.parentNode.insertBefore( script, s );
+            },
+
+            // parse anything into a number
+            parseValue: function( val ) {
+                if (typeof val == 'number') {
+                    return val;
+                } else if (typeof val == 'string') {
+                    var arr = val.match(/\-?\d/g);
+                    return arr && arr.constructor == Array ? arr.join('') * 1 : 0;
+                } else {
+                    return 0;
+                }
+            },
+
+            // timestamp abstraction
+            timestamp: function() {
+                return new Date().getTime();
+            },
+
+            // this is pretty crap, but works for now
+            // it will add a callback, but it can't guarantee that the styles can be fetched
+            // using getComputedStyle further checking needed, possibly a dummy element
+            loadCSS : function( href, id, callback ) {
+
+                var link,
+                    ready = false,
+                    length;
+
+                // look for manual css
+                $('link[rel=stylesheet]').each(function() {
+                    if ( new RegExp( href ).test( this.href ) ) {
+                        link = this;
+                        return false;
+                    }
+                });
+
+                if ( typeof id == 'function' ) {
+                    callback = id;
+                    id = undef;
+                }
+
+                callback = callback || function() {}; // dirty
+
+                // if already present, return
+                if ( link ) {
+                    callback.call( link, link );
+                    return link;
+                }
+
+                // save the length of stylesheets to check against
+                length = doc.styleSheets.length;
+
+                // add timestamp if DEBUG is true
+                if ( DEBUG ) {
+                    href += '?' + Utils.timestamp();
+                }
+
+                // check for existing id
+                if( $('#'+id).length ) {
+                    $('#'+id).attr('href', href);
+                    length--;
+                    ready = true;
+                } else {
+                    link = $( '<link>' ).attr({
+                        rel: 'stylesheet',
+                        href: href,
+                        id: id
+                    }).get(0);
+
+                    window.setTimeout(function() {
+                        var styles = $('link[rel="stylesheet"], style');
+                        if ( styles.length ) {
+                            styles.get(0).parentNode.insertBefore( link, styles[0] );
+                        } else {
+                            DOM().head.appendChild( link );
+                        }
+
+                        if ( IE ) {
+                            link.attachEvent( 'onreadystatechange', function(e) {
+                                if( link.readyState == 'complete' ) {
+                                    ready = true;
+                                }
+                            });
+                        } else {
+                            // what to do here? returning for now.
+                            ready = true;
+                        }
+                    }, 10);
+                }
+
+                if (typeof callback == 'function') {
+
+                    Utils.wait({
+                        until: function() {
+                            return ready && doc.styleSheets.length > length;
+                        },
+                        success: function() {
+                            Utils.addTimer( 'css', function() {
+                                callback.call( link, link );
+                            }, 100);
+                        },
+                        error: function() {
+                            Galleria.raise( 'Theme CSS could not load' );
+                        },
+                        timeout: 1000
+                    });
+                }
+                return link;
+            }
+        };
+    })();
+
+/**
+    The main Galleria class
+
+    @class
+
+    @example var gallery = new Galleria();
+
+    @author http://aino.se
+
+    @requires jQuery
+
+    @returns {Galleria}
+*/
+
+Galleria = function() {
+
+    var self = this;
+
+    // the theme used
+    this._theme = undef;
+
+    // internal options
+    this._options = {};
+
+    // flag for controlling play/pause
+    this._playing = false;
+
+    // internal interval for slideshow
+    this._playtime = 5000;
+
+    // internal variable for the currently active image
+    this._active = null;
+
+    // the internal queue, arrayified
+    this._queue = { length: 0 };
+
+    // the internal data array
+    this._data = [];
+
+    // the internal dom collection
+    this._dom = {};
+
+    // the internal thumbnails array
+    this._thumbnails = [];
+
+    // internal init flag
+    this._initialized = false;
+
+    // global stagewidth/height
+    this._stageWidth = 0;
+    this._stageHeight = 0;
+
+    // target holder
+    this._target = undef;
+
+    // instance id
+    this._id = Utils.timestamp();
+
+    // add some elements
+    var divs =  'container stage images image-nav image-nav-left image-nav-right ' +
+                'info info-text info-title info-description info-author ' +
+                'thumbnails thumbnails-list thumbnails-container thumb-nav-left thumb-nav-right ' +
+                'loader counter tooltip',
+        spans = 'current total';
+
+    $.each( divs.split(' '), function( i, elemId ) {
+        self._dom[ elemId ] = Utils.create( 'galleria-' + elemId );
+    });
+
+    $.each( spans.split(' '), function( i, elemId ) {
+        self._dom[ elemId ] = Utils.create( 'galleria-' + elemId, 'span' );
+    });
+
+    // the internal keyboard object
+    // keeps reference of the keybinds and provides helper methods for binding keys
+    var keyboard = this._keyboard = {
+
+        keys : {
+            'UP': 38,
+            'DOWN': 40,
+            'LEFT': 37,
+            'RIGHT': 39,
+            'RETURN': 13,
+            'ESCAPE': 27,
+            'BACKSPACE': 8,
+            'SPACE': 32
+        },
+
+        map : {},
+
+        bound: false,
+
+        press: function(e) {
+            var key = e.keyCode || e.which;
+            if ( key in keyboard.map && typeof keyboard.map[key] == 'function' ) {
+                keyboard.map[key].call(self, e);
+            }
+        },
+
+        attach: function(map) {
+            for( var key in map ) {
+                var up = key.toUpperCase();
+                if ( up in keyboard.keys ) {
+                    keyboard.map[ keyboard.keys[up] ] = map[key];
+                }
+            }
+            if ( !keyboard.bound ) {
+                keyboard.bound = true;
+                $doc.bind('keydown', keyboard.press);
+            }
+        },
+
+        detach: function() {
+            keyboard.bound = false;
+            $doc.unbind('keydown', keyboard.press);
+        }
+    };
+
+    // internal controls for keeping track of active / inactive images
+    var controls = this._controls = {
+
+        0: undef,
+
+        1: undef,
+
+        active : 0,
+
+        swap : function() {
+            controls.active = controls.active ? 0 : 1;
+        },
+
+        getActive : function() {
+            return controls[ controls.active ];
+        },
+
+        getNext : function() {
+            return controls[ 1 - controls.active ];
+        }
+    };
+
+    // internal carousel object
+    var carousel = this._carousel = {
+
+        // shortcuts
+        next: self.$('thumb-nav-right'),
+        prev: self.$('thumb-nav-left'),
+
+        // cache the width
+        width: 0,
+
+        // track the current position
+        current: 0,
+
+        // cache max value
+        max: 0,
+
+        // save all hooks for each width in an array
+        hooks: [],
+
+        // update the carousel
+        // you can run this method anytime, f.ex on window.resize
+        update: function() {
+            var w = 0,
+                h = 0,
+                hooks = [0];
+
+            $.each( self._thumbnails, function( i, thumb ) {
+                if ( thumb.ready ) {
+                    w += thumb.outerWidth || $( thumb.container ).outerWidth( true );
+                    hooks[ i+1 ] = w;
+                    h = Math.max( h, thumb.outerHeight || $( thumb.container).outerHeight() );
+                }
+            });
+            self.$( 'thumbnails-container' ).toggleClass( 'galleria-carousel', w > self._stageWidth );
+
+            self.$( 'thumbnails' ).css({
+                width: w,
+                height: h
+            });
+
+            carousel.max = w;
+            carousel.hooks = hooks;
+            carousel.width = self.$( 'thumbnails-list' ).width();
+            carousel.setClasses();
+
+            // todo: fix so the carousel moves to the left
+        },
+
+        bindControls: function() {
+
+            carousel.next.bind( CLICK(), function(e) {
+                e.preventDefault();
+
+                if ( self._options.carousel_steps == 'auto' ) {
+
+                    for ( var i = carousel.current; i < carousel.hooks.length; i++ ) {
+                        if ( carousel.hooks[i] - carousel.hooks[ carousel.current ] > carousel.width ) {
+                            carousel.set(i - 2);
+                            break;
+                        }
+                    }
+
+                } else {
+                    carousel.set( carousel.current + self._options.carousel_steps);
+                }
+            });
+
+            carousel.prev.bind( CLICK(), function(e) {
+                e.preventDefault();
+
+                if ( self._options.carousel_steps == 'auto' ) {
+
+                    for ( var i = carousel.current; i >= 0; i-- ) {
+                        if ( carousel.hooks[ carousel.current ] - carousel.hooks[i] > carousel.width ) {
+                            carousel.set( i + 2 );
+                            break;
+                        } else if ( i == 0 ) {
+                            carousel.set( 0 );
+                            break;
+                        }
+                    }
+                } else {
+                    carousel.set( carousel.current - self._options.carousel_steps );
+                }
+            });
+        },
+
+        // calculate and set positions
+        set: function( i ) {
+            i = Math.max( i, 0 );
+            while ( carousel.hooks[i - 1] + carousel.width > carousel.max && i >= 0 ) {
+                i--;
+            }
+            carousel.current = i;
+            carousel.animate();
+        },
+
+        // get the last position
+        getLast: function(i) {
+            return ( i || carousel.current ) - 1;
+        },
+
+        // follow the active image
+        follow: function(i) {
+
+            //don't follow if position fits
+            if ( i == 0 || i == carousel.hooks.length - 2 ) {
+                carousel.set( i );
+                return;
+            }
+
+            // calculate last position
+            var last = carousel.current;
+            while( carousel.hooks[last] - carousel.hooks[ carousel.current ] <
+                   carousel.width && last <= carousel.hooks.length ) {
+                last ++;
+            }
+
+            // set position
+            if ( i - 1 < carousel.current ) {
+                carousel.set( i - 1 );
+            } else if ( i + 2 > last) {
+                carousel.set( i - last + carousel.current + 2 );
+            }
+        },
+
+        // helper for setting disabled classes
+        setClasses: function() {
+            carousel.prev.toggleClass( 'disabled', !carousel.current );
+            carousel.next.toggleClass( 'disabled', carousel.hooks[ carousel.current ] + carousel.width > carousel.max );
+        },
+
+        // the animation method
+        animate: function(to) {
+            carousel.setClasses();
+            var num = carousel.hooks[ carousel.current ] * -1;
+
+            if ( isNaN( num ) ) {
+                return;
+            }
+
+            self.$( 'thumbnails' ).animate({
+                left: num
+            },{
+                duration: self._options.carousel_speed,
+                easing: self._options.easing,
+                queue: false
+            });
+        }
+    };
+
+    // tooltip control
+    // added in 1.2
+    var tooltip = this._tooltip = {
+
+        initialized : false,
+
+        active: null,
+
+        open: false,
+
+        init: function() {
+
+            tooltip.initialized = true;
+
+            var css = '.galleria-tooltip{padding:3px 8px;max-width:50%;background:#ffe;color:#000;z-index:3;position:absolute;font-size:11px;line-height:1.3' +
+                      'opacity:0;box-shadow:0 0 2px rgba(0,0,0,.4);-moz-box-shadow:0 0 2px rgba(0,0,0,.4);-webkit-box-shadow:0 0 2px rgba(0,0,0,.4);}';
+
+            Utils.insertStyleTag(css);
+
+            self.$( 'tooltip' ).css('opacity', .8);
+            Utils.hide( self.get('tooltip') );
+
+        },
+
+        // move handler
+        move: function( e ) {
+            var mouseX = self.getMousePosition(e).x,
+                mouseY = self.getMousePosition(e).y,
+                $elem = self.$( 'tooltip' ),
+                x = mouseX,
+                y = mouseY,
+                height = $elem.outerHeight( true ) + 1,
+                width = $elem.outerWidth( true ),
+                limitY = height + 15;
+
+            var maxX = self._stageWidth - width,
+                maxY = self._stageHeight - height;
+
+            if ( !isNaN(x) && !isNaN(y) ) {
+
+                x += 15;
+                y -= 35;
+
+                x = Math.max( 0, Math.min( maxX, x ) );
+                y = Math.max( 0, Math.min( maxY, y ) );
+
+                if( mouseY < limitY ) {
+                    y = limitY;
+                }
+
+                $elem.css({ left: x, top: y });
+            }
+        },
+
+        // bind elements to the tooltip
+        // you can bind multiple elementIDs using { elemID : function } or { elemID : string }
+        // you can also bind single DOM elements using bind(elem, string)
+        bind: function( elem, value ) {
+
+            if (! tooltip.initialized ) {
+                tooltip.init();
+            }
+
+            var hover = function( elem, value) {
+
+                tooltip.define( elem, value );
+
+                $( elem ).hover(function() {
+
+                    tooltip.active = elem;
+                    Utils.clearTimer('switch_tooltip');
+                    self.$('container').unbind( 'mousemove', tooltip.move ).bind( 'mousemove', tooltip.move ).trigger( 'mousemove' );
+                    tooltip.show( elem );
+
+                    Galleria.utils.addTimer( 'tooltip', function() {
+                        self.$( 'tooltip' ).stop();
+                        Utils.show( self.get( 'tooltip' ), 400 );
+                        tooltip.open = true;
+
+                    }, tooltip.open ? 0 : 1000);
+
+                }, function() {
+
+                    tooltip.active = null;
+
+                    self.$( 'container' ).unbind( 'mousemove', tooltip.move );
+                    Utils.clearTimer( 'tooltip' );
+
+                    Utils.hide( self.get( 'tooltip' ), 200, function() {
+                        Utils.addTimer('switch_tooltip', function() {
+                            tooltip.open = false;
+                        }, 1000);
+                    });
+                });
+            };
+
+            if (typeof value == 'string') {
+                hover( ( elem in self._dom ? self.get(elem) : elem ), value );
+            } else {
+                // asume elemID here
+                $.each( elem, function( elemID, val ) {
+                    hover( self.get(elemID), val );
+                });
+            }
+        },
+
+        show: function( elem ) {
+            var text = $(elem).data('tt');
+            if ( ! text ) {
+                return;
+            }
+            text = typeof text == 'function' ? text() : text;
+            self.$( 'tooltip' ).html( text.replace(/\s/, '&nbsp;') );
+        },
+
+        // redefine the tooltip here
+        define: function( elem, value ) {
+
+            // we store functions, not strings
+            if (typeof value !== 'function') {
+                var s = value;
+                value = function() {
+                    return s;
+                };
+            }
+
+            if ( elem in self._dom ) {
+                elem = self.get( elem );
+            }
+
+            $(elem).data('tt', value);
+
+            tooltip.show( elem );
+
+        },
+
+        refresh: function() {
+            $.each( arguments, function(i, elem) {
+                if ( tooltip.active == elem ) {
+                    tooltip.show( elem );
+                }
+            });
+        }
+    };
+
+    // internal fullscreen control
+    // added in 1.195
+    // still kind of experimental
+    var fullscreen = this._fullscreen = {
+        scrolled: 0,
+        enter: function(callback) {
+
+            // hide the image until rescale is complete
+            Utils.hide( self.getActiveImage() );
+
+            self.$( 'container' ).addClass( 'fullscreen' );
+
+            fullscreen.scrolled = $(window).scrollTop();
+
+            // begin styleforce
+            Utils.forceStyles(self.get('container'), {
+                position: 'fixed',
+                top: 0,
+                left: 0,
+                width: '100%',
+                height: '100%',
+                zIndex: 10000
+            });
+
+            var htmlbody = {
+                height: '100%',
+                overflow: 'hidden',
+                margin:0,
+                padding:0
+            };
+
+            Utils.forceStyles( DOM().html, htmlbody );
+            Utils.forceStyles( DOM().body, htmlbody );
+
+            // attach some keys
+            self.attachKeyboard({
+                escape: self.exitFullscreen,
+                right: self.next,
+                left: self.prev
+            });
+
+            // init the first rescale and attach callbacks
+            self.rescale(function() {
+
+                Utils.addTimer('fullscreen_enter', function() {
+                    // show the image after 50 ms
+                    Utils.show( self.getActiveImage() );
+
+                    if (typeof callback == 'function') {
+                        callback.call( self );
+                    }
+
+                }, 100);
+
+                self.trigger( Galleria.FULLSCREEN_ENTER );
+            });
+
+            // bind the scaling to the resize event
+            $(window).resize( function() {
+                fullscreen.scale();
+            } );
+        },
+
+        scale : function() {
+            self.rescale();
+        },
+
+        exit: function(callback) {
+
+            Utils.hide( self.getActiveImage() );
+
+            self.$('container').removeClass( 'fullscreen' );
+
+            // revert all styles
+            Utils.revertStyles( self.get('container'), DOM().html, DOM().body );
+
+            // scroll back
+            window.scrollTo(0, fullscreen.scrolled);
+
+            // detach all keyboard events (is this good?)
+            self.detachKeyboard();
+
+            self.rescale(function() {
+                Utils.addTimer('fullscreen_exit', function() {
+
+                    // show the image after 50 ms
+                    Utils.show( self.getActiveImage() );
+
+                    if ( typeof callback == 'function' ) {
+                        callback.call( self );
+                    }
+
+                }, 50);
+
+                self.trigger( Galleria.FULLSCREEN_EXIT );
+            });
+
+            $(window).unbind('resize', fullscreen.scale);
+        }
+    };
+
+    // the internal idle object for controlling idle states
+    // TODO occational event conflicts
+    var idle = this._idle = {
+
+        trunk: [],
+
+        bound: false,
+
+        add: function(elem, to) {
+            if (!elem) {
+                return;
+            }
+            if (!idle.bound) {
+                idle.addEvent();
+            }
+            elem = $(elem);
+
+            var from = {};
+
+            for (var style in to) {
+                from[style] = elem.css(style);
+            }
+            elem.data('idle', {
+                from: from,
+                to: to,
+                complete: true,
+                busy: false
+            });
+            idle.addTimer();
+            idle.trunk.push(elem);
+        },
+
+        remove: function(elem) {
+
+            elem = jQuery(elem);
+
+            $.each(idle.trunk, function(i, el) {
+                if ( el.length && !el.not(elem).length ) {
+                    self._idle.show(elem);
+                    self._idle.trunk.splice(i, 1);
+                }
+            });
+
+            if (!idle.trunk.length) {
+                idle.removeEvent();
+                Utils.clearTimer('idle');
+            }
+        },
+
+        addEvent : function() {
+            idle.bound = true;
+            self.$('container').bind('mousemove click', idle.showAll );
+        },
+
+        removeEvent : function() {
+            idle.bound = false;
+            self.$('container').unbind('mousemove click', idle.showAll );
+        },
+
+        addTimer : function() {
+            Utils.addTimer('idle', function() {
+                self._idle.hide();
+            }, self._options.idle_time );
+        },
+
+        hide : function() {
+            self.trigger( Galleria.IDLE_ENTER );
+
+            $.each( idle.trunk, function(i, elem) {
+
+                var data = elem.data('idle');
+
+                if (! data) {
+                    return;
+                }
+
+                data.complete = false;
+
+                elem.stop().animate(data.to, {
+                    duration: 600,
+                    queue: false,
+                    easing: 'swing'
+                });
+            });
+        },
+
+        showAll : function() {
+            Utils.clearTimer('idle');
+
+            $.each(self._idle.trunk, function( i, elem ) {
+                self._idle.show( elem );
+            });
+        },
+
+        show: function(elem) {
+
+            var data = elem.data('idle');
+
+            if (!data.busy && !data.complete) {
+
+                data.busy = true;
+
+                self.trigger( Galleria.IDLE_EXIT );
+
+                elem.animate(data.from, {
+                    duration: 300,
+                    queue: false,
+                    easing: 'swing',
+                    complete: function() {
+                        $(this).data('idle').busy = false;
+                        $(this).data('idle').complete = true;
+                    }
+                });
+            }
+            idle.addTimer();
+        }
+    };
+
+    // internal lightbox object
+    // creates a predesigned lightbox for simple popups of images in galleria
+    var lightbox = this._lightbox = {
+
+        width : 0,
+
+        height : 0,
+
+        initialized : false,
+
+        active : null,
+
+        image : null,
+
+        elems : {},
+
+        init : function() {
+
+            // trigger the event
+            self.trigger( Galleria.LIGHTBOX_OPEN );
+
+            if ( lightbox.initialized ) {
+                return;
+            }
+            lightbox.initialized = true;
+
+            // create some elements to work with
+            var elems = 'overlay box content shadow title info close prevholder prev nextholder next counter image',
+                el = {},
+                op = self._options,
+                css = '',
+                cssMap = {
+                    overlay:    'position:fixed;display:none;opacity:'+op.overlay_opacity+';top:0;left:0;width:100%;height:100%;background:'+op.overlay_background+';z-index:99990',
+                    box:        'position:fixed;display:none;width:400px;height:400px;top:50%;left:50%;margin-top:-200px;margin-left:-200px;z-index:99991',
+                    shadow:     'position:absolute;background:#000;width:100%;height:100%;',
+                    content:    'position:absolute;background-color:#fff;top:10px;left:10px;right:10px;bottom:10px;overflow:hidden',
+                    info:       'position:absolute;bottom:10px;left:10px;right:10px;color:#444;font:11px/13px arial,sans-serif;height:13px',
+                    close:      'position:absolute;top:10px;right:10px;height:20px;width:20px;background:#fff;text-align:center;cursor:pointer;color:#444;font:16px/22px arial,sans-serif;z-index:99999',
+                    image:      'position:absolute;top:10px;left:10px;right:10px;bottom:30px;overflow:hidden',
+                    prevholder: 'position:absolute;width:50%;height:100%;cursor:pointer',
+                    nextholder: 'position:absolute;width:50%;height:100%;right:0;cursor:pointer',
+                    prev:       'position:absolute;top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;left:20px;display:none;line-height:40px;text-align:center;color:#000',
+                    next:       'position:absolute;top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;right:20px;left:auto;display:none;line-height:40px;text-align:center;color:#000',
+                    title:      'float:left',
+                    counter:    'float:right;margin-left:8px'
+                },
+                hover = function(elem) {
+                    return elem.hover(
+                        function() { $(this).css( 'color', '#bbb' ); },
+                        function() { $(this).css( 'color', '#444' ); }
+                    );
+                };
+
+            // create and insert CSS
+            $.each(cssMap, function( key, value ) {
+                css += '.galleria-lightbox-'+key+'{'+value+'}';
+            });
+
+            Utils.insertStyleTag( css );
+
+            // create the elements
+            $.each(elems.split(' '), function( i, elemId ) {
+                self.addElement( 'lightbox-' + elemId );
+                el[ elemId ] = lightbox.elems[ elemId ] = self.get( 'lightbox-' + elemId );
+            });
+
+            // initiate the image
+            lightbox.image = new Galleria.Picture();
+
+            // append the elements
+            self.append({
+                'lightbox-box': ['lightbox-shadow','lightbox-content', 'lightbox-close','lightbox-prevholder','lightbox-nextholder'],
+                'lightbox-info': ['lightbox-title','lightbox-counter'],
+                'lightbox-content': ['lightbox-info', 'lightbox-image'],
+                'lightbox-prevholder': 'lightbox-prev',
+                'lightbox-nextholder': 'lightbox-next'
+            });
+
+            $( el.image ).append( lightbox.image.container );
+
+            $( DOM().body ).append( el.overlay, el.box );
+
+            // add the prev/next nav and bind some controls
+
+            hover( $( el.close ).bind( CLICK(), lightbox.hide ).html('&#215;') );
+
+            $.each( ['Prev','Next'], function(i, dir) {
+
+                var $d = $( el[ dir.toLowerCase() ] ).html( /v/.test( dir ) ? '‹&nbsp;' : '&nbsp;›' );
+
+                $( el[ dir.toLowerCase()+'holder'] ).hover(function() {
+                    $d.show();
+                }, function() {
+                    $d.fadeOut( 200 );
+                }).bind( CLICK(), function() {
+                    lightbox[ 'show' + dir ]();
+                });
+
+            });
+            $( el.overlay ).bind( CLICK(), lightbox.hide );
+
+        },
+
+        rescale: function(event) {
+
+            // calculate
+            var width = Math.min( $(window).width(), lightbox.width ),
+                height = Math.min( $(window).height(), lightbox.height ),
+                ratio = Math.min( (width - 60) / lightbox.width, (height - 80) / lightbox.height ),
+                destWidth = ( lightbox.width * ratio ) + 40,
+                destHeight = ( lightbox.height * ratio ) + 60,
+                to = {
+                    width: destWidth,
+                    height: destHeight,
+                    marginTop: Math.ceil( destHeight / 2 ) *- 1,
+                    marginLeft: Math.ceil( destWidth / 2 ) *- 1
+                };
+
+            // if rescale event, don't animate
+            if ( event ) {
+                $( lightbox.elems.box ).css( to );
+            } else {
+                $( lightbox.elems.box ).animate(
+                    to,
+                    self._options.lightbox_transition_speed,
+                    self._options.easing,
+                    function() {
+                        var image = lightbox.image,
+                            speed = self._options.lightbox_fade_speed;
+
+                        self.trigger({
+                            type: Galleria.LIGHTBOX_IMAGE,
+                            imageTarget: image.image
+                        });
+
+                        image.show();
+                        Utils.show( image.image, speed );
+                        Utils.show( lightbox.elems.info, speed );
+                    }
+                );
+            }
+        },
+
+        hide: function() {
+
+            // remove the image
+            lightbox.image.image = null;
+
+            $(window).unbind('resize', lightbox.rescale);
+
+            $( lightbox.elems.box ).hide();
+
+            Utils.hide( lightbox.elems.info );
+
+            Utils.hide( lightbox.elems.overlay, 200, function() {
+                $( this ).hide().css( 'opacity', self._options.overlay_opacity );
+                self.trigger( Galleria.LIGHTBOX_CLOSE );
+            });
+        },
+
+        showNext: function() {
+            lightbox.show( self.getNext( lightbox.active ) );
+        },
+
+        showPrev: function() {
+            lightbox.show( self.getPrev( lightbox.active ) );
+        },
+
+        show: function(index) {
+
+            lightbox.active = index = typeof index == 'number' ? index : self.getIndex();
+
+            if ( !lightbox.initialized ) {
+                lightbox.init();
+            }
+
+            $(window).unbind('resize', lightbox.rescale );
+
+            var data = self.getData(index),
+                total = self.getDataLength();
+
+            Utils.hide( lightbox.elems.info );
+
+            lightbox.image.load( data.image, function( image ) {
+
+                lightbox.width = image.original.width;
+                lightbox.height = image.original.height;
+
+                $( image.image ).css({
+                    width: '100.5%',
+                    height: '100.5%',
+                    top: 0,
+                    zIndex: 99998,
+                    opacity: 0
+                });
+
+                lightbox.elems.title.innerHTML = data.title;
+                lightbox.elems.counter.innerHTML = (index + 1) + ' / ' + total;
+                $(window).resize( lightbox.rescale );
+                lightbox.rescale();
+            });
+
+            $( lightbox.elems.overlay ).show();
+            $( lightbox.elems.box ).show();
+        }
+    };
+
+    return this;
+};
+
+// end Galleria constructor
+
+Galleria.prototype = {
+
+    /**
+        Use this function to initialize the gallery and start loading.
+        Should only be called once per instance.
+
+        @param {HTML Element} target The target element
+        @param {Object} options The gallery options
+
+        @returns {Galleria}
+    */
+
+    init: function( target, options ) {
+
+        var self = this;
+
+        // save the instance
+        _galleries.push( this );
+
+        // save the original ingredients
+        this._original = {
+            target: target,
+            options: options,
+            data: null
+        };
+
+        // save the target here
+        this._target = this._dom.target = target.nodeName ? target : $( target ).get(0);
+
+        // raise error if no target is detected
+        if ( !this._target ) {
+             Galleria.raise('Target not found.');
+             return;
+        }
+
+        // apply options
+        this._options = {
+            autoplay: false,
+            carousel: true,
+            carousel_follow: true,
+            carousel_speed: 400,
+            carousel_steps: 'auto',
+            clicknext: false,
+            data_config : function( elem ) { return {}; },
+            data_selector: 'img',
+            data_source: this._target,
+            debug: undef,
+            easing: 'galleria',
+            extend: function(options) {},
+            height: 'auto',
+            idle_time: 3000,
+            image_crop: false,
+            image_margin: 0,
+            image_pan: false,
+            image_pan_smoothness: 12,
+            image_position: '50%',
+            keep_source: false,
+            lightbox_fade_speed: 200,
+            lightbox_transition_speed: 500,
+            link_source_images: true,
+            max_scale_ratio: undef,
+            min_scale_ratio: undef,
+            on_image: function(img,thumb) {},
+            overlay_opacity: .85,
+            overlay_background: '#0b0b0b',
+            pause_on_interaction: true, // 1.9.96
+            popup_links: false,
+            preload: 2,
+            queue: true,
+            show: 0,
+            show_info: true,
+            show_counter: true,
+            show_imagenav: true,
+            thumb_crop: true,
+            thumb_event_type: CLICK(),
+            thumb_fit: true,
+            thumb_margin: 0,
+            thumb_quality: 'auto',
+            thumbnails: true,
+            transition: 'fade',
+            transition_initial: undef,
+            transition_speed: 400,
+            width: 'auto'
+        };
+
+        // apply debug
+        if ( options && options.debug === true ) {
+            DEBUG = true;
+        }
+
+        // hide all content
+        $( this._target ).children().hide();
+
+        // now we just have to wait for the theme...
+        if ( Galleria.theme ) {
+            this._init();
+        } else {
+            Utils.addTimer('themeload', function() {
+                Galleria.raise( 'No theme found.', true);
+            }, 2000);
+
+            $doc.one( Galleria.THEMELOAD, function() {
+                Utils.clearTimer( 'themeload' );
+                self._init.call( self );
+            });
+        }
+    },
+
+    // the internal _init is called when the THEMELOAD event is triggered
+    // this method should only be called once per instance
+    // for manipulation of data, use the .load method
+
+    _init: function() {
+        var self = this;
+
+        if ( this._initialized ) {
+            Galleria.raise( 'Init failed: Gallery instance already initialized.' );
+            return this;
+        }
+
+        this._initialized = true;
+
+        if ( !Galleria.theme ) {
+            Galleria.raise( 'Init failed: No theme found.' );
+            return this;
+        }
+
+        // merge the theme & caller options
+        $.extend( true, this._options, Galleria.theme.defaults, this._original.options );
+
+        // bind the gallery to run when data is ready
+        this.bind( Galleria.DATA, function() {
+
+            // save the new data
+            this._original.data = this._data;
+
+            // lets show the counter here
+            this.get('total').innerHTML = this.getDataLength();
+
+            // cache the container
+            var $container = this.$( 'container' );
+
+            // the gallery is ready, let's just wait for the css
+            var num = { width: 0, height: 0 };
+            var testElem =  Utils.create('galleria-image');
+
+            // check container and thumbnail height
+            Utils.wait({
+                until: function() {
+
+                    // keep trying to get the value
+                    $.each(['width', 'height'], function( i, m ) {
+
+                        if (self._options[ m ] && typeof self._options[ m ] == 'number') {
+                            num[ m ] = self._options[ m ];
+                        } else {
+                            num[m] = Utils.parseValue( self.$( 'target' ).css( m ) ) ||
+                                     Utils.parseValue( $container.css( m ) ) ||
+                                     self.$( 'target' )[ m ]() ||
+                                     $container[ m ]()
+                        }
+
+                    });
+
+                    var thumbHeight = function() {
+                        return true;
+                    };
+
+                    // make sure thumbnails have a height as well
+                    if ( self._options.thumbnails ) {
+                        self.$('thumbnails').append( testElem );
+                        thumbHeight = function() {
+                            return !!$( testElem ).height();
+                        };
+                    }
+
+                    return thumbHeight() && num.width && num.height > 50;
+
+                },
+                success: function() {
+
+                    // remove the testElem
+                    $( testElem ).remove();
+
+                    // apply the new meassures
+                    $container.width( num.width );
+                    $container.height( num.height );
+
+                    // for some strange reason, webkit needs a single setTimeout to play ball
+                    if ( Galleria.WEBKIT ) {
+                        window.setTimeout( function() {
+                            self._run();
+                        }, 1);
+                    } else {
+                        self._run();
+                    }
+                },
+                error: function() {
+                    // Height was probably not set, raise a hard error
+                    Galleria.raise('Width & Height not found.', true);
+                },
+                timeout: 2000
+            });
+        });
+
+        // postrun some stuff after the gallery is ready
+        // make sure it only runs once
+        var one = false;
+
+        this.bind( Galleria.READY, function() {
+
+            // show counter
+            Utils.show( this.get('counter') );
+
+            // bind clicknext
+            if ( this._options.clicknext ) {
+                $.each( this._data, function( i, data ) {
+                    delete data.link;
+                });
+                this.$( 'stage' ).css({ cursor : 'pointer' }).bind( CLICK(), function(e) {
+                    self.next();
+                });
+            }
+
+            // bind carousel nav
+            if ( this._options.carousel ) {
+                this._carousel.bindControls();
+            }
+
+            // start autoplay
+            if ( this._options.autoplay ) {
+
+                this.pause();
+
+                if ( typeof this._options.autoplay == 'number' ) {
+                    this._playtime = this._options.autoplay;
+                }
+
+                this.trigger( Galleria.PLAY );
+                this._playing = true;
+            }
+
+            // if second load, just do the show and return
+            if ( one ) {
+                if ( typeof this._options.show == 'number' ) {
+                    this.show( this._options.show );
+                }
+                return;
+            }
+
+            one = true;
+
+            // initialize the History plugin
+            if ( Galleria.History ) {
+
+                // bind the show method
+                Galleria.History.change(function(e) {
+
+                    // grab history ID
+                    var val = parseInt( e.value.replace( /\//, '' ) );
+
+                    // if ID is NaN, the user pressed back from the first image
+                    // return to previous address
+                    if (isNaN(val)) {
+                        window.history.go(-1);
+
+                    // else show the image
+                    } else {
+                        self.show( val, undef, true );
+                    }
+                });
+            }
+
+            // call the theme init method
+            Galleria.theme.init.call( this, this._options );
+
+            // call the extend option
+            this._options.extend.call( this, this._options );
+
+            // show the initial image
+            // first test for permalinks in history
+            if ( /^[0-9]{1,4}$/.test( HASH ) && Galleria.History ) {
+                this.show( HASH, undef, true );
+
+            } else {
+                this.show( this._options.show );
+            }
+
+        });
+
+        // build the gallery frame
+        this.append({
+            'info-text' :
+                ['info-title', 'info-description', 'info-author'],
+            'info' :
+                ['info-text'],
+            'image-nav' :
+                ['image-nav-right', 'image-nav-left'],
+            'stage' :
+                ['images', 'loader', 'counter', 'image-nav'],
+            'thumbnails-list' :
+                ['thumbnails'],
+            'thumbnails-container' :
+                ['thumb-nav-left', 'thumbnails-list', 'thumb-nav-right'],
+            'container' :
+                ['stage', 'thumbnails-container', 'info', 'tooltip']
+        });
+
+        Utils.hide( this.$( 'counter' ).append(
+            this.get( 'current' ),
+            ' / ',
+            this.get( 'total' )
+        ) );
+
+        this.setCounter('&#8211;');
+
+        // add images to the controls
+        $.each( new Array(2), function(i) {
+
+            // create a new Picture instance
+            var image = new Galleria.Picture();
+
+            // apply some styles
+            $( image.container ).css({
+                position: 'absolute',
+                top: 0,
+                left: 0
+            });
+
+            // append the image
+            self.$( 'images' ).append( image.container );
+
+            // reload the controls
+            self._controls[i] = image;
+
+        });
+
+        // some forced generic styling
+        this.$( 'images' ).css({
+            position: 'relative',
+            top: 0,
+            left: 0,
+            width: '100%',
+            height: '100%'
+        });
+
+        this.$( 'thumbnails, thumbnails-list' ).css({
+            overflow: 'hidden',
+            position: 'relative'
+        });
+
+        // bind image navigation arrows
+        this.$( 'image-nav-right, image-nav-left' ).bind( CLICK(), function(e) {
+
+            // tune the clicknext option
+            if ( self._options.clicknext ) {
+                e.stopPropagation();
+            }
+
+            // pause if options is set
+            if ( self._options.pause_on_interaction ) {
+                self.pause();
+            }
+
+            // navigate
+            var fn = /right/.test( this.className ) ? 'next' : 'prev';
+            self[ fn ]();
+
+        });
+
+        // hide controls if chosen to
+        $.each( ['info','counter','image-nav'], function( i, el ) {
+            if ( self._options[ 'show_' + el.replace(/-/, '') ] === false ) {
+                Utils.moveOut( self.get( el ) );
+            }
+        });
+
+        // load up target content
+        this.load();
+
+        // now it's usually safe to remove the content
+        // IE will never stop loading if we remove it, so let's keep it hidden for IE (it's usually fast enough anyway)
+        if ( !this._options.keep_source && !IE ) {
+            this._target.innerHTML = '';
+        }
+
+        // append the gallery frame
+        this.$( 'target' ).append( this.get( 'container' ) );
+
+        // parse the carousel on each thumb load
+        if ( this._options.carousel ) {
+            this.bind( Galleria.THUMBNAIL, function() {
+                this.updateCarousel();
+            });
+        }
+
+        // bind on_image helper
+        this.bind( Galleria.IMAGE, function( e ) {
+            this._options.on_image.call( this, e.imageTarget, e.thumbTarget );
+        });
+
+        return this;
+    },
+
+    // the internal _run method should be called after loading data into galleria
+    // creates thumbnails and makes sure the gallery has proper meassurements
+    _run : function() {
+        // shortcuts
+        var self = this,
+            o = this._options,
+
+            // width/height for calculations
+            width  = 0,
+            height = 0,
+
+            // cache the thumbnail option
+            optval = typeof o.thumbnails == 'string' ? o.thumbnails.toLowerCase() : null;
+
+        // loop through data and create thumbnails
+        for( var i = 0; this._data[i]; i++ ) {
+
+            var thumb,
+                data = this._data[i],
+                $container;
+
+            if ( o.thumbnails === true ) {
+
+                // add a new Picture instance
+                thumb = new Galleria.Picture(i);
+
+                // get source from thumb or image
+                var src = data.thumb || data.image;
+
+                // append the thumbnail
+                this.$( 'thumbnails' ).append( thumb.container );
+
+                // cache the container
+                $container = $( thumb.container );
+
+                // move some data into the instance
+                // for some reason, jQuery cant handle css(property) when zooming in FF, breaking the gallery
+                // so we resort to getComputedStyle for browsers who support it
+                var getStyle = function( prop ) {
+                    return doc.defaultView && doc.defaultView.getComputedStyle ?
+                        doc.defaultView.getComputedStyle( thumb.container, null )[ prop ] :
+                        $container.css( prop );
+                };
+
+                thumb.data = {
+                    width  : Utils.parseValue( getStyle( 'width' ) ),
+                    height : Utils.parseValue( getStyle( 'height' ) ),
+                    order  : i
+                };
+
+                // grab & reset size for smoother thumbnail loads
+                $container.css(( o.thumb_fit && o.thumb_crop !== true ) ?
+                    { width: 0, height: 0 } :
+                    { width: thumb.data.width, height: thumb.data.height });
+
+                // load the thumbnail
+                thumb.load( src, function( thumb ) {
+
+                    // scale when ready
+                    thumb.scale({
+                        width:    thumb.data.width,
+                        height:   thumb.data.height,
+                        crop:     o.thumb_crop,
+                        margin:   o.thumb_margin,
+                        complete: function( thumb ) {
+
+                            // shrink thumbnails to fit
+                            var top = ['left', 'top'];
+                            var arr = ['Width', 'Height'];
+
+                            // calculate shrinked positions
+                            $.each(arr, function( i, meassure ) {
+                                var m = meassure.toLowerCase();
+                                if ( (o.thumb_crop !== true || o.thumb_crop == m ) && o.thumb_fit ) {
+                                    var css = {};
+                                    css[m] = thumb[m];
+                                    $( thumb.container ).css( css );
+                                    css = {};
+                                    css[top[i]] = 0;
+                                    $( thumb.image ).css( css);
+                                }
+
+                                // cache outer meassures
+                                thumb['outer' + meassure] = $( thumb.container )['outer' + meassure]( true );
+                            });
+
+                            // set high quality if downscale is moderate
+                            Utils.toggleQuality( thumb.image,
+                                o.thumb_quality === true ||
+                                ( o.thumb_quality == 'auto' && thumb.original.width < thumb.width * 3 )
+                            );
+
+                            // trigger the THUMBNAIL event
+                            self.trigger({
+                                type: Galleria.THUMBNAIL,
+                                thumbTarget: thumb.image,
+                                index: thumb.data.order
+                            });
+                        }
+                    });
+                });
+
+                // preload all images here
+                if ( o.preload == 'all' ) {
+                    thumb.add( data.image );
+                }
+
+            // create empty spans if thumbnails is set to 'empty'
+            } else if ( optval == 'empty' || optval == 'numbers' ) {
+
+                thumb = {
+                    container:  Utils.create( 'galleria-image' ),
+                    image: Utils.create( 'img', 'span' ),
+                    ready: true
+                };
+
+                // create numbered thumbnails
+                if ( optval == 'numbers' ) {
+                    $( thumb.image ).text( i + 1 );
+                }
+
+                $( thumb.container ).append( thumb.image );
+                this.$( 'thumbnails' ).append( thumb.container );
+
+                self.trigger({
+                    type: Galleria.THUMBNAIL,
+                    thumbTarget: thumb.image,
+                    index: i
+                });
+
+            // create null object to silent errors
+            } else {
+                thumb = {
+                    container: null,
+                    image: null
+                };
+            }
+
+            // add events for thumbnails
+            // you can control the event type using thumb_event_type
+            // we'll add the same event to the source if it's kept
+
+            $( thumb.container ).add( o.keep_source && o.link_source_images ? data.original : null )
+                .data('index', i).bind(o.thumb_event_type, function(e) {
+                    // pause if option is set
+                    if ( o.pause_on_interaction ) {
+                        self.pause();
+                    }
+
+                    // extract the index from the data
+                    var index = $( e.currentTarget ).data( 'index' );
+                    if ( self.getIndex() !== index ) {
+                        self.show( index );
+                    }
+
+                    e.preventDefault();
+            });
+
+            this._thumbnails.push( thumb );
+        }
+
+        // make sure we have a stageHeight && stageWidth
+
+        Utils.wait({
+
+            until: function() {
+                self._stageWidth  = self.$( 'stage' ).width();
+                self._stageHeight = self.$( 'stage' ).height();
+                return( self._stageWidth && self._stageHeight > 50 ); // what is an acceptable height?
+            },
+
+            success: function() {
+                self.trigger( Galleria.READY );
+            },
+
+            error: function() {
+                Galleria.raise('stage meassures not found');
+            }
+
+        });
+    },
+
+    /**
+        Loads data into the gallery.
+        You can call this method on an existing gallery to reload the gallery with new data.
+
+        @param {Array or String} source Optional JSON array of data or selector of where to find data in the document.
+        Defaults to the Galleria target or data_source option.
+
+        @param {String} selector Optional element selector of what elements to parse.
+        Defaults to 'img'.
+
+        @param {Function} config Optional function to modify the data extraction proceedure from the selector.
+        See the data_config option for more information.
+
+        @returns {Galleria}
+    */
+
+    load : function( source, selector, config ) {
+
+        var self = this;
+
+        // empty the data array
+        this._data = [];
+
+        // empty the thumbnails
+        this._thumbnails = [];
+        this.$('thumbnails').empty();
+
+        // shorten the arguments
+        if ( typeof selector == 'function' ) {
+            config = selector;
+            selector = null;
+        }
+
+        // use the source set by target
+        source = source || this._options.data_source;
+
+        // use selector set by option
+        selector = selector || this._options.data_selector;
+
+        // use the data_config set by option
+        config = config || this._options.data_config;
+
+        // check if the data is an array already
+        if ( source.constructor == Array ) {
+            if ( this.validate( source) ) {
+                this._data = source;
+                this.trigger( Galleria.DATA );
+            } else {
+                Galleria.raise( 'Load failed: JSON Array not valid.' );
+            }
+            return this;
+        }
+        // loop through images and set data
+        $( source ).find( selector ).each( function( i, img ) {
+            var data = {},
+                img = $( img ),
+                parent = img.parent(),
+                href = parent.attr( 'href' );
+
+            // check if it's a link to another image
+            if ( /\.(png|gif|jpg|jpeg)$/i.test(href) ) {
+                data.image = href;
+
+            // else assign the href as a link if it exists
+            } else if ( href ) {
+                data.link = href;
+            }
+
+            // mix default extractions with the hrefs and config
+            // and push it into the data array
+            self._data.push( $.extend({
+
+                title:       img.attr('title'),
+                thumb:       img.attr('src'),
+                image:       img.attr('src'),
+                description: img.attr('alt'),
+                link:        img.attr('longdesc'),
+                original:    img.get(0) // saved as a reference
+
+            }, data, config( img ) ) );
+
+        });
+        // trigger the DATA event and return
+        if ( this.getDataLength() ) {
+            this.trigger( Galleria.DATA );
+        } else {
+            Galleria.raise('Load failed: no data found.');
+        }
+        return this;
+
+    },
+
+    _getActive: function() {
+        return this._controls.getActive();
+    },
+
+    validate : function( data ) {
+        // todo: validate a custom data array
+        return true;
+    },
+
+    /**
+        Bind any event to Galleria
+
+        @param {String} type The Event type to listen for
+        @param {Function} fn The function to execute when the event is triggered
+
+        @example this.bind( Galleria.IMAGE, function() { Galleria.log('image shown') });
+
+        @returns {Galleria}
+    */
+
+    bind : function(type, fn) {
+        this.$( 'container' ).bind( type, this.proxy(fn) );
+        return this;
+    },
+
+    /**
+        Unbind any event to Galleria
+
+        @param {String} type The Event type to forget
+
+        @returns {Galleria}
+    */
+
+    unbind : function(type) {
+        this.$( 'container' ).unbind( type );
+        return this;
+    },
+
+    /**
+        Manually trigger a Galleria event
+
+        @param {String} type The Event to trigger
+
+        @returns {Galleria}
+    */
+
+    trigger : function( type ) {
+        type = typeof type == 'object' ?
+            $.extend( type, { scope: this } ) :
+            { type: type, scope: this };
+        this.$( 'container' ).trigger( type );
+        return this;
+    },
+
+    /**
+        Assign an "idle state" to any element.
+        The idle state will be applied after a certain amount of idle time
+        Useful to hide f.ex navigation when the gallery is inactive
+
+        @param {HTML Element or String} elem The Dom node or selector to apply the idle state to
+        @param {Object} styles the CSS styles to apply
+
+        @example addIdleState( this.get('image-nav'), { opacity: 0 });
+        @example addIdleState( '.galleria-image-nav', { top: -200 });
+
+        @returns {Galleria}
+    */
+
+    addIdleState: function( elem, styles ) {
+        this._idle.add.apply( this._idle, Utils.array( arguments ) );
+        return this;
+    },
+
+    /**
+        Removes any idle state previously set using addIdleState()
+
+        @param {HTML Element or String} elem The Dom node or selector to remove the idle state from.
+
+        @returns {Galleria}
+    */
+
+    removeIdleState: function( elem ) {
+        this._idle.remove.apply( this._idle, Utils.array( arguments ) );
+        return this;
+    },
+
+    /**
+        Force Galleria to enter idle mode.
+
+        @returns {Galleria}
+    */
+
+    enterIdleMode: function() {
+        this._idle.hide();
+        return this;
+    },
+
+    /**
+        Force Galleria to exit idle mode.
+
+        @returns {Galleria}
+    */
+
+    exitIdleMode: function() {
+        this.idle._show();
+        return this;
+    },
+
+    /**
+        Enter FullScreen mode
+
+        @param {Function} callback the function to be executed when the fullscreen mode is fully applied.
+
+        @returns {Galleria}
+    */
+
+    enterFullscreen: function( callback ) {
+        this._fullscreen.enter.apply( this, Utils.array( arguments ) );
+        return this;
+    },
+
+    /**
+        Exits FullScreen mode
+
+        @param {Function} callback the function to be executed when the fullscreen mode is fully applied.
+
+        @returns {Galleria}
+    */
+
+    exitFullscreen: function( callback ) {
+        this._fullscreen.exit.apply( this, Utils.array( arguments ) );
+        return this;
+    },
+
+    /**
+        Adds a tooltip to any element.
+        You can also call this method with an object as argument with elemID:value pairs to apply tooltips to (see examples)
+
+        @param {HTML Element} elem The DOM Node to attach the event to
+        @param {String or Function} value The tooltip message. Can also be a function that returns a string.
+
+        @example this.bindTooltip( this.get('thumbnails'), 'My thumbnails');
+        @example this.bindTooltip( this.get('thumbnails'), function() { return 'My thumbs' });
+        @example this.bindTooltip( { image_nav: 'Navigation' });
+
+        @returns {Galleria}
+    */
+
+    bindTooltip: function( elem, value ) {
+        this._tooltip.bind.apply( this._tooltip, Utils.array(arguments) );
+        return this;
+    },
+
+    /**
+        Redefine a tooltip
+        Use this if you want to change the tooltip value at runtime
+
+        @param {HTML Element} elem The DOM Node to attach the event to
+        @param {String or Function} value The tooltip message. Can also be a function that returns a string.
+
+        @returns {Galleria}
+    */
+
+    defineTooltip: function( elem, value ) {
+        this._tooltip.define.apply( this._tooltip, Utils.array(arguments) );
+        return this;
+    },
+
+    // leave this out of the API for now
+
+    refreshTooltip: function() {
+        this._tooltip.refresh.apply( this._tooltip, Utils.array(arguments) );
+        return this;
+    },
+
+    /**
+        Open a pre-designed lightbox with the currently active image.
+        You can control some visuals using gallery options.
+
+        @returns {Galleria}
+    */
+
+    openLightbox: function() {
+        this._lightbox.show.apply( this._lightbox, Utils.array( arguments ) );
+        return this;
+    },
+
+    /**
+        Close the lightbox.
+
+        @returns {Galleria}
+    */
+
+    closeLightbox: function() {
+        this._lightbox.hide.apply( this._lightbox, Utils.array( arguments ) );
+        return this;
+    },
+
+    /**
+        Get the currently active image element.
+
+        @returns {HTML Element} The image element
+    */
+
+    getActiveImage: function() {
+        return this._getActive().image || undef;
+    },
+
+    /**
+        Get the currently active thumbnail element.
+
+        @returns {HTML Element} The thumbnail element
+    */
+
+    getActiveThumb: function() {
+        return this._thumbnails[ this._active ].image || undef;
+    },
+
+    /**
+        Get the mouse position relative to the gallery container
+
+        @param e The mouse event
+
+        @example
+
+var gallery = this;
+$(document).mousemove(function(e) {
+    console.log( gallery.getMousePosition(e).x );
+});
+
+        @returns {Object} Object with x & y of the relative mouse postion
+    */
+
+    getMousePosition : function(e) {
+        return {
+            x: e.pageX - this.$( 'stage' ).offset().left,
+            y: e.pageY - this.$( 'stage' ).offset().top
+        };
+    },
+
+    /**
+        Adds a panning effect to the image
+
+        @param img The optional image element. If not specified it takes the currently active image
+
+        @returns {Galleria}
+    */
+
+    addPan : function( img ) {
+
+        if ( this._options.image_crop === false ) {
+            return;
+        }
+
+        img = $( img || this.getActiveImage() );
+
+        // define some variables and methods
+        var self   = this,
+            x      = img.width() / 2,
+            y      = img.height() / 2,
+            curX   = destX = parseInt( img.css( 'left' ) ) || 0,
+            curY   = destY = parseInt( img.css( 'top' ) ) || 0,
+            distX  = 0,
+            distY  = 0,
+            active = false,
+            ts     = Utils.timestamp(),
+            cache  = 0,
+            move   = 0,
+
+            // positions the image
+            position = function( dist, cur, pos ) {
+                if ( dist > 0 ) {
+                    move = Math.round( Math.max( dist * -1, Math.min( 0, cur ) ) );
+                    if ( cache != move ) {
+
+                        cache = move;
+
+                        if ( IE == 8 ) { // scroll is faster for IE
+                            img.parent()[ 'scroll' + pos ]( move * -1 );
+                        } else {
+                            var css = {};
+                            css[ pos.toLowerCase() ] = move;
+                            img.css(css);
+                        }
+                    }
+                }
+            },
+
+            // calculates mouse position after 50ms
+            calculate = function(e) {
+                if (Utils.timestamp() - ts < 50) {
+                    return;
+                }
+                active = true;
+                x = self.getMousePosition(e).x;
+                y = self.getMousePosition(e).y;
+            },
+
+            // the main loop to check
+            loop = function(e) {
+
+                if (!active) {
+                    return;
+                }
+
+                distX = img.width() - self._stageWidth;
+                distY = img.height() - self._stageHeight;
+                destX = x / self._stageWidth * distX * -1;
+                destY = y / self._stageHeight * distY * -1;
+                curX += ( destX - curX ) / self._options.image_pan_smoothness;
+                curY += ( destY - curY ) / self._options.image_pan_smoothness;
+
+                position( distY, curY, 'Top' );
+                position( distX, curX, 'Left' );
+
+            };
+
+        // we need to use scroll in IE8 to speed things up
+        if ( IE == 8 ) {
+
+            img.parent().scrollTop( curY * -1 ).scrollLeft( curX * -1 );
+            img.css({
+                top: 0,
+                left: 0
+            });
+
+        }
+
+        // unbind and bind event
+        this.$( 'stage' ).unbind( 'mousemove', calculate ).bind( 'mousemove', calculate );
+
+        // loop the loop
+        Utils.addTimer('pan', loop, 50, true);
+
+        return this;
+    },
+
+    /**
+        Brings the scope into any callback
+
+        @param fn The callback to bring the scope into
+        @param scope Optional scope to bring
+
+        @example $('#fullscreen').click( this.proxy(function() { this.enterFullscreen(); }) )
+
+        @returns {Function} Return the callback with the gallery scope
+    */
+
+    proxy : function( fn, scope ) {
+        if ( typeof fn !== 'function' ) {
+            return function() {};
+        }
+        scope = scope || this;
+        return function() {
+            return fn.apply( scope, Utils.array( arguments ) );
+        };
+    },
+
+    /**
+        Removes the panning effect set by addPan()
+
+        @returns {Galleria}
+    */
+
+    removePan: function() {
+
+        if ( IE == 8 ) {
+            // todo: doublecheck this
+        }
+        this.$( 'stage' ).unbind( 'mousemove' );
+
+        Utils.clearTimer('pan');
+
+        this.rescale();
+
+        return this;
+    },
+
+    /**
+        Adds an element to the Galleria DOM array.
+        When you add an element here, you can access it using element ID in many API calls
+
+        @param {String} id The element ID you wish to use. You can add many elements by adding more arguments.
+
+        @example addElement('mybutton');
+        @example addElement('mybutton','mylink');
+
+        @returns {Galleria}
+    */
+
+    addElement : function( id ) {
+
+        var dom = this._dom;
+
+        $.each( Utils.array(arguments), function( i, blueprint ) {
+           dom[ blueprint ] = Utils.create( 'galleria-' + blueprint );
+        });
+
+        return this;
+    },
+
+    /**
+        Attach keyboard events to Galleria
+
+        @param {Object} map The map object of events.
+        Possible keys are 'UP', 'DOWN', 'LEFT', 'RIGHT', 'RETURN', 'ESCAPE', 'BACKSPACE', and 'SPACE'.
+
+        @example
+
+this.attachKeyboard({
+    right: this.next,
+    left: this.prev,
+    up: function() {
+        console.log( 'up key pressed' )
+    }
+});
+
+        @returns {Galleria}
+    */
+
+    attachKeyboard : function( map ) {
+        this._keyboard.attach.apply( this._keyboard, Utils.array( arguments ) );
+        return this;
+    },
+
+    /**
+        Detach all keyboard events to Galleria
+
+        @returns {Galleria}
+    */
+
+    detachKeyboard : function() {
+        this._keyboard.detach.apply( this._keyboard, Utils.array( arguments ) );
+        return this;
+    },
+
+    /**
+        Fast helper for appending galleria elements that you added using addElement()
+
+        @param {String} parentID The parent element ID where the element will be appended
+        @param {String} childID the element ID that should be appended
+
+        @example this.addElement('myElement');
+        this.appendChild( 'info', 'myElement' );
+
+        @returns {Galleria}
+    */
+
+    appendChild : function( parentID, childID ) {
+        this.$( parentID ).append( this.get( childID ) || childID );
+        return this;
+    },
+
+    /**
+        Fast helper for appending galleria elements that you added using addElement()
+
+        @param {String} parentID The parent element ID where the element will be preppended
+        @param {String} childID the element ID that should be preppended
+
+        @example
+
+this.addElement('myElement');
+this.prependChild( 'info', 'myElement' );
+
+        @returns {Galleria}
+    */
+
+    prependChild : function( parentID, childID ) {
+        this.$( parentID ).prepend( this.get( childID ) || childID );
+        return this;
+    },
+
+    /**
+        Remove an element by blueprint
+
+        @param {String} elemID The element to be removed.
+        You can remove multiple elements by adding arguments.
+
+        @returns {Galleria}
+    */
+
+    remove : function( elemID ) {
+        this.$( Utils.array( arguments ).join(',') ).remove();
+        return this;
+    },
+
+    // a fast helper for building dom structures
+    // leave this out of the API for now
+
+    append : function( data ) {
+        for( var i in data) {
+            if ( data[i].constructor == Array ) {
+                for( var j = 0; data[i][j]; j++ ) {
+                    this.appendChild( i, data[i][j] );
+                }
+            } else {
+                this.appendChild( i, data[i] );
+            }
+        }
+        return this;
+    },
+
+    // an internal helper for scaling according to options
+    _scaleImage : function( image, options ) {
+
+        options = $.extend({
+            width:    this._stageWidth,
+            height:   this._stageHeight,
+            crop:     this._options.image_crop,
+            max:      this._options.max_scale_ratio,
+            min:      this._options.min_scale_ratio,
+            margin:   this._options.image_margin,
+            position: this._options.image_position
+        }, options );
+
+       ( image || this._controls.getActive() ).scale( options );
+
+        return this;
+    },
+
+    /**
+        Updates the carousel,
+        useful if you resize the gallery and want to re-check if the carousel nav is needed.
+
+        @returns {Galleria}
+    */
+
+    updateCarousel : function() {
+        this._carousel.update();
+        return this;
+    },
+
+    /**
+        Rescales the gallery
+
+        @param {Number} width The target width
+        @param {Number} height The target height
+        @param {Function} complete The callback to be called when the scaling is complete
+
+        @returns {Galleria}
+    */
+
+    rescale : function( width, height, complete ) {
+
+        var self = this;
+
+        // allow rescale(fn)
+        if ( typeof width == 'function' ) {
+            complete = width;
+            width = undef;
+        }
+
+        var scale = function() {
+
+            // shortcut
+            var o = self._options;
+
+            // set stagewidth
+            self._stageWidth = width || self.$( 'stage' ).width();
+            self._stageHeight = height || self.$( 'stage' ).height();
+
+            // scale the active image
+            self._scaleImage();
+
+            if ( self._options.carousel ) {
+                self.updateCarousel();
+            }
+
+            self.trigger( Galleria.RESCALE );
+
+            if ( typeof complete == 'function' ) {
+                complete.call( self );
+            }
+        };
+
+        if ( Galleria.WEBKIT && !width && !height ) {
+            Utils.addTimer( 'scale', scale, 5 );// webkit is too fast
+        } else {
+            scale.call( self );
+        }
+
+        return this;
+    },
+
+    /**
+        Refreshes the gallery.
+        Useful if you change image options at runtime and want to apply the changes to the active image.
+
+        @returns {Galleria}
+    */
+
+    refreshImage : function() {
+        this._scaleImage();
+        if ( this._options.image_pan ) {
+            this.addPan();
+        }
+        return this;
+    },
+
+    /**
+        Shows an image by index
+
+        @param {Number} index The index to show
+        @param {Boolean} rewind A boolean that should be true if you want the transition to go back
+
+        @returns {Galleria}
+    */
+
+    show : function( index, rewind, _history ) {
+
+        // do nothing if index is false or queue is false and transition is in progress
+        if ( index === false || !this._options.queue && this._queue.stalled ) {
+            return;
+        }
+        
+        index = Math.max( 0, Math.min( parseInt(index), this.getDataLength() - 1 ) );
+
+        rewind = typeof rewind != 'undefined' ? !!rewind : index < this.getIndex();
+
+        _history = _history || false;
+
+        // do the history thing and return
+        if ( !_history && Galleria.History ) {
+            Galleria.History.value( index.toString() );
+            return;
+        }
+
+        this._active = index;
+
+        Array.prototype.push.call( this._queue, {
+            index : index,
+            rewind : rewind
+        });
+        if ( !this._queue.stalled ) {
+            this._show();
+        }
+
+        return this;
+    },
+
+    // the internal _show method does the actual showing
+    _show : function() {
+
+        // shortcuts
+        var self   = this,
+            queue  = this._queue[ 0 ],
+            data   = this.getData( queue.index );
+        
+        if ( !data ) {
+            return;
+        }
+        
+        var src    = data.image,
+            active = this._controls.getActive(),
+            next   = this._controls.getNext(),
+            cached = next.isCached( src ),
+            thumb  = this._thumbnails[ queue.index ];
+
+            // to be fired when loading & transition is complete:
+        var complete = function() {
+
+            // remove stalled
+            self._queue.stalled = false;
+
+            // optimize quality
+            Utils.toggleQuality( next.image, self._options.image_quality );
+
+            // swap
+            $( active.container ).css({
+                zIndex: 0,
+                opacity: 0
+            });
+            $( next.container ).css({
+                zIndex: 1,
+                opacity: 1
+            });
+            self._controls.swap();
+
+            // add pan according to option
+            if ( self._options.image_pan ) {
+                self.addPan( next.image );
+            }
+
+            // make the image link
+            if ( data.link ) {
+                $( next.image ).css({
+                    cursor: 'pointer'
+                }).bind( CLICK(), function() {
+
+                    // popup link
+                    if ( self._options.popup_links ) {
+                        var win = window.open( data.link, '_blank' );
+                    } else {
+                        window.location.href = data.link;
+                    }
+                });
+            }
+
+            // remove the queued image
+            Array.prototype.shift.call( self._queue );
+
+            // if we still have images in the queue, show it
+            if ( self._queue.length ) {
+                self._show();
+            }
+
+            // check if we are playing
+            self._playCheck();
+
+            // trigger IMAGE event
+            self.trigger({
+                type:        Galleria.IMAGE,
+                index:       queue.index,
+                imageTarget: next.image,
+                thumbTarget: thumb.image
+            });
+        };
+
+        // let the carousel follow
+        if ( this._options.carousel && this._options.carousel_follow ) {
+            this._carousel.follow( queue.index );
+        }
+
+        // preload images
+        if ( this._options.preload ) {
+
+            var p,
+                n = this.getNext();
+
+            try {
+                for ( var i = this._options.preload; i > 0; i-- ) {
+                    p = new Galleria.Picture();
+                    p.add( self.getData( n ).image );
+                    n = self.getNext( n );
+                }
+            } catch(e) {}
+        }
+
+        // show the next image, just in case
+        Utils.show( next.container );
+
+        // add active classes
+        $( self._thumbnails[ queue.index ].container )
+            .addClass( 'active' )
+            .siblings( '.active' )
+            .removeClass( 'active' );
+
+        // trigger the LOADSTART event
+        self.trigger( {

[... 1005 lines stripped ...]


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org