summaryrefslogtreecommitdiff
path: root/plugins/InfiniteScroll/jquery.infinitescroll.js
blob: ec31bb0863af40b06a27f6ab18ef408f7dceab8f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261

/*!
// 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; 
    
    		if ( opts.infiniteScroll && !isNearBottom(opts,props) ) return; 
    		  
    		// 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();
    		if(opts.infiniteScroll) $( opts.navSelector ).hide(); 
    		
    		// 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');
      } 
    });
    
    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;
        }
      );
    }
    
    
    return this;
  
  }  // end of $.fn.infinitescroll()
  

  
  // options and read-only properties object
  
  $.infinitescroll = {     
        defaults      : {
                          debug           : false,
                          infiniteScroll  : true,
                          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);