jquery.infinitescroll.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /*!
  2. // Infinite Scroll jQuery plugin
  3. // copyright Paul Irish, licensed GPL & MIT
  4. // version 1.2.090804
  5. // home and docs: http://www.infinite-scroll.com
  6. */
  7. // todo: add preloading option.
  8. ;(function($){
  9. $.fn.infinitescroll = function(options,callback){
  10. // console log wrapper.
  11. function debug(){
  12. if (opts.debug) { window.console && console.log.call(console,arguments)}
  13. }
  14. // grab each selector option and see if any fail.
  15. function areSelectorsValid(opts){
  16. for (var key in opts){
  17. if (key.indexOf && (key.indexOf('Selector') != -1) && $(opts[key]).length === 0){
  18. debug('Your ' + key + ' found no elements.');
  19. return false;
  20. }
  21. return true;
  22. }
  23. }
  24. // find the number to increment in the path.
  25. function determinePath(path){
  26. path.match(relurl) ? path.match(relurl)[2] : path;
  27. // there is a 2 in the url surrounded by slashes, e.g. /page/2/
  28. if ( path.match(/^(.*?)\b2\b(.*?$)/) ){
  29. path = path.match(/^(.*?)\b2\b(.*?$)/).slice(1);
  30. } else
  31. // if there is any 2 in the url at all.
  32. if (path.match(/^(.*?)2(.*?$)/)){
  33. debug('Trying backup next selector parse technique. Treacherous waters here, matey.');
  34. path = path.match(/^(.*?)2(.*?$)/).slice(1);
  35. } else {
  36. 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.');
  37. props.isInvalidPage = true; //prevent it from running on this page.
  38. }
  39. return path;
  40. }
  41. // 'document' means the full document usually, but sometimes the content of the overflow'd div in local mode
  42. function getDocumentHeight(){
  43. // weird doubletouch of scrollheight because http://soulpass.com/2006/07/24/ie-and-scrollheight/
  44. return opts.localMode ? ($(props.container)[0].scrollHeight && $(props.container)[0].scrollHeight)
  45. // needs to be document's height. (not props.container's) html's height is wrong in IE.
  46. : $(document).height()
  47. }
  48. function isNearBottom(opts,props){
  49. // distance remaining in the scroll
  50. // computed as: document height - distance already scroll - viewport height - buffer
  51. var pixelsFromWindowBottomToBottom = getDocumentHeight() -
  52. (opts.localMode ? $(props.container).scrollTop() :
  53. // have to do this bs because safari doesnt report a scrollTop on the html element
  54. ($(props.container).scrollTop() || $(props.container.ownerDocument.body).scrollTop())) -
  55. $(opts.localMode ? props.container : window).height();
  56. debug('math:',pixelsFromWindowBottomToBottom, props.pixelsFromNavToBottom);
  57. // if distance remaining in the scroll (including buffer) is less than the orignal nav to bottom....
  58. return (pixelsFromWindowBottomToBottom - opts.bufferPx < props.pixelsFromNavToBottom);
  59. }
  60. function showDoneMsg(){
  61. props.loadingMsg
  62. .find('img').hide()
  63. .parent()
  64. .find('div').html(opts.donetext).animate({opacity: 1},2000).fadeOut('normal');
  65. // user provided callback when done
  66. opts.errorCallback();
  67. }
  68. function infscrSetup(path,opts,props,callback){
  69. if (props.isDuringAjax || props.isInvalidPage || props.isDone) return;
  70. if ( opts.infiniteScroll && !isNearBottom(opts,props) ) return;
  71. // we dont want to fire the ajax multiple times
  72. props.isDuringAjax = true;
  73. // show the loading message and hide the previous/next links
  74. props.loadingMsg.appendTo( opts.contentSelector ).show();
  75. if(opts.infiniteScroll) $( opts.navSelector ).hide();
  76. // increment the URL bit. e.g. /page/3/
  77. props.currPage++;
  78. debug('heading into ajax',path);
  79. // if we're dealing with a table we can't use DIVs
  80. var box = $(opts.contentSelector).is('table') ? $('<tbody/>') : $('<div/>');
  81. box
  82. .attr('id','infscr-page-'+props.currPage)
  83. .addClass('infscr-pages')
  84. .appendTo( opts.contentSelector )
  85. .load( path.join( props.currPage ) + ' ' + opts.itemSelector,null,function(){
  86. // if we've hit the last page...
  87. if (props.isDone){
  88. showDoneMsg();
  89. return false;
  90. } else {
  91. // if it didn't return anything
  92. if (box.children().length == 0){
  93. // fake an ajaxError so we can quit.
  94. $.event.trigger( "ajaxError", [{status:404}] );
  95. }
  96. // fadeout currently makes the <em>'d text ugly in IE6
  97. props.loadingMsg.fadeOut('normal' );
  98. // smooth scroll to ease in the new content
  99. if (opts.animate){
  100. var scrollTo = $(window).scrollTop() + $('#infscr-loading').height() + opts.extraScrollPx + 'px';
  101. $('html,body').animate({scrollTop: scrollTo}, 800,function(){ props.isDuringAjax = false; });
  102. }
  103. // pass in the new DOM element as context for the callback
  104. callback.call( box[0] );
  105. if (!opts.animate) props.isDuringAjax = false; // once the call is done, we can allow it again.
  106. }
  107. }); // end of load()
  108. } // end of infscrSetup()
  109. // lets get started.
  110. var opts = $.extend({}, $.infinitescroll.defaults, options);
  111. var props = $.infinitescroll; // shorthand
  112. callback = callback || function(){};
  113. if (!areSelectorsValid(opts)){ return false; }
  114. // we doing this on an overflow:auto div?
  115. props.container = opts.localMode ? this : document.documentElement;
  116. // contentSelector we'll use for our .load()
  117. opts.contentSelector = opts.contentSelector || this;
  118. // get the relative URL - everything past the domain name.
  119. var relurl = /(.*?\/\/).*?(\/.*)/;
  120. var path = $(opts.nextSelector).attr('href');
  121. if (!path) { debug('Navigation selector not found'); return; }
  122. // set the path to be a relative URL from root.
  123. path = determinePath(path);
  124. // reset scrollTop in case of page refresh:
  125. if (opts.localMode) $(props.container)[0].scrollTop = 0;
  126. // distance from nav links to bottom
  127. // computed as: height of the document + top offset of container - top offset of nav link
  128. props.pixelsFromNavToBottom = getDocumentHeight() +
  129. $(props.container).offset().top -
  130. $(opts.navSelector).offset().top;
  131. // define loading msg
  132. props.loadingMsg = $('<div id="infscr-loading" style="text-align: center;"><img alt="Loading..." src="'+
  133. opts.loadingImg+'" /><div>'+opts.loadingText+'</div></div>');
  134. // preload the image
  135. (new Image()).src = opts.loadingImg;
  136. // set up our bindings
  137. $(document).ajaxError(function(e,xhr,opt){
  138. debug('Page not found. Self-destructing...');
  139. // die if we're out of pages.
  140. if (xhr.status == 404){
  141. showDoneMsg();
  142. props.isDone = true;
  143. $(opts.localMode ? this : window).unbind('scroll.infscr');
  144. }
  145. });
  146. if(opts.infiniteScroll){
  147. // bind scroll handler to element (if its a local scroll) or window
  148. $(opts.localMode ? this : window)
  149. .bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } )
  150. .trigger('scroll.infscr'); // trigger the event, in case it's a short page
  151. }else{
  152. $(opts.nextSelector).click(
  153. function(){
  154. infscrSetup(path,opts,props,callback);
  155. return false;
  156. }
  157. );
  158. }
  159. return this;
  160. } // end of $.fn.infinitescroll()
  161. // options and read-only properties object
  162. $.infinitescroll = {
  163. defaults : {
  164. debug : false,
  165. infiniteScroll : true,
  166. preload : false,
  167. nextSelector : "div.navigation a:first",
  168. loadingImg : "http://www.infinite-scroll.com/loading.gif",
  169. loadingText : "<em>Loading the next set of posts...</em>",
  170. donetext : "<em>Congratulations, you've reached the end of the internet.</em>",
  171. navSelector : "div.navigation",
  172. contentSelector : null, // not really a selector. :) it's whatever the method was called on..
  173. extraScrollPx : 150,
  174. itemSelector : "div.post",
  175. animate : false,
  176. localMode : false,
  177. bufferPx : 40,
  178. errorCallback : function(){}
  179. },
  180. loadingImg : undefined,
  181. loadingMsg : undefined,
  182. container : undefined,
  183. currPage : 1,
  184. currDOMChunk : null, // defined in setup()'s load()
  185. isDuringAjax : false,
  186. isInvalidPage : false,
  187. isDone : false // for when it goes all the way through the archive.
  188. };
  189. })(jQuery);