1 /* 2 * Galleria v 1.2 prerelease 1.1 2010-10-29 3 * http://galleria.aino.se 4 * 5 * Copyright (c) 2010, Aino 6 * Licensed under the MIT license. 7 */ 8 9 (function($) { 10 11 // some references 12 var undef, 13 window = this, 14 doc = document, 15 $doc = $( doc ); 16 17 // internal constants 18 var DEBUG = false, 19 NAV = navigator.userAgent.toLowerCase(), 20 HASH = window.location.hash.replace(/#\//, ''), 21 CLICK = function() { 22 // use this to make touch devices snappier 23 return Galleria.TOUCH ? 'touchstart' : 'click'; 24 }, 25 IE = (function() { 26 var v = 3, 27 div = doc.createElement( 'div' ); 28 while ( 29 div.innerHTML = '<!--[if gt IE '+(++v)+']><i></i><![endif]-->', 30 div.getElementsByTagName('i')[0] 31 ); 32 return v > 4 ? v : undef; 33 }() ), 34 DOM = function() { 35 return { 36 html: doc.documentElement, 37 body: doc.body, 38 head: doc.getElementsByTagName('head')[0], 39 title: doc.title 40 }; 41 }, 42 43 // the internal timeouts object 44 // provides helper methods for controlling timeouts 45 _timeouts = { 46 47 trunk: {}, 48 49 add: function( id, fn, delay, loop ) { 50 loop = loop || false; 51 this.clear( id ); 52 if ( loop ) { 53 var old = fn; 54 fn = function() { 55 old(); 56 _timeouts.add( id, fn, delay ); 57 }; 58 } 59 this.trunk[ id ] = window.setTimeout( fn, delay ); 60 }, 61 62 clear: function( id ) { 63 64 var del = function( i ) { 65 window.clearTimeout( this.trunk[ i ] ); 66 delete this.trunk[ i ]; 67 }; 68 69 if ( !!id && id in this.trunk ) { 70 del.call( _timeouts, id ); 71 72 } else if ( typeof id == 'undefined' ) { 73 for ( var i in this.trunk ) { 74 del.call( _timeouts, i ); 75 } 76 } 77 } 78 }, 79 80 // the internal gallery holder 81 _galleries = [], 82 83 // the transitions holder 84 _transitions = { 85 86 fade: function(params, complete) { 87 $(params.next).css('opacity', 0).show().animate({ 88 opacity: 1 89 }, params.speed, complete); 90 91 if (params.prev) { 92 $(params.prev).css('opacity', 1).show().animate({ 93 opacity: 0 94 }, params.speed); 95 } 96 }, 97 98 flash: function(params, complete) { 99 $(params.next).css('opacity', 0); 100 if (params.prev) { 101 $(params.prev).animate({ 102 opacity: 0 103 }, (params.speed / 2), function() { 104 $(params.next).animate({ 105 opacity: 1 106 }, params.speed, complete); 107 }); 108 } else { 109 $(params.next).animate({ 110 opacity: 1 111 }, params.speed, complete); 112 } 113 }, 114 115 pulse: function(params, complete) { 116 if (params.prev) { 117 $(params.prev).hide(); 118 } 119 $(params.next).css('opacity', 0).animate({ 120 opacity:1 121 }, params.speed, complete); 122 }, 123 124 slide: function(params, complete) { 125 var image = $(params.next).parent(), 126 images = this.$('images'), // ?? 127 width = this._stageWidth, 128 easing = this.getOptions( 'easing' ); 129 130 image.css({ 131 left: width * ( params.rewind ? -1 : 1 ) 132 }); 133 images.animate({ 134 left: width * ( params.rewind ? 1 : -1 ) 135 }, { 136 duration: params.speed, 137 queue: false, 138 easing: easing, 139 complete: function() { 140 images.css('left', 0); 141 image.css('left', 0); 142 complete(); 143 } 144 }); 145 }, 146 147 fadeslide: function(params, complete) { 148 149 var x = 0, 150 easing = this.getOptions('easing'); 151 152 if (params.prev) { 153 x = Utils.parseValue( $(params.prev).css('left') ); 154 $(params.prev).css({ 155 opacity: 1, 156 left: x 157 }).animate({ 158 opacity: 0, 159 left: x + ( 50 * ( params.rewind ? 1 : -1 ) ) 160 },{ 161 duration: params.speed, 162 queue: false, 163 easing: easing 164 }); 165 } 166 167 x = Utils.parseValue( $(params.next).css('left') ); 168 169 $(params.next).css({ 170 left: x + ( 50 * ( params.rewind ? -1 : 1 ) ), 171 opacity: 0 172 }).animate({ 173 opacity: 1, 174 left: x 175 }, { 176 duration: params.speed, 177 complete: complete, 178 queue: false, 179 easing: easing 180 }); 181 } 182 }, 183 184 // the Utils singleton 185 Utils = (function() { 186 187 return { 188 189 array : function( obj ) { 190 return Array.prototype.slice.call(obj); 191 }, 192 193 create : function( className, nodeName ) { 194 nodeName = nodeName || 'div'; 195 var elem = doc.createElement( nodeName ); 196 elem.className = className; 197 return elem; 198 }, 199 200 forceStyles : function( elem, styles ) { 201 elem = $(elem); 202 if ( elem.attr( 'style' ) ) { 203 elem.data( 'styles', elem.attr( 'style' ) ).removeAttr( 'style' ); 204 } 205 elem.css( styles ); 206 }, 207 208 revertStyles : function() { 209 $.each( Utils.array( arguments ), function( i, elem ) { 210 211 elem = $( elem ).removeAttr( 'style' ); 212 213 if ( elem.data( 'styles' ) ) { 214 elem.attr( 'style', elem.data('styles') ).data( 'styles', null ); 215 } 216 }); 217 }, 218 219 moveOut : function( elem ) { 220 Utils.forceStyles( elem, { 221 position: 'absolute', 222 left: -10000 223 }); 224 }, 225 226 moveIn : function() { 227 Utils.revertStyles.apply( Utils, Utils.array( arguments ) ); 228 }, 229 230 hide : function( elem, speed, callback ) { 231 elem = $(elem); 232 233 // save the value if not exist 234 if (! elem.data('opacity') ) { 235 elem.data('opacity', elem.css('opacity') ); 236 } 237 238 // always hide 239 var style = { opacity: 0 }; 240 241 if (speed) { 242 elem.stop().animate( style, speed, callback ); 243 } else { 244 elem.css( style ); 245 }; 246 }, 247 248 show : function( elem, speed, callback ) { 249 elem = $(elem); 250 251 // bring back saved opacity 252 var saved = parseFloat( elem.data('opacity') ) || 1, 253 style = { opacity: saved }; 254 255 // reset save if opacity == 1 256 if (saved == 1) { 257 elem.data('opacity', null); 258 } 259 260 // animate or toggle 261 if (speed) { 262 elem.stop().animate( style, speed, callback ); 263 } else { 264 elem.css( style ); 265 }; 266 }, 267 268 addTimer : function() { 269 _timeouts.add.apply( _timeouts, Utils.array( arguments ) ); 270 return this; 271 }, 272 273 clearTimer : function() { 274 _timeouts.clear.apply( _timeouts, Utils.array( arguments ) ); 275 return this; 276 }, 277 278 wait : function(options) { 279 options = $.extend({ 280 until : function() { return false; }, 281 success : function() {}, 282 error : function() { Galleria.raise('Could not complete wait function.'); }, 283 timeout: 3000 284 }, options); 285 286 var start = Utils.timestamp(), 287 elapsed, 288 now; 289 290 window.setTimeout(function() { 291 now = Utils.timestamp(); 292 elapsed = now - start; 293 if ( options.until( elapsed ) ) { 294 options.success(); 295 return false; 296 } 297 298 if (now >= start + options.timeout) { 299 options.error(); 300 return false; 301 } 302 window.setTimeout(arguments.callee, 2); 303 }, 2); 304 }, 305 306 toggleQuality : function( img, force ) { 307 308 if ( !( IE == 7 || IE == 8 ) || !!img === false ) { 309 return; 310 } 311 312 if ( typeof force === 'undefined' ) { 313 force = img.style.msInterpolationMode == 'nearest-neighbor'; 314 } 315 316 img.style.msInterpolationMode = force ? 'bicubic' : 'nearest-neighbor'; 317 }, 318 319 insertStyleTag : function( styles ) { 320 var style = doc.createElement( 'style' ); 321 DOM().head.appendChild( style ); 322 323 if ( style.styleSheet ) { // IE 324 style.styleSheet.cssText = styles; 325 } else { 326 var cssText = doc.createTextNode( styles ); 327 style.appendChild( cssText ); 328 } 329 }, 330 331 // a loadscript method that works for local scripts 332 loadScript: function( url, callback ) { 333 var done = false, 334 script = $('<scr'+'ipt>').attr({ 335 src: url, 336 async: true 337 }).get(0); 338 339 // Attach handlers for all browsers 340 script.onload = script.onreadystatechange = function() { 341 if ( !done && (!this.readyState || 342 this.readyState == 'loaded' || this.readyState == 'complete') ) { 343 done = true; 344 345 if (typeof callback == 'function') { 346 callback.call( this, this ); 347 } 348 349 // Handle memory leak in IE 350 script.onload = script.onreadystatechange = null; 351 } 352 }; 353 354 var s = doc.getElementsByTagName( 'script' )[0]; 355 s.parentNode.insertBefore( script, s ); 356 }, 357 358 // parse anything into a number 359 parseValue: function( val ) { 360 if (typeof val == 'number') { 361 return val; 362 } else if (typeof val == 'string') { 363 var arr = val.match(/\-?\d/g); 364 return arr && arr.constructor == Array ? arr.join('') * 1 : 0; 365 } else { 366 return 0; 367 } 368 }, 369 370 // timestamp abstraction 371 timestamp: function() { 372 return new Date().getTime(); 373 }, 374 375 // this is pretty crap, but works for now 376 // it will add a callback, but it can't guarantee that the styles can be fetched 377 // using getComputedStyle further checking needed, possibly a dummy element 378 loadCSS : function( href, id, callback ) { 379 380 var link, 381 ready = false, 382 length; 383 384 // look for manual css 385 $('link[rel=stylesheet]').each(function() { 386 if ( new RegExp( href ).test( this.href ) ) { 387 link = this; 388 return false; 389 } 390 }); 391 392 if ( typeof id == 'function' ) { 393 callback = id; 394 id = undef; 395 } 396 397 callback = callback || function() {}; // dirty 398 399 // if already present, return 400 if ( link ) { 401 callback.call( link, link ); 402 return link; 403 } 404 405 // save the length of stylesheets to check against 406 length = doc.styleSheets.length; 407 408 // add timestamp if DEBUG is true 409 if ( DEBUG ) { 410 href += '?' + Utils.timestamp(); 411 } 412 413 // check for existing id 414 if( $('#'+id).length ) { 415 $('#'+id).attr('href', href); 416 length--; 417 ready = true; 418 } else { 419 link = $( '<link>' ).attr({ 420 rel: 'stylesheet', 421 href: href, 422 id: id 423 }).get(0); 424 425 window.setTimeout(function() { 426 var styles = $('link[rel="stylesheet"], style'); 427 if ( styles.length ) { 428 styles.get(0).parentNode.insertBefore( link, styles[0] ); 429 } else { 430 DOM().head.appendChild( link ); 431 } 432 433 if ( IE ) { 434 link.attachEvent( 'onreadystatechange', function(e) { 435 if( link.readyState == 'complete' ) { 436 ready = true; 437 } 438 }); 439 } else { 440 // what to do here? returning for now. 441 ready = true; 442 } 443 }, 10); 444 } 445 446 if (typeof callback == 'function') { 447 448 Utils.wait({ 449 until: function() { 450 return ready && doc.styleSheets.length > length; 451 }, 452 success: function() { 453 Utils.addTimer( 'css', function() { 454 callback.call( link, link ); 455 }, 100); 456 }, 457 error: function() { 458 Galleria.raise( 'Theme CSS could not load' ); 459 }, 460 timeout: 1000 461 }); 462 } 463 return link; 464 } 465 }; 466 })(); 467 468 /** 469 The main Galleria class 470 471 @class 472 473 @example var gallery = new Galleria(); 474 475 @author http://aino.se 476 477 @requires jQuery 478 479 @returns {Galleria} 480 */ 481 482 Galleria = function() { 483 484 var self = this; 485 486 // the theme used 487 this._theme = undef; 488 489 // internal options 490 this._options = {}; 491 492 // flag for controlling play/pause 493 this._playing = false; 494 495 // internal interval for slideshow 496 this._playtime = 5000; 497 498 // internal variable for the currently active image 499 this._active = null; 500 501 // the internal queue, arrayified 502 this._queue = { length: 0 }; 503 504 // the internal data array 505 this._data = []; 506 507 // the internal dom collection 508 this._dom = {}; 509 510 // the internal thumbnails array 511 this._thumbnails = []; 512 513 // internal init flag 514 this._initialized = false; 515 516 // global stagewidth/height 517 this._stageWidth = 0; 518 this._stageHeight = 0; 519 520 // target holder 521 this._target = undef; 522 523 // instance id 524 this._id = Utils.timestamp(); 525 526 // add some elements 527 var divs = 'container stage images image-nav image-nav-left image-nav-right ' + 528 'info info-text info-title info-description info-author ' + 529 'thumbnails thumbnails-list thumbnails-container thumb-nav-left thumb-nav-right ' + 530 'loader counter tooltip', 531 spans = 'current total'; 532 533 $.each( divs.split(' '), function( i, elemId ) { 534 self._dom[ elemId ] = Utils.create( 'galleria-' + elemId ); 535 }); 536 537 $.each( spans.split(' '), function( i, elemId ) { 538 self._dom[ elemId ] = Utils.create( 'galleria-' + elemId, 'span' ); 539 }); 540 541 // the internal keyboard object 542 // keeps reference of the keybinds and provides helper methods for binding keys 543 var keyboard = this._keyboard = { 544 545 keys : { 546 'UP': 38, 547 'DOWN': 40, 548 'LEFT': 37, 549 'RIGHT': 39, 550 'RETURN': 13, 551 'ESCAPE': 27, 552 'BACKSPACE': 8 553 }, 554 555 map : {}, 556 557 bound: false, 558 559 press: function(e) { 560 var key = e.keyCode || e.which; 561 if ( key in k.map && typeof keyboard.map[key] == 'function' ) { 562 keyboard.map[key].call(self, e); 563 } 564 }, 565 566 attach: function(map) { 567 for( var key in map ) { 568 var up = key.toUpperCase(); 569 if ( up in keyboard.keys ) { 570 keyboard.map[ keyboard.keys[up] ] = map[key]; 571 } 572 } 573 if ( !keyboard.bound ) { 574 keyboard.bound = true; 575 $doc.bind('keydown', keyboard.press); 576 } 577 }, 578 579 detach: function() { 580 keyboard.bound = false; 581 $doc.unbind('keydown', keyboard.press); 582 } 583 }; 584 585 // internal controls for keeping track of active / inactive images 586 var controls = this._controls = { 587 588 0: undef, 589 590 1: undef, 591 592 active : 0, 593 594 swap : function() { 595 controls.active = controls.active ? 0 : 1; 596 }, 597 598 getActive : function() { 599 return controls[ controls.active ]; 600 }, 601 602 getNext : function() { 603 return controls[ 1 - controls.active ]; 604 } 605 }; 606 607 // internal carousel object 608 var carousel = this._carousel = { 609 610 // shortcuts 611 next: self.$('thumb-nav-right'), 612 prev: self.$('thumb-nav-left'), 613 614 // cache the width 615 width: 0, 616 617 // track the current position 618 current: 0, 619 620 // cache max value 621 max: 0, 622 623 // save all hooks for each width in an array 624 hooks: [], 625 626 // update the carousel 627 // you can run this method anytime, f.ex on window.resize 628 update: function() { 629 var w = 0, 630 h = 0, 631 hooks = [0]; 632 633 $.each( self._thumbnails, function( i, thumb ) { 634 if ( thumb.ready ) { 635 w += thumb.outerWidth || $( thumb.container ).outerWidth( true ); 636 hooks[ i+1 ] = w; 637 h = Math.max( h, thumb.outerHeight || $( thumb.container).outerHeight() ); 638 } 639 }); 640 self.$( 'thumbnails-container' ).toggleClass( 'galleria-carousel', w > self._stageWidth ); 641 642 self.$( 'thumbnails' ).css({ 643 width: w, 644 height: h 645 }); 646 647 carousel.max = w; 648 carousel.hooks = hooks; 649 carousel.width = self.$( 'thumbnails-list' ).width(); 650 carousel.setClasses(); 651 652 // todo: fix so the carousel moves to the left 653 }, 654 655 bindControls: function() { 656 657 carousel.next.bind( CLICK(), function(e) { 658 e.preventDefault(); 659 660 if ( self._options.carousel_steps == 'auto' ) { 661 662 for ( var i = carousel.current; i < carousel.hooks.length; i++ ) { 663 if ( carousel.hooks[i] - carousel.hooks[ carousel.current ] > carousel.width ) { 664 carousel.set(i - 2); 665 break; 666 } 667 } 668 669 } else { 670 carousel.set( carousel.current + self._options.carousel_steps); 671 } 672 }); 673 674 carousel.prev.bind( CLICK(), function(e) { 675 e.preventDefault(); 676 677 if ( self._options.carousel_steps == 'auto' ) { 678 679 for ( var i = carousel.current; i >= 0; i-- ) { 680 if ( carousel.hooks[ carousel.current ] - carousel.hooks[i] > carousel.width ) { 681 carousel.set( i + 2 ); 682 break; 683 } else if ( i == 0 ) { 684 carousel.set( 0 ); 685 break; 686 } 687 } 688 } else { 689 carousel.set( carousel.current - self._options.carousel_steps ); 690 } 691 }); 692 }, 693 694 // calculate and set positions 695 set: function( i ) { 696 i = Math.max( i, 0 ); 697 while ( carousel.hooks[i - 1] + carousel.width > carousel.max && i >= 0 ) { 698 i--; 699 } 700 carousel.current = i; 701 carousel.animate(); 702 }, 703 704 // get the last position 705 getLast: function(i) { 706 return ( i || carousel.current ) - 1; 707 }, 708 709 // follow the active image 710 follow: function(i) { 711 712 //don't follow if position fits 713 if ( i == 0 || i == carousel.hooks.length - 2 ) { 714 carousel.set( i ); 715 return; 716 } 717 718 // calculate last position 719 var last = carousel.current; 720 while( carousel.hooks[last] - carousel.hooks[ carousel.current ] < 721 carousel.width && last <= carousel.hooks.length ) { 722 last ++; 723 } 724 725 // set position 726 if ( i - 1 < carousel.current ) { 727 carousel.set( i - 1 ); 728 } else if ( i + 2 > last) { 729 carousel.set( i - last + carousel.current + 2 ); 730 } 731 }, 732 733 // helper for setting disabled classes 734 setClasses: function() { 735 carousel.prev.toggleClass( 'disabled', !carousel.current ); 736 carousel.next.toggleClass( 'disabled', carousel.hooks[ carousel.current ] + carousel.width > carousel.max ); 737 }, 738 739 // the animation method 740 animate: function(to) { 741 carousel.setClasses(); 742 var num = carousel.hooks[ carousel.current ] * -1; 743 744 if ( isNaN( num ) ) { 745 return; 746 } 747 748 self.$( 'thumbnails' ).animate({ 749 left: num 750 },{ 751 duration: self._options.carousel_speed, 752 easing: self._options.easing, 753 queue: false 754 }); 755 } 756 }; 757 758 // tooltip control 759 // added in 1.2 760 var tooltip = this._tooltip = { 761 762 initialized : false, 763 764 active: null, 765 766 open: false, 767 768 init: function() { 769 770 tooltip.initialized = true; 771 772 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' + 773 '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);}'; 774 775 Utils.insertStyleTag(css); 776 777 self.$( 'tooltip' ).css('opacity', .8); 778 Utils.hide( self.get('tooltip') ); 779 780 }, 781 782 // move handler 783 move: function( e ) { 784 var mouseX = self.getMousePosition(e).x, 785 mouseY = self.getMousePosition(e).y, 786 $elem = self.$( 'tooltip' ), 787 x = mouseX, 788 y = mouseY, 789 height = $elem.outerHeight( true ) + 1, 790 width = $elem.outerWidth( true ), 791 limitY = height + 15; 792 793 var maxX = self._stageWidth - width, 794 maxY = self._stageHeight - height; 795 796 if ( !isNaN(x) && !isNaN(y) ) { 797 798 x += 15; 799 y -= 35; 800 801 x = Math.max( 0, Math.min( maxX, x ) ); 802 y = Math.max( 0, Math.min( maxY, y ) ); 803 804 if( mouseY < limitY ) { 805 y = limitY; 806 } 807 808 $elem.css({ left: x, top: y }); 809 } 810 }, 811 812 // bind elements to the tooltip 813 // you can bind multiple elementIDs using { elemID : function } or { elemID : string } 814 // you can also bind single DOM elements using bind(elem, string) 815 bind: function( elem, value ) { 816 817 if (! tooltip.initialized ) { 818 tooltip.init(); 819 } 820 821 var hover = function( elem, value) { 822 823 tooltip.define( elem, value ); 824 825 $( elem ).hover(function() { 826 827 tooltip.active = elem; 828 Utils.clearTimer('switch_tooltip'); 829 self.$('container').unbind( 'mousemove', tooltip.move ).bind( 'mousemove', tooltip.move ).trigger( 'mousemove' ); 830 tooltip.show( elem ); 831 832 Galleria.utils.addTimer( 'tooltip', function() { 833 834 Utils.show( self.get( 'tooltip' ), 400 ); 835 tooltip.open = true; 836 837 }, tooltip.open ? 0 : 1000); 838 839 }, function() { 840 841 tooltip.active = null; 842 843 self.$( 'container' ).unbind( 'mousemove', tooltip.move ); 844 Utils.clearTimer( 'tooltip' ); 845 846 Utils.hide( self.get( 'tooltip' ), 200, function() { 847 Utils.addTimer('switch_tooltip', function() { 848 tooltip.open = false; 849 }, 1000); 850 }); 851 }); 852 }; 853 854 if (typeof value == 'string') { 855 hover( ( elem in self._dom ? self.get(elem) : elem ), value ); 856 } else { 857 // asume elemID here 858 $.each( elem, function( elemID, val ) { 859 hover( self.get(elemID), val ); 860 }); 861 } 862 }, 863 864 show: function( elem ) { 865 var text = $(elem).data('tt'); 866 if ( ! text ) { 867 return; 868 } 869 text = typeof text == 'function' ? text() : text; 870 self.$( 'tooltip' ).html( text.replace(/\s/, ' ') ); 871 }, 872 873 // redefine the tooltip here 874 define: function( elem, value ) { 875 876 // we store functions, not strings 877 if (typeof value !== 'function') { 878 var s = value; 879 value = function() { 880 return s; 881 }; 882 } 883 884 if ( elem in self._dom ) { 885 elem = self.get( elem ); 886 } 887 888 $(elem).data('tt', value); 889 890 tooltip.show( elem ); 891 892 }, 893 894 refresh: function() { 895 $.each( arguments, function(i, elem) { 896 if ( tooltip.active == elem ) { 897 tooltip.show( elem ); 898 } 899 }); 900 } 901 }; 902 903 // internal fullscreen control 904 // added in 1.195 905 // still kind of experimental 906 var fullscreen = this._fullscreen = { 907 scrolled: 0, 908 enter: function(callback) { 909 910 // hide the image until rescale is complete 911 Utils.hide( self.getActiveImage() ); 912 913 self.$( 'container' ).addClass( 'fullscreen' ); 914 915 fullscreen.scrolled = $(window).scrollTop(); 916 917 // begin styleforce 918 Utils.forceStyles(self.get('container'), { 919 position: 'fixed', 920 top: 0, 921 left: 0, 922 width: '100%', 923 height: '100%', 924 zIndex: 10000 925 }); 926 927 var htmlbody = { 928 height: '100%', 929 overflow: 'hidden', 930 margin:0, 931 padding:0 932 }; 933 934 Utils.forceStyles( DOM().html, htmlbody ); 935 Utils.forceStyles( DOM().body, htmlbody ); 936 937 // attach some keys 938 self.attachKeyboard({ 939 escape: self.exitFullscreen, 940 right: self.next, 941 left: self.prev 942 }); 943 944 // init the first rescale and attach callbacks 945 self.rescale(function() { 946 947 Utils.addTimer('fullscreen_enter', function() { 948 // show the image after 50 ms 949 Utils.show( self.getActiveImage() ); 950 951 if (typeof callback == 'function') { 952 callback.call( self ); 953 } 954 955 }, 50); 956 957 self.trigger( Galleria.FULLSCREEN_ENTER ); 958 }); 959 960 // bind the scaling to the resize event 961 $(window).resize( function() { 962 fullscreen.scale(); 963 } ); 964 }, 965 966 scale : function() { 967 self.rescale(); 968 }, 969 970 exit: function(callback) { 971 972 Utils.hide( self.getActiveImage() ); 973 974 self.$('container').removeClass( 'fullscreen' ); 975 976 // revert all styles 977 Utils.revertStyles( self.get('container'), DOM().html, DOM().body ); 978 979 // scroll back 980 window.scrollTo(0, fullscreen.scrolled); 981 982 // detach all keyboard events (is this good?) 983 self.detachKeyboard(); 984 985 self.rescale(function() { 986 Utils.addTimer('fullscreen_exit', function() { 987 988 // show the image after 50 ms 989 Utils.show( self.getActiveImage() ); 990 991 if ( typeof callback == 'function' ) { 992 callback.call( self ); 993 } 994 995 }, 50); 996 997 self.trigger( Galleria.FULLSCREEN_EXIT ); 998 }); 999 1000 $(window).unbind('resize', fullscreen.scale); 1001 } 1002 }; 1003 1004 // the internal idle object for controlling idle states 1005 // TODO occational event conflicts 1006 var idle = this._idle = { 1007 1008 trunk: [], 1009 1010 bound: false, 1011 1012 add: function(elem, to) { 1013 if (!elem) { 1014 return; 1015 } 1016 if (!idle.bound) { 1017 idle.addEvent(); 1018 } 1019 elem = $(elem); 1020 1021 var from = {}; 1022 1023 for (var style in to) { 1024 from[style] = elem.css(style); 1025 } 1026 elem.data('idle', { 1027 from: from, 1028 to: to, 1029 complete: true, 1030 busy: false 1031 }); 1032 idle.addTimer(); 1033 idle.trunk.push(elem); 1034 }, 1035 1036 remove: function(elem) { 1037 1038 elem = jQuery(elem); 1039 1040 $.each(idle.trunk, function(i, el) { 1041 if ( el.length && !el.not(elem).length ) { 1042 self._idle.show(elem); 1043 self._idle.trunk.splice(i, 1); 1044 } 1045 }); 1046 1047 if (!idle.trunk.length) { 1048 idle.removeEvent(); 1049 Utils.clearTimer('idle'); 1050 } 1051 }, 1052 1053 addEvent : function() { 1054 idle.bound = true; 1055 self.$('container').bind('mousemove click', idle.showAll ); 1056 }, 1057 1058 removeEvent : function() { 1059 idle.bound = false; 1060 self.$('container').unbind('mousemove click', idle.showAll ); 1061 }, 1062 1063 addTimer : function() { 1064 Utils.addTimer('idle', function() { 1065 self._idle.hide(); 1066 }, self._options.idle_time ); 1067 }, 1068 1069 hide : function() { 1070 self.trigger( Galleria.IDLE_ENTER ); 1071 1072 $.each( idle.trunk, function(i, elem) { 1073 1074 var data = elem.data('idle'); 1075 1076 if (! data) { 1077 return; 1078 } 1079 1080 data.complete = false; 1081 1082 elem.stop().animate(data.to, { 1083 duration: 600, 1084 queue: false, 1085 easing: 'swing' 1086 }); 1087 }); 1088 }, 1089 1090 showAll : function() { 1091 Utils.clearTimer('idle'); 1092 1093 $.each(self._idle.trunk, function( i, elem ) { 1094 self._idle.show( elem ); 1095 }); 1096 }, 1097 1098 show: function(elem) { 1099 1100 var data = elem.data('idle'); 1101 1102 if (!data.busy && !data.complete) { 1103 1104 data.busy = true; 1105 1106 self.trigger( Galleria.IDLE_EXIT ); 1107 1108 elem.animate(data.from, { 1109 duration: 300, 1110 queue: false, 1111 easing: 'swing', 1112 complete: function() { 1113 $(this).data('idle').busy = false; 1114 $(this).data('idle').complete = true; 1115 } 1116 }); 1117 } 1118 idle.addTimer(); 1119 } 1120 }; 1121 1122 // internal lightbox object 1123 // creates a predesigned lightbox for simple popups of images in galleria 1124 var lightbox = this._lightbox = { 1125 1126 width : 0, 1127 1128 height : 0, 1129 1130 initialized : false, 1131 1132 active : null, 1133 1134 image : null, 1135 1136 elems : {}, 1137 1138 init : function() { 1139 1140 // trigger the event 1141 self.trigger( Galleria.LIGHTBOX_OPEN ); 1142 1143 if ( lightbox.initialized ) { 1144 return; 1145 } 1146 lightbox.initialized = true; 1147 1148 // create some elements to work with 1149 var elems = 'overlay box content shadow title info close prevholder prev nextholder next counter image', 1150 el = {}, 1151 op = self._options, 1152 css = '', 1153 cssMap = { 1154 overlay: 'position:fixed;display:none;opacity:'+op.overlay_opacity+';top:0;left:0;width:100%;height:100%;background:'+op.overlay_background+';z-index:99990', 1155 box: 'position:fixed;display:none;width:400px;height:400px;top:50%;left:50%;margin-top:-200px;margin-left:-200px;z-index:99991', 1156 shadow: 'position:absolute;background:#000;width:100%;height:100%;', 1157 content: 'position:absolute;background-color:#fff;top:10px;left:10px;right:10px;bottom:10px;overflow:hidden', 1158 info: 'position:absolute;bottom:10px;left:10px;right:10px;color:#444;font:11px/13px arial,sans-serif;height:13px', 1159 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', 1160 image: 'position:absolute;top:10px;left:10px;right:10px;bottom:30px;overflow:hidden', 1161 prevholder: 'position:absolute;width:50%;height:100%;cursor:pointer', 1162 nextholder: 'position:absolute;width:50%;height:100%;right:0;cursor:pointer', 1163 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', 1164 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', 1165 title: 'float:left', 1166 counter: 'float:right;margin-left:8px' 1167 }, 1168 hover = function(elem) { 1169 return elem.hover( 1170 function() { $(this).css( 'color', '#bbb' ); }, 1171 function() { $(this).css( 'color', '#444' ); } 1172 ); 1173 }; 1174 1175 // create and insert CSS 1176 $.each(cssMap, function( key, value ) { 1177 css += '.galleria-lightbox-'+key+'{'+value+'}'; 1178 }); 1179 1180 Utils.insertStyleTag( css ); 1181 1182 // create the elements 1183 $.each(elems.split(' '), function( i, elemId ) { 1184 self.addElement( 'lightbox-' + elemId ); 1185 el[ elemId ] = lightbox.elems[ elemId ] = self.get( 'lightbox-' + elemId ); 1186 }); 1187 1188 // initiate the image 1189 lightbox.image = new Galleria.Picture(); 1190 1191 // append the elements 1192 self.append({ 1193 'lightbox-box': ['lightbox-shadow','lightbox-content', 'lightbox-close','lightbox-prevholder','lightbox-nextholder'], 1194 'lightbox-info': ['lightbox-title','lightbox-counter'], 1195 'lightbox-content': ['lightbox-info', 'lightbox-image'], 1196 'lightbox-prevholder': 'lightbox-prev', 1197 'lightbox-nextholder': 'lightbox-next' 1198 }); 1199 1200 $( el.image ).append( lightbox.image.container ); 1201 1202 $( DOM().body ).append( el.overlay, el.box ); 1203 1204 // add the prev/next nav and bind some controls 1205 1206 hover( $( el.close ).bind( CLICK(), lightbox.hide ).html('×') ); 1207 1208 $.each( ['Prev','Next'], function(i, dir) { 1209 1210 var $d = $( el[ dir.toLowerCase() ] ).html( /v/.test( dir ) ? '‹ ' : ' ›' ); 1211 1212 $( el[ dir.toLowerCase()+'holder'] ).hover(function() { 1213 $d.show(); 1214 }, function() { 1215 $d.fadeOut( 200 ); 1216 }).bind( CLICK(), function() { 1217 lightbox[ 'show' + dir ](); 1218 }); 1219 1220 }); 1221 $( el.overlay ).bind( CLICK(), lightbox.hide ); 1222 1223 }, 1224 1225 rescale: function(event) { 1226 1227 // calculate 1228 var width = Math.min( $(window).width(), lightbox.width ), 1229 height = Math.min( $(window).height(), lightbox.height ), 1230 ratio = Math.min( (width - 60) / lightbox.width, (height - 80) / lightbox.height ), 1231 destWidth = ( lightbox.width * ratio ) + 40, 1232 destHeight = ( lightbox.height * ratio ) + 60, 1233 to = { 1234 width: destWidth, 1235 height: destHeight, 1236 marginTop: Math.ceil( destHeight / 2 ) *- 1, 1237 marginLeft: Math.ceil( destWidth / 2 ) *- 1 1238 }; 1239 1240 // if rescale event, don't animate 1241 if ( event ) { 1242 $( lightbox.elems.box ).css( to ); 1243 } else { 1244 $( lightbox.elems.box ).animate( 1245 to, 1246 self._options.lightbox_transition_speed, 1247 self._options.easing, 1248 function() { 1249 var image = lightbox.image, 1250 speed = self._options.lightbox_fade_speed; 1251 1252 self.trigger({ 1253 type: Galleria.LIGHTBOX_IMAGE, 1254 imageTarget: image.image 1255 }); 1256 1257 image.show(); 1258 Utils.show( image.image, speed ); 1259 Utils.show( lightbox.elems.info, speed ); 1260 } 1261 ); 1262 } 1263 }, 1264 1265 hide: function() { 1266 1267 // remove the image 1268 lightbox.image.image = null; 1269 1270 $(window).unbind('resize', lightbox.rescale); 1271 1272 $( lightbox.elems.box ).hide(); 1273 1274 Utils.hide( lightbox.elems.info ); 1275 1276 Utils.hide( lightbox.elems.overlay, 200, function() { 1277 $( this ).hide().css( 'opacity', self._options.overlay_opacity ); 1278 self.trigger( Galleria.LIGHTBOX_CLOSE ); 1279 }); 1280 }, 1281 1282 showNext: function() { 1283 lightbox.show( self.getNext( lightbox.active ) ); 1284 }, 1285 1286 showPrev: function() { 1287 lightbox.show( self.getPrev( lightbox.active ) ); 1288 }, 1289 1290 show: function(index) { 1291 1292 lightbox.active = index = typeof index == 'number' ? index : self.getIndex(); 1293 1294 if ( !lightbox.initialized ) { 1295 lightbox.init(); 1296 } 1297 1298 $(window).unbind('resize', lightbox.rescale ); 1299 1300 var data = self.getData(index), 1301 total = self.getDataLength(); 1302 1303 Utils.hide( lightbox.elems.info ); 1304 1305 lightbox.image.load( data.image, function( image ) { 1306 1307 lightbox.width = image.original.width; 1308 lightbox.height = image.original.height; 1309 1310 $( image.image ).css({ 1311 width: '100.5%', 1312 height: '100.5%', 1313 top: 0, 1314 zIndex: 99998, 1315 opacity: 0 1316 }); 1317 1318 lightbox.elems.title.innerHTML = data.title; 1319 lightbox.elems.counter.innerHTML = (index + 1) + ' / ' + total; 1320 $(window).resize( lightbox.rescale ); 1321 lightbox.rescale(); 1322 }); 1323 1324 $( lightbox.elems.overlay ).show(); 1325 $( lightbox.elems.box ).show(); 1326 } 1327 }; 1328 1329 return this; 1330 }; 1331 1332 // end Galleria constructor 1333 1334 Galleria.prototype = { 1335 1336 /** 1337 Use this function to initialize the gallery and start loading. 1338 Should only be called once per instance. 1339 1340 @param {HTML Element} target The target element 1341 @param {Object} options The gallery options 1342 1343 @returns {Galleria} 1344 */ 1345 1346 init: function( target, options ) { 1347 1348 var self = this; 1349 1350 // save the instance 1351 _galleries.push( this ); 1352 1353 // save the original ingredients 1354 this._original = { 1355 target: target, 1356 options: options, 1357 data: null 1358 }; 1359 1360 // save the target here 1361 this._target = this._dom.target = target.nodeName ? target : $( target ).get(0); 1362 1363 // raise error if no target is detected 1364 if ( !this._target ) { 1365 Galleria.raise('Target not found.'); 1366 return; 1367 } 1368 1369 // apply options 1370 this._options = { 1371 autoplay: false, 1372 carousel: true, 1373 carousel_follow: true, 1374 carousel_speed: 400, 1375 carousel_steps: 'auto', 1376 clicknext: false, 1377 data_config : function( elem ) { return {}; }, 1378 data_selector: 'img', 1379 data_source: this._target, 1380 debug: undef, 1381 easing: 'galleria', 1382 extend: function(options) {}, 1383 height: 'auto', 1384 idle_time: 3000, 1385 image_crop: false, 1386 image_margin: 0, 1387 image_pan: false, 1388 image_pan_smoothness: 12, 1389 image_position: '50%', 1390 keep_source: false, 1391 lightbox_fade_speed: 200, 1392 lightbox_transition_speed: 500, 1393 link_source_images: true, 1394 max_scale_ratio: undef, 1395 min_scale_ratio: undef, 1396 on_image: function(img,thumb) {}, 1397 overlay_opacity: .85, 1398 overlay_background: '#0b0b0b', 1399 pause_on_interaction: true, // 1.9.96 1400 popup_links: false, 1401 preload: 2, 1402 queue: true, 1403 show: 0, 1404 show_info: true, 1405 show_counter: true, 1406 show_imagenav: true, 1407 thumb_crop: true, 1408 thumb_event_type: CLICK(), 1409 thumb_fit: true, 1410 thumb_margin: 0, 1411 thumb_quality: 'auto', 1412 thumbnails: true, 1413 transition: 'fade', 1414 transition_initial: undef, 1415 transition_speed: 400, 1416 width: 'auto' 1417 }; 1418 1419 // apply debug 1420 if ( options && options.debug === true ) { 1421 DEBUG = true; 1422 } 1423 1424 // hide all content 1425 $( this._target ).children().hide(); 1426 1427 // now we just have to wait for the theme... 1428 if ( Galleria.theme ) { 1429 this._init(); 1430 } else { 1431 Utils.addTimer('themeload', function() { 1432 Galleria.raise( 'No theme found. '); 1433 }, 2000); 1434 1435 $doc.one( Galleria.THEMELOAD, function() { 1436 Utils.clearTimer( 'themeload' ); 1437 self._init.call( self ); 1438 }); 1439 } 1440 }, 1441 1442 // the internal _init is called when the THEMELOAD event is triggered 1443 // this method should only be called once per instance 1444 // for manipulation of data, use the .load method 1445 1446 _init: function() { 1447 var self = this; 1448 1449 if ( this._initialized ) { 1450 Galleria.raise( 'Init failed: Gallery instance already initialized.' ); 1451 return this; 1452 } 1453 1454 this._initialized = true; 1455 1456 if ( !Galleria.theme ) { 1457 Galleria.raise( 'Init failed: No theme found.' ); 1458 return this; 1459 } 1460 1461 // merge the theme & caller options 1462 $.extend( true, this._options, Galleria.theme.defaults, this._original.options ); 1463 1464 // bind the gallery to run when data is ready 1465 this.bind( Galleria.DATA, function() { 1466 1467 // save the new data 1468 this._original.data = this._data; 1469 1470 // lets show the counter here 1471 this.get('total').innerHTML = this.getDataLength(); 1472 1473 // cache the container 1474 var $container = this.$( 'container' ); 1475 1476 // the gallery is ready, let's just wait for the css 1477 var num = { width: 0, height: 0 }; 1478 var testElem = Utils.create('galleria-image'); 1479 1480 // check container and thumbnail height 1481 Utils.wait({ 1482 until: function() { 1483 1484 // keep trying to get the value 1485 $.each(['width', 'height'], function( i, m ) { 1486 1487 if (self._options[ m ] && typeof self._options[ m ] == 'number') { 1488 num[ m ] = self._options[ m ]; 1489 } else { 1490 num[m] = Utils.parseValue( self.$( 'target' ).css( m ) ) || 1491 Utils.parseValue( $container.css( m ) ) || 1492 self.$( 'target' )[ m ]() || 1493 $container[ m ]() 1494 } 1495 1496 $container[m]( num[ m ] ); 1497 }); 1498 1499 var thumbHeight = function() { 1500 return true; 1501 }; 1502 1503 // make sure thumbnails have a height as well 1504 if ( self._options.thumbnails ) { 1505 self.$('thumbnails').append( testElem ); 1506 thumbHeight = function() { 1507 return !!$( testElem ).height(); 1508 }; 1509 } 1510 1511 return thumbHeight() && num.width && num.height > 50; 1512 1513 }, 1514 success: function() { 1515 1516 // remove the testElem 1517 $( testElem ).remove(); 1518 1519 // for some strange reason, webkit needs a single setTimeout to play ball 1520 if ( Galleria.WEBKIT ) { 1521 window.setTimeout( function() { 1522 self._run(); 1523 }, 1); 1524 } else { 1525 self._run(); 1526 } 1527 }, 1528 error: function() { 1529 // Height was probably not set, raise a hard error 1530 Galleria.raise('Width & Height not found.', true); 1531 }, 1532 timeout: 2000 1533 }); 1534 }); 1535 1536 // postrun some stuff after the gallery is ready 1537 // make sure it only runs once 1538 var one = false; 1539 1540 this.bind( Galleria.READY, function() { 1541 1542 // show counter 1543 Utils.show( this.get('counter') ); 1544 1545 // bind clicknext 1546 if ( this._options.clicknext ) { 1547 $.each( this._data, function( i, data ) { 1548 delete data.link; 1549 }); 1550 this.$( 'stage' ).css({ cursor : 'pointer' }).bind( CLICK(), function(e) { 1551 self.next(); 1552 }); 1553 } 1554 1555 // bind carousel nav 1556 if ( this._options.carousel ) { 1557 this._carousel.bindControls(); 1558 } 1559 1560 // start autoplay 1561 if ( this._options.autoplay ) { 1562 1563 this.pause(); 1564 1565 if ( typeof this._options.autoplay == 'number' ) { 1566 this._playtime = this._options.autoplay; 1567 } 1568 1569 this.trigger( Galleria.PLAY ); 1570 this._playing = true; 1571 } 1572 1573 // if second load, just do the show and return 1574 if ( one ) { 1575 this.show( this._options.show ); 1576 return; 1577 } 1578 1579 one = true; 1580 1581 // initialize the History plugin 1582 if ( Galleria.History ) { 1583 1584 // bind the show method 1585 Galleria.History.change(function(e) { 1586 1587 // grab history ID 1588 var val = parseInt( e.value.replace( /\//, '' ) ); 1589 1590 // if ID is NaN, the user pressed back from the first image 1591 // return to previous address 1592 if (isNaN(val)) { 1593 window.history.go(-1); 1594 1595 // else show the image 1596 } else { 1597 self.show( val, undef, true ); 1598 } 1599 }); 1600 } 1601 1602 // call the theme init method 1603 Galleria.theme.init.call( this, this._options ); 1604 1605 // call the extend option 1606 this._options.extend.call( this, this._options ); 1607 1608 // show the initial image 1609 // first test for permalinks in history 1610 if ( /^[0-9]{1,4}$/.test( HASH ) && Galleria.History ) { 1611 this.show( HASH, undef, true ); 1612 1613 } else { 1614 this.show( this._options.show ); 1615 } 1616 1617 }); 1618 1619 // build the gallery frame 1620 this.append({ 1621 'info-text' : 1622 ['info-title', 'info-description', 'info-author'], 1623 'info' : 1624 ['info-text'], 1625 'image-nav' : 1626 ['image-nav-right', 'image-nav-left'], 1627 'stage' : 1628 ['images', 'loader', 'counter', 'image-nav'], 1629 'thumbnails-list' : 1630 ['thumbnails'], 1631 'thumbnails-container' : 1632 ['thumb-nav-left', 'thumbnails-list', 'thumb-nav-right'], 1633 'container' : 1634 ['stage', 'thumbnails-container', 'info', 'tooltip'] 1635 }); 1636 1637 Utils.hide( this.$( 'counter' ).append( 1638 this.get( 'current' ), 1639 ' / ', 1640 this.get( 'total' ) 1641 ) ); 1642 1643 this.setCounter('–'); 1644 1645 // add images to the controls 1646 $.each( new Array(2), function(i) { 1647 1648 // create a new Picture instance 1649 var image = new Galleria.Picture(); 1650 1651 // apply some styles 1652 $( image.container ).css({ 1653 position: 'absolute', 1654 top: 0, 1655 left: 0 1656 }); 1657 1658 // append the image 1659 self.$( 'images' ).append( image.container ); 1660 1661 // reload the controls 1662 self._controls[i] = image; 1663 1664 }); 1665 1666 // some forced generic styling 1667 this.$( 'images' ).css({ 1668 position: 'relative', 1669 top: 0, 1670 left: 0, 1671 width: '100%', 1672 height: '100%' 1673 }); 1674 1675 this.$( 'thumbnails, thumbnails-list' ).css({ 1676 overflow: 'hidden', 1677 position: 'relative' 1678 }); 1679 1680 // bind image navigation arrows 1681 this.$( 'image-nav-right, image-nav-left' ).bind( CLICK(), function(e) { 1682 1683 // tune the clicknext option 1684 if ( self._options.clicknext ) { 1685 e.stopPropagation(); 1686 } 1687 1688 // pause if options is set 1689 if ( self._options.pause_on_interaction ) { 1690 self.pause(); 1691 } 1692 1693 // navigate 1694 var fn = /right/.test( this.className ) ? 'next' : 'prev'; 1695 self[ fn ](); 1696 1697 }); 1698 1699 // hide controls if chosen to 1700 $.each( ['info','counter','image-nav'], function( i, el ) { 1701 if ( self._options[ 'show_' + el.replace(/-/, '') ] === false ) { 1702 Utils.moveOut( self.get( el ) ); 1703 } 1704 }); 1705 1706 // load up target content 1707 this.load(); 1708 1709 // now it's usually safe to remove the content 1710 // IE will never stop loading if we remove it, so let's keep it hidden for IE (it's usually fast enough anyway) 1711 if ( !this._options.keep_source && !IE ) { 1712 this._target.innerHTML = ''; 1713 } 1714 1715 // append the gallery frame 1716 this.$( 'target' ).append( this.get( 'container' ) ); 1717 1718 // parse the carousel on each thumb load 1719 if ( this._options.carousel ) { 1720 this.bind( Galleria.THUMBNAIL, function() { 1721 this.updateCarousel(); 1722 }); 1723 } 1724 1725 // bind on_image helper 1726 this.bind( Galleria.IMAGE, function( e ) { 1727 this._options.on_image.call( this, e.imageTarget, e.thumbTarget ); 1728 }); 1729 1730 return this; 1731 }, 1732 1733 // the internal _run method should be called after loading data into galleria 1734 // creates thumbnails and makes sure the gallery has proper meassurements 1735 _run : function() { 1736 // shortcuts 1737 var self = this, 1738 o = this._options, 1739 1740 // width/height for calculations 1741 width = 0, 1742 height = 0, 1743 1744 // cache the thumbnail option 1745 optval = typeof o.thumbnails == 'string' ? o.thumbnails.toLowerCase() : null; 1746 1747 // loop through data and create thumbnails 1748 for( var i = 0; this._data[i]; i++ ) { 1749 1750 var thumb, 1751 data = this._data[i], 1752 $container; 1753 1754 if ( o.thumbnails === true ) { 1755 1756 // add a new Picture instance 1757 thumb = new Galleria.Picture(i); 1758 1759 // get source from thumb or image 1760 var src = data.thumb || data.image; 1761 1762 // append the thumbnail 1763 this.$( 'thumbnails' ).append( thumb.container ); 1764 1765 // cache the container 1766 $container = $( thumb.container ); 1767 1768 // move some data into the instance 1769 thumb.data = { 1770 width : Utils.parseValue( $container.css('width') ), 1771 height : Utils.parseValue( $container.css('height') ), 1772 order : i 1773 }; 1774 1775 // grab & reset size for smoother thumbnail loads 1776 $container.css(( o.thumb_fit && o.thumb_crop !== true ) ? 1777 { width: 0, height: 0 } : 1778 { width: thumb.data.width, height: thumb.data.height }); 1779 1780 // load the thumbnail 1781 thumb.load( src, function( thumb ) { 1782 1783 // scale when ready 1784 thumb.scale({ 1785 width: thumb.data.width, 1786 height: thumb.data.height, 1787 crop: o.thumb_crop, 1788 margin: o.thumb_margin, 1789 complete: function( thumb ) { 1790 1791 // shrink thumbnails to fit 1792 var top = ['left', 'top']; 1793 var arr = ['Width', 'Height']; 1794 1795 // calculate shrinked positions 1796 $.each(arr, function( i, meassure ) { 1797 var m = meassure.toLowerCase(); 1798 if ( (o.thumb_crop !== true || o.thumb_crop == m ) && o.thumb_fit ) { 1799 var css = {}; 1800 css[m] = thumb[m]; 1801 $( thumb.container ).css( css ); 1802 css = {}; 1803 css[top[i]] = 0; 1804 $( thumb.image ).css( css); 1805 } 1806 1807 // cache outer meassures 1808 thumb['outer' + meassure] = $( thumb.container )['outer' + meassure]( true ); 1809 }); 1810 1811 // set high quality if downscale is moderate 1812 Utils.toggleQuality( thumb.image, 1813 o.thumb_quality === true || 1814 ( o.thumb_quality == 'auto' && thumb.original.width < thumb.width * 3 ) 1815 ); 1816 1817 // trigger the THUMBNAIL event 1818 self.trigger({ 1819 type: Galleria.THUMBNAIL, 1820 thumbTarget: thumb.image, 1821 thumbOrder: thumb.data.order 1822 }); 1823 } 1824 }); 1825 }); 1826 1827 // preload all images here 1828 if ( o.preload == 'all' ) { 1829 thumb.add( data.image ); 1830 } 1831 1832 // create empty spans if thumbnails is set to 'empty' 1833 } else if ( optval == 'empty' || optval == 'numbers' ) { 1834 1835 thumb = { 1836 container: Utils.create( 'galleria-image' ), 1837 image: Utils.create( 'img', 'span' ), 1838 ready: true 1839 }; 1840 1841 // create numbered thumbnails 1842 if ( optval == 'numbers' ) { 1843 $( thumb.image ).text( i + 1 ); 1844 } 1845 1846 $( thumb.container ).append( thumb.image ); 1847 this.$( 'thumbnails' ).append( thumb.container ); 1848 1849 self.trigger({ 1850 type: Galleria.THUMBNAIL, 1851 thumbTarget: thumb.image, 1852 thumbOrder: i 1853 }); 1854 1855 // create null object to silent errors 1856 } else { 1857 thumb = { 1858 container: null, 1859 image: null 1860 }; 1861 } 1862 1863 // add events for thumbnails 1864 // you can control the event type using thumb_event_type 1865 // we'll add the same event to the source if it's kept 1866 1867 $( thumb.container ).add( o.keep_source && o.link_source_images ? data.original : null ) 1868 .data('index', i).bind(o.thumb_event_type, function(e) { 1869 // pause if option is set 1870 if ( o.pause_on_interaction ) { 1871 self.pause(); 1872 } 1873 1874 // extract the index from the data 1875 var index = $( e.currentTarget ).data( 'index' ); 1876 if ( self.getIndex() !== index ) { 1877 self.show( index ); 1878 } 1879 1880 e.preventDefault(); 1881 }); 1882 1883 this._thumbnails.push( thumb ); 1884 } 1885 1886 // make sure we have a stageHeight && stageWidth 1887 1888 Utils.wait({ 1889 1890 until: function() { 1891 self._stageWidth = self.$( 'stage' ).width(); 1892 self._stageHeight = self.$( 'stage' ).height(); 1893 return( self._stageWidth && self._stageHeight > 50 ); // what is an acceptable height? 1894 }, 1895 1896 success: function() { 1897 self.trigger( Galleria.READY ); 1898 }, 1899 1900 error: function() { 1901 Galleria.raise('stage meassures not found'); 1902 } 1903 1904 }); 1905 }, 1906 1907 /** 1908 Loads data into the gallery. 1909 You can call this method on an existing gallery to reload the gallery with new data. 1910 1911 @param {Array or String} source Optional JSON array of data or selector of where to find data in the document. 1912 Defaults to the Galleria target or data_source option. 1913 1914 @param {String} selector Optional element selector of what elements to parse. 1915 Defaults to 'img'. 1916 1917 @param {Function} config Optional function to modify the data extraction proceedure from the selector. 1918 See the data_config option for more information. 1919 1920 @returns {Galleria} 1921 */ 1922 1923 load : function( source, selector, config ) { 1924 1925 var self = this; 1926 1927 // empty the data array 1928 this._data = []; 1929 1930 // empty the thumbnails 1931 this._thumbnails = []; 1932 this.$('thumbnails').empty(); 1933 1934 // shorten the arguments 1935 if ( typeof selector == 'function' ) { 1936 config = selector; 1937 selector = null; 1938 } 1939 1940 // use the source set by target 1941 source = source || this._options.data_source; 1942 1943 // use selector set by option 1944 selector = selector || this._options.data_selector; 1945 1946 // use the data_config set by option 1947 config = config || this._options.data_config; 1948 1949 // check if the data is an array already 1950 if ( source.constructor == Array ) { 1951 if ( this.validate( source) ) { 1952 this._data = source; 1953 this.trigger( Galleria.DATA ); 1954 } else { 1955 Galleria.raise( 'Load failed: JSON Array not valid.' ); 1956 } 1957 return this; 1958 } 1959 // loop through images and set data 1960 $( source ).find( selector ).each( function( i, img ) { 1961 var data = {}, 1962 img = $( img ), 1963 parent = img.parent(), 1964 href = parent.attr( 'href' ); 1965 1966 // check if it's a link to another image 1967 if ( /\.(png|gif|jpg|jpeg)$/i.test(href) ) { 1968 data.image = href; 1969 1970 // else assign the href as a link if it exists 1971 } else if ( href ) { 1972 data.link = href; 1973 } 1974 1975 // mix default extractions with the hrefs and config 1976 // and push it into the data array 1977 self._data.push( $.extend({ 1978 1979 title: img.attr('title'), 1980 thumb: img.attr('src'), 1981 image: img.attr('src'), 1982 description: img.attr('alt'), 1983 link: img.attr('longdesc'), 1984 original: img.get(0) // saved as a reference 1985 1986 }, data, config( img ) ) ); 1987 1988 }); 1989 // trigger the DATA event and return 1990 if ( this.getDataLength() ) { 1991 this.trigger( Galleria.DATA ); 1992 } else { 1993 Galleria.raise('Load failed: no data found.'); 1994 } 1995 return this; 1996 1997 }, 1998 1999 _getActive: function() { 2000 return this._controls.getActive(); 2001 }, 2002 2003 validate : function( data ) { 2004 // todo: validate a custom data array 2005 return true; 2006 }, 2007 2008 /** 2009 Bind any event to Galleria 2010 2011 @param {String} type The Event type to listen for 2012 @param {Function} fn The function to execute when the event is triggered 2013 2014 @example this.bind( Galleria.IMAGE, function() { Galleria.log('image shown') }); 2015 2016 @returns {Galleria} 2017 */ 2018 2019 bind : function(type, fn) { 2020 this.$( 'container' ).bind( type, this.proxy(fn) ); 2021 return this; 2022 }, 2023 2024 /** 2025 Unbind any event to Galleria 2026 2027 @param {String} type The Event type to forget 2028 2029 @returns {Galleria} 2030 */ 2031 2032 unbind : function(type) { 2033 this.$( 'container' ).unbind( type ); 2034 return this; 2035 }, 2036 2037 /** 2038 Manually trigger a Galleria event 2039 2040 @param {String} type The Event to trigger 2041 2042 @returns {Galleria} 2043 */ 2044 2045 trigger : function( type ) { 2046 type = typeof type == 'object' ? 2047 $.extend( type, { scope: this } ) : 2048 { type: type, scope: this }; 2049 this.$( 'container' ).trigger( type ); 2050 return this; 2051 }, 2052 2053 /** 2054 Assign an "idle state" to any element. 2055 The idle state will be applied after a certain amount of idle time 2056 Useful to hide f.ex navigation when the gallery is inactive 2057 2058 @param {HTML Element or String} elem The Dom node or selector to apply the idle state to 2059 @param {Object} styles the CSS styles to apply 2060 2061 @example addIdleState( this.get('image-nav'), { opacity: 0 }); 2062 @example addIdleState( '.galleria-image-nav', { top: -200 }); 2063 2064 @returns {Galleria} 2065 */ 2066 2067 addIdleState: function( elem, styles ) { 2068 this._idle.add.apply( this._idle, Utils.array( arguments ) ); 2069 return this; 2070 }, 2071 2072 /** 2073 Removes any idle state previously set using addIdleState() 2074 2075 @param {HTML Element or String} elem The Dom node or selector to remove the idle state from. 2076 2077 @returns {Galleria} 2078 */ 2079 2080 removeIdleState: function( elem ) { 2081 this._idle.remove.apply( this._idle, Utils.array( arguments ) ); 2082 return this; 2083 }, 2084 2085 /** 2086 Force Galleria to enter idle mode. 2087 2088 @returns {Galleria} 2089 */ 2090 2091 enterIdleMode: function() { 2092 this._idle.hide(); 2093 return this; 2094 }, 2095 2096 /** 2097 Force Galleria to exit idle mode. 2098 2099 @returns {Galleria} 2100 */ 2101 2102 exitIdleMode: function() { 2103 this.idle._show(); 2104 return this; 2105 }, 2106 2107 /** 2108 Enter FullScreen mode 2109 2110 @param {Function} callback the function to be executed when the fullscreen mode is fully applied. 2111 2112 @returns {Galleria} 2113 */ 2114 2115 enterFullscreen: function( callback ) { 2116 this._fullscreen.enter.apply( this, Utils.array( arguments ) ); 2117 return this; 2118 }, 2119 2120 /** 2121 Exits FullScreen mode 2122 2123 @param {Function} callback the function to be executed when the fullscreen mode is fully applied. 2124 2125 @returns {Galleria} 2126 */ 2127 2128 exitFullscreen: function( callback ) { 2129 this._fullscreen.exit.apply( this, Utils.array( arguments ) ); 2130 return this; 2131 }, 2132 2133 /** 2134 Adds a tooltip to any element. 2135 You can also call this method with an object as argument with elemID:value pairs to apply tooltips to (see examples) 2136 2137 @param {HTML Element} elem The DOM Node to attach the event to 2138 @param {String or Function} value The tooltip message. Can also be a function that returns a string. 2139 2140 @example this.bindTooltip( this.get('thumbnails'), 'My thumbnails'); 2141 @example this.bindTooltip( this.get('thumbnails'), function() { return 'My thumbs' }); 2142 @example this.bindTooltip( { image_nav: 'Navigation' }); 2143 2144 @returns {Galleria} 2145 */ 2146 2147 bindTooltip: function( elem, value ) { 2148 this._tooltip.bind.apply( this._tooltip, Utils.array(arguments) ); 2149 return this; 2150 }, 2151 2152 /** 2153 Redefine a tooltip 2154 Use this if you want to change the tooltip value at runtime 2155 2156 @param {HTML Element} elem The DOM Node to attach the event to 2157 @param {String or Function} value The tooltip message. Can also be a function that returns a string. 2158 2159 @returns {Galleria} 2160 */ 2161 2162 defineTooltip: function( elem, value ) { 2163 this._tooltip.define.apply( this._tooltip, Utils.array(arguments) ); 2164 return this; 2165 }, 2166 2167 // leave this out of the API for now 2168 2169 refreshTooltip: function() { 2170 this._tooltip.refresh.apply( this._tooltip, Utils.array(arguments) ); 2171 return this; 2172 }, 2173 2174 /** 2175 Open a pre-designed lightbox with the currently active image. 2176 You can control some visuals using gallery options. 2177 2178 @returns {Galleria} 2179 */ 2180 2181 openLightbox: function() { 2182 this._lightbox.show.apply( this._lightbox, Utils.array( arguments ) ); 2183 return this; 2184 }, 2185 2186 /** 2187 Close the lightbox. 2188 2189 @returns {Galleria} 2190 */ 2191 2192 closeLightbox: function() { 2193 this._lightbox.hide.apply( this._lightbox, Utils.array( arguments ) ); 2194 return this; 2195 }, 2196 2197 /** 2198 Get the currently active image element. 2199 2200 @returns {HTML Element} The image element 2201 */ 2202 2203 getActiveImage: function() { 2204 return this._getActive().image || undef; 2205 }, 2206 2207 /** 2208 Get the currently active thumbnail element. 2209 2210 @returns {HTML Element} The thumbnail element 2211 */ 2212 2213 getActiveThumb: function() { 2214 return this._thumbnails[ this._active ].image || undef; 2215 }, 2216 2217 /** 2218 Get the mouse position relative to the gallery container 2219 2220 @param e The mouse event 2221 2222 @example 2223 2224 var gallery = this; 2225 $(document).mousemove(function(e) { 2226 console.log( gallery.getMousePosition(e).x ); 2227 }); 2228 2229 @returns {Object} Object with x & y of the relative mouse postion 2230 */ 2231 2232 getMousePosition : function(e) { 2233 return { 2234 x: e.pageX - this.$( 'stage' ).offset().left, 2235 y: e.pageY - this.$( 'stage' ).offset().top 2236 }; 2237 }, 2238 2239 /** 2240 Adds a panning effect to the image 2241 2242 @param img The optional image element. If not specified it takes the currently active image 2243 2244 @returns {Galleria} 2245 */ 2246 2247 addPan : function( img ) { 2248 2249 if ( this._options.image_crop === false ) { 2250 return; 2251 } 2252 2253 img = $( img || this.getActiveImage() ); 2254 2255 // define some variables and methods 2256 var self = this, 2257 x = img.width() / 2, 2258 y = img.height() / 2, 2259 curX = destX = parseInt( img.css( 'left' ) ) || 0, 2260 curY = destY = parseInt( img.css( 'top' ) ) || 0, 2261 distX = 0, 2262 distY = 0, 2263 active = false, 2264 ts = Utils.timestamp(), 2265 cache = 0, 2266 move = 0, 2267 2268 // positions the image 2269 position = function( dist, cur, pos ) { 2270 if ( dist > 0 ) { 2271 move = Math.round( Math.max( dist * -1, Math.min( 0, cur ) ) ); 2272 if ( cache != move ) { 2273 2274 cache = move; 2275 2276 if ( IE == 8 ) { // scroll is faster for IE 2277 img.parent()[ 'scroll' + pos ]( move * -1 ); 2278 } else { 2279 var css = {}; 2280 css[ pos.toLowerCase() ] = move; 2281 img.css(css); 2282 } 2283 } 2284 } 2285 }, 2286 2287 // calculates mouse position after 50ms 2288 calculate = function(e) { 2289 if (Utils.timestamp() - ts < 50) { 2290 return; 2291 } 2292 active = true; 2293 x = self.getMousePosition(e).x; 2294 y = self.getMousePosition(e).y; 2295 }, 2296 2297 // the main loop to check 2298 loop = function(e) { 2299 2300 if (!active) { 2301 return; 2302 } 2303 2304 distX = img.width() - self._stageWidth; 2305 distY = img.height() - self._stageHeight; 2306 destX = x / self._stageWidth * distX * -1; 2307 destY = y / self._stageHeight * distY * -1; 2308 curX += ( destX - curX ) / self._options.image_pan_smoothness; 2309 curY += ( destY - curY ) / self._options.image_pan_smoothness; 2310 2311 position( distY, curY, 'Top' ); 2312 position( distX, curX, 'Left' ); 2313 2314 }; 2315 2316 // we need to use scroll in IE8 to speed things up 2317 if ( IE == 8 ) { 2318 2319 img.parent().scrollTop( curY * -1 ).scrollLeft( curX * -1 ); 2320 img.css({ 2321 top: 0, 2322 left: 0 2323 }); 2324 2325 } 2326 2327 // unbind and bind event 2328 this.$( 'stage' ).unbind( 'mousemove', calculate ).bind( 'mousemove', calculate ); 2329 2330 // loop the loop 2331 Utils.addTimer('pan', loop, 50, true); 2332 2333 return this; 2334 }, 2335 2336 /** 2337 Brings the scope into any callback 2338 2339 @param fn The callback to bring the scope into 2340 @param scope Optional scope to bring 2341 2342 @example $('#fullscreen').click( this.proxy(function() { this.enterFullscreen(); }) ) 2343 2344 @returns {Function} Return the callback with the gallery scope 2345 */ 2346 2347 proxy : function( fn, scope ) { 2348 if ( typeof fn !== 'function' ) { 2349 return function() {}; 2350 } 2351 scope = scope || this; 2352 return function() { 2353 return fn.apply( scope, Utils.array( arguments ) ); 2354 }; 2355 }, 2356 2357 /** 2358 Removes the panning effect set by addPan() 2359 2360 @returns {Galleria} 2361 */ 2362 2363 removePan: function() { 2364 2365 if ( IE == 8 ) { 2366 // todo: doublecheck this 2367 } 2368 this.$( 'stage' ).unbind( 'mousemove' ); 2369 2370 Utils.clearTimer('pan'); 2371 2372 this.rescale(); 2373 2374 return this; 2375 }, 2376 2377 /** 2378 Adds an element to the Galleria DOM array. 2379 When you add an element here, you can access it using element ID in many API calls 2380 2381 @param {String} id The element ID you wish to use. You can add many elements by adding more arguments. 2382 2383 @example addElement('mybutton'); 2384 @example addElement('mybutton','mylink'); 2385 2386 @returns {Galleria} 2387 */ 2388 2389 addElement : function( id ) { 2390 2391 var dom = this._dom; 2392 2393 $.each( Utils.array(arguments), function( i, blueprint ) { 2394 dom[ blueprint ] = Utils.create( 'galleria-' + blueprint ); 2395 }); 2396 2397 return this; 2398 }, 2399 2400 /** 2401 Attach keyboard events to Galleria 2402 2403 @param {Object} map The map object of events. 2404 Possible keys are 'UP', 'DOWN', 'LEFT', 'RIGHT', 'RETURN', 'ESCAPE' and 'BACKSPACE'. 2405 2406 @example 2407 2408 this.attachKeyboard({ 2409 right: this.next, 2410 left: this.prev, 2411 up: function() { 2412 console.log( 'up key pressed' ) 2413 } 2414 }); 2415 2416 @returns {Galleria} 2417 */ 2418 2419 attachKeyboard : function( map ) { 2420 this._keyboard.attach.apply( this._keyboard, Utils.array( arguments ) ); 2421 return this; 2422 }, 2423 2424 /** 2425 Detach all keyboard events to Galleria 2426 2427 @returns {Galleria} 2428 */ 2429 2430 detachKeyboard : function() { 2431 this._keyboard.detach.apply( this._keyboard, Utils.array( arguments ) ); 2432 return this; 2433 }, 2434 2435 /** 2436 Fast helper for appending galleria elements that you added using addElement() 2437 2438 @param {String} parentID The parent element ID where the element will be appended 2439 @param {String} childID the element ID that should be appended 2440 2441 @example this.addElement('myElement'); 2442 this.appendChild( 'info', 'myElement' ); 2443 2444 @returns {Galleria} 2445 */ 2446 2447 appendChild : function( parentID, childID ) { 2448 this.$( parentID ).append( this.get( childID ) || childID ); 2449 return this; 2450 }, 2451 2452 /** 2453 Fast helper for appending galleria elements that you added using addElement() 2454 2455 @param {String} parentID The parent element ID where the element will be preppended 2456 @param {String} childID the element ID that should be preppended 2457 2458 @example 2459 2460 this.addElement('myElement'); 2461 this.prependChild( 'info', 'myElement' ); 2462 2463 @returns {Galleria} 2464 */ 2465 2466 prependChild : function( parenID, childID ) { 2467 this.$( parentID ).prepend( this.get( childID ) || childID ); 2468 return this; 2469 }, 2470 2471 /** 2472 Remove an element by blueprint 2473 2474 @param {String} elemID The element to be removed. 2475 You can remove multiple elements by adding arguments. 2476 2477 @returns {Galleria} 2478 */ 2479 2480 remove : function( elemID ) { 2481 this.$( Utils.array( arguments ).join(',') ).remove(); 2482 return this; 2483 }, 2484 2485 // a fast helper for building dom structures 2486 // leave this out of the API for now 2487 2488 append : function( data ) { 2489 for( var i in data) { 2490 if ( data[i].constructor == Array ) { 2491 for( var j = 0; data[i][j]; j++ ) { 2492 this.appendChild( i, data[i][j] ); 2493 } 2494 } else { 2495 this.appendChild( i, data[i] ); 2496 } 2497 } 2498 return this; 2499 }, 2500 2501 // an internal helper for scaling according to options 2502 _scaleImage : function( image, options ) { 2503 2504 options = $.extend({ 2505 width: this._stageWidth, 2506 height: this._stageHeight, 2507 crop: this._options.image_crop, 2508 max: this._options.max_scale_ratio, 2509 min: this._options.min_scale_ratio, 2510 margin: this._options.image_margin, 2511 position: this._options.image_position 2512 }, options ); 2513 2514 ( image || this._controls.getActive() ).scale( options ); 2515 2516 return this; 2517 }, 2518 2519 /** 2520 Updates the carousel, 2521 useful if you resize the gallery and want to re-check if the carousel nav is needed. 2522 2523 @returns {Galleria} 2524 */ 2525 2526 updateCarousel : function() { 2527 this._carousel.update(); 2528 return this; 2529 }, 2530 2531 /** 2532 Rescales the gallery 2533 2534 @param {Number} width The target width 2535 @param {Number} height The target height 2536 @param {Function} complete The callback to be called when the scaling is complete 2537 2538 @returns {Galleria} 2539 */ 2540 2541 rescale : function( width, height, complete ) { 2542 2543 var self = this; 2544 2545 // allow rescale(fn) 2546 if ( typeof width == 'function' ) { 2547 complete = width; 2548 width = undef; 2549 } 2550 2551 var scale = function() { 2552 2553 // shortcut 2554 var o = self._options; 2555 2556 // set stagewidth 2557 self._stageWidth = width || self.$( 'stage' ).width(); 2558 self._stageHeight = height || self.$( 'stage' ).height(); 2559 2560 // scale the active image 2561 self._scaleImage(); 2562 2563 if ( self._options.carousel ) { 2564 self.updateCarousel(); 2565 } 2566 2567 self.trigger( Galleria.RESCALE ); 2568 2569 if ( typeof complete == 'function' ) { 2570 complete.call( self ); 2571 } 2572 }; 2573 2574 if ( Galleria.WEBKIT && !width && !height ) { 2575 Utils.addTimer( 'scale', scale, 5 );// webkit is too fast 2576 } else { 2577 scale.call( self ); 2578 } 2579 2580 return this; 2581 }, 2582 2583 /** 2584 Refreshes the gallery. 2585 Useful if you change image options at runtime and want to apply the changes to the active image. 2586 2587 @returns {Galleria} 2588 */ 2589 2590 refreshImage : function() { 2591 this._scaleImage(); 2592 if ( this._options.image_pan ) { 2593 this.addPan(); 2594 } 2595 return this; 2596 }, 2597 2598 /** 2599 Shows an image by index 2600 2601 @param {Number} index The index to show 2602 @param {Boolean} rewind A boolean that should be true if you want the transition to go back 2603 2604 @returns {Galleria} 2605 */ 2606 2607 show : function( index, rewind, _history ) { 2608 // do nothing if queue is false and transition is in progress 2609 if ( !this._options.queue && this._queue.stalled ) { 2610 return; 2611 } 2612 index = Math.max( 0, Math.min( parseInt(index), this.getDataLength() - 1 ) ); 2613 2614 rewind = typeof rewind != 'undefined' ? !!rewind : index < this.getIndex(); 2615 2616 _history = _history || false; 2617 2618 // do the history thing and return 2619 if ( !_history && Galleria.History ) { 2620 Galleria.History.value( index.toString() ); 2621 return; 2622 } 2623 2624 this._active = index; 2625 2626 Array.prototype.push.call( this._queue, { 2627 index : index, 2628 rewind : rewind 2629 }); 2630 if ( !this._queue.stalled ) { 2631 this._show(); 2632 } 2633 2634 return this; 2635 }, 2636 2637 // the internal _show method does the actual showing 2638 _show : function() { 2639 2640 // shortcuts 2641 var self = this, 2642 queue = this._queue[ 0 ], 2643 data = this.getData( queue.index ), 2644 src = data.image, 2645 active = this._controls.getActive(), 2646 next = this._controls.getNext(), 2647 cached = next.isCached( src ), 2648 thumb = this._thumbnails[ queue.index ]; 2649 2650 // to be fired when loading & transition is complete: 2651 var complete = function() { 2652 2653 // remove stalled 2654 self._queue.stalled = false; 2655 2656 // optimize quality 2657 Utils.toggleQuality( next.image, self._options.image_quality ); 2658 2659 // swap 2660 $( active.container ).css({ 2661 zIndex: 0, 2662 opacity: 0 2663 }); 2664 $( next.container ).css({ 2665 zIndex: 1, 2666 opacity: 1 2667 }); 2668 self._controls.swap(); 2669 2670 // add pan according to option 2671 if ( self._options.image_pan ) { 2672 self.addPan( next.image ); 2673 } 2674 2675 // make the image link 2676 if ( data.link ) { 2677 $( next.image ).css({ 2678 cursor: 'pointer' 2679 }).bind( CLICK(), function() { 2680 2681 // popup link 2682 if ( self._options.popup_links ) { 2683 var win = window.open( data.link, '_blank' ); 2684 } else { 2685 window.location.href = data.link; 2686 } 2687 }); 2688 } 2689 2690 // remove the queued image 2691 Array.prototype.shift.call( self._queue ); 2692 2693 // if we still have images in the queue, show it 2694 if ( self._queue.length ) { 2695 self._show(); 2696 } 2697 2698 // check if we are playing 2699 self._playCheck(); 2700 2701 // trigger IMAGE event 2702 self.trigger({ 2703 type: Galleria.IMAGE, 2704 index: queue.index, 2705 imageTarget: next.image, 2706 thumbTarget: thumb.image 2707 }); 2708 }; 2709 2710 // let the carousel follow 2711 if ( this._options.carousel && this._options.carousel_follow ) { 2712 this._carousel.follow( queue.index ); 2713 } 2714 2715 // preload images 2716 if ( this._options.preload ) { 2717 2718 var p, 2719 n = this.getNext(); 2720 2721 try { 2722 for ( var i = this._options.preload; i > 0; i-- ) { 2723 p = new Galleria.Picture(); 2724 p.add( self.getData( n ).image ); 2725 n = self.getNext( n ); 2726 } 2727 } catch(e) {} 2728 } 2729 2730 // show the next image, just in case 2731 Utils.show( next.container ); 2732 2733 // add active classes 2734 $( self._thumbnails[ queue.index ].container ) 2735 .addClass( 'active' ) 2736 .siblings( '.active' ) 2737 .removeClass( 'active' ); 2738 2739 // trigger the LOADSTART event 2740 self.trigger( { 2741 type: Galleria.LOADSTART, 2742 cached: cached, 2743 index: queue.index, 2744 imageTarget: next.image, 2745 thumbTarget: thumb.image 2746 }); 2747 2748 // begin loading the next image 2749 next.load( src, function( next ) { 2750 self._scaleImage( next, { 2751 2752 complete: function( next ) { 2753 2754 Utils.show( next.container ); 2755 2756 // toggle low quality for IE 2757 if ( 'image' in active ) { 2758 Utils.toggleQuality( active.image, false ); 2759 } 2760 Utils.toggleQuality( next.image, false ); 2761 2762 // stall the queue 2763 self._queue.stalled = true; 2764 2765 // remove the image panning, if applied 2766 self.removePan(); 2767 2768 // set the captions and counter 2769 self.setInfo( queue.index ); 2770 self.setCounter( queue.index ); 2771 2772 // trigger the LOADFINISH event 2773 self.trigger({ 2774 type: Galleria.LOADFINISH, 2775 cached: cached, 2776 index: queue.index, 2777 imageTarget: next.image, 2778 thumbTarget: self._thumbnails[ queue.index ].image 2779 }); 2780 2781 var transition = active.image === null && self._options.transition_initial ? 2782 self._options.transition_initial : self._options.transition; 2783 2784 // validate the transition 2785 if ( transition in _transitions === false ) { 2786 2787 complete(); 2788 2789 } else { 2790 var params = { 2791 prev: active.image, 2792 next: next.image, 2793 rewind: queue.rewind, 2794 speed: self._options.transition_speed || 400 2795 }; 2796 2797 // call the transition function and send some stuff 2798 _transitions[ transition ].call(self, params, complete ); 2799 2800 } 2801 } 2802 }); 2803 }); 2804 }, 2805 2806 /** 2807 Gets the next index 2808 2809 @param {Number} base Optional starting point 2810 2811 @returns {Number} the next index, or the first if you are at the first (looping) 2812 */ 2813 2814 getNext : function( base ) { 2815 base = typeof base == 'number' ? base : this.getIndex(); 2816 return base == this.getDataLength() - 1 ? 0 : base + 1; 2817 }, 2818 2819 /** 2820 Gets the previous index 2821 2822 @param {Number} base Optional starting point 2823 2824 @returns {Number} the previous index, or the last if you are at the first (looping) 2825 */ 2826 2827 getPrev : function( base ) { 2828 base = typeof base == 'number' ? base : this.getIndex(); 2829 return base === 0 ? this.getDataLength() - 1 : base - 1; 2830 }, 2831 2832 /** 2833 Shows the next image in line 2834 2835 @returns {Galleria} 2836 */ 2837 2838 next : function() { 2839 if ( this.getDataLength() > 1 ) { 2840 this.show( this.getNext(), false ); 2841 } 2842 return this; 2843 }, 2844 2845 /** 2846 Shows the previous image in line 2847 2848 @returns {Galleria} 2849 */ 2850 2851 prev : function() { 2852 if ( this.getDataLength() > 1 ) { 2853 this.show( this.getPrev(), true ); 2854 } 2855 return this; 2856 }, 2857 2858 /** 2859 Retrieve a DOM element by element ID 2860 2861 @param {String} elemId The delement ID to fetch 2862 2863 @returns {HTML Element} The elements DOM node or null if not found. 2864 */ 2865 2866 get : function( elemId ) { 2867 return elemId in this._dom ? this._dom[ elemId ] : null; 2868 }, 2869 2870 /** 2871 Retrieve a data object 2872 2873 @param {Number} index The data index to retrieve. 2874 If no index specified it will take the currently active image 2875 2876 @returns {Object} The data object 2877 */ 2878 2879 getData : function( index ) { 2880 return index in this._data ? 2881 this._data[ index ] : this._data[ this._active ]; 2882 }, 2883 2884 /** 2885 Retrieve the number of data items 2886 2887 @returns {Number} The data length 2888 */ 2889 getDataLength : function() { 2890 return this._data.length; 2891 }, 2892 2893 /** 2894 Retrieve the currently active index 2895 2896 @returns {Number} The active index 2897 */ 2898 2899 getIndex : function() { 2900 return typeof this._active === 'number' ? this._active : 0; 2901 }, 2902 2903 /** 2904 Retrieve the stage height 2905 2906 @returns {Number} The stage height 2907 */ 2908 2909 getStageHeight : function() { 2910 return this._stageHeight; 2911 }, 2912 2913 /** 2914 Retrieve the stage width 2915 2916 @returns {Number} The stage width 2917 */ 2918 2919 getStageWidth : function() { 2920 return this._stageWidth; 2921 }, 2922 2923 /** 2924 Retrieve the option 2925 2926 @param {String} key The option key to retrieve. If no key specified it will return all options in an object. 2927 2928 @returns option or options 2929 */ 2930 2931 getOptions : function( key ) { 2932 return typeof key == 'undefined' ? this._options : this._options[ key ]; 2933 }, 2934 2935 /** 2936 Set options to the instance. 2937 You can set options using a key & value argument or a single object argument (see examples) 2938 2939 @param {String} key The option key 2940 @param {String} value the the options value 2941 2942 @example setOptions( 'autoplay', true ) 2943 @example setOptions({ autoplay: true }); 2944 2945 @returns {Galleria} 2946 */ 2947 2948 setOptions : function( key, value ) { 2949 if ( typeof key == 'object' ) { 2950 $.extend( this._options, key ); 2951 } else { 2952 this._options[ key ] = value; 2953 } 2954 return this; 2955 }, 2956 2957 /** 2958 Starts playing the slideshow 2959 2960 @param {Number} delay Sets the slideshow interval in milliseconds. 2961 If you set it once, you can just call play() and get the same interval the next time. 2962 2963 @returns {Galleria} 2964 */ 2965 2966 play : function( delay ) { 2967 2968 this.trigger( Galleria.PLAY ); 2969 2970 this._playing = true; 2971 this._playtime = delay || this._playtime; 2972 2973 this._playCheck(); 2974 2975 return this; 2976 }, 2977 2978 /** 2979 Stops the slideshow if currently playing 2980 2981 @returns {Galleria} 2982 */ 2983 2984 pause : function() { 2985 this.trigger( Galleria.PAUSE ); 2986 this._playing = false; 2987 return this; 2988 }, 2989 2990 _playCheck : function() { 2991 var self = this, 2992 played = 0, 2993 interval = 20, 2994 now = Utils.timestamp(); 2995 2996 if ( this._playing ) { 2997 2998 Utils.clearTimer('play'); 2999 var fn = function() { 3000 3001 played = Utils.timestamp() - now; 3002 if ( played >= self._playtime && self._playing ) { 3003 Utils.clearTimer('play'); 3004 self.next(); 3005 return; 3006 } 3007 if ( self._playing ) { 3008 3009 // trigger the PROGRESS event 3010 self.trigger({ 3011 type: Galleria.PROGRESS, 3012 percent: Math.ceil( played / self._playtime * 100 ), 3013 seconds: Math.floor( played / 1000 ), 3014 milliseconds: played 3015 }); 3016 3017 Utils.addTimer( 'play', fn, interval ); 3018 } 3019 }; 3020 Utils.addTimer( 'play', fn, interval ); 3021 } 3022 }, 3023 3024 setIndex: function( val ) { 3025 this._active = val; 3026 return this; 3027 }, 3028 3029 /** 3030 Manually modify the counter 3031 3032 @param {Number} index Optional data index to fectch, 3033 if no index found it assumes the currently active index 3034 3035 @returns {Galleria} 3036 */ 3037 3038 setCounter: function( index ) { 3039 3040 if ( typeof index == 'number' ) { 3041 index++; 3042 } else if ( typeof index == 'undefined' ) { 3043 index = this.getIndex()+1; 3044 } 3045 3046 this.get( 'current' ).innerHTML = index; 3047 3048 if ( IE == 8 ) { // weird IE8 bug 3049 3050 var opacity = this.$( 'counter' ).css( 'opacity' ); 3051 this.$( 'counter' ).css( 'opacity', opacity ); 3052 3053 } 3054 3055 return this; 3056 }, 3057 3058 /** 3059 Manually set captions 3060 3061 @param {Number} index Optional data index to fectch and apply as caption, 3062 if no index found it assumes the currently active index 3063 3064 @returns {Galleria} 3065 */ 3066 3067 setInfo : function( index ) { 3068 3069 var self = this, 3070 data = this.getData( index ); 3071 3072 $.each( ['title','description','author'], function( i, type ) { 3073 3074 var elem = self.$( 'info-' + type ); 3075 3076 if ( !!data[type] ) { 3077 elem[ data[ type ].length ? 'show' : 'hide' ]().html( data[ type ] ); 3078 } else { 3079 elem.empty().hide(); 3080 } 3081 }); 3082 3083 return this; 3084 }, 3085 3086 /** 3087 Checks if the data contains any captions 3088 3089 @param {Number} index Optional data index to fectch, 3090 if no index found it assumes the currently active index. 3091 3092 @returns {Boolean} 3093 */ 3094 3095 hasInfo : function( index ) { 3096 3097 var d = this.getData( index ); 3098 var check = 'title description'.split(' '); 3099 for ( var i = 0; check[i]; i++ ) { 3100 if ( !!this.getData( index )[ check[i] ] ) { 3101 return true; 3102 } 3103 } 3104 return false; 3105 3106 }, 3107 3108 jQuery : function( str ) { 3109 3110 var self = this, 3111 ret = []; 3112 3113 $.each( str.split(','), function( i, elemId ) { 3114 elemId = $.trim( elemId ); 3115 3116 if ( self.get( elemId ) ) { 3117 ret.push( elemId ); 3118 } 3119 }); 3120 3121 var jQ = $( self.get( ret.shift() ) ); 3122 3123 $.each( ret, function( i, elemId ) { 3124 jQ = jQ.add( self.get( elemId ) ); 3125 }); 3126 3127 return jQ; 3128 3129 }, 3130 3131 /** 3132 Converts element IDs into a jQuery collection 3133 You can call for multiple IDs separated with commas. 3134 3135 @param {String} str One or more element IDs (comma-separated) 3136 3137 @returns {jQuery} 3138 3139 @example this.$('info,container').hide(); 3140 */ 3141 3142 $ : function() { 3143 return this.jQuery.apply( this, Utils.array( arguments ) ); 3144 } 3145 3146 }; 3147 3148 // End of Galleria prototype 3149 3150 $.extend( Galleria, { 3151 3152 // Event placeholders 3153 DATA: 'g_data', 3154 READY: 'g_ready', 3155 THUMBNAIL: 'g_thumbnail', 3156 LOADSTART: 'g_loadstart', 3157 LOADFINISH: 'g_loadfinish', 3158 IMAGE: 'g_image', 3159 THEMELOAD: 'g_themeload', 3160 PLAY: 'g_play', 3161 PAUSE: 'g_pause', 3162 PROGRESS: 'g_progress', 3163 FULLSCREEN_ENTER: 'g_fullscreen_enter', 3164 FULLSCREEN_EXIT: 'g_fullscreen_exit', 3165 IDLE_ENTER: 'g_idle_enter', 3166 IDLE_EXIT: 'g_idle_exit', 3167 RESCALE: 'g_rescale', 3168 LIGHTBOX_OPEN: 'g_lightbox_open', 3169 LIGHTBOX_CLOSE: 'g_lightbox_close', 3170 LIGHTBOX_IMAGE: 'g_lightbox_image', 3171 3172 // Browser helpers 3173 IE9: IE == 9, 3174 IE8: IE == 8, 3175 IE7: IE == 7, 3176 IE6: IE == 6, 3177 IE: !!IE, 3178 WEBKIT: /webkit/.test( NAV ), 3179 SAFARI: /safari/.test( NAV ), 3180 CHROME: /chrome/.test( NAV ), 3181 QUIRK: ( IE && doc.compatMode && doc.compatMode == "BackCompat" ), 3182 MAC: /mac/.test( navigator.platform.toLowerCase() ), 3183 OPERA: !!window.opera, 3184 IPHONE: /iphone/.test( NAV ), 3185 IPAD: /ipad/.test( NAV ), 3186 ANDROID: /android/.test( NAV ), 3187 3188 // Todo detect touch devices in a better way, possibly using event detection 3189 TOUCH: !!( /iphone/.test( NAV ) || /ipad/.test( NAV ) || /android/.test( NAV ) ) 3190 3191 }); 3192 3193 // Galleria static methods 3194 3195 /** 3196 Adds a theme that you can use for your Gallery 3197 3198 @param {Object} theme Object that should contain all your theme settings. 3199 <ul> 3200 <li>name – name of the theme</li> 3201 <li>author - name of the author</li> 3202 <li>version - version number</li> 3203 <li>css - css file name (not path)</li> 3204 <li>defaults - default options to apply, including theme-specific options</li> 3205 <li>init - the init function</li> 3206 </ul> 3207 3208 @returns {Object} theme 3209 */ 3210 3211 Galleria.addTheme = function( theme ) { 3212 3213 // make sure we have a name 3214 if ( !!theme['name'] === false ) { 3215 Galleria.raise('No theme name specified'); 3216 } 3217 3218 if ( typeof theme.defaults != 'object' ) { 3219 theme.defaults = {}; 3220 } 3221 3222 if ( typeof theme.css == 'string' ) { 3223 3224 var css; 3225 3226 // look for the absolute path 3227 $('script').each(function( i, script ) { 3228 3229 // look for the theme script 3230 var reg = new RegExp( 'galleria\\.' + theme.name.toLowerCase() + '\\.' ); 3231 if( reg.test( script.src )) { 3232 3233 // we have a match 3234 css = script.src.replace(/[^\/]*$/, '') + theme.css; 3235 3236 Utils.addTimer( "css", function() { 3237 Utils.loadCSS( css, 'galleria-theme', function() { 3238 Galleria.theme = theme; 3239 $doc.trigger( Galleria.THEMELOAD ); 3240 }); 3241 }, 1); 3242 3243 } 3244 }); 3245 3246 if ( !css ) { 3247 Galleria.raise('No theme CSS loaded'); 3248 } 3249 } 3250 return theme; 3251 }; 3252 3253 /** 3254 loadTheme loads a theme js file and attaches a load event to Galleria 3255 3256 @param {String} src The relative path to the theme source file 3257 3258 @param {Object} option Optional options you want to apply 3259 */ 3260 3261 Galleria.loadTheme = function( src, options ) { 3262 3263 var loaded = false, 3264 length = _galleries.length; 3265 3266 // first clear the current theme, if exists 3267 Galleria.theme = undef; 3268 3269 // load the theme 3270 Utils.loadScript( src, function() { 3271 loaded = true; 3272 } ); 3273 3274 // set a 1 sec timeout, then display a hard error if no theme is loaded 3275 Utils.wait({ 3276 until: function() { 3277 return loaded; 3278 }, 3279 error: function() { 3280 Galleria.raise( "Theme at " + src + " could not load, check theme path.", true ); 3281 }, 3282 success: function() { 3283 3284 // check for existing galleries and reload them with the new theme 3285 if ( length ) { 3286 3287 // temporary save the new galleries 3288 var refreshed = []; 3289 3290 // refresh all instances 3291 // when adding a new theme to an existing gallery, all options will be resetted but the data will be kept 3292 // you can apply new options as a second argument 3293 $.each( Galleria.get(), function(i, instance) { 3294 3295 // mix the old data and options into the new instance 3296 var op = $.extend( instance._original.options, { 3297 data_source: instance._data 3298 }, options); 3299 3300 // remove the old container 3301 instance.$('container').remove(); 3302 3303 // create a new instance 3304 var g = new Galleria(); 3305 3306 // move the id 3307 g._id = instance._id; 3308 3309 // initialize the new instance 3310 g.init( instance._original.target, op ); 3311 3312 // push the new instance 3313 refreshed.push( g ); 3314 }); 3315 3316 // now overwrite the old holder with the new instances 3317 _galleries = refreshed; 3318 } 3319 }, 3320 timeout: 1000 3321 }); 3322 }; 3323 3324 /** 3325 Retrieves a Galleria instance. 3326 3327 @param {Number} index Optional index to retrieve. 3328 If no index is supplied, the method will return all instances in an array. 3329 3330 @returns {Galleria or Array} 3331 */ 3332 3333 Galleria.get = function( index ) { 3334 if ( !!_galleries[ index ] ) { 3335 return _galleries[ index ]; 3336 } else if ( typeof index !== 'number' ) { 3337 return _galleries; 3338 } else { 3339 Galleria.raise('Gallery index ' + index + ' not found'); 3340 } 3341 }; 3342 3343 /** 3344 Creates a transition to be used in your gallery 3345 3346 @param {String} name The name of the transition that you will use as an option 3347 3348 @param {Function} fn The function to be executed in the transition. 3349 The function contains two arguments, params and complete. 3350 Use the params Object to integrate the transition, and then call complete when you are done. 3351 3352 */ 3353 3354 Galleria.addTransition = function( name, fn ) { 3355 _transitions[name] = fn; 3356 }; 3357 3358 Galleria.utils = Utils; 3359 3360 /** 3361 A helper metod for cross-browser logging. 3362 It uses the console log if available otherwise it falls back to the opera 3363 debugger and finally <code>alert()</code> 3364 3365 @example Galleria.log("hello", document.body, [1,2,3]); 3366 */ 3367 3368 Galleria.log = function() { 3369 try { 3370 window.console.log.apply( window.console, Utils.array(arguments) ); 3371 } catch( e ) { 3372 try { 3373 opera.postError.apply( opera, arguments ); 3374 } catch( er ) { 3375 alert( Utils.array(arguments).split(', ') ); 3376 } 3377 } 3378 }; 3379 3380 /** 3381 Method for raising errors 3382 3383 @param {String} msg The message to throw 3384 3385 @param {Boolean} fatal Set this to true to override debug settings and display a fatal error 3386 */ 3387 3388 Galleria.raise = function( msg, fatal ) { 3389 if ( DEBUG || fatal ) { 3390 var type = fatal ? 'Fatal error' : 'Error'; 3391 throw new Error( type + ': ' + msg ); 3392 } 3393 }; 3394 3395 /** 3396 Adds preload, cache, scale and crop functionality 3397 3398 @constructor 3399 3400 @requires jQuery 3401 3402 @param {Number} id Optional id to keep track of instances 3403 */ 3404 3405 Galleria.Picture = function( id ) { 3406 3407 // save the id 3408 this.id = id || null; 3409 3410 // the image should be null until loaded 3411 this.image = null; 3412 3413 // Create a new container 3414 this.container = Utils.create('galleria-image'); 3415 3416 // add container styles 3417 $( this.container ).css({ 3418 overflow: 'hidden', 3419 position: 'relative' // for IE Standards mode 3420 }); 3421 3422 // saves the original meassurements 3423 this.original = { 3424 width: 0, 3425 height: 0 3426 }; 3427 3428 // flag when the image is ready 3429 this.ready = false; 3430 3431 // flag when the image is loaded 3432 this.loaded = false; 3433 3434 }; 3435 3436 Galleria.Picture.prototype = { 3437 3438 // the inherited cache object 3439 cache: {}, 3440 3441 // creates a new image and adds it to cache when loaded 3442 add: function( src ) { 3443 3444 var self = this; 3445 3446 // create the image 3447 var image = new Image(); 3448 3449 // force a block display 3450 $( image ).css( 'display', 'block'); 3451 3452 if ( self.cache[ src ] ) { 3453 // no need to onload if the image is cached 3454 image.src = src; 3455 self.loaded = true; 3456 self.original = { 3457 height: image.height, 3458 width: image.width 3459 }; 3460 return image; 3461 } 3462 3463 // begin preload and insert in cache when done 3464 image.onload = function() { 3465 self.original = { 3466 height: this.height, 3467 width: this.width 3468 }; 3469 self.cache[ src ] = src; // will override old cache 3470 self.loaded = true; 3471 }; 3472 3473 image.src = src; 3474 return image; 3475 3476 }, 3477 3478 // show the image on stage 3479 show: function() { 3480 Utils.show( this.image ); 3481 }, 3482 3483 // hide the image 3484 hide: function() { 3485 Utils.moveOut( this.image ); 3486 }, 3487 3488 clear: function() { 3489 this.image = null; 3490 }, 3491 3492 /** 3493 Checks if an image is in cache 3494 3495 @param {String} src The image source path, ex '/path/to/img.jpg' 3496 3497 @returns {Boolean} 3498 */ 3499 3500 isCached: function( src ) { 3501 return !!this.cache[src]; 3502 }, 3503 3504 /** 3505 Loads an image and call the callback when ready. 3506 Will also add the image to cache. 3507 3508 @param {String} src The image source path, ex '/path/to/img.jpg' 3509 @param {Function} callback The function to be executed when the image is loaded & scaled 3510 3511 @returns {jQuery} The image container object 3512 */ 3513 3514 load: function(src, callback) { 3515 3516 // save the instance 3517 var self = this; 3518 3519 $( this.container ).empty(true); 3520 3521 // add the image to cache and hide it 3522 this.image = this.add( src ); 3523 Utils.hide( this.image ); 3524 3525 // append the image into the container 3526 $( this.container ).append( this.image ); 3527 3528 // check for loaded image using a timeout 3529 Utils.wait({ 3530 until: function() { 3531 // TODO this should be properly tested in Opera 3532 return self.loaded && self.image.complete && self.image.width; 3533 }, 3534 success: function() { 3535 // call success 3536 window.setTimeout(function() { callback.call( self, self ); }, 1 ); 3537 }, 3538 error: function() { 3539 window.setTimeout(function() { callback.call( self, self ); }, 1 ); 3540 Galleria.raise('image not loaded in 10 seconds: '+ src); 3541 }, 3542 timeout: 10000 3543 }); 3544 3545 // return the container 3546 return this.container; 3547 }, 3548 3549 /** 3550 Scales and crops the image 3551 3552 @param {Object} options The method takes an object with a number of options: 3553 3554 <ul> 3555 <li>width - width of the container</li> 3556 <li>height - height of the container</li> 3557 <li>min - minimum scale ratio</li> 3558 <li>max - maximum scale ratio</li> 3559 <li>margin - distance in pixels from the image border to the container</li> 3560 <li>complete - a callback that fires when scaling is complete</li> 3561 <li>position - positions the image, works like the css background-image property.</li> 3562 <li>crop - defines how to crop. Can be true, false, 'width' or 'height'</li> 3563 </ul> 3564 3565 @returns {jQuery} The image container object 3566 */ 3567 3568 scale: function( options ) { 3569 3570 // extend some defaults 3571 options = $.extend({ 3572 width: 0, 3573 height: 0, 3574 min: undef, 3575 max: undef, 3576 margin: 0, 3577 complete: function() {}, 3578 position: 'center', 3579 crop: false 3580 }, options); 3581 3582 // return the element if no image found 3583 if (!this.image) { 3584 return this.container; 3585 } 3586 3587 // store locale variables of width & height 3588 var width, 3589 height, 3590 self = this, 3591 $container = $( self.container ); 3592 3593 // wait for the width/height 3594 Utils.wait({ 3595 until: function() { 3596 3597 width = options.width 3598 || $container.width() 3599 || Utils.parseValue( $container.css('width') ); 3600 3601 height = options.height 3602 || $container.height() 3603 || Utils.parseValue( $container.css('height') ); 3604 3605 return width && height; 3606 }, 3607 success: function() { 3608 // calculate some cropping 3609 var newWidth = ( width - options.margin * 2 ) / self.original.width, 3610 newHeight = ( height - options.margin * 2 ) / self.original.height, 3611 cropMap = { 3612 'true' : Math.max( newWidth, newHeight ), 3613 'width' : newWidth, 3614 'height': newHeight, 3615 'false' : Math.min( newWidth, newHeight ) 3616 }, 3617 ratio = cropMap[ options.crop.toString() ]; 3618 3619 // allow max_scale_ratio 3620 if ( options.max ) { 3621 ratio = Math.min( options.max, ratio ); 3622 } 3623 3624 // allow min_scale_ratio 3625 if ( options.min ) { 3626 ratio = Math.max( options.min, ratio ); 3627 } 3628 3629 $( self.container ).width( width ).height( height ); 3630 3631 // round up the width / height 3632 $.each( ['width','height'], function( i, m ) { 3633 $( self.image )[ m ]( self[ m ] = Math.ceil( self.original[ m ] * ratio ) ); 3634 }); 3635 3636 // calculate image_position 3637 var pos = {}, 3638 mix = {}, 3639 getPosition = function(value, meassure, margin) { 3640 var result = 0; 3641 if (/\%/.test(value)) { 3642 var flt = parseInt(value) / 100; 3643 result = Math.ceil( $( self.image )[ meassure ]() * -1 * flt + margin * flt ); 3644 } else { 3645 result = Utils.parseValue( value ); 3646 } 3647 return result; 3648 }, 3649 positionMap = { 3650 'top': { top: 0 }, 3651 'left': { left: 0 }, 3652 'right': { left: '100%' }, 3653 'bottom': { top: '100%' } 3654 }; 3655 3656 $.each( options.position.toLowerCase().split(' '), function( i, value ) { 3657 if ( value == 'center' ) { 3658 value = '50%'; 3659 } 3660 pos[i ? 'top' : 'left'] = value; 3661 }); 3662 3663 $.each( pos, function( i, value ) { 3664 if ( positionMap.hasOwnProperty( value ) ) { 3665 $.extend( mix, positionMap[ value ] ); 3666 } 3667 }); 3668 3669 pos = pos.top ? $.extend( pos, mix ) : mix; 3670 3671 pos = $.extend({ 3672 top: '50%', 3673 left: '50%' 3674 }, pos); 3675 3676 // apply position 3677 $( self.image ).css({ 3678 position : 'relative', 3679 top : getPosition(pos.top, 'height', height), 3680 left : getPosition(pos.left, 'width', width) 3681 }); 3682 3683 // show the image 3684 self.show(); 3685 3686 // flag ready and call the callback 3687 self.ready = true; 3688 options.complete.call( self, self ); 3689 }, 3690 error: function() { 3691 Galleria.raise('Could not scale image: '+self.image.src); 3692 }, 3693 timeout: 1000 3694 }); 3695 return this; 3696 } 3697 }; 3698 3699 // our own easings 3700 $.extend( $.easing, { 3701 galleria: function (_, t, b, c, d) { 3702 if ((t/=d/2) < 1) { 3703 return c/2*t*t*t*t + b; 3704 } 3705 return -c/2 * ((t-=2)*t*t*t - 2) + b; 3706 }, 3707 galleriaIn: function (_, t, b, c, d) { 3708 return c*(t/=d)*t*t*t + b; 3709 }, 3710 galleriaOut: function (_, t, b, c, d) { 3711 return -c * ((t=t/d-1)*t*t*t - 1) + b; 3712 } 3713 }); 3714 3715 // the plugin initializer 3716 $.fn.galleria = function( options ) { 3717 3718 return this.each(function() { 3719 3720 var gallery = new Galleria(); 3721 gallery.init( this, options ); 3722 3723 }); 3724 }; 3725 3726 // expose Galleria 3727 window.Galleria = Galleria; 3728 3729 // phew 3730 3731 })( jQuery );