File "infinite-scroll.js"

Full Path: /home/magiggjm/magistvandroids.com/wp-content/plugins/kadence-pro/dist/infinite-scroll/src/infinite-scroll.js
File size: 49.21 KB
MIME-type: text/plain
Charset: utf-8

/*!
 * Infinite Scroll PACKAGED v4.0.1
 * Automatically add next page
 *
 * Licensed GPLv3 for open source use
 * or Infinite Scroll Commercial License for commercial use
 *
 * https://infinite-scroll.com
 * Copyright 2018-2020 Metafizzy
 */

/**
 * Bridget makes jQuery widgets
 * v3.0.0
 * MIT license
 */

( function( window, factory ) {
	// module definition
   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 ) {
  
  // ----- utils ----- //
  
  // helper function for logging errors
  // $.error breaks jQuery chaining
  let console = window.console;
  let 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 ) {
		if ( !opts ) return;
  
		this.options = Object.assign( this.options || {}, opts );
	  };
	}
  
	// make jQuery plugin
	$.fn[ namespace ] = function( arg0, ...args ) {
	  if ( typeof arg0 == 'string' ) {
		// method call $().plugin( 'methodName', { options } )
		return methodCall( this, arg0, args );
	  }
	  // just $().plugin({ options })
	  plainCall( this, arg0 );
	  return this;
	};
  
	// $().plugin('methodName')
	function methodCall( $elems, methodName, args ) {
	  let returnValue;
	  let pluginMethodStr = `$().${namespace}("${methodName}")`;
  
	  $elems.each( function( i, elem ) {
		// get instance
		let instance = $.data( elem, namespace );
		if ( !instance ) {
		  logError( `${namespace} not initialized.` +
			` Cannot call method ${pluginMethodStr}` );
		  return;
		}
  
		let method = instance[ methodName ];
		if ( !method || methodName.charAt( 0 ) == '_' ) {
		  logError(`${pluginMethodStr} is not a valid method`);
		  return;
		}
  
		// apply method, get return value
		let 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 ) {
		let 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 );
		}
	  } );
	}
  
  }
  
  // -----  ----- //
  
  return jQueryBridget;
  
  } ) );
  /**
   * EvEmitter v2.0.0
   * Lil' event emitter
   * MIT License
   */
  
  ( function( global, factory ) {
	// universal module definition
	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() {}
  
  let proto = EvEmitter.prototype;
  
  proto.on = function( eventName, listener ) {
	if ( !eventName || !listener ) return this;
  
	// set events hash
	let events = this._events = this._events || {};
	// set listeners array
	let listeners = events[ eventName ] = events[ eventName ] || [];
	// only add once
	if ( !listeners.includes( listener ) ) {
	  listeners.push( listener );
	}
  
	return this;
  };
  
  proto.once = function( eventName, listener ) {
	if ( !eventName || !listener ) return this;
  
	// add event
	this.on( eventName, listener );
	// set once flag
	// set onceEvents hash
	let onceEvents = this._onceEvents = this._onceEvents || {};
	// set onceListeners object
	let onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
	// set flag
	onceListeners[ listener ] = true;
  
	return this;
  };
  
  proto.off = function( eventName, listener ) {
	let listeners = this._events && this._events[ eventName ];
	if ( !listeners || !listeners.length ) return this;
  
	let index = listeners.indexOf( listener );
	if ( index != -1 ) {
	  listeners.splice( index, 1 );
	}
  
	return this;
  };
  
  proto.emitEvent = function( eventName, args ) {
	let listeners = this._events && this._events[ eventName ];
	if ( !listeners || !listeners.length ) return this;
  
	// copy over to avoid interference if .off() in listener
	listeners = listeners.slice( 0 );
	args = args || [];
	// once stuff
	let onceListeners = this._onceEvents && this._onceEvents[ eventName ];
  
	for ( let listener of listeners ) {
	  let 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 );
	}
  
	return this;
  };
  
  proto.allOff = function() {
	delete this._events;
	delete this._onceEvents;
	return this;
  };
  
  return EvEmitter;
  
  } ) );
  /**
   * Fizzy UI utils v3.0.0
   * MIT license
   */
  
  ( function( global, factory ) {
	// universal module definition
	if ( typeof module == 'object' && module.exports ) {
	  // CommonJS
	  module.exports = factory( global );
	} else {
	  // browser global
	  global.fizzyUIUtils = factory( global );
	}
  
  }( this, function factory( global ) {
  
  let utils = {};
  
  // ----- extend ----- //
  
  // extends objects
  utils.extend = function( a, b ) {
	return Object.assign( a, b );
  };
  
  // ----- modulo ----- //
  
  utils.modulo = function( num, div ) {
	return ( ( num % div ) + div ) % div;
  };
  
  // ----- makeArray ----- //
  
  // turn element or nodeList into an array
  utils.makeArray = function( obj ) {
	// use object if already an array
	if ( Array.isArray( obj ) ) return obj;
  
	// return empty array if undefined or null. #6
	if ( obj === null || obj === undefined ) return [];
  
	let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
	// convert nodeList to array
	if ( isArrayLike ) return [ ...obj ];
  
	// array of single index
	return [ obj ];
  };
  
  // ----- removeFrom ----- //
  
  utils.removeFrom = function( ary, obj ) {
	let 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 ( elem.matches( 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 ) {
	let method = 'on' + event.type;
	if ( this[ method ] ) {
	  this[ method ]( event );
	}
  };
  
  // ----- filterFindElements ----- //
  
  utils.filterFindElements = function( elems, selector ) {
	// make array of elems
	elems = utils.makeArray( elems );
  
	return elems
	  // check that elem is an actual element
	  .filter( ( elem ) => elem instanceof HTMLElement )
	  .reduce( ( ffElems, elem ) => {
		// add elem if no selector
		if ( !selector ) {
		  ffElems.push( elem );
		  return ffElems;
		}
		// filter & find items if we have a selector
		// filter
		if ( elem.matches( selector ) ) {
		  ffElems.push( elem );
		}
		// find children
		let childElems = elem.querySelectorAll( selector );
		// concat childElems to filterFound array
		ffElems = ffElems.concat( ...childElems );
		return ffElems;
	  }, [] );
  };
  
  // ----- debounceMethod ----- //
  
  utils.debounceMethod = function( _class, methodName, threshold ) {
	threshold = threshold || 100;
	// original method
	let method = _class.prototype[ methodName ];
	let timeoutName = methodName + 'Timeout';
  
	_class.prototype[ methodName ] = function() {
	  clearTimeout( this[ timeoutName ] );
  
	  let args = arguments;
	  this[ timeoutName ] = setTimeout( () => {
		method.apply( this, args );
		delete this[ timeoutName ];
	  }, threshold );
	};
  };
  
  // ----- docReady ----- //
  
  utils.docReady = function( onDocReady ) {
	let readyState = document.readyState;
	if ( readyState == 'complete' || readyState == 'interactive' ) {
	  // do async to allow for other scripts to run. metafizzy/flickity#441
	  setTimeout( onDocReady );
	} else {
	  document.addEventListener( 'DOMContentLoaded', onDocReady );
	}
  };
  
  // ----- htmlInit ----- //
  
  // http://bit.ly/3oYLusc
  utils.toDashed = function( str ) {
	return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
	  return $1 + '-' + $2;
	} ).toLowerCase();
  };
  
  let console = global.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() {
	  let dashedNamespace = utils.toDashed( namespace );
	  let dataAttr = 'data-' + dashedNamespace;
	  let dataAttrElems = document.querySelectorAll( `[${dataAttr}]` );
	  let jQuery = global.jQuery;
  
	  [ ...dataAttrElems ].forEach( ( elem ) => {
		let attr = elem.getAttribute( dataAttr );
		let 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
		let 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
	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 ) {
  
  let jQuery = window.jQuery;
  // internal store of all InfiniteScroll intances
  let instances = {};
  
  function InfiniteScroll( element, options ) {
	let 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 ) {
	  let instance = instances[ element.infiniteScrollGUID ];
	  instance.option( options );
	  return instance;
	}
  
	this.element = element;
	// options
	this.options = { ...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 = {};
  
  let proto = InfiniteScroll.prototype;
  // inherit EvEmitter
  Object.assign( proto, EvEmitter.prototype );
  
  // --------------------------  -------------------------- //
  
  // globally unique identifiers
  let GUID = 0;
  
  proto.create = function() {
	// create core
	// add id for InfiniteScroll.data
	let 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, or returns falsey #776
	let hasPath = this.getPath && this.getPath();
	if ( !hasPath ) {
	  console.error('Disabling InfiniteScroll');
	  return;
	}
	this.updateGetAbsolutePath();
	this.log( 'initialized', [ this.element.className ] );
	this.callOnInit();
	// create features
	for ( let method in InfiniteScroll.create ) {
	  InfiniteScroll.create[ method ].call( this );
	}
  };
  
  proto.option = function( opts ) {
	Object.assign( this.options, opts );
  };
  
  // call onInit option, used for binding events on init
  proto.callOnInit = function() {
	let onInit = this.options.onInit;
	if ( onInit ) {
	  onInit.call( this, this );
	}
  };
  
  // ----- events ----- //
  
  proto.dispatchEvent = function( type, event, args ) {
	this.log( type, args );
	let emitArgs = event ? [ event ].concat( args ) : args;
	this.emitEvent( type, emitArgs );
	// trigger jQuery event
	if ( !jQuery || !this.$element ) {
	  return;
	}
	// namespace jQuery event
	type += '.infiniteScroll';
	let $event = type;
	if ( event ) {
	  // create jQuery event
	  /* eslint-disable-next-line new-cap */
	  let jQEvent = jQuery.Event( event );
	  jQEvent.type = type;
	  $event = jQEvent;
	}
	this.$element.trigger( $event, args );
  };
  
  let loggers = {
	initialized: ( className ) => `on ${className}`,
	request: ( path ) => `URL: ${path}`,
	load: ( response, path ) => `${response.title || ''}. URL: ${path}`,
	error: ( error, path ) => `${error}. URL: ${path}`,
	append: ( response, path, items ) => `${items.length} items. URL: ${path}`,
	last: ( response, path ) => `URL: ${path}`,
	history: ( title, path ) => `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;
  
	let message = `[InfiniteScroll] ${type}`;
	let logger = loggers[ type ];
	if ( logger ) message += '. ' + logger.apply( this, args );
	console.log( message );
  };
  
  // -------------------------- methods used amoung features -------------------------- //
  
  proto.updateMeasurements = function() {
	this.windowHeight = window.innerHeight;
	let rect = this.element.getBoundingClientRect();
	this.top = rect.top + window.scrollY;
  };
  
  proto.updateScroller = function() {
	let 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 new Error(`Unable to find elementScroll: ${elementScroll}`);
	}
  };
  
  // -------------------------- page path -------------------------- //
  
  proto.updateGetPath = function() {
	let optPath = this.options.path;
	if ( !optPath ) {
	  console.error(`InfiniteScroll path option required. Set as: ${optPath}`);
	  return;
	}
	// function
	let type = typeof optPath;
	if ( type == 'function' ) {
	  this.getPath = optPath;
	  return;
	}
	// template string: '/pages/{{#}}.html'
	let 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 = () => {
	  let nextIndex = this.pageIndex + 1;
	  return optPath.replace( '{{#}}', nextIndex );
	};
	// get pageIndex from location
	// convert path option into regex to look for pattern in location
	// escape query (?) in url, allows for parsing GET parameters
	let regexString = optPath
	  .replace( /(\\\?|\?)/, '\\?' )
	  .replace( '{{#}}', '(\\d\\d?\\d?)' );
	let templateRe = new RegExp( regexString );
	let match = location.href.match( templateRe );
  
	if ( match ) {
	  this.pageIndex = parseInt( match[1], 10 );
	  this.log( 'pageIndex', [ this.pageIndex, 'template string' ] );
	}
  };
  
  let 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)(.*?$)/,
  ];
  
  // try matching href to pathRegexes patterns
  let getPathParts = InfiniteScroll.getPathParts = function( href ) {
	if ( !href ) return;
	for ( let regex of pathRegexes ) {
	  let match = href.match( regex );
	  if ( match ) {
		let [ , begin, index, end ] = match;
		return { begin, index, end };
	  }
	}
  };
  
  proto.updateGetPathSelector = function( optPath ) {
	// parse href of link: '.next-page-link'
	let hrefElem = document.querySelector( optPath );
	if ( !hrefElem ) {
	  console.error(`Bad InfiniteScroll path option. Next link not found: ${optPath}`);
	  return;
	}
  
	let href = hrefElem.getAttribute('href');
	let pathParts = getPathParts( href );
	if ( !pathParts ) {
	  console.error(`InfiniteScroll unable to parse next link href: ${href}`);
	  return;
	}
  
	let { begin, index, end } = pathParts;
	this.isPathSelector = true; // flag for checkLastPage()
	this.getPath = () => begin + ( this.pageIndex + 1 ) + end;
	// get pageIndex from href
	this.pageIndex = parseInt( index, 10 ) - 1;
	this.log( 'pageIndex', [ this.pageIndex, 'next link' ] );
  };
  
  proto.updateGetAbsolutePath = function() {
	let path = this.getPath();
	// path doesn't start with http or /
	let isAbsolute = path.match( /^http/ ) || path.match( /^\// );
	if ( isAbsolute ) {
	  this.getAbsolutePath = this.getPath;
	  return;
	}
  
	let { pathname } = location;
	// query parameter #829. example.com/?pg=2
	let isQuery = path.match( /^\?/ );
	// /foo/bar/index.html => /foo/bar
	let directory = pathname.substring( 0, pathname.lastIndexOf('/') );
	let pathStart = isQuery ? pathname : directory + '/';
  
	this.getAbsolutePath = () => pathStart + this.getPath();
  };
  
  // -------------------------- nav -------------------------- //
  
  // hide navigation
  InfiniteScroll.create.hideNav = function() {
	let 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 ( let method in InfiniteScroll.destroy ) {
	  InfiniteScroll.destroy[ method ].call( this );
	}
  
	delete this.element.infiniteScrollGUID;
	delete instances[ this.guid ];
	// remove jQuery data. #807
	if ( jQuery && this.$element ) {
	  jQuery.removeData( this.element, 'infiniteScroll' );
	}
  };
  
  // -------------------------- utilities -------------------------- //
  
  // https://remysharp.com/2010/07/21/throttling-function-calls
  InfiniteScroll.throttle = function( fn, threshold ) {
	threshold = threshold || 200;
	let last, timeout;
  
	return function() {
	  let now = +new Date();
	  let args = arguments;
	  let trigger = () => {
		last = now;
		fn.apply( this, args );
	  };
	  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 );
	let id = elem && elem.infiniteScrollGUID;
	return id && instances[ id ];
  };
  
  // set internal jQuery, for Webpack + jQuery v3
  InfiniteScroll.setJQuery = function( jqry ) {
	jQuery = jqry;
  };
  
  // -------------------------- setup -------------------------- //
  
  utils.htmlInit( InfiniteScroll, 'infinite-scroll' );
  
  // add noop _init method for jQuery Bridget. #768
  proto._init = function() {};
  
  let { jQueryBridget } = window;
  if ( jQuery && jQueryBridget ) {
	jQueryBridget( 'infiniteScroll', InfiniteScroll, jQuery );
  }
  
  // --------------------------  -------------------------- //
  
  return InfiniteScroll;
  
  } ) );
  // page-load
  ( function( window, factory ) {
	// universal module definition
	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 ) {
  
  let proto = InfiniteScroll.prototype;
  
  Object.assign( InfiniteScroll.defaults, {
	// append: false,
	loadOnScroll: true,
	checkLastPage: true,
	responseBody: 'text',
	domParseResponse: true,
	// prefill: false,
	// outlayer: null,
  } );
  
  InfiniteScroll.create.pageLoad = function() {
	this.canLoad = true;
	this.on( 'scrollThreshold', this.onScrollThresholdLoad );
	this.on( 'load', this.checkLastPage );
	if ( this.options.outlayer ) {
	  this.on( 'append', this.onAppendOutlayer );
	}
	this.on( 'append', this.reloadSrcsetImgs );
  };
  
  proto.onScrollThresholdLoad = function() {
	if ( this.options.loadOnScroll ) this.loadNextPage();
  };
  
  let domParser = new DOMParser();
  
  proto.loadNextPage = function() {
	if ( this.isLoading || !this.canLoad ) return;
  
	let { responseBody, domParseResponse, fetchOptions } = this.options;
	let path = this.getAbsolutePath();
	this.isLoading = true;
	if ( typeof fetchOptions == 'function' ) fetchOptions = fetchOptions();
  
	let fetchPromise = fetch( path, fetchOptions )
	  .then( ( response ) => {
		if ( !response.ok ) {
		  let error = new Error( response.statusText );
		  this.onPageError( error, path, response );
		  return { response };
		}
  
		return response[ responseBody ]().then( ( body ) => {
		  let canDomParse = responseBody == 'text' && domParseResponse;
		  if ( canDomParse ) {
			body = domParser.parseFromString( body, 'text/html' );
		  }
		  if ( response.status == 204 ) {
			this.lastPageReached( body, path );
			return { body, response };
		  } else {
			return this.onPageLoad( body, path, response );
		  }
		} );
	  } )
	  .catch( ( error ) => {
		this.onPageError( error, path );
	  } );
  
	this.dispatchEvent( 'request', null, [ path, fetchPromise ] );
  
	return fetchPromise;
  };
  
  proto.onPageLoad = function( body, path, response ) {
	// done loading if not appending
	if ( !this.options.append ) {
	  this.isLoading = false;
	}
	this.pageIndex++;
	this.loadCount++;
	this.dispatchEvent( 'load', null, [ body, path, response ] );
	return this.appendNextPage( body, path, response );
  };
  
  proto.appendNextPage = function( body, path, response ) {
	let { append, responseBody, domParseResponse } = this.options;
	// do not append json
	let isDocument = responseBody == 'text' && domParseResponse;
	if ( !isDocument || !append ) return { body, response };
  
	let items = body.querySelectorAll( append );
	let promiseValue = { body, response, items };
	// last page hit if no items. #840
	if ( !items || !items.length ) {
	  this.lastPageReached( body, path );
	  return promiseValue;
	}
  
	let fragment = getItemsFragment( items );
	let appendReady = () => {
	  this.appendItems( items, fragment );
	  this.isLoading = false;
	  this.dispatchEvent( 'append', null, [ body, path, items, response ] );
	  return promiseValue;
	};
  
	// TODO add hook for option to trigger appendReady
	if ( this.options.outlayer ) {
	  return this.appendOutlayerItems( fragment, appendReady );
	} else {
	  return 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
	let fragment = document.createDocumentFragment();
	if ( items ) fragment.append( ...items );
	return fragment;
  }
  
  // replace <script>s with copies so they load
  // <script>s added by InfiniteScroll will not load
  // similar to https://stackoverflow.com/questions/610995
  function refreshScripts( fragment ) {
	let scripts = fragment.querySelectorAll('script');
	for ( let script of scripts ) {
	  let freshScript = document.createElement('script');
	  // copy attributes
	  let attrs = script.attributes;
	  for ( let attr of attrs ) {
		freshScript.setAttribute( attr.name, attr.value );
	  }
	  // copy inner script code. #718, #782
	  freshScript.innerHTML = script.innerHTML;
	  script.parentNode.replaceChild( freshScript, script );
	}
  }
  
  // ----- outlayer ----- //
  
  proto.appendOutlayerItems = function( fragment, appendReady ) {
	let imagesLoaded = InfiniteScroll.imagesLoaded || window.imagesLoaded;
	if ( !imagesLoaded ) {
	  console.error('[InfiniteScroll] imagesLoaded required for outlayer option');
	  this.isLoading = false;
	  return;
	}
	// append once images loaded
	return new Promise( function( resolve ) {
	  imagesLoaded( fragment, function() {
		let bodyResponse = appendReady();
		resolve( bodyResponse );
	  } );
	} );
  };
  proto.reloadSrcsetImgsItem = function( item ) {
	var imgs = item.querySelectorAll('img[srcset]');
	for ( var i=0; i < imgs.length; i++ ) {
	  var img = imgs[i];
	  img.outerHTML = img.outerHTML;
	}
  };
  proto.reloadSrcsetImgs = function( response, path, items ) {
	for ( var i=0; i < items.length; i++ ) {
		this.reloadSrcsetImgsItem( items[i] );
	}
  };
  proto.onAppendOutlayer = function( response, path, items ) {
	this.options.outlayer.appended( items );
  };
  
  // ----- checkLastPage ----- //
  
  // check response for next element
  proto.checkLastPage = function( body, path ) {
	let { checkLastPage, path: pathOpt } = this.options;
	if ( !checkLastPage ) return;
  
	// if path is function, check if next path is truthy
	if ( typeof pathOpt == 'function' ) {
	  let nextPath = this.getPath();
	  if ( !nextPath ) {
		this.lastPageReached( body, path );
		return;
	  }
	}
	// get selector from checkLastPage or path option
	let selector;
	if ( typeof checkLastPage == 'string' ) {
	  selector = checkLastPage;
	} else if ( this.isPathSelector ) {
	  // path option is selector string
	  selector = pathOpt;
	}
	// check last page for selector
	// bail if no selector or not document response
	if ( !selector || !body.querySelector ) return;
  
	// check if response has selector
	let nextElem = body.querySelector( selector );
	if ( !nextElem ) this.lastPageReached( body, path );
  };
  
  proto.lastPageReached = function( body, path ) {
	this.canLoad = false;
	this.dispatchEvent( 'last', null, [ body, path ] );
  };
  
  // ----- error ----- //
  
  proto.onPageError = function( error, path, response ) {
	this.isLoading = false;
	this.canLoad = false;
	this.dispatchEvent( 'error', null, [ error, path, response ] );
	return error;
  };
  
  // -------------------------- prefill -------------------------- //
  
  InfiniteScroll.create.prefill = function() {
	if ( !this.options.prefill ) return;
  
	let append = this.options.append;
	if ( !append ) {
	  console.error(`append option required for prefill. Set as :${append}`);
	  return;
	}
	this.updateMeasurements();
	this.updateScroller();
	this.isPrefilling = true;
	this.on( 'append', this.prefill );
	this.once( 'error', this.stopPrefill );
	this.once( 'last', this.stopPrefill );
	this.prefill();
  };
  
  proto.prefill = function() {
	let distance = this.getPrefillDistance();
	this.isPrefilling = distance >= 0;
	if ( this.isPrefilling ) {
	  this.log('prefill');
	  this.loadNextPage();
	} else {
	  this.stopPrefill();
	}
  };
  
  proto.getPrefillDistance = function() {
	// element scroll
	if ( this.options.elementScroll ) {
	  return this.scroller.clientHeight - this.scroller.scrollHeight;
	}
	// window
	return this.windowHeight - this.element.clientHeight;
  };
  
  proto.stopPrefill = function() {
	this.log('stopPrefill');
	this.off( 'append', this.prefill );
  };
  
  // --------------------------  -------------------------- //
  
  return InfiniteScroll;
  
  } ) );
  // scroll-watch
  ( function( window, factory ) {
	// universal module definition
	if ( typeof module == 'object' && module.exports ) {
	  // CommonJS
	  module.exports = factory(
		  window,
		  require('./core'),
		  require('fizzy-ui-utils'),
	  );
	} else {
	  // browser global
	  factory(
		  window,
		  window.InfiniteScroll,
		  window.fizzyUIUtils,
	  );
	}
  
  }( window, function factory( window, InfiniteScroll, utils ) {
  
  let proto = InfiniteScroll.prototype;
  
  // default options
  Object.assign( InfiniteScroll.defaults, {
	scrollThreshold: 400,
	// elementScroll: null,
  } );
  
  InfiniteScroll.create.scrollWatch = function() {
	// events
	this.pageScrollHandler = this.onPageScroll.bind( this );
	this.resizeHandler = this.onResize.bind( this );
  
	let scrollThreshold = this.options.scrollThreshold;
	let isEnable = scrollThreshold || scrollThreshold === 0;
	if ( isEnable ) this.enableScrollWatch();
  };
  
  InfiniteScroll.destroy.scrollWatch = function() {
	this.disableScrollWatch();
  };
  
  proto.enableScrollWatch = function() {
	if ( this.isScrollWatching ) return;
  
	this.isScrollWatching = true;
	this.updateMeasurements();
	this.updateScroller();
	// TODO disable after error?
	this.on( 'last', this.disableScrollWatch );
	this.bindScrollWatchEvents( true );
  };
  
  proto.disableScrollWatch = function() {
	if ( !this.isScrollWatching ) return;
  
	this.bindScrollWatchEvents( false );
	delete this.isScrollWatching;
  };
  
  proto.bindScrollWatchEvents = function( isBind ) {
	let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
	this.scroller[ addRemove ]( 'scroll', this.pageScrollHandler );
	window[ addRemove ]( 'resize', this.resizeHandler );
  };
  
  proto.onPageScroll = InfiniteScroll.throttle( function() {
	let distance = this.getBottomDistance();
	if ( distance <= this.options.scrollThreshold ) {
	  this.dispatchEvent('scrollThreshold');
	}
  } );
  
  proto.getBottomDistance = function() {
	let bottom, scrollY;
	if ( this.options.elementScroll ) {
	  bottom = this.scroller.scrollHeight;
	  scrollY = this.scroller.scrollTop + this.scroller.clientHeight;
	} else {
	  bottom = this.top + this.element.clientHeight;
	  scrollY = window.scrollY + this.windowHeight;
	}
	return bottom - scrollY;
  };
  
  proto.onResize = function() {
	this.updateMeasurements();
  };
  
  utils.debounceMethod( InfiniteScroll, 'onResize', 150 );
  
  // --------------------------  -------------------------- //
  
  return InfiniteScroll;
  
  } ) );
  // history
  ( function( window, factory ) {
	// universal module definition
	if ( typeof module == 'object' && module.exports ) {
	  // CommonJS
	  module.exports = factory(
		  window,
		  require('./core'),
		  require('fizzy-ui-utils'),
	  );
	} else {
	  // browser global
	  factory(
		  window,
		  window.InfiniteScroll,
		  window.fizzyUIUtils,
	  );
	}
  
  }( window, function factory( window, InfiniteScroll, utils ) {
  
  let proto = InfiniteScroll.prototype;
  
  Object.assign( InfiniteScroll.defaults, {
	history: 'replace',
	// historyTitle: false,
  } );
  
  let link = document.createElement('a');
  
  // ----- create/destroy ----- //
  
  InfiniteScroll.create.history = function() {
	if ( !this.options.history ) return;
  
	// check for same origin
	link.href = this.getAbsolutePath();
	// MS Edge does not have origin on link
	// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12236493/
	let linkOrigin = link.origin || link.protocol + '//' + link.host;
	let isSameOrigin = linkOrigin == location.origin;
	if ( !isSameOrigin ) {
	  console.error( '[InfiniteScroll] cannot set history with different origin: ' +
		`${link.origin} on ${location.origin} . History behavior disabled.` );
	  return;
	}
  
	// two ways to handle changing history
	if ( this.options.append ) {
	  this.createHistoryAppend();
	} else {
	  this.createHistoryPageLoad();
	}
  };
  
  proto.createHistoryAppend = function() {
	this.updateMeasurements();
	this.updateScroller();
	// array of scroll positions of appended pages
	this.scrollPages = [
	  // first page
	  {
		top: 0,
		path: location.href,
		title: document.title,
	  },
	];
	this.scrollPage = this.scrollPages[0];
	// events
	this.scrollHistoryHandler = this.onScrollHistory.bind( this );
	this.unloadHandler = this.onUnload.bind( this );
	this.scroller.addEventListener( 'scroll', this.scrollHistoryHandler );
	this.on( 'append', this.onAppendHistory );
	this.bindHistoryAppendEvents( true );
  };
  
  proto.bindHistoryAppendEvents = function( isBind ) {
	let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
	this.scroller[ addRemove ]( 'scroll', this.scrollHistoryHandler );
	window[ addRemove ]( 'unload', this.unloadHandler );
  };
  
  proto.createHistoryPageLoad = function() {
	this.on( 'load', this.onPageLoadHistory );
  };
  
  InfiniteScroll.destroy.history =
  proto.destroyHistory = function() {
	let isHistoryAppend = this.options.history && this.options.append;
	if ( isHistoryAppend ) {
	  this.bindHistoryAppendEvents( false );
	}
  };
  
  // ----- append history ----- //
  
  proto.onAppendHistory = function( response, path, items ) {
	// do not proceed if no items. #779
	if ( !items || !items.length ) return;
  
	let firstItem = items[0];
	let elemScrollY = this.getElementScrollY( firstItem );
	// resolve path
	link.href = path;
	// add page data to hash
	this.scrollPages.push({
	  top: elemScrollY,
	  path: link.href,
	  title: response.title,
	});
  };
  
  proto.getElementScrollY = function( elem ) {
	if ( this.options.elementScroll ) {
	  return elem.offsetTop - this.top;
	} else {
	  let rect = elem.getBoundingClientRect();
	  return rect.top + window.scrollY;
	}
  };
  
  proto.onScrollHistory = function() {
	// cycle through positions, find biggest without going over
	let scrollPage = this.getClosestScrollPage();
	// set history if changed
	if ( scrollPage != this.scrollPage ) {
	  this.scrollPage = scrollPage;
	  this.setHistory( scrollPage.title, scrollPage.path );
	}
  };
  
  utils.debounceMethod( InfiniteScroll, 'onScrollHistory', 150 );
  
  proto.getClosestScrollPage = function() {
	let scrollViewY;
	if ( this.options.elementScroll ) {
	  scrollViewY = this.scroller.scrollTop + this.scroller.clientHeight / 2;
	} else {
	  scrollViewY = window.scrollY + this.windowHeight / 2;
	}
  
	let scrollPage;
	for ( let page of this.scrollPages ) {
	  if ( page.top >= scrollViewY ) break;
  
	  scrollPage = page;
	}
	return scrollPage;
  };
  
  proto.setHistory = function( title, path ) {
	let optHistory = this.options.history;
	let historyMethod = optHistory && history[ optHistory + 'State' ];
	if ( !historyMethod ) return;
  
	history[ optHistory + 'State' ]( null, title, path );
	if ( this.options.historyTitle ) document.title = title;
	this.dispatchEvent( 'history', null, [ title, path ] );
  };
  
  // scroll to top to prevent initial scroll-reset after page refresh
  // https://stackoverflow.com/a/18633915/182183
  proto.onUnload = function() {
	if ( this.scrollPage.top === 0 ) return;
  
	// calculate where scroll position would be on refresh
	let scrollY = window.scrollY - this.scrollPage.top + this.top;
	// disable scroll event before setting scroll #679
	this.destroyHistory();
	scrollTo( 0, scrollY );
  };
  
  // ----- load history ----- //
  
  // update URL
  proto.onPageLoadHistory = function( response, path ) {
	this.setHistory( response.title, path );
  };
  
  // --------------------------  -------------------------- //
  
  return InfiniteScroll;
  
  } ) );
  // button
  ( function( window, factory ) {
	// universal module definition
	if ( typeof module == 'object' && module.exports ) {
	  // CommonJS
	  module.exports = factory(
		  window,
		  require('./core'),
		  require('fizzy-ui-utils'),
	  );
	} else {
	  // browser global
	  factory(
		  window,
		  window.InfiniteScroll,
		  window.fizzyUIUtils,
	  );
	}
  
  }( window, function factory( window, InfiniteScroll, utils ) {
  
  // -------------------------- InfiniteScrollButton -------------------------- //
  
  class InfiniteScrollButton {
	constructor( element, infScroll ) {
	  this.element = element;
	  this.infScroll = infScroll;
	  // events
	  this.clickHandler = this.onClick.bind( this );
	  this.element.addEventListener( 'click', this.clickHandler );
	  infScroll.on( 'request', this.disable.bind( this ) );
	  infScroll.on( 'load', this.enable.bind( this ) );
	  infScroll.on( 'error', this.hide.bind( this ) );
	  infScroll.on( 'last', this.hide.bind( this ) );
	}
  
	onClick( event ) {
	  event.preventDefault();
	  this.infScroll.loadNextPage();
	}
  
	enable() {
	  this.element.removeAttribute('disabled');
	}
  
	disable() {
	  this.element.disabled = 'disabled';
	}
  
	hide() {
	  this.element.style.display = 'none';
	}
  
	destroy() {
	  this.element.removeEventListener( 'click', this.clickHandler );
	}
  
  }
  
  // -------------------------- InfiniteScroll methods -------------------------- //
  
  // InfiniteScroll.defaults.button = null;
  
  InfiniteScroll.create.button = function() {
	let buttonElem = utils.getQueryElement( this.options.button );
	if ( buttonElem ) {
	  this.button = new InfiniteScrollButton( buttonElem, this );
	}
  };
  
  InfiniteScroll.destroy.button = function() {
	if ( this.button ) this.button.destroy();
  };
  
  // --------------------------  -------------------------- //
  
  InfiniteScroll.Button = InfiniteScrollButton;
  
  return InfiniteScroll;
  
  } ) );
  // status
  ( function( window, factory ) {
	// universal module definition
	if ( typeof module == 'object' && module.exports ) {
	  // CommonJS
	  module.exports = factory(
		  window,
		  require('./core'),
		  require('fizzy-ui-utils'),
	  );
	} else {
	  // browser global
	  factory(
		  window,
		  window.InfiniteScroll,
		  window.fizzyUIUtils,
	  );
	}
  
  }( window, function factory( window, InfiniteScroll, utils ) {
  
  let proto = InfiniteScroll.prototype;
  
  // InfiniteScroll.defaults.status = null;
  
  InfiniteScroll.create.status = function() {
	let statusElem = utils.getQueryElement( this.options.status );
	if ( !statusElem ) return;
  
	// elements
	this.statusElement = statusElem;
	this.statusEventElements = {
	  request: statusElem.querySelector('.infinite-scroll-request'),
	  error: statusElem.querySelector('.infinite-scroll-error'),
	  last: statusElem.querySelector('.infinite-scroll-last'),
	};
	// events
	this.on( 'request', this.showRequestStatus );
	this.on( 'error', this.showErrorStatus );
	this.on( 'last', this.showLastStatus );
	this.bindHideStatus('on');
  };
  
  proto.bindHideStatus = function( bindMethod ) {
	let hideEvent = this.options.append ? 'append' : 'load';
	this[ bindMethod ]( hideEvent, this.hideAllStatus );
  };
  
  proto.showRequestStatus = function() {
	this.showStatus('request');
  };
  
  proto.showErrorStatus = function() {
	this.showStatus('error');
  };
  
  proto.showLastStatus = function() {
	this.showStatus('last');
	// prevent last then append event race condition from showing last status #706
	this.bindHideStatus('off');
  };
  
  proto.showStatus = function( eventName ) {
	show( this.statusElement );
	this.hideStatusEventElements();
	let eventElem = this.statusEventElements[ eventName ];
	show( eventElem );
  };
  
  proto.hideAllStatus = function() {
	hide( this.statusElement );
	this.hideStatusEventElements();
  };
  
  proto.hideStatusEventElements = function() {
	for ( let type in this.statusEventElements ) {
	  let eventElem = this.statusEventElements[ type ];
	  hide( eventElem );
	}
  };
  
  // --------------------------  -------------------------- //
  
  function hide( elem ) {
	setDisplay( elem, 'none' );
  }
  
  function show( elem ) {
	setDisplay( elem, 'block' );
  }
  
  function setDisplay( elem, value ) {
	if ( elem ) {
	  elem.style.display = value;
	}
  }
  
  // --------------------------  -------------------------- //
  
  return InfiniteScroll;
  
  } ) );
  /*!
   * imagesLoaded v4.1.4
   * JavaScript is all like "You images are done yet or what?"
   * MIT License
   */
  
  ( function( window, factory ) { 'use strict';
	// universal module definition
  
	/*global define: false, module: false, require: false */
  
	if ( typeof define == 'function' && define.amd ) {
	  // AMD
	  define( [
		'ev-emitter/ev-emitter'
	  ], function( EvEmitter ) {
		return factory( window, EvEmitter );
	  });
	} else if ( typeof module == 'object' && module.exports ) {
	  // CommonJS
	  module.exports = factory(
		window,
		require('ev-emitter')
	  );
	} else {
	  // browser global
	  window.imagesLoaded = factory(
		window,
		window.EvEmitter
	  );
	}
  
  })( typeof window !== 'undefined' ? window : this,
  
  // --------------------------  factory -------------------------- //
  
  function factory( window, EvEmitter ) {
  
  'use strict';
  
  var $ = window.jQuery;
  var console = window.console;
  
  // -------------------------- helpers -------------------------- //
  
  // extend objects
  function extend( a, b ) {
	for ( var prop in b ) {
	  a[ prop ] = b[ prop ];
	}
	return a;
  }
  
  var arraySlice = Array.prototype.slice;
  
  // turn element or nodeList into an array
  function makeArray( obj ) {
	if ( Array.isArray( obj ) ) {
	  // use object if already an array
	  return obj;
	}
  
	var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
	if ( isArrayLike ) {
	  // convert nodeList to array
	  return arraySlice.call( obj );
	}
  
	// array of single index
	return [ obj ];
  }
  
  // -------------------------- imagesLoaded -------------------------- //
  
  /**
   * @param {Array, Element, NodeList, String} elem
   * @param {Object or Function} options - if function, use as callback
   * @param {Function} onAlways - callback function
   */
  function ImagesLoaded( elem, options, onAlways ) {
	// coerce ImagesLoaded() without new, to be new ImagesLoaded()
	if ( !( this instanceof ImagesLoaded ) ) {
	  return new ImagesLoaded( elem, options, onAlways );
	}
	// use elem as selector string
	var queryElem = elem;
	if ( typeof elem == 'string' ) {
	  queryElem = document.querySelectorAll( elem );
	}
	// bail if bad element
	if ( !queryElem ) {
	  console.error( 'Bad element for imagesLoaded ' + ( queryElem || elem ) );
	  return;
	}
  
	this.elements = makeArray( queryElem );
	this.options = extend( {}, this.options );
	// shift arguments if no options set
	if ( typeof options == 'function' ) {
	  onAlways = options;
	} else {
	  extend( this.options, options );
	}
  
	if ( onAlways ) {
	  this.on( 'always', onAlways );
	}
  
	this.getImages();
  
	if ( $ ) {
	  // add jQuery Deferred object
	  this.jqDeferred = new $.Deferred();
	}
  
	// HACK check async to allow time to bind listeners
	setTimeout( this.check.bind( this ) );
  }
  
  ImagesLoaded.prototype = Object.create( EvEmitter.prototype );
  
  ImagesLoaded.prototype.options = {};
  
  ImagesLoaded.prototype.getImages = function() {
	this.images = [];
  
	// filter & find items if we have an item selector
	this.elements.forEach( this.addElementImages, this );
  };
  
  /**
   * @param {Node} element
   */
  ImagesLoaded.prototype.addElementImages = function( elem ) {
	// filter siblings
	if ( elem.nodeName == 'IMG' ) {
	  this.addImage( elem );
	}
	// get background image on element
	if ( this.options.background === true ) {
	  this.addElementBackgroundImages( elem );
	}
  
	// find children
	// no non-element nodes, #143
	var nodeType = elem.nodeType;
	if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
	  return;
	}
	var childImgs = elem.querySelectorAll('img');
	// concat childElems to filterFound array
	for ( var i=0; i < childImgs.length; i++ ) {
	  var img = childImgs[i];
	  this.addImage( img );
	}
  
	// get child background images
	if ( typeof this.options.background == 'string' ) {
	  var children = elem.querySelectorAll( this.options.background );
	  for ( i=0; i < children.length; i++ ) {
		var child = children[i];
		this.addElementBackgroundImages( child );
	  }
	}
  };
  
  var elementNodeTypes = {
	1: true,
	9: true,
	11: true
  };
  
  ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
	var style = getComputedStyle( elem );
	if ( !style ) {
	  // Firefox returns null if in a hidden iframe https://bugzil.la/548397
	  return;
	}
	// get url inside url("...")
	var reURL = /url\((['"])?(.*?)\1\)/gi;
	var matches = reURL.exec( style.backgroundImage );
	while ( matches !== null ) {
	  var url = matches && matches[2];
	  if ( url ) {
		this.addBackground( url, elem );
	  }
	  matches = reURL.exec( style.backgroundImage );
	}
  };
  
  /**
   * @param {Image} img
   */
  ImagesLoaded.prototype.addImage = function( img ) {
	var loadingImage = new LoadingImage( img );
	this.images.push( loadingImage );
  };
  
  ImagesLoaded.prototype.addBackground = function( url, elem ) {
	var background = new Background( url, elem );
	this.images.push( background );
  };
  
  ImagesLoaded.prototype.check = function() {
	var _this = this;
	this.progressedCount = 0;
	this.hasAnyBroken = false;
	// complete if no images
	if ( !this.images.length ) {
	  this.complete();
	  return;
	}
  
	function onProgress( image, elem, message ) {
	  // HACK - Chrome triggers event before object properties have changed. #83
	  setTimeout( function() {
		_this.progress( image, elem, message );
	  });
	}
  
	this.images.forEach( function( loadingImage ) {
	  loadingImage.once( 'progress', onProgress );
	  loadingImage.check();
	});
  };
  
  ImagesLoaded.prototype.progress = function( image, elem, message ) {
	this.progressedCount++;
	this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
	// progress event
	this.emitEvent( 'progress', [ this, image, elem ] );
	if ( this.jqDeferred && this.jqDeferred.notify ) {
	  this.jqDeferred.notify( this, image );
	}
	// check if completed
	if ( this.progressedCount == this.images.length ) {
	  this.complete();
	}
  
	if ( this.options.debug && console ) {
	  console.log( 'progress: ' + message, image, elem );
	}
  };
  
  ImagesLoaded.prototype.complete = function() {
	var eventName = this.hasAnyBroken ? 'fail' : 'done';
	this.isComplete = true;
	this.emitEvent( eventName, [ this ] );
	this.emitEvent( 'always', [ this ] );
	if ( this.jqDeferred ) {
	  var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
	  this.jqDeferred[ jqMethod ]( this );
	}
  };
  
  // --------------------------  -------------------------- //
  
  function LoadingImage( img ) {
	this.img = img;
  }
  
  LoadingImage.prototype = Object.create( EvEmitter.prototype );
  
  LoadingImage.prototype.check = function() {
	// If complete is true and browser supports natural sizes,
	// try to check for image status manually.
	var isComplete = this.getIsImageComplete();
	if ( isComplete ) {
	  // report based on naturalWidth
	  this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
	  return;
	}
  
	// If none of the checks above matched, simulate loading on detached element.
	this.proxyImage = new Image();
	this.proxyImage.addEventListener( 'load', this );
	this.proxyImage.addEventListener( 'error', this );
	// bind to image as well for Firefox. #191
	this.img.addEventListener( 'load', this );
	this.img.addEventListener( 'error', this );
	this.proxyImage.src = this.img.src;
  };
  
  LoadingImage.prototype.getIsImageComplete = function() {
	// check for non-zero, non-undefined naturalWidth
	// fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671
	return this.img.complete && this.img.naturalWidth;
  };
  
  LoadingImage.prototype.confirm = function( isLoaded, message ) {
	this.isLoaded = isLoaded;
	this.emitEvent( 'progress', [ this, this.img, message ] );
  };
  
  // ----- events ----- //
  
  // trigger specified handler for event type
  LoadingImage.prototype.handleEvent = function( event ) {
	var method = 'on' + event.type;
	if ( this[ method ] ) {
	  this[ method ]( event );
	}
  };
  
  LoadingImage.prototype.onload = function() {
	this.confirm( true, 'onload' );
	this.unbindEvents();
  };
  
  LoadingImage.prototype.onerror = function() {
	this.confirm( false, 'onerror' );
	this.unbindEvents();
  };
  
  LoadingImage.prototype.unbindEvents = function() {
	this.proxyImage.removeEventListener( 'load', this );
	this.proxyImage.removeEventListener( 'error', this );
	this.img.removeEventListener( 'load', this );
	this.img.removeEventListener( 'error', this );
  };
  
  // -------------------------- Background -------------------------- //
  
  function Background( url, element ) {
	this.url = url;
	this.element = element;
	this.img = new Image();
  }
  
  // inherit LoadingImage prototype
  Background.prototype = Object.create( LoadingImage.prototype );
  
  Background.prototype.check = function() {
	this.img.addEventListener( 'load', this );
	this.img.addEventListener( 'error', this );
	this.img.src = this.url;
	// check if image is already complete
	var isComplete = this.getIsImageComplete();
	if ( isComplete ) {
	  this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
	  this.unbindEvents();
	}
  };
  
  Background.prototype.unbindEvents = function() {
	this.img.removeEventListener( 'load', this );
	this.img.removeEventListener( 'error', this );
  };
  
  Background.prototype.confirm = function( isLoaded, message ) {
	this.isLoaded = isLoaded;
	this.emitEvent( 'progress', [ this, this.element, message ] );
  };
  
  // -------------------------- jQuery -------------------------- //
  
  ImagesLoaded.makeJQueryPlugin = function( jQuery ) {
	jQuery = jQuery || window.jQuery;
	if ( !jQuery ) {
	  return;
	}
	// set local variable
	$ = jQuery;
	// $().imagesLoaded()
	$.fn.imagesLoaded = function( options, callback ) {
	  var instance = new ImagesLoaded( this, options, callback );
	  return instance.jqDeferred.promise( $(this) );
	};
  };
  // try making plugin
  ImagesLoaded.makeJQueryPlugin();
  
  // --------------------------  -------------------------- //
  
  return ImagesLoaded;
  
  });