]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/InfiniteScroll/jquery.infinitescroll.js
Merge branch 'testing' of gitorious.org:statusnet/mainline into testing
[quix0rs-gnu-social.git] / plugins / InfiniteScroll / jquery.infinitescroll.js
1
2 /*!
3 // Infinite Scroll jQuery plugin
4 // copyright Paul Irish, licensed GPL & MIT
5 // version 1.2.090804
6
7 // home and docs: http://www.infinite-scroll.com
8 */
9
10 // todo: add preloading option.
11  
12 ;(function($){
13     
14   $.fn.infinitescroll = function(options,callback){
15     
16     // console log wrapper.
17     function debug(){
18       if (opts.debug) { window.console && console.log.call(console,arguments)}
19     }
20     
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.');    
26             return false;
27         } 
28         return true;
29       }
30     }
31
32
33     // find the number to increment in the path.
34     function determinePath(path){
35       
36       path.match(relurl) ? path.match(relurl)[2] : path; 
37
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);
41       } else 
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);
46       } else {
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.
49       }
50       
51       return path;
52     }
53
54
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()
61     }
62     
63     
64         
65     function isNearBottom(opts,props){
66       
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();
74       
75       debug('math:',pixelsFromWindowBottomToBottom, props.pixelsFromNavToBottom);
76       
77       // if distance remaining in the scroll (including buffer) is less than the orignal nav to bottom....
78       return (pixelsFromWindowBottomToBottom  - opts.bufferPx < props.pixelsFromNavToBottom);    
79     }    
80     
81     function showDoneMsg(){
82       props.loadingMsg
83         .find('img').hide()
84         .parent()
85           .find('div').html(opts.donetext).animate({opacity: 1},2000).fadeOut('normal');
86       
87       // user provided callback when done    
88       opts.errorCallback();
89     }
90     
91     function infscrSetup(path,opts,props,callback){
92     
93         if (props.isDuringAjax || props.isInvalidPage || props.isDone) return; 
94     
95                 if ( opts.infiniteScroll && !isNearBottom(opts,props) ) return; 
96                   
97                 // we dont want to fire the ajax multiple times
98                 props.isDuringAjax = true; 
99                 
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(); 
103                 
104                 // increment the URL bit. e.g. /page/3/
105                 props.currPage++;
106                 
107                 debug('heading into ajax',path);
108                 
109                 // if we're dealing with a table we can't use DIVs
110                 var box = $(opts.contentSelector).is('table') ? $('<tbody/>') : $('<div/>');  
111                 
112                 box
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(){
117                     
118                         // if we've hit the last page...
119                         if (props.isDone){ 
120                     showDoneMsg();
121                                       return false;    
122                                       
123                     } else {
124                       
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}] ); 
129                         } 
130                         
131                         // fadeout currently makes the <em>'d text ugly in IE6
132                             props.loadingMsg.fadeOut('normal' ); 
133   
134                             // smooth scroll to ease in the new content
135                             if (opts.animate){ 
136                             var scrollTo = $(window).scrollTop() + $('#infscr-loading').height() + opts.extraScrollPx + 'px';
137                       $('html,body').animate({scrollTop: scrollTo}, 800,function(){ props.isDuringAjax = false; }); 
138                             }
139                     
140                     // pass in the new DOM element as context for the callback
141                     callback.call( box[0] );
142                     
143                             if (!opts.animate) props.isDuringAjax = false; // once the call is done, we can allow it again.
144                     }
145                     }); // end of load()
146                         
147                     
148       }  // end of infscrSetup()
149           
150   
151     
152       
153     // lets get started.
154     
155     var opts    = $.extend({}, $.infinitescroll.defaults, options);
156     var props   = $.infinitescroll; // shorthand
157     callback    = callback || function(){};
158     
159     if (!areSelectorsValid(opts)){ return false;  }
160     
161      // we doing this on an overflow:auto div?
162     props.container   =  opts.localMode ? this : document.documentElement;
163                           
164     // contentSelector we'll use for our .load()
165     opts.contentSelector = opts.contentSelector || this; 
166     
167     
168     // get the relative URL - everything past the domain name.
169     var relurl        = /(.*?\/\/).*?(\/.*)/;
170     var path          = $(opts.nextSelector).attr('href');
171     
172     
173     if (!path) { debug('Navigation selector not found'); return; }
174     
175     // set the path to be a relative URL from root.
176     path          = determinePath(path);
177     
178
179     // reset scrollTop in case of page refresh:
180     if (opts.localMode) $(props.container)[0].scrollTop = 0;
181
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;
187     
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>');    
191      // preload the image
192     (new Image()).src    = opts.loadingImg;
193                       
194
195   
196     // set up our bindings
197     $(document).ajaxError(function(e,xhr,opt){
198       debug('Page not found. Self-destructing...');    
199       
200       // die if we're out of pages.
201       if (xhr.status == 404){ 
202         showDoneMsg();
203         props.isDone = true; 
204         $(opts.localMode ? this : window).unbind('scroll.infscr');
205       } 
206     });
207     
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
213     }else{
214       $(opts.nextSelector).click(
215         function(){
216           infscrSetup(path,opts,props,callback);
217           return false;
218         }
219       );
220     }
221     
222     
223     return this;
224   
225   }  // end of $.fn.infinitescroll()
226   
227
228   
229   // options and read-only properties object
230   
231   $.infinitescroll = {     
232         defaults      : {
233                           debug           : false,
234                           infiniteScroll  : true,
235                           preload         : false,
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..
242                           extraScrollPx   : 150,
243                           itemSelector    : "div.post",
244                           animate         : false,
245                           localMode      : false,
246                           bufferPx        : 40,
247                           errorCallback   : function(){}
248                         }, 
249         loadingImg    : undefined,
250         loadingMsg    : undefined,
251         container     : undefined,
252         currPage      : 1,
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.
257   };
258   
259
260
261 })(jQuery);