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