2009-08-05 22:09:19 +01:00
/ * !
// Infinite Scroll jQuery plugin
// copyright Paul Irish, licensed GPL & MIT
// version 1.2.090804
// home and docs: http://www.infinite-scroll.com
* /
// todo: add preloading option.
; ( 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' ) && $ ( 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 ) {
path . match ( relurl ) ? path . match ( relurl ) [ 2 ] : path ;
// there is a 2 in the url surrounded by slashes, e.g. /page/2/
if ( path . match ( /^(.*?)\b2\b(.*?$)/ ) ) {
path = path . match ( /^(.*?)\b2\b(.*?$)/ ) . slice ( 1 ) ;
} else
// if there is any 2 in the url at all.
if ( path . match ( /^(.*?)2(.*?$)/ ) ) {
debug ( 'Trying backup next selector parse technique. Treacherous waters here, matey.' ) ;
path = path . match ( /^(.*?)2(.*?$)/ ) . slice ( 1 ) ;
} 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 ;
}
// 'document' means the full document usually, but sometimes the content of the overflow'd div in local mode
function getDocumentHeight ( ) {
// weird doubletouch of scrollheight because http://soulpass.com/2006/07/24/ie-and-scrollheight/
return opts . localMode ? ( $ ( props . container ) [ 0 ] . scrollHeight && $ ( props . container ) [ 0 ] . scrollHeight )
// needs to be document's height. (not props.container's) html's height is wrong in IE.
: $ ( document ) . height ( )
}
function isNearBottom ( opts , props ) {
// distance remaining in the scroll
// computed as: document height - distance already scroll - viewport height - buffer
var pixelsFromWindowBottomToBottom = getDocumentHeight ( ) -
( opts . localMode ? $ ( props . container ) . scrollTop ( ) :
// have to do this bs because safari doesnt report a scrollTop on the html element
( $ ( props . container ) . scrollTop ( ) || $ ( props . container . ownerDocument . body ) . scrollTop ( ) ) ) -
$ ( opts . localMode ? props . container : window ) . height ( ) ;
debug ( 'math:' , pixelsFromWindowBottomToBottom , props . pixelsFromNavToBottom ) ;
// if distance remaining in the scroll (including buffer) is less than the orignal nav to bottom....
return ( pixelsFromWindowBottomToBottom - opts . bufferPx < props . pixelsFromNavToBottom ) ;
}
function showDoneMsg ( ) {
props . loadingMsg
. find ( 'img' ) . hide ( )
. parent ( )
. find ( 'div' ) . html ( opts . donetext ) . animate ( { opacity : 1 } , 2000 ) . fadeOut ( 'normal' ) ;
// user provided callback when done
opts . errorCallback ( ) ;
}
function infscrSetup ( path , opts , props , callback ) {
if ( props . isDuringAjax || props . isInvalidPage || props . isDone ) return ;
2009-09-20 03:53:39 +01:00
if ( opts . infiniteScroll && ! isNearBottom ( opts , props ) ) return ;
2009-08-05 22:09:19 +01:00
// we dont want to fire the ajax multiple times
props . isDuringAjax = true ;
// show the loading message and hide the previous/next links
props . loadingMsg . appendTo ( opts . contentSelector ) . show ( ) ;
2009-09-20 03:53:39 +01:00
if ( opts . infiniteScroll ) $ ( opts . navSelector ) . hide ( ) ;
2009-08-05 22:09:19 +01:00
// increment the URL bit. e.g. /page/3/
props . currPage ++ ;
debug ( 'heading into ajax' , path ) ;
// if we're dealing with a table we can't use DIVs
var box = $ ( opts . contentSelector ) . is ( 'table' ) ? $ ( '<tbody/>' ) : $ ( '<div/>' ) ;
box
. attr ( 'id' , 'infscr-page-' + props . currPage )
. addClass ( 'infscr-pages' )
. appendTo ( opts . contentSelector )
. load ( path . join ( props . currPage ) + ' ' + opts . itemSelector , null , function ( ) {
// if we've hit the last page...
if ( props . isDone ) {
showDoneMsg ( ) ;
return false ;
} else {
// if it didn't return anything
if ( box . children ( ) . length == 0 ) {
// fake an ajaxError so we can quit.
$ . event . trigger ( "ajaxError" , [ { status : 404 } ] ) ;
}
// 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 ( ) { props . isDuringAjax = false ; } ) ;
}
// pass in the new DOM element as context for the callback
callback . call ( box [ 0 ] ) ;
if ( ! opts . animate ) props . isDuringAjax = false ; // once the call is done, we can allow it again.
}
} ) ; // end of load()
} // end of infscrSetup()
// lets get started.
var opts = $ . extend ( { } , $ . infinitescroll . defaults , options ) ;
var props = $ . infinitescroll ; // shorthand
callback = callback || function ( ) { } ;
if ( ! areSelectorsValid ( opts ) ) { return false ; }
// we doing this on an overflow:auto div?
props . container = opts . localMode ? this : document . documentElement ;
// contentSelector we'll use for our .load()
opts . contentSelector = opts . contentSelector || this ;
// get the relative URL - everything past the domain name.
var relurl = /(.*?\/\/).*?(\/.*)/ ;
var path = $ ( opts . nextSelector ) . attr ( 'href' ) ;
if ( ! path ) { debug ( 'Navigation selector not found' ) ; return ; }
// set the path to be a relative URL from root.
path = determinePath ( path ) ;
// reset scrollTop in case of page refresh:
if ( opts . localMode ) $ ( props . container ) [ 0 ] . scrollTop = 0 ;
// distance from nav links to bottom
// computed as: height of the document + top offset of container - top offset of nav link
props . pixelsFromNavToBottom = getDocumentHeight ( ) +
$ ( props . container ) . offset ( ) . top -
$ ( opts . navSelector ) . offset ( ) . top ;
// 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 ;
// set up our bindings
$ ( document ) . ajaxError ( function ( e , xhr , opt ) {
debug ( 'Page not found. Self-destructing...' ) ;
// die if we're out of pages.
if ( xhr . status == 404 ) {
showDoneMsg ( ) ;
props . isDone = true ;
$ ( opts . localMode ? this : window ) . unbind ( 'scroll.infscr' ) ;
}
} ) ;
2009-09-20 03:53:39 +01:00
if ( opts . infiniteScroll ) {
// bind scroll handler to element (if its a local scroll) or window
$ ( opts . localMode ? this : window )
. bind ( 'scroll.infscr' , function ( ) { infscrSetup ( path , opts , props , callback ) ; } )
. trigger ( 'scroll.infscr' ) ; // trigger the event, in case it's a short page
} else {
$ ( opts . nextSelector ) . click (
function ( ) {
infscrSetup ( path , opts , props , callback ) ;
return false ;
}
) ;
}
2009-08-05 22:09:19 +01:00
return this ;
} // end of $.fn.infinitescroll()
// options and read-only properties object
$ . infinitescroll = {
defaults : {
debug : false ,
2009-09-20 03:53:39 +01:00
infiniteScroll : true ,
2009-08-05 22:09:19 +01:00
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..
extraScrollPx : 150 ,
itemSelector : "div.post" ,
animate : false ,
localMode : false ,
bufferPx : 40 ,
errorCallback : function ( ) { }
} ,
loadingImg : undefined ,
loadingMsg : undefined ,
container : undefined ,
currPage : 1 ,
currDOMChunk : null , // defined in setup()'s load()
isDuringAjax : false ,
isInvalidPage : false ,
isDone : false // for when it goes all the way through the archive.
} ;
} ) ( jQuery ) ;