Created
September 5, 2013 08:08
-
-
Save vgrish/6447311 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
// Infinite Scroll jQuery plugin | |
// copyright Paul Irish, licensed GPL & MIT | |
// version 1.5.110124 | |
// home and docs: http://www.infinite-scroll.com | |
// support for MODX with Ditto | |
// Usage | |
// [!Ditto? &parents=`2` &depth=`1` &tpl=`@FILE:assets/templates/site/chunks/article.html` &paginate=`1` &display=`5`!] | |
// [+next+] | |
*/ | |
;(function($){ | |
$.fn.infinitescroll = function(options,callback){ | |
// console log wrapper. | |
function debug(){ | |
if (opts.debug) { window.console && console.log.call(console,arguments)} | |
} | |
// grab each selector option and see if any fail. | |
function areSelectorsValid(opts){ | |
for (var key in opts){ | |
if (key.indexOf && key.indexOf('Selector') > -1 && $(opts[key]).length === 0){ | |
debug('Your ' + key + ' found no elements.'); | |
return false; | |
} | |
return true; | |
} | |
} | |
// find the number to increment in the path. | |
function determinePath(path){ | |
debug('determinePath page: ' + path); | |
if ( path.match(/^(.*?)\b2\b(.*?$)/) ){ | |
debug('0 step) ' + path); | |
// path = path.match(/^(.*?)\b2\b(.*?$)/).slice(1); | |
debug('1 step) ' + path); | |
// if there is any 2 in the url at all. | |
} else if (path.match(/^(.*?)2(.*?$)/)){ | |
debug('2 step) '); | |
// page= is used in django: | |
// http://www.infinite-scroll.com/changelog/comment-page-1/#comment-127 | |
if ( path.match(/^(.*?page=)2(\/.*|$)/) ){ | |
path = path.match(/^(.*?page=)2(\/.*|$)/).slice(1); | |
debug('3 step) '); | |
return path; | |
} | |
debug('Trying backup next selector parse technique. Treacherous waters here, matey.'); | |
path = path.match(/^(.*?)2(.*?$)/).slice(1); | |
// if var is page - for MODX + Ditto(uses page in the url) | |
// the default path is returned | |
} else if (path.match(/^(.*?)page(.*?$)/)){ | |
debug('4 step) '); | |
return path; | |
} else { | |
debug('5 step) '); | |
// page= is used in drupal too but second page is page=1 not page=2: | |
// thx Jerod Fritz, vladikoff | |
if (path.match(/^(.*?page=)1(\/.*|$)/)) { | |
debug('6 step) '); | |
path = path.match(/^(.*?page=)1(\/.*|$)/).slice(1); | |
return path; | |
} | |
if ($.isFunction(opts.pathParse)){ | |
debug('7 step) '); | |
return [path]; | |
} else { | |
debug('Sorry, we couldn\'t parse your Next (Previous Posts) URL. Verify your the css selector points to the correct A tag. If you still get this error: yell, scream, and kindly ask for help at infinite-scroll.com.'); | |
props.isInvalidPage = true; //prevent it from running on this page. | |
} | |
} | |
return path; | |
} | |
// determine filtering nav for multiple instances | |
function filterNav() { | |
opts.isFiltered = true; | |
return binder.trigger( "error.infscr."+opts.infid, [302] ); | |
} | |
// Calculate internal height (used for local scroll) | |
function hiddenHeight(element) | |
{ | |
var height = 0; | |
$(element).children().each(function() { | |
height = height + $(this).outerHeight(false); | |
}); | |
return height; | |
} | |
//Generate InstanceID based on random data (to give consistent but different ID's) | |
function generateInstanceID(element) | |
{ | |
var number = $(element).length + $(element).html().length + $(element).attr("class").length | |
+ $(element).attr("id").length; | |
opts.infid = number; | |
} | |
function isNearBottom(){ | |
// distance remaining in the scroll | |
// computed as: document height - distance already scroll - viewport height - buffer | |
if(opts.container.nodeName=="HTML") | |
{ | |
var pixelsFromWindowBottomToBottom = 0 | |
+ $(document).height() | |
// have to do this bs because safari doesnt report a scrollTop on the html element | |
- ($(opts.container).scrollTop() || $(opts.container.ownerDocument.body).scrollTop()) | |
- $(window).height(); | |
} | |
else | |
{ | |
var pixelsFromWindowBottomToBottom = 0 | |
+ hiddenHeight(opts.container) - $(opts.container).scrollTop() - $(opts.container).height(); | |
} | |
debug('math:', pixelsFromWindowBottomToBottom, opts.pixelsFromNavToBottom); | |
// if distance remaining in the scroll (including buffer) is less than the orignal nav to bottom.... | |
return (pixelsFromWindowBottomToBottom - opts.bufferPx < opts.pixelsFromNavToBottom); | |
} | |
function showDoneMsg(){ | |
props.loadingMsg | |
.find('img').hide() | |
.parent() | |
.find('div').html(opts.donetext).animate({opacity: 1},2000, function() { | |
$(this).parent().fadeOut('normal'); | |
}); | |
// user provided callback when done | |
opts.errorCallback(); | |
} | |
function infscrSetup(){ | |
if (opts.isDuringAjax || opts.isInvalidPage || opts.isDone || opts.isFiltered || opts.isPaused) return; | |
if ( !isNearBottom(opts,props) ) return ; | |
$(document).trigger('retrieve.infscr.'+opts.infid); | |
} // end of infscrSetup() | |
function kickOffAjax(){ | |
// we dont want to fire the ajax multiple times | |
opts.isDuringAjax = true; | |
// show the loading message quickly | |
// then hide the previous/next links after we're | |
// sure the loading message was visible | |
props.loadingMsg.appendTo( opts.loadMsgSelector ).show(opts.loadingMsgRevealSpeed, function(){ | |
$( opts.navSelector ).hide(); | |
// increment the URL bit. e.g. /page/3/ | |
opts.currPage++; | |
debug('heading into ajax',path); | |
// if we're dealing with a table we can't use DIVs | |
box = $(opts.contentSelector).is('table') ? $('<tbody/>') : $('<div/>'); | |
frag = document.createDocumentFragment(); | |
//detect MODX with Ditto | |
debug(path); | |
if (path.match(/^(.*?)page(.*?$)/)) { | |
if((opts.currPage - 1) > 1){ | |
var splitPath = path.split("page="); //split by page | |
//put page number * current page - 1 | |
desturl = splitPath[0] + 'page=' + (parseInt(splitPath[1])*(opts.currPage - 1)); | |
}else{ | |
//first ditto call | |
desturl = path; | |
} | |
debug('desturl = '+desturl); | |
}else if ($.isFunction(opts.pathParse)){ | |
// if we got a custom path parsing function, pass in our path guess and page iteration | |
desturl = opts.pathParse(path.join('2'), opts.currPage); | |
debug('desturl join'); | |
} else { | |
desturl = path.join( opts.currPage ); | |
} | |
debug('desturl2 = '+desturl); | |
box.load( desturl + ' ' + opts.itemSelector,null,loadCallback); | |
}); | |
} | |
function loadCallback(){ | |
// if we've hit the last page.. | |
if (opts.isDone){ | |
showDoneMsg(); | |
return false; | |
} else { | |
var children = box.children(); | |
// if it didn't return anything | |
if (children.length == 0 || children.hasClass('error404')){ | |
// trigger a 404 error so we can quit. | |
return infscrError([404]); | |
} | |
// use a documentFragment because it works when content is going into a table or UL | |
while (box[0].firstChild){ | |
frag.appendChild( box[0].firstChild ); | |
} | |
$(opts.contentSelector)[0].appendChild(frag); | |
// fadeout currently makes the <em>'d text ugly in IE6 | |
props.loadingMsg.fadeOut('normal' ); | |
// smooth scroll to ease in the new content | |
if (opts.animate){ | |
var scrollTo = $(window).scrollTop() + $('#infscr-loading').height() + opts.extraScrollPx + 'px'; | |
$('html,body').animate({scrollTop: scrollTo}, 800,function(){ opts.isDuringAjax = false; }); | |
} | |
// previously, we would pass in the new DOM element as context for the callback | |
// however we're now using a documentfragment, which doesnt havent parents or children, | |
// so the context is the contentContainer guy, and we pass in an array | |
// of the elements collected as the first argument. | |
callback.call( $(opts.contentSelector)[0], children.get() ); | |
if (!opts.animate) opts.isDuringAjax = false; // once the call is done, we can allow it again. | |
} | |
} | |
function initPause(pauseValue) { | |
if (pauseValue == "pause") { | |
opts.isPaused = true; | |
} else if (pauseValue == "resume") { | |
opts.isPaused = false; | |
} else { | |
opts.isPaused = !opts.isPaused; | |
} | |
debug('Paused: ' + opts.isPaused); | |
return false; | |
} | |
function infscrError(xhr){ | |
if (!opts.isDone && xhr == 404) { | |
// die if we're out of pages. | |
debug('Page not found. Self-destructing...'); | |
showDoneMsg(); | |
opts.isDone = true; | |
opts.currPage = 1; // if you need to go back to this instance | |
binder.unbind('scroll.infscr.'+opts.infid); | |
$(document).unbind('retrieve.infscr.'+opts.infid); | |
} | |
if (opts.isFiltered && xhr == 302) { | |
// die if filtered. | |
debug('Filtered. Going to next instance...'); | |
opts.isDone = true; | |
opts.currPage = 1; // if you need to go back to this instance | |
opts.isPaused = false; | |
binder.unbind('scroll.infscr.'+opts.infid, infscrSetup) | |
.unbind('pause.infscr.'+opts.infid) | |
.unbind('filter.infscr.'+opts.infid) | |
.unbind('error.infscr.'+opts.infid); | |
$(document).unbind('retrieve.infscr.'+opts.infid,kickOffAjax); | |
} | |
} | |
// smartscroll = debounced scroll event | |
// http://paulirish.com/2009/throttled-smartresize-jquery-event-handler/ | |
// lets get pageed. | |
$.browser.ie6 = $.browser.msie && $.browser.version < 7; | |
var opts = $.extend({}, $.infinitescroll.defaults, options), | |
props = $.infinitescroll, // shorthand | |
box, frag, desturl, thisPause, errorStatus; | |
callback = callback || function(){}; | |
if (!areSelectorsValid(opts)){ return false; } | |
opts.container = opts.container || document.documentElement; | |
// contentSelector we'll use for our .load() | |
opts.contentSelector = opts.contentSelector || this; | |
// Generate unique instance ID | |
if(opts.infid==0) | |
generateInstanceID(opts.contentSelector); | |
// loadMsgSelector - if we want to place the load message in a specific selector, defaulted to the contentSelector | |
opts.loadMsgSelector = opts.loadMsgSelector || opts.contentSelector; | |
// get the relative URL - everything past the domain name. | |
var relurl = /(.*?\/\/).*?(\/.*)/, | |
path = $(opts.nextSelector).attr('href'); | |
debug ('next link '+path); | |
if (!path) { debug('Navigation selector not found'); return; } | |
// set the path to be a relative URL from root. | |
path = determinePath(path); | |
// define loading msg | |
props.loadingMsg = $('<div id="infscr-loading" style="text-align: center;"><img alt="Loading..." src="'+ | |
opts.loadingImg+'" /><div>'+opts.loadingText+'</div></div>'); | |
// preload the image | |
(new Image()).src = opts.loadingImg; | |
//Check if its HTML (window scroll) | |
if(opts.container.nodeName=="HTML") | |
{ | |
debug("Window Scroll"); | |
var innerContainerHeight = $(document).height(); | |
var binder = $(window); | |
} | |
else | |
{ | |
debug("Local Scroll"); | |
var innerContainerHeight = hiddenHeight(opts.container); | |
var binder = $(opts.container); | |
} | |
// distance from nav links to bottom | |
// computed as: height of the document + top offset of container - top offset of nav link | |
opts.pixelsFromNavToBottom = innerContainerHeight + | |
(opts.container == document.documentElement ? 0 : $(opts.container).offset().top )- | |
$(opts.navSelector).offset().top; | |
// set up our bindings | |
// bind scroll handler to element (if its a local scroll) or window | |
binder | |
.bind('scroll.infscr.'+opts.infid, infscrSetup) | |
.bind('filter.infscr.'+opts.infid, filterNav) | |
.bind('error.infscr.'+opts.infid, function(event,errorStatus) { infscrError(errorStatus); }) | |
.bind('pause.infscr.'+opts.infid, function(event,thisPause) { initPause(thisPause); }) | |
.trigger('scroll.infscr.'+opts.infid); // trigger the event, in case it's a short page | |
$(document).bind('retrieve.infscr.'+opts.infid,kickOffAjax); | |
return this; | |
} // end of $.fn.infinitescroll() | |
// options and read-only properties object | |
$.infinitescroll = { | |
defaults : { | |
debug : false, | |
preload : false, | |
nextSelector : "div.navigation a:first", | |
loadingImg : "http://www.infinite-scroll.com/loading.gif", | |
loadingText : "<em>Loading the next set of posts...</em>", | |
donetext : "<em>Congratulations, you've reached the end of the internet.</em>", | |
navSelector : "div.navigation", | |
contentSelector : null, // not really a selector. :) it's whatever the method was called on.. | |
loadMsgSelector : null, | |
loadingMsgRevealSpeed : 'fast', // controls how fast you want the loading message to come in, ex: 'fast', 'slow', 200 (milliseconds) | |
extraScrollPx : 150, | |
itemSelector : "div.post", | |
animate : false, | |
pathParse : undefined, | |
bufferPx : 40, | |
errorCallback : function(){}, | |
currPage : 1, | |
infid : 0, //Instance ID (Generated at setup) | |
isDuringAjax : false, | |
isInvalidPage : false, | |
isFiltered : false, | |
isDone : false, // for when it goes all the way through the archive. | |
isPaused : false, | |
container : undefined, //If left undefined uses window scroll, set as container for local scroll | |
pixelsFromNavToBottom : undefined | |
}, | |
loadingImg : undefined, | |
loadingMsg : undefined, | |
currDOMChunk : null // defined in setup()'s load() | |
}; | |
})(jQuery);;;;;;;;;;;;;;; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment