/*! * Infinite Scroll PACKAGED v3.0.2 * Automatically add next page * * Licensed GPLv3 for open source use * or Infinite Scroll Commercial License for commercial use * * https://infinite-scroll.com * Copyright 2017 Metafizzy */ /** * Bridget makes jQuery widgets * v2.0.1 * MIT license */ /* jshint browser: true, strict: true, undef: true, unused: true */ ( function( window, factory ) { // universal module definition /*jshint strict: false */ /* globals define, module, require */ if ( typeof define == 'function' && define.amd ) { // AMD define( 'jquery-bridget/jquery-bridget',[ 'jquery' ], function( jQuery ) { return factory( window, jQuery ); }); } else if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('jquery') ); } else { // browser global window.jQueryBridget = factory( window, window.jQuery ); } }( window, function factory( window, jQuery ) { 'use strict'; // ----- utils ----- // var arraySlice = Array.prototype.slice; // helper function for logging errors // $.error breaks jQuery chaining var console = window.console; var logError = typeof console == 'undefined' ? function() {} : function( message ) { console.error( message ); }; // ----- jQueryBridget ----- // function jQueryBridget( namespace, PluginClass, $ ) { $ = $ || jQuery || window.jQuery; if ( !$ ) { return; } // add option method -> $().plugin('option', {...}) if ( !PluginClass.prototype.option ) { // option setter PluginClass.prototype.option = function( opts ) { // bail out if not an object if ( !$.isPlainObject( opts ) ){ return; } this.options = $.extend( true, this.options, opts ); }; } // make jQuery plugin $.fn[ namespace ] = function( arg0 /*, arg1 */ ) { if ( typeof arg0 == 'string' ) { // method call $().plugin( 'methodName', { options } ) // shift arguments by 1 var args = arraySlice.call( arguments, 1 ); return methodCall( this, arg0, args ); } // just $().plugin({ options }) plainCall( this, arg0 ); return this; }; // $().plugin('methodName') function methodCall( $elems, methodName, args ) { var returnValue; var pluginMethodStr = '$().' + namespace + '("' + methodName + '")'; $elems.each( function( i, elem ) { // get instance var instance = $.data( elem, namespace ); if ( !instance ) { logError( namespace + ' not initialized. Cannot call methods, i.e. ' + pluginMethodStr ); return; } var method = instance[ methodName ]; if ( !method || methodName.charAt(0) == '_' ) { logError( pluginMethodStr + ' is not a valid method' ); return; } // apply method, get return value var value = method.apply( instance, args ); // set return value if value is returned, use only first value returnValue = returnValue === undefined ? value : returnValue; }); return returnValue !== undefined ? returnValue : $elems; } function plainCall( $elems, options ) { $elems.each( function( i, elem ) { var instance = $.data( elem, namespace ); if ( instance ) { // set options & init instance.option( options ); instance._init(); } else { // initialize new instance instance = new PluginClass( elem, options ); $.data( elem, namespace, instance ); } }); } updateJQuery( $ ); } // ----- updateJQuery ----- // // set $.bridget for v1 backwards compatibility function updateJQuery( $ ) { if ( !$ || ( $ && $.bridget ) ) { return; } $.bridget = jQueryBridget; } updateJQuery( jQuery || window.jQuery ); // ----- ----- // return jQueryBridget; })); /** * EvEmitter v1.1.0 * Lil' event emitter * MIT License */ /* jshint unused: true, undef: true, strict: true */ ( function( global, factory ) { // universal module definition /* jshint strict: false */ /* globals define, module, window */ if ( typeof define == 'function' && define.amd ) { // AMD - RequireJS define( 'ev-emitter/ev-emitter',factory ); } else if ( typeof module == 'object' && module.exports ) { // CommonJS - Browserify, Webpack module.exports = factory(); } else { // Browser globals global.EvEmitter = factory(); } }( typeof window != 'undefined' ? window : this, function() { function EvEmitter() {} var proto = EvEmitter.prototype; proto.on = function( eventName, listener ) { if ( !eventName || !listener ) { return; } // set events hash var events = this._events = this._events || {}; // set listeners array var listeners = events[ eventName ] = events[ eventName ] || []; // only add once if ( listeners.indexOf( listener ) == -1 ) { listeners.push( listener ); } return this; }; proto.once = function( eventName, listener ) { if ( !eventName || !listener ) { return; } // add event this.on( eventName, listener ); // set once flag // set onceEvents hash var onceEvents = this._onceEvents = this._onceEvents || {}; // set onceListeners object var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {}; // set flag onceListeners[ listener ] = true; return this; }; proto.off = function( eventName, listener ) { var listeners = this._events && this._events[ eventName ]; if ( !listeners || !listeners.length ) { return; } var index = listeners.indexOf( listener ); if ( index != -1 ) { listeners.splice( index, 1 ); } return this; }; proto.emitEvent = function( eventName, args ) { var listeners = this._events && this._events[ eventName ]; if ( !listeners || !listeners.length ) { return; } var i = 0; var listener = listeners[i]; args = args || []; // once stuff var onceListeners = this._onceEvents && this._onceEvents[ eventName ]; while ( listener ) { var isOnce = onceListeners && onceListeners[ listener ]; if ( isOnce ) { // remove listener // remove before trigger to prevent recursion this.off( eventName, listener ); // unset once flag delete onceListeners[ listener ]; } // trigger listener listener.apply( this, args ); // get next listener i += isOnce ? 0 : 1; listener = listeners[i]; } return this; }; proto.allOff = proto.removeAllListeners = function() { delete this._events; delete this._onceEvents; }; return EvEmitter; })); /** * matchesSelector v2.0.2 * matchesSelector( element, '.selector' ) * MIT license */ /*jshint browser: true, strict: true, undef: true, unused: true */ ( function( window, factory ) { /*global define: false, module: false */ 'use strict'; // universal module definition if ( typeof define == 'function' && define.amd ) { // AMD define( 'desandro-matches-selector/matches-selector',factory ); } else if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory(); } else { // browser global window.matchesSelector = factory(); } }( window, function factory() { 'use strict'; var matchesMethod = ( function() { var ElemProto = window.Element.prototype; // check for the standard method name first if ( ElemProto.matches ) { return 'matches'; } // check un-prefixed if ( ElemProto.matchesSelector ) { return 'matchesSelector'; } // check vendor prefixes var prefixes = [ 'webkit', 'moz', 'ms', 'o' ]; for ( var i=0; i < prefixes.length; i++ ) { var prefix = prefixes[i]; var method = prefix + 'MatchesSelector'; if ( ElemProto[ method ] ) { return method; } } })(); return function matchesSelector( elem, selector ) { return elem[ matchesMethod ]( selector ); }; })); /** * Fizzy UI utils v2.0.5 * MIT license */ /*jshint browser: true, undef: true, unused: true, strict: true */ ( function( window, factory ) { // universal module definition /*jshint strict: false */ /*globals define, module, require */ if ( typeof define == 'function' && define.amd ) { // AMD define( 'fizzy-ui-utils/utils',[ 'desandro-matches-selector/matches-selector' ], function( matchesSelector ) { return factory( window, matchesSelector ); }); } else if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('desandro-matches-selector') ); } else { // browser global window.fizzyUIUtils = factory( window, window.matchesSelector ); } }( window, function factory( window, matchesSelector ) { var utils = {}; // ----- extend ----- // // extends objects utils.extend = function( a, b ) { for ( var prop in b ) { a[ prop ] = b[ prop ]; } return a; }; // ----- modulo ----- // utils.modulo = function( num, div ) { return ( ( num % div ) + div ) % div; }; // ----- makeArray ----- // // turn element or nodeList into an array utils.makeArray = function( obj ) { var ary = []; if ( Array.isArray( obj ) ) { // use object if already an array ary = obj; } else if ( obj && typeof obj == 'object' && typeof obj.length == 'number' ) { // convert nodeList to array for ( var i=0; i < obj.length; i++ ) { ary.push( obj[i] ); } } else { // array of single index ary.push( obj ); } return ary; }; // ----- removeFrom ----- // utils.removeFrom = function( ary, obj ) { var index = ary.indexOf( obj ); if ( index != -1 ) { ary.splice( index, 1 ); } }; // ----- getParent ----- // utils.getParent = function( elem, selector ) { while ( elem.parentNode && elem != document.body ) { elem = elem.parentNode; if ( matchesSelector( elem, selector ) ) { return elem; } } }; // ----- getQueryElement ----- // // use element as selector string utils.getQueryElement = function( elem ) { if ( typeof elem == 'string' ) { return document.querySelector( elem ); } return elem; }; // ----- handleEvent ----- // // enable .ontype to trigger from .addEventListener( elem, 'type' ) utils.handleEvent = function( event ) { var method = 'on' + event.type; if ( this[ method ] ) { this[ method ]( event ); } }; // ----- filterFindElements ----- // utils.filterFindElements = function( elems, selector ) { // make array of elems elems = utils.makeArray( elems ); var ffElems = []; elems.forEach( function( elem ) { // check that elem is an actual element if ( !( elem instanceof HTMLElement ) ) { return; } // add elem if no selector if ( !selector ) { ffElems.push( elem ); return; } // filter & find items if we have a selector // filter if ( matchesSelector( elem, selector ) ) { ffElems.push( elem ); } // find children var childElems = elem.querySelectorAll( selector ); // concat childElems to filterFound array for ( var i=0; i < childElems.length; i++ ) { ffElems.push( childElems[i] ); } }); return ffElems; }; // ----- debounceMethod ----- // utils.debounceMethod = function( _class, methodName, threshold ) { // original method var method = _class.prototype[ methodName ]; var timeoutName = methodName + 'Timeout'; _class.prototype[ methodName ] = function() { var timeout = this[ timeoutName ]; if ( timeout ) { clearTimeout( timeout ); } var args = arguments; var _this = this; this[ timeoutName ] = setTimeout( function() { method.apply( _this, args ); delete _this[ timeoutName ]; }, threshold || 100 ); }; }; // ----- docReady ----- // utils.docReady = function( callback ) { var readyState = document.readyState; if ( readyState == 'complete' || readyState == 'interactive' ) { // do async to allow for other scripts to run. metafizzy/flickity#441 setTimeout( callback ); } else { document.addEventListener( 'DOMContentLoaded', callback ); } }; // ----- htmlInit ----- // // http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/ utils.toDashed = function( str ) { return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) { return $1 + '-' + $2; }).toLowerCase(); }; var console = window.console; /** * allow user to initialize classes via [data-namespace] or .js-namespace class * htmlInit( Widget, 'widgetName' ) * options are parsed from data-namespace-options */ utils.htmlInit = function( WidgetClass, namespace ) { utils.docReady( function() { var dashedNamespace = utils.toDashed( namespace ); var dataAttr = 'data-' + dashedNamespace; var dataAttrElems = document.querySelectorAll( '[' + dataAttr + ']' ); var jsDashElems = document.querySelectorAll( '.js-' + dashedNamespace ); var elems = utils.makeArray( dataAttrElems ) .concat( utils.makeArray( jsDashElems ) ); var dataOptionsAttr = dataAttr + '-options'; var jQuery = window.jQuery; elems.forEach( function( elem ) { var attr = elem.getAttribute( dataAttr ) || elem.getAttribute( dataOptionsAttr ); var options; try { options = attr && JSON.parse( attr ); } catch ( error ) { // log error, do not initialize if ( console ) { console.error( 'Error parsing ' + dataAttr + ' on ' + elem.className + ': ' + error ); } return; } // initialize var instance = new WidgetClass( elem, options ); // make available via $().data('namespace') if ( jQuery ) { jQuery.data( elem, namespace, instance ); } }); }); }; // ----- ----- // return utils; })); // core ( function( window, factory ) { // universal module definition /* globals define, module, require */ if ( typeof define == 'function' && define.amd ) { // AMD define( 'infinite-scroll/js/core',[ 'ev-emitter/ev-emitter', 'fizzy-ui-utils/utils', ], function( EvEmitter, utils) { return factory( window, EvEmitter, utils ); }); } else if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('ev-emitter'), require('fizzy-ui-utils') ); } else { // browser global window.InfiniteScroll = factory( window, window.EvEmitter, window.fizzyUIUtils ); } }( window, function factory( window, EvEmitter, utils ) { var jQuery = window.jQuery; // internal store of all InfiniteScroll intances var instances = {}; function InfiniteScroll( element, options ) { var queryElem = utils.getQueryElement( element ); if ( !queryElem ) { console.error( 'Bad element for InfiniteScroll: ' + ( queryElem || element ) ); return; } element = queryElem; // do not initialize twice on same element if ( element.infiniteScrollGUID ) { var instance = instances[ element.infiniteScrollGUID ]; instance.option( options ); return instance; } this.element = element; // options this.options = utils.extend( {}, InfiniteScroll.defaults ); this.option( options ); // add jQuery if ( jQuery ) { this.$element = jQuery( this.element ); } this.create(); } // defaults InfiniteScroll.defaults = { // path: null, // hideNav: null, // debug: false, }; // create & destroy methods InfiniteScroll.create = {}; InfiniteScroll.destroy = {}; var proto = InfiniteScroll.prototype; // inherit EvEmitter utils.extend( proto, EvEmitter.prototype ); // -------------------------- -------------------------- // // globally unique identifiers var GUID = 0; proto.create = function() { // create core // add id for InfiniteScroll.data var id = this.guid = ++GUID; this.element.infiniteScrollGUID = id; // expando instances[ id ] = this; // associate via id // properties this.pageIndex = 1; // default to first page this.loadCount = 0; this.updateGetPath(); // bail if getPath not set if ( !this.getPath ) { console.error('Disabling InfiniteScroll'); return; } this.updateGetAbsolutePath(); this.log( 'initialized', [ this.element.className ] ); this.callOnInit(); // create features for ( var method in InfiniteScroll.create ) { InfiniteScroll.create[ method ].call( this ); } }; proto.option = function( opts ) { utils.extend( this.options, opts ); }; // call onInit option, used for binding events on init proto.callOnInit = function() { var onInit = this.options.onInit; if ( onInit ) { onInit.call( this, this ); } }; // ----- events ----- // proto.dispatchEvent = function( type, event, args ) { this.log( type, args ); var emitArgs = event ? [ event ].concat( args ) : args; this.emitEvent( type, emitArgs ); // trigger jQuery event if ( !jQuery || !this.$element ) { return; } // namespace jQuery event type += '.infiniteScroll'; var $event = type; if ( event ) { // create jQuery event var jQEvent = jQuery.Event( event ); jQEvent.type = type; $event = jQEvent; } this.$element.trigger( $event, args ); }; var loggers = { initialized: function( className ) { return 'on ' + className; }, request: function( path ) { return 'URL: ' + path; }, load: function( response, path ) { return ( response.title || '' ) + '. URL: ' + path; }, error: function( error, path ) { return error + '. URL: ' + path; }, append: function( response, path, items ) { return items.length + ' items. URL: ' + path; }, last: function( response, path ) { return 'URL: ' + path; }, history: function( title, path ) { return 'URL: ' + path; }, pageIndex: function( index, origin ) { return 'current page determined to be: ' + index + ' from ' + origin; }, }; // log events proto.log = function( type, args ) { if ( !this.options.debug ) { return; } var message = '[InfiniteScroll] ' + type; var logger = loggers[ type ]; if ( logger ) { message += '. ' + logger.apply( this, args ); } console.log( message ); }; // -------------------------- methods used amoung features -------------------------- // proto.updateMeasurements = function() { this.windowHeight = window.innerHeight; var rect = this.element.getBoundingClientRect(); this.top = rect.top + window.pageYOffset; }; proto.updateScroller = function() { var elementScroll = this.options.elementScroll; if ( !elementScroll ) { // default, use window this.scroller = window; return; } // if true, set to element, otherwise use option this.scroller = elementScroll === true ? this.element : utils.getQueryElement( elementScroll ); if ( !this.scroller ) { throw 'Unable to find elementScroll: ' + elementScroll; } }; // -------------------------- page path -------------------------- // proto.updateGetPath = function() { var optPath = this.options.path; if ( !optPath ) { console.error( 'InfiniteScroll path option required. Set as: ' + optPath ); return; } // function var type = typeof optPath; if ( type == 'function' ) { this.getPath = optPath; return; } // template string: '/pages/{{#}}.html' var templateMatch = type == 'string' && optPath.match('{{#}}'); if ( templateMatch ) { this.updateGetPathTemplate( optPath ); return; } // selector: '.next-page-selector' this.updateGetPathSelector( optPath ); }; proto.updateGetPathTemplate = function( optPath ) { // set getPath with template string this.getPath = function() { var nextIndex = this.pageIndex + 1; return optPath.replace( '{{#}}', nextIndex ); }.bind( this ); // get pageIndex from location // convert path option into regex to look for pattern in location var regexString = optPath.replace( '{{#}}', '(\\d\\d?\\d?)' ); var templateRe = new RegExp( regexString ); var match = location.href.match( templateRe ); if ( match ) { this.pageIndex = parseInt( match[1], 10 ); this.log( 'pageIndex', this.pageIndex, 'template string' ); } }; var pathRegexes = [ // WordPress & Tumblr - example.com/page/2 // Jekyll - example.com/page2 /^(.*?\/?page\/?)(\d\d?\d?)(.*?$)/, // Drupal - example.com/?page=1 /^(.*?\/?\?page=)(\d\d?\d?)(.*?$)/, // catch all, last occurence of a number /(.*?)(\d\d?\d?)(?!.*\d)(.*?$)/, ]; proto.updateGetPathSelector = function( optPath ) { // parse href of link: '.next-page-link' var hrefElem = document.querySelector( optPath ); if ( !hrefElem ) { console.error( 'Bad InfiniteScroll path option. Next link not found: ' + optPath ); return; } var href = hrefElem.getAttribute('href'); // try matching href to pathRegexes patterns var pathParts, regex; for ( var i=0; href && i < pathRegexes.length; i++ ) { regex = pathRegexes[i]; var match = href.match( regex ); if ( match ) { pathParts = match.slice(1); // remove first part break; } } if ( !pathParts ) { console.error( 'InfiniteScroll unable to parse next link href: ' + href ); return; } this.isPathSelector = true; // flag for checkLastPage() this.getPath = function() { var nextIndex = this.pageIndex + 1; return pathParts[0] + nextIndex + pathParts[2]; }.bind( this ); // get pageIndex from href this.pageIndex = parseInt( pathParts[1], 10 ) - 1; this.log( 'pageIndex', [ this.pageIndex, 'next link' ] ); }; proto.updateGetAbsolutePath = function() { var path = this.getPath(); // path doesn't start with http or / var isAbsolute = path.match( /^http/ ) || path.match( /^\// ); if ( isAbsolute ) { this.getAbsolutePath = this.getPath; return; } var pathname = location.pathname; // /foo/bar/index.html => /foo/bar var directory = pathname.substring( 0, pathname.lastIndexOf('/') ); this.getAbsolutePath = function() { return directory + '/' + this.getPath(); }; }; // -------------------------- nav -------------------------- // // hide navigation InfiniteScroll.create.hideNav = function() { var nav = utils.getQueryElement( this.options.hideNav ); if ( !nav ) { return; } nav.style.display = 'none'; this.nav = nav; }; InfiniteScroll.destroy.hideNav = function() { if ( this.nav ) { this.nav.style.display = ''; } }; // -------------------------- destroy -------------------------- // proto.destroy = function() { this.allOff(); // remove all event listeners // call destroy methods for ( var method in InfiniteScroll.destroy ) { InfiniteScroll.destroy[ method ].call( this ); } delete this.element.infiniteScrollGUID; delete instances[ this.guid ]; }; // -------------------------- utilities -------------------------- // // https://remysharp.com/2010/07/21/throttling-function-calls InfiniteScroll.throttle = function( fn, threshold ) { threshold = threshold || 200; var last, timeout; return function() { var now = +new Date(); var args = arguments; var trigger = function() { last = now; fn.apply( this, args ); }.bind( this ); if ( last && now < last + threshold ) { // hold on to it clearTimeout( timeout ); timeout = setTimeout( trigger, threshold ); } else { trigger(); } }; }; InfiniteScroll.data = function( elem ) { elem = utils.getQueryElement( elem ); var id = elem && elem.infiniteScrollGUID; return id && instances[ id ]; }; // set internal jQuery, for Webpack + jQuery v3 InfiniteScroll.setJQuery = function( $ ) { jQuery = $; }; // -------------------------- setup -------------------------- // utils.htmlInit( InfiniteScroll, 'infinite-scroll' ); if ( jQuery && jQuery.bridget ) { jQuery.bridget( 'infiniteScroll', InfiniteScroll ); } // -------------------------- -------------------------- // return InfiniteScroll; })); // page-load ( function( window, factory ) { // universal module definition /* globals define, module, require */ if ( typeof define == 'function' && define.amd ) { // AMD define( 'infinite-scroll/js/page-load',[ './core', ], function( InfiniteScroll ) { return factory( window, InfiniteScroll ); }); } else if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('./core') ); } else { // browser global factory( window, window.InfiniteScroll ); } }( window, function factory( window, InfiniteScroll ) { var proto = InfiniteScroll.prototype; // InfiniteScroll.defaults.append = false; InfiniteScroll.defaults.loadOnScroll = true; InfiniteScroll.defaults.checkLastPage = true; InfiniteScroll.defaults.responseType = 'document'; // InfiniteScroll.defaults.prefill = false; // InfiniteScroll.defaults.outlayer = null; InfiniteScroll.create.pageLoad = function() { this.canLoad = true; this.on( 'scrollThreshold', this.onScrollThresholdLoad ); this.on( 'append', this.checkLastPage ); if ( this.options.outlayer ) { this.on( 'append', this.onAppendOutlayer ); } }; proto.onScrollThresholdLoad = function() { if ( this.options.loadOnScroll ) { this.loadNextPage(); } }; proto.loadNextPage = function() { if ( this.isLoading || !this.canLoad ) { return; } var path = this.getAbsolutePath(); this.isLoading = true; var onLoad = function( response ) { this.onPageLoad( response, path ); }.bind( this ); var onError = function( error ) { this.onPageError( error, path ); }.bind( this ); request( path, this.options.responseType, onLoad, onError ); this.dispatchEvent( 'request', null, [ path ] ); }; proto.onPageLoad = function( response, path ) { // done loading if not appending if ( !this.options.append ) { this.isLoading = false; } this.pageIndex++; this.loadCount++; this.dispatchEvent( 'load', null, [ response, path ] ); this.appendNextPage( response, path ); return response; }; proto.appendNextPage = function( response, path ) { var optAppend = this.options.append; // do not append json var isDocument = this.options.responseType == 'document'; if ( !isDocument || !optAppend ) { return; } var items = response.querySelectorAll( optAppend ); var fragment = getItemsFragment( items ); var appendReady = function () { this.appendItems( items, fragment ); this.isLoading = false; this.dispatchEvent( 'append', null, [ response, path, items ] ); }.bind( this ); // TODO add hook for option to trigger appendReady if ( this.options.outlayer ) { this.appendOutlayerItems( fragment, appendReady ); } else { appendReady(); } }; proto.appendItems = function( items, fragment ) { if ( !items || !items.length ) { return; } // get fragment if not provided fragment = fragment || getItemsFragment( items ); refreshScripts( fragment ); this.element.appendChild( fragment ); }; function getItemsFragment( items ) { // add items to fragment var fragment = document.createDocumentFragment(); for ( var i=0; items && i < items.length; i++ ) { fragment.appendChild( items[i] ); } return fragment; } // replace