2 * Galleria v 1.1.95 2010-08-06
3 * http://galleria.aino.se
5 * Copyright (c) 2010, Aino
6 * Licensed under the MIT license.
11 var initializing = false,
12 fnTest = /xyz/.test(function(){xyz;}) ? /\b__super\b/ : /.*/,
16 Class.extend = function(prop) {
17 var __super = this.prototype;
19 var proto = new this();
21 for (var name in prop) {
23 proto[name] = typeof prop[name] == "function" &&
24 typeof __super[name] == "function" && fnTest.test(prop[name]) ?
27 var tmp = this.__super;
28 this.__super = __super[name];
29 var ret = fn.apply(this, arguments);
33 })(name, prop[name]) : prop[name];
38 if ( !initializing && this.__constructor ) {
39 this.__constructor.apply(this, arguments);
42 Class.prototype = proto;
43 Class.constructor = Class;
44 Class.extend = arguments.callee;
48 var Base = Class.extend({
49 loop : function( elem, fn) {
51 if (typeof elem == 'number') {
52 elem = new Array(elem);
54 jQuery.each(elem, function() {
55 fn.call(scope, arguments[1], arguments[0]);
59 create : function( elem, className ) {
61 var el = document.createElement(elem);
63 el.className = className;
67 getElements : function( selector ) {
69 this.loop( jQuery(selector), this.proxy(function( elem ) {
70 this.push(elem, elems);
74 setStyle : function( elem, css ) {
75 jQuery(elem).css(css);
78 getStyle : function( elem, styleProp, parse ) {
79 var val = jQuery(elem).css(styleProp);
80 return parse ? this.parseValue( val ) : val;
82 cssText : function( string ) {
83 var style = document.createElement('style');
84 this.getElements('head')[0].appendChild(style);
85 if (style.styleSheet) { // IE
86 style.styleSheet.cssText = string;
88 var cssText = document.createTextNode(string);
89 style.appendChild(cssText);
93 touch : function(el) {
94 var sibling = el.nextSibling;
95 var parent = el.parentNode;
96 parent.removeChild(el);
98 parent.insertBefore(el, sibling);
100 parent.appendChild(el);
102 if (el.styleSheet && el.styleSheet.imports.length) {
103 this.loop(el.styleSheet.imports, function(i) {
104 el.styleSheet.addImport(i.href);
108 loadCSS : function(href, callback) {
109 var exists = this.getElements('link[href="'+href+'"]').length;
114 var link = this.create('link');
115 link.rel = 'stylesheet';
118 if (typeof callback == 'function') {
119 // a new css check method, still experimental...
120 this.wait(function() {
121 return !!document.body;
123 var testElem = this.create('div', 'galleria-container galleria-stage');
124 this.moveOut(testElem);
125 document.body.appendChild(testElem);
126 var getStyles = this.proxy(function() {
129 if (document.defaultView && document.defaultView.getComputedStyle) {
130 props = document.defaultView.getComputedStyle(testElem, "");
131 this.loop(props, function(prop) {
132 str += prop + props.getPropertyValue(prop);
134 } else if (testElem.currentStyle) { // IE
135 props = testElem.currentStyle;
136 this.loop(props, function(val, prop) {
142 var current = getStyles();
143 this.wait(function() {
144 return getStyles() !== current;
146 document.body.removeChild(testElem);
149 G.raise('Could not confirm theme CSS');
153 window.setTimeout(this.proxy(function() {
154 var styles = this.getElements('link[rel="stylesheet"],style');
156 styles[0].parentNode.insertBefore(link, styles[0]);
158 this.getElements('head')[0].appendChild(link);
160 // IE needs a manual touch to re-order the cascade
162 this.loop(styles, function(el) {
169 moveOut : function( elem ) {
170 return this.setStyle(elem, {
171 position: 'absolute',
176 moveIn : function( elem ) {
177 return this.setStyle(elem, {
181 reveal : function( elem ) {
182 return jQuery( elem ).show();
184 hide : function( elem ) {
185 return jQuery( elem ).hide();
188 return jQuery.extend.apply(jQuery, arguments);
190 proxy : function( fn, scope ) {
191 if ( typeof fn !== 'function' ) {
192 return function() {};
194 scope = scope || this;
196 return fn.apply( scope, Array.prototype.slice.call(arguments) );
199 listen : function( elem, type, fn ) {
200 jQuery(elem).bind( type, fn );
202 forget : function( elem, type, fn ) {
203 jQuery(elem).unbind(type, fn);
205 dispatch : function( elem, type ) {
206 jQuery(elem).trigger(type);
208 clone : function( elem, keepEvents ) {
209 keepEvents = keepEvents || false;
210 return jQuery(elem).clone(keepEvents)[0];
212 removeAttr : function( elem, attributes ) {
213 this.loop( attributes.split(' '), function(attr) {
214 jQuery(elem).removeAttr(attr);
217 push : function( elem, obj ) {
218 if (typeof obj.length == 'undefined') {
221 Array.prototype.push.call( obj, elem );
224 width : function( elem, outer ) {
225 return this.meassure(elem, outer, 'Width');
227 height : function( elem, outer ) {
228 return this.meassure(elem, outer, 'Height');
230 meassure : function(el, outer, meassure) {
231 var elem = jQuery( el );
232 var ret = outer ? elem['outer'+meassure](true) : elem[meassure.toLowerCase()]();
235 var which = meassure == "Width" ? [ "left", "right" ] : [ "top", "bottom" ];
236 this.loop(which, function(s) {
237 ret += elem.css('border-' + s + '-width').replace(/[^\d]/g,'') * 1;
238 ret += elem.css('padding-' + s).replace(/[^\d]/g,'') * 1;
243 toggleClass : function( elem, className, arg ) {
244 if (typeof arg !== 'undefined') {
245 var fn = arg ? 'addClass' : 'removeClass';
246 jQuery(elem)[fn](className);
249 jQuery(elem).toggleClass(className);
252 hideAll : function( el ) {
253 jQuery(el).find('*').hide();
255 animate : function( el, options ) {
256 options.complete = this.proxy(options.complete);
257 var elem = jQuery(el);
264 elem.animate(options.to, {
265 duration: options.duration || 400,
266 complete: options.complete,
267 easing: options.easing || 'swing'
270 wait : function(fn, callback, err, max) {
272 callback = this.proxy(callback);
273 err = this.proxy(err);
274 var ts = new Date().getTime() + (max || 3000);
275 window.setTimeout(function() {
280 if (new Date().getTime() >= ts) {
285 window.setTimeout(arguments.callee, 2);
289 loadScript: function(url, callback) {
290 var script = document.createElement('script');
292 script.async = true; // HTML5
297 // Attach handlers for all browsers
298 script.onload = script.onreadystatechange = function() {
299 if ( !done && (!this.readyState ||
300 this.readyState == "loaded" || this.readyState == "complete") ) {
303 if (typeof callback == 'function') {
304 callback.call(scope, this);
307 // Handle memory leak in IE
308 script.onload = script.onreadystatechange = null;
311 var s = document.getElementsByTagName('script')[0];
312 s.parentNode.insertBefore(script, s);
316 parseValue: function(val) {
317 if (typeof val == 'number') {
319 } else if (typeof val == 'string') {
320 var arr = val.match(/\-?\d/g);
321 return arr && arr.constructor == Array ? arr.join('')*1 : 0;
328 var Picture = Base.extend({
329 __constructor : function(order) {
331 this.elem = this.create('div', 'galleria-image');
332 this.setStyle( this.elem, {
334 position: 'relative' // for IE Standards mode
337 this.orig = { w:0, h:0, r:1 };
344 if (this.cache[src]) {
345 return this.cache[src];
347 var image = new Image();
349 this.setStyle(image, {display: 'block'});
350 if (image.complete && image.width) {
351 this.cache[src] = image;
354 image.onload = (function(scope) {
356 scope.cache[src] = image;
362 isCached: function(src) {
363 return this.cache[src] ? this.cache[src].complete : false;
366 make: function(src) {
367 var i = this.cache[src] || this.add(src);
368 return this.clone(i);
371 load: function(src, callback) {
372 callback = this.proxy( callback );
373 this.elem.innerHTML = '';
374 this.image = this.make( src );
375 this.moveOut( this.image );
376 this.elem.appendChild( this.image );
377 this.wait(function() {
378 return (this.image.complete && this.image.width);
381 h: this.h || this.image.height,
382 w: this.w || this.image.width
384 callback( {target: this.image, scope: this} );
386 G.raise('image not loaded in 20 seconds: '+ src);
391 scale: function(options) {
398 complete: function(){},
406 this.wait(function() {
407 width = o.width || this.width(this.elem);
408 height = o.height || this.height(this.elem);
409 return width && height;
411 var nw = (width - o.margin*2) / this.orig.w;
412 var nh = (height- o.margin*2) / this.orig.h;
414 'true': Math.max(nw,nh),
417 'false': Math.min(nw,nh)
419 var ratio = rmap[o.crop.toString()];
421 ratio = Math.min(o.max, ratio);
424 ratio = Math.max(o.min, ratio);
426 this.setStyle(this.elem, {
430 this.image.width = Math.ceil(this.orig.w * ratio);
431 this.image.height = Math.ceil(this.orig.h * ratio);
433 var getPosition = this.proxy(function(value, img, m) {
435 if (/\%/.test(value)) {
436 var pos = parseInt(value) / 100;
437 result = Math.ceil(this.image[img] * -1 * pos + m * pos);
439 result = parseInt(value);
447 'right': { left: '100%' },
448 'bottom': { top: '100%' }
454 this.loop(o.position.toLowerCase().split(' '), function(p, i) {
458 pos[i ? 'top' : 'left'] = p;
461 this.loop(pos, function(val, key) {
462 if (map.hasOwnProperty(val)) {
463 mix = this.mix(mix, map[val]);
467 pos = pos.top ? this.mix(pos, mix) : mix;
474 this.setStyle(this.image, {
475 position : 'relative',
476 top : getPosition(pos.top, 'height', height),
477 left : getPosition(pos.left, 'width', width)
480 o.complete.call(this);
486 var G = window.Galleria = Base.extend({
488 __constructor : function(options) {
489 this.theme = undefined;
490 this.options = options;
491 this.playing = false;
492 this.playtime = 5000;
498 var kb = this.keyboard = {
510 press: this.proxy(function(e) {
511 var key = e.keyCode || e.which;
512 if (kb.map[key] && typeof kb.map[key] == 'function') {
513 kb.map[key].call(this, e);
516 attach: this.proxy(function(map) {
517 for( var i in map ) {
518 var k = i.toUpperCase();
520 kb.map[kb.keys[k]] = map[i];
525 this.listen(document, 'keydown', kb.press);
528 detach: this.proxy(function() {
530 this.forget(document, 'keydown', kb.press);
536 add: function(id, fn, delay, loop) {
537 loop = loop || false;
544 self.add(id,fn,delay);
547 this.trunk[id] = window.setTimeout(fn,delay);
549 clear: function(id) {
550 if (id && this.trunk[id]) {
551 window.clearTimeout(this.trunk[id]);
552 delete this.trunk[id];
553 } else if (typeof id == 'undefined') {
554 for (var i in this.trunk) {
555 window.clearTimeout(this.trunk[i]);
556 delete this.trunk[i];
567 this.active = this.active ? 0 : 1;
569 getActive : function() {
570 return this[this.active];
572 getNext : function() {
573 return this[Math.abs(this.active - 1)];
577 var fs = this.fullscreen = {
579 enter: this.proxy(function() {
580 this.toggleClass( this.get('container'), 'fullscreen');
581 fs.scrolled = jQuery(window).scrollTop();
582 this.loop(fs.getElements(), function(el, i) {
583 fs.styles[i] = el.getAttribute('style');
584 el.removeAttribute('style');
586 this.setStyle(fs.getElements(0), {
600 this.setStyle( fs.getElements(1), bh );
601 this.setStyle( fs.getElements(2), bh );
602 this.attachKeyboard({
603 escape: this.exitFullscreen,
607 this.rescale(this.proxy(function() {
608 this.trigger(G.FULLSCREEN_ENTER);
610 this.listen(window, 'resize', fs.scale);
612 scale: this.proxy(function() {
615 exit: this.proxy(function() {
616 this.toggleClass( this.get('container'), 'fullscreen', false);
617 if (!fs.styles.length) {
620 this.loop(fs.getElements(), function(el, i) {
621 el.removeAttribute('style');
622 el.setAttribute('style', fs.styles[i]);
624 window.scrollTo(0, fs.scrolled);
625 this.detachKeyboard();
626 this.rescale(this.proxy(function() {
627 this.trigger(G.FULLSCREEN_EXIT);
629 this.forget(window, 'resize', fs.scale);
632 getElements: this.proxy(function(i) {
633 var elems = [ this.get('container'), document.body, this.getElements('html')[0] ];
634 return i ? elems[i] : elems;
638 var idle = this.idle = {
641 add: this.proxy(function(elem, styles, fn) {
650 for (var style in styles) {
651 orig[style] = elem.css(style);
661 idle.trunk.push(elem);
663 remove: this.proxy(function(elem) {
665 this.loop(idle.trunk, function(el, i) {
666 if ( el && !el.not(elem).length ) {
668 idle.trunk.splice(i,1);
671 if (!idle.trunk.length) {
673 this.clearTimer('idle');
676 addEvent: this.proxy(function() {
678 this.listen( this.get('container'), 'mousemove click', idle.showAll );
680 removeEvent: this.proxy(function() {
682 this.forget( this.get('container'), 'mousemove click', idle.showAll );
684 addTimer: this.proxy(function() {
685 this.addTimer('idle', this.proxy(function() {
687 }),this.options.idle_time);
689 hide: this.proxy(function() {
690 this.trigger(G.IDLE_ENTER);
691 this.loop(idle.trunk, function(elem) {
692 var data = elem.data('idle');
693 data.complete = false;
695 elem.animate(data.to, {
702 showAll: this.proxy(function() {
703 this.clearTimer('idle');
704 this.loop(idle.trunk, function(elem) {
708 show: this.proxy(function(elem) {
709 var data = elem.data('idle');
710 if (!data.busy && !data.complete) {
712 this.trigger(G.IDLE_EXIT);
713 elem.animate(data.from, {
717 complete: function() {
718 $(this).data('idle').busy = false;
719 $(this).data('idle').complete = true;
727 var lightbox = this.lightbox = {
732 init: this.proxy(function() {
733 if (lightbox.initialized) {
736 lightbox.initialized = true;
737 var elems = 'lightbox-overlay lightbox-box lightbox-content lightbox-shadow lightbox-title ' +
738 'lightbox-info lightbox-close lightbox-prev lightbox-next lightbox-counter';
739 this.loop(elems.split(' '), function(el) {
741 lightbox[el.split('-')[1]] = this.get(el);
744 lightbox.image = new Galleria.Picture();
747 'lightbox-box': ['lightbox-shadow','lightbox-content', 'lightbox-close'],
748 'lightbox-info': ['lightbox-title','lightbox-counter','lightbox-next','lightbox-prev'],
749 'lightbox-content': ['lightbox-info']
751 document.body.appendChild( lightbox.overlay );
752 document.body.appendChild( lightbox.box );
753 lightbox.content.appendChild( lightbox.image.elem );
755 lightbox.close.innerHTML = '×';
756 lightbox.prev.innerHTML = '◄';
757 lightbox.next.innerHTML = '►';
759 this.listen( lightbox.close, 'click', lightbox.hide );
760 this.listen( lightbox.overlay, 'click', lightbox.hide );
761 this.listen( lightbox.next, 'click', lightbox.showNext );
762 this.listen( lightbox.prev, 'click', lightbox.showPrev );
764 if (this.options.lightbox_clicknext) {
765 this.setStyle( lightbox.image.elem, {cursor:'pointer'} );
766 this.listen( lightbox.image.elem, 'click', lightbox.showNext);
768 this.setStyle( lightbox.overlay, {
769 position: 'fixed', display: 'none',
770 opacity: this.options.overlay_opacity,
771 top: 0, left: 0, width: '100%', height: '100%',
772 background: this.options.overlay_background, zIndex: 99990
774 this.setStyle( lightbox.box, {
775 position: 'fixed', display: 'none',
776 width: 400, height: 400, top: '50%', left: '50%',
777 marginTop: -200, marginLeft: -200, zIndex: 99991
779 this.setStyle( lightbox.shadow, {
780 background:'#000', opacity:.4, width: '100%', height: '100%', position: 'absolute'
782 this.setStyle( lightbox.content, {
783 backgroundColor:'#fff',position: 'absolute',
784 top: 10, left: 10, right: 10, bottom: 10, overflow: 'hidden'
786 this.setStyle( lightbox.info, {
787 color: '#444', fontSize: '11px', fontFamily: 'arial,sans-serif', height: 13, lineHeight: '13px',
788 position: 'absolute', bottom: 10, left: 10, right: 10, opacity: 0
790 this.setStyle( lightbox.close, {
791 background: '#fff', height: 20, width: 20, position: 'absolute', textAlign: 'center', cursor: 'pointer',
792 top: 10, right: 10, lineHeight: '22px', fontSize: '16px', fontFamily:'arial,sans-serif',color:'#444', zIndex: 99999
794 this.setStyle( lightbox.image.elem, {
795 top: 10, left: 10, right: 10, bottom: 30, position: 'absolute'
797 this.loop('title prev next counter'.split(' '), function(el) {
798 var css = { display: 'inline', 'float':'left' };
800 this.mix(css, { 'float': 'right'});
801 if (el != 'counter') {
802 this.mix(css, { cursor: 'pointer'});
804 this.mix(css, { marginLeft: 8 });
807 this.setStyle(lightbox[el], css);
809 this.loop('prev next close'.split(' '), function(el) {
810 this.listen(lightbox[el], 'mouseover', this.proxy(function() {
811 this.setStyle(lightbox[el], { color:'#000' });
813 this.listen(lightbox[el], 'mouseout', this.proxy(function() {
814 this.setStyle(lightbox[el], { color:'#444' });
817 this.trigger(G.LIGHTBOX_OPEN);
819 rescale: this.proxy(function(e) {
820 var w = Math.min( this.width(window), lightbox.w );
821 var h = Math.min( this.height(window), lightbox.h );
822 var r = Math.min( (w-60) / lightbox.w, (h-80) / lightbox.h );
823 var destW = (lightbox.w * r) + 40;
824 var destH = (lightbox.h * r) + 60;
828 marginTop: Math.ceil(destH/2)*-1,
829 marginLeft: Math.ceil(destW)/2*-1
832 this.animate( lightbox.box, {
834 duration: this.options.lightbox_transition_speed,
836 complete: function() {
838 type: G.LIGHTBOX_IMAGE,
839 imageTarget: lightbox.image.image
841 this.moveIn( lightbox.image.image );
842 this.animate( lightbox.image.image, { to: { opacity:1 }, duration: this.options.lightbox_fade_speed } );
843 this.animate( lightbox.info, { to: { opacity:1 }, duration: this.options.lightbox_fade_speed } );
847 this.setStyle( lightbox.box, dest );
850 hide: this.proxy(function() {
851 lightbox.image.image = null;
852 this.forget(window, 'resize', lightbox.rescale);
853 this.hide( lightbox.box );
854 this.setStyle( lightbox.info, { opacity: 0 } );
855 this.animate( lightbox.overlay, {
858 complete: function() {
859 this.hide( lightbox.overlay );
860 this.setStyle( lightbox.overlay, { opacity: this.options.overlay_opacity});
861 this.trigger(G.LIGHTBOX_CLOSE);
865 showNext: this.proxy(function() {
866 lightbox.show(this.getNext(lightbox.active));
868 showPrev: this.proxy(function() {
869 lightbox.show(this.getPrev(lightbox.active));
871 show: this.proxy(function(index) {
872 if (!lightbox.initialized) {
875 this.forget( window, 'resize', lightbox.rescale );
876 index = typeof index == 'number' ? index : this.getIndex();
877 lightbox.active = index;
879 var data = this.getData(index);
880 var total = this.data.length;
881 this.setStyle( lightbox.info, {opacity:0} );
883 lightbox.image.load( data.image, function(o) {
884 lightbox.w = o.scope.orig.w;
885 lightbox.h = o.scope.orig.h;
886 this.setStyle(o.target, {
893 lightbox.title.innerHTML = data.title;
894 lightbox.counter.innerHTML = (index+1) + ' / ' + total;
895 this.listen( window, 'resize', lightbox.rescale );
898 this.reveal( lightbox.overlay );
899 this.reveal( lightbox.box );
903 this.thumbnails = { width: 0 };
905 this.stageHeight = 0;
907 var elems = 'container stage images image-nav image-nav-left image-nav-right ' +
908 'info info-text info-title info-description info-author ' +
909 'thumbnails thumbnails-list thumbnails-container thumb-nav-left thumb-nav-right ' +
911 elems = elems.split(' ');
913 this.loop(elems, function(blueprint) {
914 this.dom[ blueprint ] = this.create('div', 'galleria-' + blueprint);
917 this.target = this.dom.target = options.target.nodeName ?
918 options.target : this.getElements(options.target)[0];
921 G.raise('Target not found.');
927 this.options = this.mix(G.theme.defaults, this.options);
928 this.options = this.mix({
931 carousel_follow: true,
933 carousel_steps: 'auto',
935 data_config : function( elem ) { return {}; },
936 data_image_selector: 'img',
937 data_source: this.target,
940 extend: function(options) {},
946 image_pan_smoothness: 12,
947 image_position: '50%',
949 lightbox_clicknext: true,
950 lightbox_fade_speed: 200,
951 lightbox_transition_speed: 300,
952 link_source_images: true,
953 max_scale_ratio: undefined,
954 min_scale_ratio: undefined,
955 on_image: function(img,thumb) {},
956 overlay_opacity: .85,
957 overlay_background: '#0b0b0b',
968 thumb_quality: 'auto',
970 transition: G.transitions.fade,
971 transition_speed: 400
974 var o = this.options;
976 this.bind(G.DATA, function() {
981 this.loop(this.data, function(data) {
984 this.setStyle(this.get('stage'), { cursor: 'pointer'} );
985 this.listen(this.get('stage'), 'click', this.proxy(function() {
990 this.bind(G.IMAGE, function(e) {
991 o.on_image.call(this, e.imageTarget, e.thumbTarget);
994 this.bind(G.READY, function() {
996 G.History.change(this.proxy(function(e) {
997 var val = parseInt(e.value.replace(/\//,''));
999 window.history.go(-1);
1001 this.show(val, undefined, true);
1006 G.theme.init.call(this, o);
1007 o.extend.call(this, o);
1009 if (/^[0-9]{1,4}$/.test(hash) && G.History) {
1010 this.show(hash, undefined, true);
1011 } else if (typeof o.show == 'number') {
1016 if (typeof o.autoplay == 'number') {
1017 this.playtime = o.autoplay;
1019 this.trigger( G.PLAY );
1020 this.playing = true;
1027 bind : function(type, fn) {
1028 this.listen( this.get('container'), type, this.proxy(fn) );
1032 unbind : function(type) {
1033 this.forget( this.get('container'), type );
1036 trigger : function( type ) {
1037 type = typeof type == 'object' ?
1038 this.mix( type, { scope: this } ) :
1039 { type: type, scope: this };
1040 this.dispatch( this.get('container'), type );
1044 addIdleState: function() {
1045 this.idle.add.apply(this, arguments);
1049 removeIdleState: function() {
1050 this.idle.remove.apply(this, arguments);
1054 enterIdleMode: function() {
1059 exitIdleMode: function() {
1064 addTimer: function() {
1065 this.timeouts.add.apply(this.timeouts, arguments);
1069 clearTimer: function() {
1070 this.timeouts.clear.apply(this.timeouts, arguments);
1074 enterFullscreen: function() {
1075 this.fullscreen.enter.apply(this, arguments);
1079 exitFullscreen: function() {
1080 this.fullscreen.exit.apply(this, arguments);
1084 openLightbox: function() {
1085 this.lightbox.show.apply(this, arguments);
1088 closeLightbox: function() {
1089 this.lightbox.hide.apply(this, arguments);
1092 getActive: function() {
1093 return this.controls.getActive();
1096 getActiveImage: function() {
1097 return this.getActive().image || null;
1101 var o = this.options;
1102 if (!this.data.length) {
1103 G.raise('Data is empty.');
1105 if (!o.keep_source && !Galleria.IE) {
1106 this.target.innerHTML = '';
1108 this.loop(2, function() {
1109 var image = new Picture();
1110 this.setStyle( image.elem, {
1111 position: 'absolute',
1115 this.setStyle(this.get( 'images' ), {
1116 position: 'relative',
1122 this.get( 'images' ).appendChild( image.elem );
1123 this.push(image, this.controls);
1127 // try the carousel on each thumb load
1128 this.bind(G.THUMBNAIL, this.parseCarousel);
1132 this.target.appendChild(this.get('container'));
1134 this.loop(['info','counter','image-nav'], function(el) {
1135 if ( o[ 'show_'+el.replace(/-/,'') ] === false ) {
1136 this.moveOut( this.get(el) );
1143 for( var i=0; this.data[i]; i++ ) {
1145 if (o.thumbnails === true) {
1146 thumb = new Picture(i);
1147 var src = this.data[i].thumb || this.data[i].image;
1149 this.get( 'thumbnails' ).appendChild( thumb.elem );
1151 w = this.getStyle(thumb.elem, 'width', true);
1152 h = this.getStyle(thumb.elem, 'height', true);
1154 // grab & reset size for smoother thumbnail loads
1155 if (o.thumb_fit && o.thum_crop !== true) {
1156 this.setStyle(thumb.elem, { width:0, height: 0});
1159 thumb.load(src, this.proxy(function(e) {
1160 var orig = e.target.width;
1165 margin: o.thumb_margin,
1166 complete: this.proxy(function() {
1167 // shrink thumbnails to fit
1168 var top = ['left', 'top'];
1169 var arr = ['Height', 'Width'];
1170 this.loop(arr, function(m,i) {
1171 if ((!o.thumb_crop || o.thumb_crop == m.toLowerCase()) && o.thumb_fit) {
1173 var opp = arr[Math.abs(i-1)].toLowerCase();
1174 css[opp] = e.target[opp];
1175 this.setStyle(e.target.parentNode, css);
1178 this.setStyle(e.target, css);
1180 e.scope['outer'+m] = this[m.toLowerCase()](e.target.parentNode, true);
1182 // set high quality if downscale is moderate
1183 this.toggleQuality(e.target, o.thumb_quality === true || ( o.thumb_quality == 'auto' && orig < e.target.width * 3 ));
1186 thumbTarget: e.target,
1187 thumbOrder: e.scope.order
1192 if (o.preload == 'all') {
1193 thumb.add(this.data[i].image);
1195 } else if (o.thumbnails == 'empty') {
1197 elem: this.create('div','galleria-image'),
1198 image: this.create('span','img')
1200 thumb.elem.appendChild(thumb.image);
1201 this.get( 'thumbnails' ).appendChild( thumb.elem );
1208 var activate = this.proxy(function(e) {
1211 var ind = e.currentTarget.rel;
1212 if (this.active !== ind) {
1216 if (o.thumbnails !== false) {
1218 this.listen(thumb.elem, 'click', activate);
1220 if (o.link_source_images && o.keep_source && this.data[i].elem) {
1221 this.data[i].elem.rel = i;
1222 this.listen(this.data[i].elem, 'click', activate);
1224 this.push(thumb, this.thumbnails );
1226 this.setStyle( this.get('thumbnails'), { opacity: 0 } );
1228 if (o.height && o.height != 'auto') {
1229 this.setStyle( this.get('container'), { height: o.height })
1232 this.wait(function() {
1233 // the most sensitive piece of code in Galleria, we need to have all the meassurements right to continue
1234 var cssHeight = this.getStyle( this.get( 'container' ), 'height', true );
1235 this.stageWidth = this.width(this.get( 'stage' ));
1236 this.stageHeight = this.height( this.get( 'stage' ));
1237 if (this.stageHeight < 50 && o.height == 'auto') {
1238 // no height detected for sure, set reasonable ratio (16/9)
1239 this.setStyle( this.get( 'container' ), {
1240 height: Math.round( this.stageWidth*9/16 )
1242 this.stageHeight = this.height( this.get( 'stage' ));
1244 return this.stageHeight && this.stageWidth;
1246 this.listen(this.get('image-nav-right'), 'click', this.proxy(function(e) {
1248 e.stopPropagation();
1253 this.listen(this.get('image-nav-left'), 'click', this.proxy(function(e) {
1255 e.stopPropagation();
1260 this.setStyle( this.get('thumbnails'), { opacity: 1 } );
1261 this.trigger( G.READY );
1263 G.raise('Galleria could not load properly. Make sure stage has a height and width.');
1267 mousePosition : function(e) {
1269 x: e.pageX - this.$('stage').offset().left + jQuery(document).scrollLeft(),
1270 y: e.pageY - this.$('stage').offset().top + jQuery(document).scrollTop()
1274 addPan : function(img) {
1275 var c = this.options.image_crop;
1276 if ( c === false ) {
1279 if (this.options.image_crop === false) {
1282 img = img || this.controls.getActive().image;
1283 if (img.tagName.toUpperCase() != 'IMG') {
1284 G.raise('Could not add pan');
1287 var x = img.width/2;
1288 var y = img.height/2;
1289 var curX = destX = this.getStyle(img, 'left', true) || 0;
1290 var curY = destY = this.getStyle(img, 'top', true) || 0;
1294 var ts = new Date().getTime();
1295 var calc = this.proxy(function(e) {
1296 if (new Date().getTime() - ts < 50) {
1300 x = this.mousePosition(e).x;
1301 y = this.mousePosition(e).y;
1303 var loop = this.proxy(function(e) {
1307 distX = img.width - this.stageWidth;
1308 distY = img.height - this.stageHeight;
1309 destX = x / this.stageWidth * distX * -1;
1310 destY = y / this.stageHeight * distY * -1;
1311 curX += (destX - curX) / this.options.image_pan_smoothness;
1312 curY += (destY - curY) / this.options.image_pan_smoothness;
1314 this.setStyle(img, { top: Math.max(distY*-1, Math.min(0, curY)) });
1317 this.setStyle(img, { left: Math.max(distX*-1, Math.min(0, curX)) });
1320 this.forget(this.get('stage'), 'mousemove');
1321 this.listen(this.get('stage'), 'mousemove', calc);
1322 this.addTimer('pan', loop, 30, true);
1325 removePan: function() {
1326 this.forget(this.get('stage'), 'mousemove');
1327 this.clearTimer('pan');
1330 parseCarousel : function(e) {
1334 this.loop(this.thumbnails, function(thumb,i) {
1336 w += thumb.outerWidth || this.width(thumb.elem, true);
1338 h = Math.max(h, this.height(thumb.elem));
1341 this.toggleClass(this.get('thumbnails-container'), 'galleria-carousel', w > this.stageWidth);
1342 this.setStyle(this.get('thumbnails-list'), {
1344 position: 'relative' // for IE Standards mode
1346 this.setStyle(this.get('thumbnails'), {
1349 position: 'relative',
1352 if (!this.carousel) {
1353 this.initCarousel();
1355 this.carousel.max = w;
1356 this.carousel.hooks = hooks;
1357 this.carousel.width = this.width(this.get('thumbnails-list'));
1358 this.carousel.setClasses();
1361 initCarousel : function() {
1362 var c = this.carousel = {
1363 right: this.get('thumb-nav-right'),
1364 left: this.get('thumb-nav-left'),
1365 update: this.proxy(function() {
1366 this.parseCarousel();
1367 // todo: fix so the carousel moves to the left
1373 while (c.hooks[i-1] + c.width > c.max && i >= 0) {
1380 getLast: function(i) {
1385 follow: function(i) {
1386 if (i == 0 || i == c.hooks.length-2) {
1390 var last = c.current;
1391 while(c.hooks[last] - c.hooks[c.current] < c.width && last<= c.hooks.length) {
1394 if (i-1 < c.current) {
1396 } else if (i+2 > last) {
1397 c.set(i - last + c.current + 2)
1401 setClasses: this.proxy(function() {
1402 this.toggleClass( c.left, 'disabled', !c.current );
1403 this.toggleClass( c.right, 'disabled', c.hooks[c.current] + c.width > c.max );
1405 animate: this.proxy(function(to) {
1407 this.animate( this.get('thumbnails'), {
1408 to: { left: c.hooks[c.current] * -1 },
1409 duration: this.options.carousel_speed,
1415 this.listen(c.right, 'click', this.proxy(function(e) {
1416 if (this.options.carousel_steps == 'auto') {
1417 for (var i = c.current; i<c.hooks.length; i++) {
1418 if (c.hooks[i] - c.hooks[c.current] > c.width) {
1424 c.set(c.current + this.options.carousel_steps);
1427 this.listen(c.left, 'click', this.proxy(function(e) {
1428 if (this.options.carousel_steps == 'auto') {
1429 for (var i = c.current; i>=0; i--) {
1430 if (c.hooks[c.current] - c.hooks[i] > c.width) {
1433 } else if (i == 0) {
1439 c.set(c.current - this.options.carousel_steps);
1443 addElement : function() {
1444 this.loop(arguments, function(b) {
1445 this.dom[b] = this.create('div', 'galleria-' + b );
1449 getDimensions: function(i) {
1453 cw: this.stageWidth,
1454 ch: this.stageHeight,
1455 top: (this.stageHeight - i.height) / 2,
1456 left: (this.stageWidth - i.width) / 2
1459 attachKeyboard : function(map) {
1460 this.keyboard.attach(map);
1463 detachKeyboard : function() {
1464 this.keyboard.detach();
1467 build : function() {
1470 ['info-title', 'info-description', 'info-author'],
1474 ['image-nav-right', 'image-nav-left'],
1476 ['images', 'loader', 'counter', 'image-nav'],
1479 'thumbnails-container' :
1480 ['thumb-nav-left', 'thumbnails-list', 'thumb-nav-right'],
1482 ['stage', 'thumbnails-container', 'info']
1485 this.current = this.create('span', 'current');
1486 this.current.innerHTML = '-';
1487 this.get('counter').innerHTML = ' / <span class="total">' + this.data.length + '</span>';
1488 this.prependChild('counter', this.current);
1491 appendChild : function(parent, child) {
1493 this.get(parent).appendChild(this.get(child));
1497 prependChild : function(parent, child) {
1498 var child = this.get(child) || child;
1500 this.get(parent).insertBefore(child, this.get(parent).firstChild);
1504 remove : function() {
1505 var a = Array.prototype.slice.call(arguments);
1506 this.jQuery(a.join(',')).remove();
1509 append : function(data) {
1510 for( var i in data) {
1511 if (data[i].constructor == Array) {
1512 for(var j=0; data[i][j]; j++) {
1513 this.appendChild(i, data[i][j]);
1516 this.appendChild(i, data[i]);
1522 rescale : function(width, height, callback) {
1524 var o = this.options;
1525 callback = this.proxy(callback);
1527 if (typeof width == 'function') {
1528 callback = this.proxy(width);
1532 var scale = this.proxy(function() {
1533 this.stageWidth = width || this.width(this.get('stage'));
1534 this.stageHeight = height || this.height(this.get('stage'));
1535 this.controls.getActive().scale({
1536 width: this.stageWidth,
1537 height: this.stageHeight,
1539 max: o.max_scale_ratio,
1540 min: o.min_scale_ratio,
1541 margin: o.image_margin,
1542 position: o.image_position
1544 if (this.carousel) {
1545 this.carousel.update();
1547 this.trigger(G.RESCALE)
1550 if ( G.WEBKIT && !width && !height ) {
1551 this.addTimer('scale', scale, 5);// webkit is too fast
1557 show : function(index, rewind, history) {
1558 if (!this.options.queue && this.queue.stalled) {
1561 rewind = typeof rewind != 'undefined' ? !!rewind : index < this.active;
1562 history = history || false;
1563 index = Math.max(0, Math.min(parseInt(index), this.data.length - 1));
1564 if (!history && G.History) {
1565 G.History.value(index.toString());
1568 this.active = index;
1569 this.push([index,rewind], this.queue);
1570 if (!this.queue.stalled) {
1576 showImage : function() {
1577 var o = this.options;
1578 var args = this.queue[0];
1579 var index = args[0];
1580 var rewind = !!args[1];
1581 if (o.carousel && this.carousel && o.carousel_follow) {
1582 this.carousel.follow(index);
1585 var src = this.getData(index).image;
1586 var active = this.controls.getActive();
1587 var next = this.controls.getNext();
1588 var cached = next.isCached(src);
1589 var complete = this.proxy(function() {
1590 this.queue.stalled = false;
1591 this.toggleQuality(next.image, o.image_quality);
1592 this.setStyle( active.elem, { zIndex : 0 } );
1593 this.setStyle( next.elem, { zIndex : 1 } );
1597 imageTarget: next.image,
1598 thumbTarget: this.thumbnails[index].image
1601 this.addPan(next.image);
1603 this.controls.swap();
1604 this.moveOut( active.image );
1605 if (this.getData( index ).link) {
1606 this.setStyle( next.image, { cursor: 'pointer' } );
1607 this.listen( next.image, 'click', this.proxy(function() {
1608 if (o.popup_links) {
1609 var win = window.open(this.getData( index ).link, '_blank');
1611 window.location.href = this.getData( index ).link;
1615 Array.prototype.shift.call( this.queue );
1616 if (this.queue.length) {
1621 if (typeof o.preload == 'number' && o.preload > 0) {
1622 var p,n = this.getNext();
1624 for (var i = o.preload; i>0; i--) {
1626 p.add(this.getData(n).image);
1627 n = this.getNext(n);
1635 imageTarget: next.image,
1636 thumbTarget: this.thumbnails[index].image
1639 jQuery(this.thumbnails[index].elem).addClass('active').siblings('.active').removeClass('active');
1641 next.load( src, this.proxy(function(e) {
1643 width: this.stageWidth,
1644 height: this.stageHeight,
1646 max: o.max_scale_ratio,
1647 min: o.min_scale_ratio,
1648 margin: o.image_margin,
1649 position: o.image_position,
1650 complete: this.proxy(function() {
1652 this.toggleQuality(active.image, false);
1654 this.toggleQuality(next.image, false);
1659 imageTarget: next.image,
1660 thumbTarget: this.thumbnails[index].image
1662 this.queue.stalled = true;
1663 var transition = G.transitions[o.transition] || o.transition;
1665 this.setInfo(index);
1666 this.setCounter(index);
1667 if (typeof transition == 'function') {
1668 transition.call(this, {
1672 speed: o.transition_speed || 400
1682 getNext : function(base) {
1683 base = typeof base == 'number' ? base : this.active;
1684 return base == this.data.length - 1 ? 0 : base + 1;
1687 getPrev : function(base) {
1688 base = typeof base == 'number' ? base : this.active;
1689 return base === 0 ? this.data.length - 1 : base - 1;
1693 if (this.data.length > 1) {
1694 this.show(this.getNext(), false);
1700 if (this.data.length > 1) {
1701 this.show(this.getPrev(), true);
1706 get : function( elem ) {
1707 return elem in this.dom ? this.dom[ elem ] : null;
1710 getData : function( index ) {
1711 return this.data[index] || this.data[this.active];
1714 getIndex : function() {
1715 return typeof this.active === 'number' ? this.active : 0;
1718 play : function(delay) {
1719 this.trigger( G.PLAY );
1720 this.playing = true;
1721 this.playtime = delay || this.playtime;
1726 pause : function() {
1727 this.trigger( G.PAUSE );
1728 this.playing = false;
1732 playCheck : function() {
1734 var i = 20; // the interval
1735 var ts = function() {
1736 return new Date().getTime();
1740 this.clearTimer('play');
1741 var fn = this.proxy(function() {
1743 if ( p >= this.playtime && this.playing ) {
1744 this.clearTimer('play');
1748 if ( this.playing ) {
1751 percent: Math.ceil(p / this.playtime * 100),
1752 seconds: Math.floor(p/1000),
1755 this.addTimer('play', fn, i);
1758 this.addTimer('play', fn, i);
1762 setActive: function(val) {
1767 setCounter: function(index) {
1768 index = index || this.active;
1769 this.current.innerHTML = index+1;
1773 setInfo : function(index) {
1774 var data = this.getData(index || this.active);
1775 this.loop(['title','description','author'], function(type) {
1776 var elem = this.get('info-'+type);
1777 var fn = data[type] && data[type].length ? 'reveal' : 'hide';
1780 elem.innerHTML = data[type];
1786 hasInfo : function(index) {
1787 var d = this.getData(index);
1788 var check = 'title description author'.split(' ');
1789 for ( var i=0; check[i]; i++ ) {
1790 if ( d[ check[i] ] && d[ check[i] ].length ) {
1797 getDataObject : function(o) {
1806 return o ? this.mix(obj,o) : obj;
1809 jQuery : function( str ) {
1811 this.loop(str.split(','), this.proxy(function(elem) {
1812 elem = elem.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
1813 if (this.get(elem)) {
1817 var jQ = jQuery(this.get(ret.shift()));
1818 this.loop(ret, this.proxy(function(elem) {
1819 jQ = jQ.add(this.get(elem));
1824 $ : function( str ) {
1825 return this.jQuery( str );
1828 toggleQuality : function(img, force) {
1829 if (!G.IE7 || typeof img == 'undefined' || !img) {
1832 if (typeof force === 'undefined') {
1833 force = img.style.msInterpolationMode == 'nearest-neighbor';
1835 img.style.msInterpolationMode = force ? 'bicubic' : 'nearest-neighbor';
1840 unload : function() {
1846 var o = this.options;
1848 (o.data_type == 'auto' &&
1849 typeof o.data_source == 'object' &&
1850 !(o.data_source instanceof jQuery) &&
1851 !o.data_source.tagName
1852 ) || o.data_type == 'json' || o.data_source.constructor == Array ) {
1853 this.data = o.data_source;
1854 this.trigger( G.DATA );
1856 } else { // assume selector
1857 var images = jQuery(o.data_source).find(o.data_image_selector);
1858 var getData = this.proxy(function( elem ) {
1859 var i,j,anchor = elem.parentNode;
1860 if (anchor && anchor.nodeName == 'A') {
1861 if (anchor.href.match(/\.(png|gif|jpg|jpeg)/i)) {
1867 var obj = this.getDataObject({
1870 image: i || elem.src,
1871 description: elem.alt,
1872 link: j || elem.getAttribute('longdesc'),
1875 return this.mix(obj, o.data_config( elem ) );
1877 this.loop(images, function( elem ) {
1879 this.push( getData( elem ), this.data );
1880 if (!o.keep_source && !Galleria.IE) {
1881 elem.parentNode.removeChild(elem);
1883 if ( loaded == images.length ) {
1884 this.trigger( G.DATA );
1891 G.log = function() {
1893 console.log.apply( console, Array.prototype.slice.call(arguments) );
1896 opera.postError.apply( opera, arguments );
1898 alert( Array.prototype.join.call( arguments, " " ) );
1903 var nav = navigator.userAgent.toLowerCase();
1904 var hash = window.location.hash.replace(/#\//,'');
1908 G.THUMBNAIL = 'thumbnail';
1909 G.LOADSTART = 'loadstart';
1910 G.LOADFINISH = 'loadfinish';
1912 G.THEMELOAD = 'themeload';
1915 G.PROGRESS = 'progress';
1916 G.FULLSCREEN_ENTER = 'fullscreen_enter';
1917 G.FULLSCREEN_EXIT = 'fullscreen_exit';
1918 G.IDLE_ENTER = 'idle_enter';
1919 G.IDLE_EXIT = 'idle_exit';
1920 G.RESCALE = 'rescale';
1921 G.LIGHTBOX_OPEN = 'lightbox_open';
1922 G.LIGHTBOX_CLOSE = 'lightbox_cloe';
1923 G.LIGHTBOX_IMAGE = 'lightbox_image';
1925 G.IE8 = (typeof(XDomainRequest) !== 'undefined')
1926 G.IE7 = !!(window.XMLHttpRequest && document.expando);
1927 G.IE6 = (!window.XMLHttpRequest);
1928 G.IE = !!(G.IE6 || G.IE7);
1929 G.WEBKIT = /webkit/.test( nav );
1930 G.SAFARI = /safari/.test( nav );
1931 G.CHROME = /chrome/.test( nav );
1932 G.QUIRK = (G.IE && document.compatMode && document.compatMode == "BackCompat");
1933 G.MAC = /mac/.test(navigator.platform.toLowerCase());
1934 G.OPERA = !!window.opera
1936 G.Picture = Picture;
1938 G.addTheme = function(obj) {
1940 var orig = ['name','author','version','defaults','init'];
1941 var proto = G.prototype;
1942 proto.loop(orig, function(val) {
1944 G.raise(val+' not specified in theme.');
1946 if (val != 'name' && val != 'init') {
1947 theme[val] = obj[val];
1950 theme.init = obj.init;
1954 proto.loop(proto.getElements('script'), function(el) {
1955 var reg = new RegExp('galleria.' + obj.name.toLowerCase() + '.js');
1956 if(reg.test(el.src)) {
1957 css = el.src.replace(/[^\/]*$/, "") + obj.css;
1958 proto.loadCSS(css, function() {
1960 jQuery(document).trigger( G.THEMELOAD );
1965 G.raise('No theme CSS loaded');
1971 G.raise = function(msg) {
1973 throw new Error( msg );
1977 G.loadTheme = function(src) {
1978 G.prototype.loadScript(src);
1982 G.get = function(index) {
1983 if (G.galleries[index]) {
1984 return G.galleries[index];
1985 } else if (typeof index !== 'number') {
1988 G.raise('Gallery index not found');
1992 jQuery.easing.galleria = function (x, t, b, c, d) {
1994 return c/2*t*t*t*t + b;
1996 return -c/2 * ((t-=2)*t*t*t - 2) + b;
2000 add: function(name, fn) {
2001 if (name != arguments.callee.name ) {
2005 fade: function(params, complete) {
2006 jQuery(params.next).show().css('opacity',0).animate({
2008 }, params.speed, complete);
2010 jQuery(params.prev).css('opacity',1).animate({
2015 flash: function(params, complete) {
2016 jQuery(params.next).css('opacity',0);
2018 jQuery(params.prev).animate({
2020 }, (params.speed/2), function() {
2021 jQuery(params.next).animate({
2023 }, params.speed, complete);
2026 jQuery(params.next).animate({
2028 }, params.speed, complete);
2031 pulse: function(params, complete) {
2033 jQuery(params.prev).css('opacity',0);
2035 jQuery(params.next).css('opacity',0).animate({
2037 }, params.speed, complete);
2039 slide: function(params, complete) {
2040 var image = jQuery(params.next).parent();
2041 var images = this.$('images');
2042 var width = this.stageWidth;
2044 left: width * ( params.rewind ? -1 : 1 )
2047 left: width * ( params.rewind ? 1 : -1 )
2049 duration: params.speed,
2052 complete: function() {
2053 images.css('left',0);
2054 image.css('left',0);
2059 fadeslide: function(params, complete) {
2061 jQuery(params.prev).css({
2066 left: 50 * ( params.rewind ? 1 : -1 )
2068 duration: params.speed,
2073 jQuery(params.next).css({
2074 left: 50 * ( params.rewind ? -1 : 1 ),
2080 duration: params.speed,
2088 G.addTransition = function() {
2089 G.transitions.add.apply(this, arguments);
2092 jQuery.fn.galleria = function(options) {
2094 options = options || {};
2095 var selector = this.selector;
2097 return this.each(function() {
2098 if ( !options.keep_source ) {
2099 jQuery(this).children().hide();
2102 options = G.prototype.mix(options, {target: this} );
2103 var height = G.prototype.height(this) || G.prototype.getStyle(this, 'height', true);
2104 if (!options.height && height) {
2105 options = G.prototype.mix( { height: height }, options );
2108 G.debug = !!options.debug;
2110 var gallery = new G(options);
2112 Galleria.galleries.push(gallery);
2117 jQuery(document).bind(G.THEMELOAD, function() {