vendor: 50,026 bytes, lines 1-1906
1/*! 2 * Infinite Scroll PACKAGED v5.0.0 3 * Automatically add next page 4 * MIT License 5 * https://infinite-scroll.com 6 * Copyright 2018-2025 Metafizzy 7 */ 8 9/** 10 * Bridget makes jQuery widgets 11 * v3.0.0 12 * MIT license 13 */ 14 15( function( window, factory ) { 16 // module definition 17 if ( typeof module == 'object' && module.exports ) { 18 // CommonJS 19 module.exports = factory( 20 window, 21 require('jquery'), 22 ); 23 } else { 24 // browser global 25 window.jQueryBridget = factory( 26 window, 27 window.jQuery, 28 ); 29 } 30 31}( window, function factory( window, jQuery ) { 32 33// ----- utils ----- // 34 35// helper function for logging errors 36// $.error breaks jQuery chaining 37let console = window.console; 38let logError = typeof console == 'undefined' ? function() {} : 39 function( message ) { 40 console.error( message ); 41 }; 42 43// ----- jQueryBridget ----- // 44 45function jQueryBridget( namespace, PluginClass, $ ) { 46 $ = $ || jQuery || window.jQuery; 47 if ( !$ ) { 48 return; 49 } 50 51 // add option method -> $().plugin('option', {...}) 52 if ( !PluginClass.prototype.option ) { 53 // option setter 54 PluginClass.prototype.option = function( opts ) { 55 if ( !opts ) return; 56 57 this.options = Object.assign( this.options || {}, opts ); 58 }; 59 } 60 61 // make jQuery plugin 62 $.fn[ namespace ] = function( arg0, ...args ) { 63 if ( typeof arg0 == 'string' ) { 64 // method call $().plugin( 'methodName', { options } ) 65 return methodCall( this, arg0, args ); 66 } 67 // just $().plugin({ options }) 68 plainCall( this, arg0 ); 69 return this; 70 }; 71 72 // $().plugin('methodName') 73 function methodCall( $elems, methodName, args ) { 74 let returnValue; 75 let pluginMethodStr = `$().${namespace}("${methodName}")`; 76 77 $elems.each( function( i, elem ) { 78 // get instance 79 let instance = $.data( elem, namespace ); 80 if ( !instance ) { 81 logError( `${namespace} not initialized.` + 82 ` Cannot call method ${pluginMethodStr}` ); 83 return; 84 } 85 86 let method = instance[ methodName ]; 87 if ( !method || methodName.charAt( 0 ) == '_' ) { 88 logError(`${pluginMethodStr} is not a valid method`); 89 return; 90 } 91 92 // apply method, get return value 93 let value = method.apply( instance, args ); 94 // set return value if value is returned, use only first value 95 returnValue = returnValue === undefined ? value : returnValue; 96 } ); 97 98 return returnValue !== undefined ? returnValue : $elems; 99 } 100 101 function plainCall( $elems, options ) { 102 $elems.each( function( i, elem ) { 103 let instance = $.data( elem, namespace ); 104 if ( instance ) { 105 // set options & init 106 instance.option( options ); 107 instance._init(); 108 } else { 109 // initialize new instance 110 instance = new PluginClass( elem, options ); 111 $.data( elem, namespace, instance ); 112 } 113 } ); 114 } 115 116} 117 118// ----- ----- // 119 120return jQueryBridget; 121 122} ) ); 123/** 124 * EvEmitter v2.0.0 125 * Lil' event emitter 126 * MIT License 127 */ 128 129( function( global, factory ) { 130 // universal module definition 131 if ( typeof module == 'object' && module.exports ) { 132 // CommonJS - Browserify, Webpack 133 module.exports = factory(); 134 } else { 135 // Browser globals 136 global.EvEmitter = factory(); 137 } 138 139}( typeof window != 'undefined' ? window : this, function() { 140 141function EvEmitter() {} 142 143let proto = EvEmitter.prototype; 144 145proto.on = function( eventName, listener ) { 146 if ( !eventName || !listener ) return this; 147 148 // set events hash 149 let events = this._events = this._events || {}; 150 // set listeners array 151 let listeners = events[ eventName ] = events[ eventName ] || []; 152 // only add once 153 if ( !listeners.includes( listener ) ) { 154 listeners.push( listener ); 155 } 156 157 return this; 158}; 159 160proto.once = function( eventName, listener ) { 161 if ( !eventName || !listener ) return this; 162 163 // add event 164 this.on( eventName, listener ); 165 // set once flag 166 // set onceEvents hash 167 let onceEvents = this._onceEvents = this._onceEvents || {}; 168 // set onceListeners object 169 let onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {}; 170 // set flag 171 onceListeners[ listener ] = true; 172 173 return this; 174}; 175 176proto.off = function( eventName, listener ) { 177 let listeners = this._events && this._events[ eventName ]; 178 if ( !listeners || !listeners.length ) return this; 179 180 let index = listeners.indexOf( listener ); 181 if ( index != -1 ) { 182 listeners.splice( index, 1 ); 183 } 184 185 return this; 186}; 187 188proto.emitEvent = function( eventName, args ) { 189 let listeners = this._events && this._events[ eventName ]; 190 if ( !listeners || !listeners.length ) return this; 191 192 // copy over to avoid interference if .off() in listener 193 listeners = listeners.slice( 0 ); 194 args = args || []; 195 // once stuff 196 let onceListeners = this._onceEvents && this._onceEvents[ eventName ]; 197 198 for ( let listener of listeners ) { 199 let isOnce = onceListeners && onceListeners[ listener ]; 200 if ( isOnce ) { 201 // remove listener 202 // remove before trigger to prevent recursion 203 this.off( eventName, listener ); 204 // unset once flag 205 delete onceListeners[ listener ]; 206 } 207 // trigger listener 208 listener.apply( this, args ); 209 } 210 211 return this; 212}; 213 214proto.allOff = function() { 215 delete this._events; 216 delete this._onceEvents; 217 return this; 218}; 219 220return EvEmitter; 221 222} ) ); 223/** 224 * Fizzy UI utils v3.0.0 225 * MIT license 226 */ 227 228( function( global, factory ) { 229 // universal module definition 230 if ( typeof module == 'object' && module.exports ) { 231 // CommonJS 232 module.exports = factory( global ); 233 } else { 234 // browser global 235 global.fizzyUIUtils = factory( global ); 236 } 237 238}( this, function factory( global ) { 239 240let utils = {}; 241 242// ----- extend ----- // 243 244// extends objects 245utils.extend = function( a, b ) { 246 return Object.assign( a, b ); 247}; 248 249// ----- modulo ----- // 250 251utils.modulo = function( num, div ) { 252 return ( ( num % div ) + div ) % div; 253}; 254 255// ----- makeArray ----- // 256 257// turn element or nodeList into an array 258utils.makeArray = function( obj ) { 259 // use object if already an array 260 if ( Array.isArray( obj ) ) return obj; 261 262 // return empty array if undefined or null. #6 263 if ( obj === null || obj === undefined ) return []; 264 265 let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number'; 266 // convert nodeList to array 267 if ( isArrayLike ) return [ ...obj ]; 268 269 // array of single index 270 return [ obj ]; 271}; 272 273// ----- removeFrom ----- // 274 275utils.removeFrom = function( ary, obj ) { 276 let index = ary.indexOf( obj ); 277 if ( index != -1 ) { 278 ary.splice( index, 1 ); 279 } 280}; 281 282// ----- getParent ----- // 283 284utils.getParent = function( elem, selector ) { 285 while ( elem.parentNode && elem != document.body ) { 286 elem = elem.parentNode; 287 if ( elem.matches( selector ) ) return elem; 288 } 289}; 290 291// ----- getQueryElement ----- // 292 293// use element as selector string 294utils.getQueryElement = function( elem ) { 295 if ( typeof elem == 'string' ) { 296 return document.querySelector( elem ); 297 } 298 return elem; 299}; 300 301// ----- handleEvent ----- // 302 303// enable .ontype to trigger from .addEventListener( elem, 'type' ) 304utils.handleEvent = function( event ) { 305 let method = 'on' + event.type; 306 if ( this[ method ] ) { 307 this[ method ]( event ); 308 } 309}; 310 311// ----- filterFindElements ----- // 312 313utils.filterFindElements = function( elems, selector ) { 314 // make array of elems 315 elems = utils.makeArray( elems ); 316 317 return elems 318 // check that elem is an actual element 319 .filter( ( elem ) => elem instanceof HTMLElement ) 320 .reduce( ( ffElems, elem ) => { 321 // add elem if no selector 322 if ( !selector ) { 323 ffElems.push( elem ); 324 return ffElems; 325 } 326 // filter & find items if we have a selector 327 // filter 328 if ( elem.matches( selector ) ) { 329 ffElems.push( elem ); 330 } 331 // find children 332 let childElems = elem.querySelectorAll( selector ); 333 // concat childElems to filterFound array 334 ffElems = ffElems.concat( ...childElems ); 335 return ffElems; 336 }, [] ); 337}; 338 339// ----- debounceMethod ----- // 340 341utils.debounceMethod = function( _class, methodName, threshold ) { 342 threshold = threshold || 100; 343 // original method 344 let method = _class.prototype[ methodName ]; 345 let timeoutName = methodName + 'Timeout'; 346 347 _class.prototype[ methodName ] = function() { 348 clearTimeout( this[ timeoutName ] ); 349 350 let args = arguments; 351 this[ timeoutName ] = setTimeout( () => { 352 method.apply( this, args ); 353 delete this[ timeoutName ]; 354 }, threshold ); 355 }; 356}; 357 358// ----- docReady ----- // 359 360utils.docReady = function( onDocReady ) { 361 let readyState = document.readyState; 362 if ( readyState == 'complete' || readyState == 'interactive' ) { 363 // do async to allow for other scripts to run. metafizzy/flickity#441 364 setTimeout( onDocReady ); 365 } else { 366 document.addEventListener( 'DOMContentLoaded', onDocReady ); 367 } 368}; 369 370// ----- htmlInit ----- // 371 372// http://bit.ly/3oYLusc 373utils.toDashed = function( str ) { 374 return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) { 375 return $1 + '-' + $2; 376 } ).toLowerCase(); 377}; 378 379let console = global.console; 380 381// allow user to initialize classes via [data-namespace] or .js-namespace class 382// htmlInit( Widget, 'widgetName' ) 383// options are parsed from data-namespace-options 384utils.htmlInit = function( WidgetClass, namespace ) { 385 utils.docReady( function() { 386 let dashedNamespace = utils.toDashed( namespace ); 387 let dataAttr = 'data-' + dashedNamespace; 388 let dataAttrElems = document.querySelectorAll( `[${dataAttr}]` ); 389 let jQuery = global.jQuery; 390 391 [ ...dataAttrElems ].forEach( ( elem ) => { 392 let attr = elem.getAttribute( dataAttr ); 393 let options; 394 try { 395 options = attr && JSON.parse( attr ); 396 } catch ( error ) { 397 // log error, do not initialize 398 if ( console ) { 399 console.error( `Error parsing ${dataAttr} on ${elem.className}: ${error}` ); 400 } 401 return; 402 } 403 // initialize 404 let instance = new WidgetClass( elem, options ); 405 // make available via $().data('namespace') 406 if ( jQuery ) { 407 jQuery.data( elem, namespace, instance ); 408 } 409 } ); 410 411 } ); 412}; 413 414// ----- ----- // 415 416return utils; 417 418} ) ); 419// core 420( function( window, factory ) { 421 // universal module definition 422 if ( typeof module == 'object' && module.exports ) { 423 // CommonJS 424 module.exports = factory( 425 window, 426 require('ev-emitter'), 427 require('fizzy-ui-utils'), 428 ); 429 } else { 430 // browser global 431 window.InfiniteScroll = factory( 432 window, 433 window.EvEmitter, 434 window.fizzyUIUtils, 435 ); 436 } 437 438}( window, function factory( window, EvEmitter, utils ) { 439 440let jQuery = window.jQuery; 441// internal store of all InfiniteScroll intances 442let instances = {}; 443 444function InfiniteScroll( element, options ) { 445 let queryElem = utils.getQueryElement( element ); 446 447 if ( !queryElem ) { 448 console.error( 'Bad element for InfiniteScroll: ' + ( queryElem || element ) ); 449 return; 450 } 451 element = queryElem; 452 // do not initialize twice on same element 453 if ( element.infiniteScrollGUID ) { 454 let instance = instances[ element.infiniteScrollGUID ]; 455 instance.option( options ); 456 return instance; 457 } 458 459 this.element = element; 460 // options 461 this.options = { ...InfiniteScroll.defaults }; 462 this.option( options ); 463 // add jQuery 464 if ( jQuery ) { 465 this.$element = jQuery( this.element ); 466 } 467 468 this.create(); 469} 470 471// defaults 472InfiniteScroll.defaults = { 473 // path: null, 474 // hideNav: null, 475 // debug: false, 476}; 477 478// create & destroy methods 479InfiniteScroll.create = {}; 480InfiniteScroll.destroy = {}; 481 482let proto = InfiniteScroll.prototype; 483// inherit EvEmitter 484Object.assign( proto, EvEmitter.prototype ); 485 486// -------------------------- -------------------------- // 487 488// globally unique identifiers 489let GUID = 0; 490 491proto.create = function() { 492 // create core 493 // add id for InfiniteScroll.data 494 let id = this.guid = ++GUID; 495 this.element.infiniteScrollGUID = id; // expando 496 instances[ id ] = this; // associate via id 497 // properties 498 this.pageIndex = 1; // default to first page 499 this.loadCount = 0; 500 this.updateGetPath(); 501 // bail if getPath not set, or returns falsey #776 502 let hasPath = this.getPath && this.getPath(); 503 if ( !hasPath ) { 504 console.error('Disabling InfiniteScroll'); 505 return; 506 } 507 this.updateGetAbsolutePath(); 508 this.log( 'initialized', [ this.element.className ] ); 509 this.callOnInit(); 510 // create features 511 for ( let method in InfiniteScroll.create ) { 512 InfiniteScroll.create[ method ].call( this ); 513 } 514}; 515 516proto.option = function( opts ) { 517 Object.assign( this.options, opts ); 518}; 519 520// call onInit option, used for binding events on init 521proto.callOnInit = function() { 522 let onInit = this.options.onInit; 523 if ( onInit ) { 524 onInit.call( this, this ); 525 } 526}; 527 528// ----- events ----- // 529 530proto.dispatchEvent = function( type, event, args ) { 531 this.log( type, args ); 532 let emitArgs = event ? [ event ].concat( args ) : args; 533 this.emitEvent( type, emitArgs ); 534 // trigger jQuery event 535 if ( !jQuery || !this.$element ) { 536 return; 537 } 538 // namespace jQuery event 539 type += '.infiniteScroll'; 540 let $event = type; 541 if ( event ) { 542 // create jQuery event 543 /* eslint-disable-next-line new-cap */ 544 let jQEvent = jQuery.Event( event ); 545 jQEvent.type = type; 546 $event = jQEvent; 547 } 548 this.$element.trigger( $event, args ); 549}; 550 551let loggers = { 552 initialized: ( className ) => `on ${className}`, 553 request: ( path ) => `URL: ${path}`, 554 load: ( response, path ) => `${response.title || ''}. URL: ${path}`, 555 error: ( error, path ) => `${error}. URL: ${path}`, 556 append: ( response, path, items ) => `${items.length} items. URL: ${path}`, 557 last: ( response, path ) => `URL: ${path}`, 558 history: ( title, path ) => `URL: ${path}`, 559 pageIndex: function( index, origin ) { 560 return `current page determined to be: ${index} from ${origin}`; 561 }, 562}; 563 564// log events 565proto.log = function( type, args ) { 566 if ( !this.options.debug ) return; 567 568 let message = `[InfiniteScroll] ${type}`; 569 let logger = loggers[ type ]; 570 if ( logger ) message += '. ' + logger.apply( this, args ); 571 console.log( message ); 572}; 573 574// -------------------------- methods used amoung features -------------------------- // 575 576proto.updateMeasurements = function() { 577 this.windowHeight = window.innerHeight; 578 let rect = this.element.getBoundingClientRect(); 579 this.top = rect.top + window.scrollY; 580}; 581 582proto.updateScroller = function() { 583 let elementScroll = this.options.elementScroll; 584 if ( !elementScroll ) { 585 // default, use window 586 this.scroller = window; 587 return; 588 } 589 // if true, set to element, otherwise use option 590 this.scroller = elementScroll === true ? this.element : 591 utils.getQueryElement( elementScroll ); 592 if ( !this.scroller ) { 593 throw new Error(`Unable to find elementScroll: ${elementScroll}`); 594 } 595}; 596 597// -------------------------- page path -------------------------- // 598 599proto.updateGetPath = function() { 600 let optPath = this.options.path; 601 if ( !optPath ) { 602 console.error(`InfiniteScroll path option required. Set as: ${optPath}`); 603 return; 604 } 605 // function 606 let type = typeof optPath; 607 if ( type == 'function' ) { 608 this.getPath = optPath; 609 return; 610 } 611 // template string: '/pages/{{#}}.html' 612 let templateMatch = type == 'string' && optPath.match('{{#}}'); 613 if ( templateMatch ) { 614 this.updateGetPathTemplate( optPath ); 615 return; 616 } 617 // selector: '.next-page-selector' 618 this.updateGetPathSelector( optPath ); 619}; 620 621proto.updateGetPathTemplate = function( optPath ) { 622 // set getPath with template string 623 this.getPath = () => { 624 let nextIndex = this.pageIndex + 1; 625 return optPath.replace( '{{#}}', nextIndex ); 626 }; 627 // get pageIndex from location 628 // convert path option into regex to look for pattern in location 629 // escape query (?) in url, allows for parsing GET parameters 630 let regexString = optPath 631 .replace( /(\\\?|\?)/, '\\?' ) 632 .replace( '{{#}}', '(\\d\\d?\\d?)' ); 633 let templateRe = new RegExp( regexString ); 634 let match = location.href.match( templateRe ); 635 636 if ( match ) { 637 this.pageIndex = parseInt( match[1], 10 ); 638 this.log( 'pageIndex', [ this.pageIndex, 'template string' ] ); 639 } 640}; 641 642let pathRegexes = [ 643 // WordPress & Tumblr - example.com/page/2 644 // Jekyll - example.com/page2 645 /^(.*?\/?page\/?)(\d\d?\d?)(.*?$)/, 646 // Drupal - example.com/?page=1 647 /^(.*?\/?\?page=)(\d\d?\d?)(.*?$)/, 648 // catch all, last occurence of a number 649 /(.*?)(\d\d?\d?)(?!.*\d)(.*?$)/, 650]; 651 652// try matching href to pathRegexes patterns 653let getPathParts = InfiniteScroll.getPathParts = function( href ) { 654 if ( !href ) return; 655 for ( let regex of pathRegexes ) { 656 let match = href.match( regex ); 657 if ( match ) { 658 let [ , begin, index, end ] = match; 659 return { begin, index, end }; 660 } 661 } 662}; 663 664proto.updateGetPathSelector = function( optPath ) { 665 // parse href of link: '.next-page-link' 666 let hrefElem = document.querySelector( optPath ); 667 if ( !hrefElem ) { 668 console.error(`Bad InfiniteScroll path option. Next link not found: ${optPath}`); 669 return; 670 } 671 672 let href = hrefElem.getAttribute('href'); 673 let pathParts = getPathParts( href ); 674 if ( !pathParts ) { 675 console.error(`InfiniteScroll unable to parse next link href: ${href}`); 676 return; 677 } 678 679 let { begin, index, end } = pathParts; 680 this.isPathSelector = true; // flag for checkLastPage() 681 this.getPath = () => begin + ( this.pageIndex + 1 ) + end; 682 // get pageIndex from href 683 this.pageIndex = parseInt( index, 10 ) - 1; 684 this.log( 'pageIndex', [ this.pageIndex, 'next link' ] ); 685}; 686 687proto.updateGetAbsolutePath = function() { 688 let path = this.getPath(); 689 // path doesn't start with http or / 690 let isAbsolute = path.match( /^http/ ) || path.match( /^\// ); 691 if ( isAbsolute ) { 692 this.getAbsolutePath = this.getPath; 693 return; 694 } 695 696 let { pathname } = location; 697 // query parameter #829. example.com/?pg=2 698 let isQuery = path.match( /^\?/ ); 699 // /foo/bar/index.html => /foo/bar 700 let directory = pathname.substring( 0, pathname.lastIndexOf('/') ); 701 let pathStart = isQuery ? pathname : directory + '/'; 702 703 this.getAbsolutePath = () => pathStart + this.getPath(); 704}; 705 706// -------------------------- nav -------------------------- // 707 708// hide navigation 709InfiniteScroll.create.hideNav = function() { 710 let nav = utils.getQueryElement( this.options.hideNav ); 711 if ( !nav ) return; 712 713 nav.style.display = 'none'; 714 this.nav = nav; 715}; 716 717InfiniteScroll.destroy.hideNav = function() { 718 if ( this.nav ) this.nav.style.display = ''; 719}; 720 721// -------------------------- destroy -------------------------- // 722 723proto.destroy = function() { 724 this.allOff(); // remove all event listeners 725 // call destroy methods 726 for ( let method in InfiniteScroll.destroy ) { 727 InfiniteScroll.destroy[ method ].call( this ); 728 } 729 730 delete this.element.infiniteScrollGUID; 731 delete instances[ this.guid ]; 732 // remove jQuery data. #807 733 if ( jQuery && this.$element ) { 734 jQuery.removeData( this.element, 'infiniteScroll' ); 735 } 736}; 737 738// -------------------------- utilities -------------------------- // 739 740// https://remysharp.com/2010/07/21/throttling-function-calls 741InfiniteScroll.throttle = function( fn, threshold ) { 742 threshold = threshold || 200; 743 let last, timeout; 744 745 return function() { 746 let now = +new Date(); 747 let args = arguments; 748 let trigger = () => { 749 last = now; 750 fn.apply( this, args ); 751 }; 752 if ( last && now < last + threshold ) { 753 // hold on to it 754 clearTimeout( timeout ); 755 timeout = setTimeout( trigger, threshold ); 756 } else { 757 trigger(); 758 } 759 }; 760}; 761 762InfiniteScroll.data = function( elem ) { 763 elem = utils.getQueryElement( elem ); 764 let id = elem && elem.infiniteScrollGUID; 765 return id && instances[ id ]; 766}; 767 768// set internal jQuery, for Webpack + jQuery v3 769InfiniteScroll.setJQuery = function( jqry ) { 770 jQuery = jqry; 771}; 772 773// -------------------------- setup -------------------------- // 774 775utils.htmlInit( InfiniteScroll, 'infinite-scroll' ); 776 777// add noop _init method for jQuery Bridget. #768 778proto._init = function() {}; 779 780let { jQueryBridget } = window; 781if ( jQuery && jQueryBridget ) { 782 jQueryBridget( 'infiniteScroll', InfiniteScroll, jQuery ); 783} 784 785// -------------------------- -------------------------- // 786 787return InfiniteScroll; 788 789} ) ); 790// page-load 791( function( window, factory ) { 792 // universal module definition 793 if ( typeof module == 'object' && module.exports ) { 794 // CommonJS 795 module.exports = factory( 796 window, 797 require('./core'), 798 ); 799 } else { 800 // browser global 801 factory( 802 window, 803 window.InfiniteScroll, 804 ); 805 } 806 807}( window, function factory( window, InfiniteScroll ) { 808 809let proto = InfiniteScroll.prototype; 810 811Object.assign( InfiniteScroll.defaults, { 812 // append: false, 813 loadOnScroll: true, 814 checkLastPage: true, 815 responseBody: 'text', 816 domParseResponse: true, 817 // prefill: false, 818 // outlayer: null, 819} ); 820 821InfiniteScroll.create.pageLoad = function() { 822 this.canLoad = true; 823 this.on( 'scrollThreshold', this.onScrollThresholdLoad ); 824 this.on( 'load', this.checkLastPage ); 825 if ( this.options.outlayer ) { 826 this.on( 'append', this.onAppendOutlayer ); 827 } 828}; 829 830proto.onScrollThresholdLoad = function() { 831 if ( this.options.loadOnScroll ) this.loadNextPage(); 832}; 833 834let domParser = new DOMParser(); 835 836proto.loadNextPage = function() { 837 if ( this.isLoading || !this.canLoad ) return; 838 839 let { responseBody, domParseResponse, fetchOptions } = this.options; 840 let path = this.getAbsolutePath(); 841 this.isLoading = true; 842 if ( typeof fetchOptions == 'function' ) fetchOptions = fetchOptions(); 843 844 let fetchPromise = fetch( path, fetchOptions ) 845 .then( ( response ) => { 846 if ( !response.ok ) { 847 let error = new Error( response.statusText ); 848 this.onPageError( error, path, response ); 849 return { response }; 850 } 851 852 return response[ responseBody ]().then( ( body ) => { 853 let canDomParse = responseBody == 'text' && domParseResponse; 854 if ( canDomParse ) { 855 body = domParser.parseFromString( body, 'text/html' ); 856 } 857 if ( response.status == 204 ) { 858 this.lastPageReached( body, path ); 859 return { body, response }; 860 } else { 861 return this.onPageLoad( body, path, response ); 862 } 863 } ); 864 } ) 865 .catch( ( error ) => { 866 this.onPageError( error, path ); 867 } ); 868 869 this.dispatchEvent( 'request', null, [ path, fetchPromise ] ); 870 871 return fetchPromise; 872}; 873 874proto.onPageLoad = function( body, path, response ) { 875 // done loading if not appending 876 if ( !this.options.append ) { 877 this.isLoading = false; 878 } 879 this.pageIndex++; 880 this.loadCount++; 881 this.dispatchEvent( 'load', null, [ body, path, response ] ); 882 return this.appendNextPage( body, path, response ); 883}; 884 885proto.appendNextPage = function( body, path, response ) { 886 let { append, responseBody, domParseResponse } = this.options; 887 // do not append json 888 let isDocument = responseBody == 'text' && domParseResponse; 889 if ( !isDocument || !append ) return { body, response }; 890 891 let items = body.querySelectorAll( append ); 892 let promiseValue = { body, response, items }; 893 // last page hit if no items. #840 894 if ( !items || !items.length ) { 895 this.lastPageReached( body, path ); 896 return promiseValue; 897 } 898 899 let fragment = getItemsFragment( items ); 900 let appendReady = () => { 901 this.appendItems( items, fragment ); 902 this.isLoading = false; 903 this.dispatchEvent( 'append', null, [ body, path, items, response ] ); 904 return promiseValue; 905 }; 906 907 // TODO add hook for option to trigger appendReady 908 if ( this.options.outlayer ) { 909 return this.appendOutlayerItems( fragment, appendReady ); 910 } else { 911 return appendReady(); 912 } 913}; 914 915proto.appendItems = function( items, fragment ) { 916 if ( !items || !items.length ) return; 917 918 // get fragment if not provided 919 fragment = fragment || getItemsFragment( items ); 920 refreshScripts( fragment ); 921 this.element.appendChild( fragment ); 922}; 923 924function getItemsFragment( items ) { 925 // add items to fragment 926 let fragment = document.createDocumentFragment(); 927 if ( items ) fragment.append( ...items ); 928 return fragment; 929} 930 931// replace <script>s with copies so they load 932// <script>s added by InfiniteScroll will not load 933// similar to https://stackoverflow.com/questions/610995 934function refreshScripts( fragment ) { 935 let scripts = fragment.querySelectorAll('script'); 936 for ( let script of scripts ) { 937 let freshScript = document.createElement('script'); 938 // copy attributes 939 let attrs = script.attributes; 940 for ( let attr of attrs ) { 941 freshScript.setAttribute( attr.name, attr.value ); 942 } 943 // copy inner script code. #718, #782 944 freshScript.innerHTML = script.innerHTML; 945 script.parentNode.replaceChild( freshScript, script ); 946 } 947} 948 949// ----- outlayer ----- // 950 951proto.appendOutlayerItems = function( fragment, appendReady ) { 952 let imagesLoaded = InfiniteScroll.imagesLoaded || window.imagesLoaded; 953 if ( !imagesLoaded ) { 954 console.error('[InfiniteScroll] imagesLoaded required for outlayer option'); 955 this.isLoading = false; 956 return; 957 } 958 // append once images loaded 959 return new Promise( function( resolve ) { 960 imagesLoaded( fragment, function() { 961 let bodyResponse = appendReady(); 962 resolve( bodyResponse ); 963 } ); 964 } ); 965}; 966 967proto.onAppendOutlayer = function( response, path, items ) { 968 this.options.outlayer.appended( items ); 969}; 970 971// ----- checkLastPage ----- // 972 973// check response for next element 974proto.checkLastPage = function( body, path ) { 975 let { checkLastPage, path: pathOpt } = this.options; 976 if ( !checkLastPage ) return; 977 978 // if path is function, check if next path is truthy 979 if ( typeof pathOpt == 'function' ) { 980 let nextPath = this.getPath(); 981 if ( !nextPath ) { 982 this.lastPageReached( body, path ); 983 return; 984 } 985 } 986 // get selector from checkLastPage or path option 987 let selector; 988 if ( typeof checkLastPage == 'string' ) { 989 selector = checkLastPage; 990 } else if ( this.isPathSelector ) { 991 // path option is selector string 992 selector = pathOpt; 993 } 994 // check last page for selector 995 // bail if no selector or not document response 996 if ( !selector || !body.querySelector ) return; 997 998 // check if response has selector 999 let nextElem = body.querySelector( selector ); 1000 if ( !nextElem ) this.lastPageReached( body, path ); 1001}; 1002 1003proto.lastPageReached = function( body, path ) { 1004 this.canLoad = false; 1005 this.dispatchEvent( 'last', null, [ body, path ] ); 1006}; 1007 1008// ----- error ----- // 1009 1010proto.onPageError = function( error, path, response ) { 1011 this.isLoading = false; 1012 this.canLoad = false; 1013 this.dispatchEvent( 'error', null, [ error, path, response ] ); 1014 return error; 1015}; 1016 1017// -------------------------- prefill -------------------------- // 1018 1019InfiniteScroll.create.prefill = function() { 1020 if ( !this.options.prefill ) return; 1021 1022 let append = this.options.append; 1023 if ( !append ) { 1024 console.error(`append option required for prefill. Set as :${append}`); 1025 return; 1026 } 1027 this.updateMeasurements(); 1028 this.updateScroller(); 1029 this.isPrefilling = true; 1030 this.on( 'append', this.prefill ); 1031 this.once( 'error', this.stopPrefill ); 1032 this.once( 'last', this.stopPrefill ); 1033 this.prefill(); 1034}; 1035 1036proto.prefill = function() { 1037 let distance = this.getPrefillDistance(); 1038 this.isPrefilling = distance >= 0; 1039 if ( this.isPrefilling ) { 1040 this.log('prefill'); 1041 this.loadNextPage(); 1042 } else { 1043 this.stopPrefill(); 1044 } 1045}; 1046 1047proto.getPrefillDistance = function() { 1048 // element scroll 1049 if ( this.options.elementScroll ) { 1050 return this.scroller.clientHeight - this.scroller.scrollHeight; 1051 } 1052 // window 1053 return this.windowHeight - this.element.clientHeight; 1054}; 1055 1056proto.stopPrefill = function() { 1057 this.log('stopPrefill'); 1058 this.off( 'append', this.prefill ); 1059}; 1060 1061// -------------------------- -------------------------- // 1062 1063return InfiniteScroll; 1064 1065} ) ); 1066// scroll-watch 1067( function( window, factory ) { 1068 // universal module definition 1069 if ( typeof module == 'object' && module.exports ) { 1070 // CommonJS 1071 module.exports = factory( 1072 window, 1073 require('./core'), 1074 require('fizzy-ui-utils'), 1075 ); 1076 } else { 1077 // browser global 1078 factory( 1079 window, 1080 window.InfiniteScroll, 1081 window.fizzyUIUtils, 1082 ); 1083 } 1084 1085}( window, function factory( window, InfiniteScroll, utils ) { 1086 1087let proto = InfiniteScroll.prototype; 1088 1089// default options 1090Object.assign( InfiniteScroll.defaults, { 1091 scrollThreshold: 400, 1092 // elementScroll: null, 1093} ); 1094 1095InfiniteScroll.create.scrollWatch = function() { 1096 // events 1097 this.pageScrollHandler = this.onPageScroll.bind( this ); 1098 this.resizeHandler = this.onResize.bind( this ); 1099 1100 let scrollThreshold = this.options.scrollThreshold; 1101 let isEnable = scrollThreshold || scrollThreshold === 0; 1102 if ( isEnable ) this.enableScrollWatch(); 1103}; 1104 1105InfiniteScroll.destroy.scrollWatch = function() { 1106 this.disableScrollWatch(); 1107}; 1108 1109proto.enableScrollWatch = function() { 1110 if ( this.isScrollWatching ) return; 1111 1112 this.isScrollWatching = true; 1113 this.updateMeasurements(); 1114 this.updateScroller(); 1115 // TODO disable after error? 1116 this.on( 'last', this.disableScrollWatch ); 1117 this.bindScrollWatchEvents( true ); 1118}; 1119 1120proto.disableScrollWatch = function() { 1121 if ( !this.isScrollWatching ) return; 1122 1123 this.bindScrollWatchEvents( false ); 1124 delete this.isScrollWatching; 1125}; 1126 1127proto.bindScrollWatchEvents = function( isBind ) { 1128 let addRemove = isBind ? 'addEventListener' : 'removeEventListener'; 1129 this.scroller[ addRemove ]( 'scroll', this.pageScrollHandler ); 1130 window[ addRemove ]( 'resize', this.resizeHandler ); 1131}; 1132 1133proto.onPageScroll = InfiniteScroll.throttle( function() { 1134 let distance = this.getBottomDistance(); 1135 if ( distance <= this.options.scrollThreshold ) { 1136 this.dispatchEvent('scrollThreshold'); 1137 } 1138} ); 1139 1140proto.getBottomDistance = function() { 1141 let bottom, scrollY; 1142 if ( this.options.elementScroll ) { 1143 bottom = this.scroller.scrollHeight; 1144 scrollY = this.scroller.scrollTop + this.scroller.clientHeight; 1145 } else { 1146 bottom = this.top + this.element.clientHeight; 1147 scrollY = window.scrollY + this.windowHeight; 1148 } 1149 return bottom - scrollY; 1150}; 1151 1152proto.onResize = function() { 1153 this.updateMeasurements(); 1154}; 1155 1156utils.debounceMethod( InfiniteScroll, 'onResize', 150 ); 1157 1158// -------------------------- -------------------------- // 1159 1160return InfiniteScroll; 1161 1162} ) ); 1163// history 1164( function( window, factory ) { 1165 // universal module definition 1166 if ( typeof module == 'object' && module.exports ) { 1167 // CommonJS 1168 module.exports = factory( 1169 window, 1170 require('./core'), 1171 require('fizzy-ui-utils'), 1172 ); 1173 } else { 1174 // browser global 1175 factory( 1176 window, 1177 window.InfiniteScroll, 1178 window.fizzyUIUtils, 1179 ); 1180 } 1181 1182}( window, function factory( window, InfiniteScroll, utils ) { 1183 1184let proto = InfiniteScroll.prototype; 1185 1186Object.assign( InfiniteScroll.defaults, { 1187 history: 'replace', 1188 // historyTitle: false, 1189} ); 1190 1191let link = document.createElement('a'); 1192 1193// ----- create/destroy ----- // 1194 1195InfiniteScroll.create.history = function() { 1196 if ( !this.options.history ) return; 1197 1198 // check for same origin 1199 link.href = this.getAbsolutePath(); 1200 // MS Edge does not have origin on link 1201 // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12236493/ 1202 let linkOrigin = link.origin || link.protocol + '//' + link.host; 1203 let isSameOrigin = linkOrigin == location.origin; 1204 if ( !isSameOrigin ) { 1205 console.error( '[InfiniteScroll] cannot set history with different origin: ' + 1206 `${link.origin} on ${location.origin} . History behavior disabled.` ); 1207 return; 1208 } 1209 1210 // two ways to handle changing history 1211 if ( this.options.append ) { 1212 this.createHistoryAppend(); 1213 } else { 1214 this.createHistoryPageLoad(); 1215 } 1216}; 1217 1218proto.createHistoryAppend = function() { 1219 this.updateMeasurements(); 1220 this.updateScroller(); 1221 // array of scroll positions of appended pages 1222 this.scrollPages = [ 1223 // first page 1224 { 1225 top: 0, 1226 path: location.href, 1227 title: document.title, 1228 }, 1229 ]; 1230 this.scrollPage = this.scrollPages[0]; 1231 // events 1232 this.scrollHistoryHandler = this.onScrollHistory.bind( this ); 1233 this.unloadHandler = this.onUnload.bind( this ); 1234 this.scroller.addEventListener( 'scroll', this.scrollHistoryHandler ); 1235 this.on( 'append', this.onAppendHistory ); 1236 this.bindHistoryAppendEvents( true ); 1237}; 1238 1239proto.bindHistoryAppendEvents = function( isBind ) { 1240 let addRemove = isBind ? 'addEventListener' : 'removeEventListener'; 1241 this.scroller[ addRemove ]( 'scroll', this.scrollHistoryHandler ); 1242 window[ addRemove ]( 'unload', this.unloadHandler ); 1243}; 1244 1245proto.createHistoryPageLoad = function() { 1246 this.on( 'load', this.onPageLoadHistory ); 1247}; 1248 1249InfiniteScroll.destroy.history = 1250proto.destroyHistory = function() { 1251 let isHistoryAppend = this.options.history && this.options.append; 1252 if ( isHistoryAppend ) { 1253 this.bindHistoryAppendEvents( false ); 1254 } 1255}; 1256 1257// ----- append history ----- // 1258 1259proto.onAppendHistory = function( response, path, items ) { 1260 // do not proceed if no items. #779 1261 if ( !items || !items.length ) return; 1262 1263 let firstItem = items[0]; 1264 let elemScrollY = this.getElementScrollY( firstItem ); 1265 // resolve path 1266 link.href = path; 1267 // add page data to hash 1268 this.scrollPages.push({ 1269 top: elemScrollY, 1270 path: link.href, 1271 title: response.title, 1272 }); 1273}; 1274 1275proto.getElementScrollY = function( elem ) { 1276 if ( this.options.elementScroll ) { 1277 return elem.offsetTop - this.top; 1278 } else { 1279 let rect = elem.getBoundingClientRect(); 1280 return rect.top + window.scrollY; 1281 } 1282}; 1283 1284proto.onScrollHistory = function() { 1285 // cycle through positions, find biggest without going over 1286 let scrollPage = this.getClosestScrollPage(); 1287 // set history if changed 1288 if ( scrollPage != this.scrollPage ) { 1289 this.scrollPage = scrollPage; 1290 this.setHistory( scrollPage.title, scrollPage.path ); 1291 } 1292}; 1293 1294utils.debounceMethod( InfiniteScroll, 'onScrollHistory', 150 ); 1295 1296proto.getClosestScrollPage = function() { 1297 let scrollViewY; 1298 if ( this.options.elementScroll ) { 1299 scrollViewY = this.scroller.scrollTop + this.scroller.clientHeight / 2; 1300 } else { 1301 scrollViewY = window.scrollY + this.windowHeight / 2; 1302 } 1303 1304 let scrollPage; 1305 for ( let page of this.scrollPages ) { 1306 if ( page.top >= scrollViewY ) break; 1307 1308 scrollPage = page; 1309 } 1310 return scrollPage; 1311}; 1312 1313proto.setHistory = function( title, path ) { 1314 let optHistory = this.options.history; 1315 let historyMethod = optHistory && history[ optHistory + 'State' ]; 1316 if ( !historyMethod ) return; 1317 1318 history[ optHistory + 'State' ]( null, title, path ); 1319 if ( this.options.historyTitle ) document.title = title; 1320 this.dispatchEvent( 'history', null, [ title, path ] ); 1321}; 1322 1323// scroll to top to prevent initial scroll-reset after page refresh 1324// https://stackoverflow.com/a/18633915/182183 1325proto.onUnload = function() { 1326 if ( this.scrollPage.top === 0 ) return; 1327 1328 // calculate where scroll position would be on refresh 1329 let scrollY = window.scrollY - this.scrollPage.top + this.top; 1330 // disable scroll event before setting scroll #679 1331 this.destroyHistory(); 1332 scrollTo( 0, scrollY ); 1333}; 1334 1335// ----- load history ----- // 1336 1337// update URL 1338proto.onPageLoadHistory = function( response, path ) { 1339 this.setHistory( response.title, path ); 1340}; 1341 1342// -------------------------- -------------------------- // 1343 1344return InfiniteScroll; 1345 1346} ) ); 1347// button 1348( function( window, factory ) { 1349 // universal module definition 1350 if ( typeof module == 'object' && module.exports ) { 1351 // CommonJS 1352 module.exports = factory( 1353 window, 1354 require('./core'), 1355 require('fizzy-ui-utils'), 1356 ); 1357 } else { 1358 // browser global 1359 factory( 1360 window, 1361 window.InfiniteScroll, 1362 window.fizzyUIUtils, 1363 ); 1364 } 1365 1366}( window, function factory( window, InfiniteScroll, utils ) { 1367 1368// -------------------------- InfiniteScrollButton -------------------------- // 1369 1370class InfiniteScrollButton { 1371 constructor( element, infScroll ) { 1372 this.element = element; 1373 this.infScroll = infScroll; 1374 // events 1375 this.clickHandler = this.onClick.bind( this ); 1376 this.element.addEventListener( 'click', this.clickHandler ); 1377 infScroll.on( 'request', this.disable.bind( this ) ); 1378 infScroll.on( 'load', this.enable.bind( this ) ); 1379 infScroll.on( 'error', this.hide.bind( this ) ); 1380 infScroll.on( 'last', this.hide.bind( this ) ); 1381 } 1382 1383 onClick( event ) { 1384 event.preventDefault(); 1385 this.infScroll.loadNextPage(); 1386 } 1387 1388 enable() { 1389 this.element.removeAttribute('disabled'); 1390 } 1391 1392 disable() { 1393 this.element.disabled = 'disabled'; 1394 } 1395 1396 hide() { 1397 this.element.style.display = 'none'; 1398 } 1399 1400 destroy() { 1401 this.element.removeEventListener( 'click', this.clickHandler ); 1402 } 1403 1404} 1405 1406// -------------------------- InfiniteScroll methods -------------------------- // 1407 1408// InfiniteScroll.defaults.button = null; 1409 1410InfiniteScroll.create.button = function() { 1411 let buttonElem = utils.getQueryElement( this.options.button ); 1412 if ( buttonElem ) { 1413 this.button = new InfiniteScrollButton( buttonElem, this ); 1414 } 1415}; 1416 1417InfiniteScroll.destroy.button = function() { 1418 if ( this.button ) this.button.destroy(); 1419}; 1420 1421// -------------------------- -------------------------- // 1422 1423InfiniteScroll.Button = InfiniteScrollButton; 1424 1425return InfiniteScroll; 1426 1427} ) ); 1428// status 1429( function( window, factory ) { 1430 // universal module definition 1431 if ( typeof module == 'object' && module.exports ) { 1432 // CommonJS 1433 module.exports = factory( 1434 window, 1435 require('./core'), 1436 require('fizzy-ui-utils'), 1437 ); 1438 } else { 1439 // browser global 1440 factory( 1441 window, 1442 window.InfiniteScroll, 1443 window.fizzyUIUtils, 1444 ); 1445 } 1446 1447}( window, function factory( window, InfiniteScroll, utils ) { 1448 1449let proto = InfiniteScroll.prototype; 1450 1451// InfiniteScroll.defaults.status = null; 1452 1453InfiniteScroll.create.status = function() { 1454 let statusElem = utils.getQueryElement( this.options.status ); 1455 if ( !statusElem ) return; 1456 1457 // elements 1458 this.statusElement = statusElem; 1459 this.statusEventElements = { 1460 request: statusElem.querySelector('.infinite-scroll-request'), 1461 error: statusElem.querySelector('.infinite-scroll-error'), 1462 last: statusElem.querySelector('.infinite-scroll-last'), 1463 }; 1464 // events 1465 this.on( 'request', this.showRequestStatus ); 1466 this.on( 'error', this.showErrorStatus ); 1467 this.on( 'last', this.showLastStatus ); 1468 this.bindHideStatus('on'); 1469}; 1470 1471proto.bindHideStatus = function( bindMethod ) { 1472 let hideEvent = this.options.append ? 'append' : 'load'; 1473 this[ bindMethod ]( hideEvent, this.hideAllStatus ); 1474}; 1475 1476proto.showRequestStatus = function() { 1477 this.showStatus('request'); 1478}; 1479 1480proto.showErrorStatus = function() { 1481 this.showStatus('error'); 1482}; 1483 1484proto.showLastStatus = function() { 1485 this.showStatus('last'); 1486 // prevent last then append event race condition from showing last status #706 1487 this.bindHideStatus('off'); 1488}; 1489 1490proto.showStatus = function( eventName ) { 1491 show( this.statusElement ); 1492 this.hideStatusEventElements(); 1493 let eventElem = this.statusEventElements[ eventName ]; 1494 show( eventElem ); 1495}; 1496 1497proto.hideAllStatus = function() { 1498 hide( this.statusElement ); 1499 this.hideStatusEventElements(); 1500}; 1501 1502proto.hideStatusEventElements = function() { 1503 for ( let type in this.statusEventElements ) { 1504 let eventElem = this.statusEventElements[ type ]; 1505 hide( eventElem ); 1506 } 1507}; 1508 1509// -------------------------- -------------------------- // 1510 1511function hide( elem ) { 1512 setDisplay( elem, 'none' ); 1513} 1514 1515function show( elem ) { 1516 setDisplay( elem, 'block' ); 1517} 1518 1519function setDisplay( elem, value ) { 1520 if ( elem ) { 1521 elem.style.display = value; 1522 } 1523} 1524 1525// -------------------------- -------------------------- // 1526 1527return InfiniteScroll; 1528 1529} ) ); 1530/*! 1531 * imagesLoaded v4.1.4 1532 * JavaScript is all like "You images are done yet or what?" 1533 * MIT License 1534 */ 1535 1536( function( window, factory ) { 'use strict'; 1537 // universal module definition 1538 1539 /*global define: false, module: false, require: false */ 1540 1541 if ( typeof define == 'function' && define.amd ) { 1542 // AMD 1543 define( [ 1544 'ev-emitter/ev-emitter' 1545 ], function( EvEmitter ) { 1546 return factory( window, EvEmitter ); 1547 }); 1548 } else if ( typeof module == 'object' && module.exports ) { 1549 // CommonJS 1550 module.exports = factory( 1551 window, 1552 require('ev-emitter') 1553 ); 1554 } else { 1555 // browser global 1556 window.imagesLoaded = factory( 1557 window, 1558 window.EvEmitter 1559 ); 1560 } 1561 1562})( typeof window !== 'undefined' ? window : this, 1563 1564// -------------------------- factory -------------------------- // 1565 1566function factory( window, EvEmitter ) { 1567 1568'use strict'; 1569 1570var $ = window.jQuery; 1571var console = window.console; 1572 1573// -------------------------- helpers -------------------------- // 1574 1575// extend objects 1576function extend( a, b ) { 1577 for ( var prop in b ) { 1578 a[ prop ] = b[ prop ]; 1579 } 1580 return a; 1581} 1582 1583var arraySlice = Array.prototype.slice; 1584 1585// turn element or nodeList into an array 1586function makeArray( obj ) { 1587 if ( Array.isArray( obj ) ) { 1588 // use object if already an array 1589 return obj; 1590 } 1591 1592 var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number'; 1593 if ( isArrayLike ) { 1594 // convert nodeList to array 1595 return arraySlice.call( obj ); 1596 } 1597 1598 // array of single index 1599 return [ obj ]; 1600} 1601 1602// -------------------------- imagesLoaded -------------------------- // 1603 1604/** 1605 * @param {Array, Element, NodeList, String} elem 1606 * @param {Object or Function} options - if function, use as callback 1607 * @param {Function} onAlways - callback function 1608 */ 1609function ImagesLoaded( elem, options, onAlways ) { 1610 // coerce ImagesLoaded() without new, to be new ImagesLoaded() 1611 if ( !( this instanceof ImagesLoaded ) ) { 1612 return new ImagesLoaded( elem, options, onAlways ); 1613 } 1614 // use elem as selector string 1615 var queryElem = elem; 1616 if ( typeof elem == 'string' ) { 1617 queryElem = document.querySelectorAll( elem ); 1618 } 1619 // bail if bad element 1620 if ( !queryElem ) { 1621 console.error( 'Bad element for imagesLoaded ' + ( queryElem || elem ) ); 1622 return; 1623 } 1624 1625 this.elements = makeArray( queryElem ); 1626 this.options = extend( {}, this.options ); 1627 // shift arguments if no options set 1628 if ( typeof options == 'function' ) { 1629 onAlways = options; 1630 } else { 1631 extend( this.options, options ); 1632 } 1633 1634 if ( onAlways ) { 1635 this.on( 'always', onAlways ); 1636 } 1637 1638 this.getImages(); 1639 1640 if ( $ ) { 1641 // add jQuery Deferred object 1642 this.jqDeferred = new $.Deferred(); 1643 } 1644 1645 // HACK check async to allow time to bind listeners 1646 setTimeout( this.check.bind( this ) ); 1647} 1648 1649ImagesLoaded.prototype = Object.create( EvEmitter.prototype ); 1650 1651ImagesLoaded.prototype.options = {}; 1652 1653ImagesLoaded.prototype.getImages = function() { 1654 this.images = []; 1655 1656 // filter & find items if we have an item selector 1657 this.elements.forEach( this.addElementImages, this ); 1658}; 1659 1660/** 1661 * @param {Node} element 1662 */ 1663ImagesLoaded.prototype.addElementImages = function( elem ) { 1664 // filter siblings 1665 if ( elem.nodeName == 'IMG' ) { 1666 this.addImage( elem ); 1667 } 1668 // get background image on element 1669 if ( this.options.background === true ) { 1670 this.addElementBackgroundImages( elem ); 1671 } 1672 1673 // find children 1674 // no non-element nodes, #143 1675 var nodeType = elem.nodeType; 1676 if ( !nodeType || !elementNodeTypes[ nodeType ] ) { 1677 return; 1678 } 1679 var childImgs = elem.querySelectorAll('img'); 1680 // concat childElems to filterFound array 1681 for ( var i=0; i < childImgs.length; i++ ) { 1682 var img = childImgs[i]; 1683 this.addImage( img ); 1684 } 1685 1686 // get child background images 1687 if ( typeof this.options.background == 'string' ) { 1688 var children = elem.querySelectorAll( this.options.background ); 1689 for ( i=0; i < children.length; i++ ) { 1690 var child = children[i]; 1691 this.addElementBackgroundImages( child ); 1692 } 1693 } 1694}; 1695 1696var elementNodeTypes = { 1697 1: true, 1698 9: true, 1699 11: true 1700}; 1701 1702ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) { 1703 var style = getComputedStyle( elem ); 1704 if ( !style ) { 1705 // Firefox returns null if in a hidden iframe https://bugzil.la/548397 1706 return; 1707 } 1708 // get url inside url("...") 1709 var reURL = /url\((['"])?(.*?)\1\)/gi; 1710 var matches = reURL.exec( style.backgroundImage ); 1711 while ( matches !== null ) { 1712 var url = matches && matches[2]; 1713 if ( url ) { 1714 this.addBackground( url, elem ); 1715 } 1716 matches = reURL.exec( style.backgroundImage ); 1717 } 1718}; 1719 1720/** 1721 * @param {Image} img 1722 */ 1723ImagesLoaded.prototype.addImage = function( img ) { 1724 var loadingImage = new LoadingImage( img ); 1725 this.images.push( loadingImage ); 1726}; 1727 1728ImagesLoaded.prototype.addBackground = function( url, elem ) { 1729 var background = new Background( url, elem ); 1730 this.images.push( background ); 1731}; 1732 1733ImagesLoaded.prototype.check = function() { 1734 var _this = this; 1735 this.progressedCount = 0; 1736 this.hasAnyBroken = false; 1737 // complete if no images 1738 if ( !this.images.length ) { 1739 this.complete(); 1740 return; 1741 } 1742 1743 function onProgress( image, elem, message ) { 1744 // HACK - Chrome triggers event before object properties have changed. #83 1745 setTimeout( function() { 1746 _this.progress( image, elem, message ); 1747 }); 1748 } 1749 1750 this.images.forEach( function( loadingImage ) { 1751 loadingImage.once( 'progress', onProgress ); 1752 loadingImage.check(); 1753 }); 1754}; 1755 1756ImagesLoaded.prototype.progress = function( image, elem, message ) { 1757 this.progressedCount++; 1758 this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded; 1759 // progress event 1760 this.emitEvent( 'progress', [ this, image, elem ] ); 1761 if ( this.jqDeferred && this.jqDeferred.notify ) { 1762 this.jqDeferred.notify( this, image ); 1763 } 1764 // check if completed 1765 if ( this.progressedCount == this.images.length ) { 1766 this.complete(); 1767 } 1768 1769 if ( this.options.debug && console ) { 1770 console.log( 'progress: ' + message, image, elem ); 1771 } 1772}; 1773 1774ImagesLoaded.prototype.complete = function() { 1775 var eventName = this.hasAnyBroken ? 'fail' : 'done'; 1776 this.isComplete = true; 1777 this.emitEvent( eventName, [ this ] ); 1778 this.emitEvent( 'always', [ this ] ); 1779 if ( this.jqDeferred ) { 1780 var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve'; 1781 this.jqDeferred[ jqMethod ]( this ); 1782 } 1783}; 1784 1785// -------------------------- -------------------------- // 1786 1787function LoadingImage( img ) { 1788 this.img = img; 1789} 1790 1791LoadingImage.prototype = Object.create( EvEmitter.prototype ); 1792 1793LoadingImage.prototype.check = function() { 1794 // If complete is true and browser supports natural sizes, 1795 // try to check for image status manually. 1796 var isComplete = this.getIsImageComplete(); 1797 if ( isComplete ) { 1798 // report based on naturalWidth 1799 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' ); 1800 return; 1801 } 1802 1803 // If none of the checks above matched, simulate loading on detached element. 1804 this.proxyImage = new Image(); 1805 this.proxyImage.addEventListener( 'load', this ); 1806 this.proxyImage.addEventListener( 'error', this ); 1807 // bind to image as well for Firefox. #191 1808 this.img.addEventListener( 'load', this ); 1809 this.img.addEventListener( 'error', this ); 1810 this.proxyImage.src = this.img.src; 1811}; 1812 1813LoadingImage.prototype.getIsImageComplete = function() { 1814 // check for non-zero, non-undefined naturalWidth 1815 // fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671 1816 return this.img.complete && this.img.naturalWidth; 1817}; 1818 1819LoadingImage.prototype.confirm = function( isLoaded, message ) { 1820 this.isLoaded = isLoaded; 1821 this.emitEvent( 'progress', [ this, this.img, message ] ); 1822}; 1823 1824// ----- events ----- // 1825 1826// trigger specified handler for event type 1827LoadingImage.prototype.handleEvent = function( event ) { 1828 var method = 'on' + event.type; 1829 if ( this[ method ] ) { 1830 this[ method ]( event ); 1831 } 1832}; 1833 1834LoadingImage.prototype.onload = function() { 1835 this.confirm( true, 'onload' ); 1836 this.unbindEvents(); 1837}; 1838 1839LoadingImage.prototype.onerror = function() { 1840 this.confirm( false, 'onerror' ); 1841 this.unbindEvents(); 1842}; 1843 1844LoadingImage.prototype.unbindEvents = function() { 1845 this.proxyImage.removeEventListener( 'load', this ); 1846 this.proxyImage.removeEventListener( 'error', this ); 1847 this.img.removeEventListener( 'load', this ); 1848 this.img.removeEventListener( 'error', this ); 1849}; 1850 1851// -------------------------- Background -------------------------- // 1852 1853function Background( url, element ) { 1854 this.url = url; 1855 this.element = element; 1856 this.img = new Image(); 1857} 1858 1859// inherit LoadingImage prototype 1860Background.prototype = Object.create( LoadingImage.prototype ); 1861 1862Background.prototype.check = function() { 1863 this.img.addEventListener( 'load', this ); 1864 this.img.addEventListener( 'error', this ); 1865 this.img.src = this.url; 1866 // check if image is already complete 1867 var isComplete = this.getIsImageComplete(); 1868 if ( isComplete ) { 1869 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' ); 1870 this.unbindEvents(); 1871 } 1872}; 1873 1874Background.prototype.unbindEvents = function() { 1875 this.img.removeEventListener( 'load', this ); 1876 this.img.removeEventListener( 'error', this ); 1877}; 1878 1879Background.prototype.confirm = function( isLoaded, message ) { 1880 this.isLoaded = isLoaded; 1881 this.emitEvent( 'progress', [ this, this.element, message ] ); 1882}; 1883 1884// -------------------------- jQuery -------------------------- // 1885 1886ImagesLoaded.makeJQueryPlugin = function( jQuery ) { 1887 jQuery = jQuery || window.jQuery; 1888 if ( !jQuery ) { 1889 return; 1890 } 1891 // set local variable 1892 $ = jQuery; 1893 // $().imagesLoaded() 1894 $.fn.imagesLoaded = function( options, callback ) { 1895 var instance = new ImagesLoaded( this, options, callback ); 1896 return instance.jqDeferred.promise( $(this) ); 1897 }; 1898}; 1899// try making plugin 1900ImagesLoaded.makeJQueryPlugin(); 1901 1902// -------------------------- -------------------------- // 1903 1904return ImagesLoaded; 1905 1906});
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.