3 // Infinite Scroll jQuery plugin
4 // copyright Paul Irish, licensed GPL & MIT
7 // home and docs: http://www.infinite-scroll.com
10 // todo: add preloading option.
14 $.fn.infinitescroll = function(options,callback){
16 // console log wrapper.
18 if (opts.debug) { window.console && console.log.call(console,arguments)}
21 // grab each selector option and see if any fail.
22 function areSelectorsValid(opts){
23 for (var key in opts){
24 if (key.indexOf && (key.indexOf('Selector') != -1) && $(opts[key]).length === 0){
25 debug('Your ' + key + ' found no elements.');
33 // find the number to increment in the path.
34 function determinePath(path){
36 path.match(relurl) ? path.match(relurl)[2] : path;
38 // there is a 2 in the url surrounded by slashes, e.g. /page/2/
39 if ( path.match(/^(.*?)\b2\b(.*?$)/) ){
40 path = path.match(/^(.*?)\b2\b(.*?$)/).slice(1);
42 // if there is any 2 in the url at all.
43 if (path.match(/^(.*?)2(.*?$)/)){
44 debug('Trying backup next selector parse technique. Treacherous waters here, matey.');
45 path = path.match(/^(.*?)2(.*?$)/).slice(1);
47 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.');
48 props.isInvalidPage = true; //prevent it from running on this page.
55 // 'document' means the full document usually, but sometimes the content of the overflow'd div in local mode
56 function getDocumentHeight(){
57 // weird doubletouch of scrollheight because http://soulpass.com/2006/07/24/ie-and-scrollheight/
58 return opts.localMode ? ($(props.container)[0].scrollHeight && $(props.container)[0].scrollHeight)
59 // needs to be document's height. (not props.container's) html's height is wrong in IE.
60 : $(document).height()
65 function isNearBottom(opts,props){
67 // distance remaining in the scroll
68 // computed as: document height - distance already scroll - viewport height - buffer
69 var pixelsFromWindowBottomToBottom = getDocumentHeight() -
70 (opts.localMode ? $(props.container).scrollTop() :
71 // have to do this bs because safari doesnt report a scrollTop on the html element
72 ($(props.container).scrollTop() || $(props.container.ownerDocument.body).scrollTop())) -
73 $(opts.localMode ? props.container : window).height();
75 debug('math:',pixelsFromWindowBottomToBottom, props.pixelsFromNavToBottom);
77 // if distance remaining in the scroll (including buffer) is less than the orignal nav to bottom....
78 return (pixelsFromWindowBottomToBottom - opts.bufferPx < props.pixelsFromNavToBottom);
81 function showDoneMsg(){
85 .find('div').html(opts.donetext).animate({opacity: 1},2000).fadeOut('normal');
87 // user provided callback when done
91 function infscrSetup(path,opts,props,callback){
93 if (props.isDuringAjax || props.isInvalidPage || props.isDone) return;
95 if ( opts.infiniteScroll && !isNearBottom(opts,props) ) return;
97 // we dont want to fire the ajax multiple times
98 props.isDuringAjax = true;
100 // show the loading message and hide the previous/next links
101 props.loadingMsg.appendTo( opts.contentSelector ).show();
102 if(opts.infiniteScroll) $( opts.navSelector ).hide();
104 // increment the URL bit. e.g. /page/3/
107 debug('heading into ajax',path);
109 // if we're dealing with a table we can't use DIVs
110 var box = $(opts.contentSelector).is('table') ? $('<tbody/>') : $('<div/>');
113 .attr('id','infscr-page-'+props.currPage)
114 .addClass('infscr-pages')
115 .appendTo( opts.contentSelector )
116 .load( path.join( props.currPage ) + ' ' + opts.itemSelector,null,function(){
118 // if we've hit the last page...
125 // if it didn't return anything
126 if (box.children().length == 0){
127 // fake an ajaxError so we can quit.
128 $.event.trigger( "ajaxError", [{status:404}] );
131 // fadeout currently makes the <em>'d text ugly in IE6
132 props.loadingMsg.fadeOut('normal' );
134 // smooth scroll to ease in the new content
136 var scrollTo = $(window).scrollTop() + $('#infscr-loading').height() + opts.extraScrollPx + 'px';
137 $('html,body').animate({scrollTop: scrollTo}, 800,function(){ props.isDuringAjax = false; });
140 // pass in the new DOM element as context for the callback
141 callback.call( box[0] );
143 if (!opts.animate) props.isDuringAjax = false; // once the call is done, we can allow it again.
148 } // end of infscrSetup()
155 var opts = $.extend({}, $.infinitescroll.defaults, options);
156 var props = $.infinitescroll; // shorthand
157 callback = callback || function(){};
159 if (!areSelectorsValid(opts)){ return false; }
161 // we doing this on an overflow:auto div?
162 props.container = opts.localMode ? this : document.documentElement;
164 // contentSelector we'll use for our .load()
165 opts.contentSelector = opts.contentSelector || this;
168 // get the relative URL - everything past the domain name.
169 var relurl = /(.*?\/\/).*?(\/.*)/;
170 var path = $(opts.nextSelector).attr('href');
173 if (!path) { debug('Navigation selector not found'); return; }
175 // set the path to be a relative URL from root.
176 path = determinePath(path);
179 // reset scrollTop in case of page refresh:
180 if (opts.localMode) $(props.container)[0].scrollTop = 0;
182 // distance from nav links to bottom
183 // computed as: height of the document + top offset of container - top offset of nav link
184 props.pixelsFromNavToBottom = getDocumentHeight() +
185 $(props.container).offset().top -
186 $(opts.navSelector).offset().top;
188 // define loading msg
189 props.loadingMsg = $('<div id="infscr-loading" style="text-align: center;"><img alt="Loading..." src="'+
190 opts.loadingImg+'" /><div>'+opts.loadingText+'</div></div>');
192 (new Image()).src = opts.loadingImg;
196 // set up our bindings
197 $(document).ajaxError(function(e,xhr,opt){
198 debug('Page not found. Self-destructing...');
200 // die if we're out of pages.
201 if (xhr.status == 404){
204 $(opts.localMode ? this : window).unbind('scroll.infscr');
208 if(opts.infiniteScroll){
209 // bind scroll handler to element (if its a local scroll) or window
210 $(opts.localMode ? this : window)
211 .bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } )
212 .trigger('scroll.infscr'); // trigger the event, in case it's a short page
214 $(opts.nextSelector).click(
216 infscrSetup(path,opts,props,callback);
225 } // end of $.fn.infinitescroll()
229 // options and read-only properties object
234 infiniteScroll : true,
236 nextSelector : "div.navigation a:first",
237 loadingImg : "http://www.infinite-scroll.com/loading.gif",
238 loadingText : "<em>Loading the next set of posts...</em>",
239 donetext : "<em>Congratulations, you've reached the end of the internet.</em>",
240 navSelector : "div.navigation",
241 contentSelector : null, // not really a selector. :) it's whatever the method was called on..
243 itemSelector : "div.post",
247 errorCallback : function(){}
249 loadingImg : undefined,
250 loadingMsg : undefined,
251 container : undefined,
253 currDOMChunk : null, // defined in setup()'s load()
254 isDuringAjax : false,
255 isInvalidPage : false,
256 isDone : false // for when it goes all the way through the archive.