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