/*! * 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