Skip to content

Instantly share code, notes, and snippets.

@rjha
Created September 12, 2012 13:52
Show Gist options
  • Save rjha/3706744 to your computer and use it in GitHub Desktop.
Save rjha/3706744 to your computer and use it in GitHub Desktop.
infinite scroll hacked version for PHP pagination with 2 variables
/*
--------------------------------
Infinite Scroll
--------------------------------
+ https://github.com/paulirish/infinite-scroll
+ version 2.0b2.120519
+ Copyright 2011/12 Paul Irish & Luke Shumard
+ Licensed under the MIT license
+ Documentation: http://infinite-scroll.com/
+
===========================================================
+ This is a hacked up version to be used on www.3mik.com
+ changes
1. added nextUrl property to opts.state
2. during setup copy nextSelector.href attribute into nextUrl
3. when loading finished: do not fade out loading message : moved to masonry callback
4. our own implementation of retrieve : to fetch nextUrl and content.
+ @see diff from original
=============================================================
*/
(function (window, $, undefined) {
$.infinitescroll = function infscr(options, callback, element) {
this.element = $(element);
// Flag the object in the event of a failed creation
if (!this._create(options, callback)) {
this.failed = true;
}
};
$.infinitescroll.defaults = {
loading: {
finished: undefined,
finishedMsg: "<em>Congratulations, you've reached the end of the internet.</em>",
img: "http://www.infinite-scroll.com/loading.gif",
msg: null,
msgText: "<em>Loading the next set of posts...</em>",
selector: null,
speed: 'fast',
start: undefined
},
state: {
isDuringAjax: false,
isInvalidPage: false,
isDestroyed: false,
isDone: false, // For when it goes all the way through the archive.
isPaused: false,
currPage: 1,
nextUrl : undefined
},
callback: undefined,
debug: false,
behavior: undefined,
binder: $(window), // used to cache the selector
nextSelector: "div.navigation a:first",
navSelector: "div.navigation",
contentSelector: null, // rename to pageFragment
extraScrollPx: 150,
itemSelector: "div.post",
animate: false,
pathParse: undefined,
dataType: 'html',
appendCallback: true,
bufferPx: 40,
errorCallback: function () { },
infid: 0, //Instance ID
pixelsFromNavToBottom: undefined,
path: undefined
};
$.infinitescroll.prototype = {
/*
----------------------------
Private methods
----------------------------
*/
// Bind or unbind from scroll
_binding: function infscr_binding(binding) {
var instance = this,
opts = instance.options;
opts.v = '2.0b2.111027';
// if behavior is defined and this function is extended, call that instead of default
if (!!opts.behavior && this['_binding_'+opts.behavior] !== undefined) {
this['_binding_'+opts.behavior].call(this);
return;
}
if (binding !== 'bind' && binding !== 'unbind') {
this._debug('Binding value ' + binding + ' not valid')
return false;
}
if (binding == 'unbind') {
(this.options.binder).unbind('smartscroll.infscr.' + instance.options.infid);
} else {
(this.options.binder)[binding]('smartscroll.infscr.' + instance.options.infid, function () {
instance.scroll();
});
};
this._debug('Binding', binding);
},
// Fundamental aspects of the plugin are initialized
_create: function infscr_create(options, callback) {
// Add custom options to defaults
var opts = $.extend(true, {}, $.infinitescroll.defaults, options);
// Validate selectors
if (!this._validate(options)) { return false; }
this.options = opts;
// Validate page fragment path
var path = $(opts.nextSelector).attr('href');
if (!path) {
this._debug('Navigation selector not found');
return false;
}
opts.state.nextUrl = path ;
// contentSelector is 'page fragment' option for .load() / .ajax() calls
opts.contentSelector = opts.contentSelector || this.element;
// loading.selector - if we want to place the load message in a specific selector, defaulted to the contentSelector
opts.loading.selector = opts.loading.selector || opts.contentSelector;
// Define loading.msg
opts.loading.msg = $('<div id="infscr-loading"><img alt="Loading..." src="' + opts.loading.img + '" /><div>' + opts.loading.msgText + '</div></div>');
// Preload loading.img
(new Image()).src = opts.loading.img;
// distance from nav links to bottom
// computed as: height of the document + top offset of container - top offset of nav link
opts.pixelsFromNavToBottom = $(document).height() - $(opts.navSelector).offset().top;
// determine loading.start actions
opts.loading.start = opts.loading.start || function() {
$(opts.navSelector).hide();
opts.loading.msg
.appendTo(opts.loading.selector)
.show(opts.loading.speed, function () {
beginAjax(opts);
});
};
// determine loading.finished actions
opts.loading.finished = opts.loading.finished || function() {
// @imp: rjha changed : we move fadeOut inside masonry callback
// opts.loading.msg.fadeOut('normal');
};
// callback loading
opts.callback = function(instance,data) {
if (!!opts.behavior && instance['_callback_'+opts.behavior] !== undefined) {
instance['_callback_'+opts.behavior].call($(opts.contentSelector)[0], data);
}
if (callback) {
callback.call($(opts.contentSelector)[0], data, opts);
}
};
this._setup();
// Return true to indicate successful creation
return true;
},
// Console log wrapper
_debug: function infscr_debug() {
if (this.options && this.options.debug) {
return window.console && console.log.call(console, arguments);
}
},
// Custom error
_error: function infscr_error(xhr) {
var opts = this.options;
// if behavior is defined and this function is extended, call that instead of default
if (!!opts.behavior && this['_error_'+opts.behavior] !== undefined) {
this['_error_'+opts.behavior].call(this,xhr);
return;
}
if (xhr !== 'destroy' && xhr !== 'end') {
xhr = 'unknown';
}
this._debug('Error', xhr);
if (xhr == 'end') {
this._showdonemsg();
}
opts.state.isDone = true;
opts.state.currPage = 1; // if you need to go back to this instance
opts.state.isPaused = false;
this._binding('unbind');
},
// Load Callback
_loadcallback: function infscr_loadcallback(box, data) {
var opts = this.options,
callback = this.options.callback, // GLOBAL OBJECT FOR CALLBACK
result = (opts.state.isDone) ? 'done' : (!opts.appendCallback) ? 'no-append' : 'append',
frag;
// if behavior is defined and this function is extended, call that instead of default
if (!!opts.behavior && this['_loadcallback_'+opts.behavior] !== undefined) {
this['_loadcallback_'+opts.behavior].call(this,box,data);
return;
}
switch (result) {
case 'done':
this._showdonemsg();
return false;
break;
case 'no-append':
if (opts.dataType == 'html') {
data = '<div>' + data + '</div>';
data = $(data).find(opts.itemSelector);
}
break;
case 'append':
var children = box.children();
// if it didn't return anything
if (children.length == 0) {
return this._error('end');
}
// use a documentFragment because it works when content is going into a table or UL
frag = document.createDocumentFragment();
while (box[0].firstChild) {
frag.appendChild(box[0].firstChild);
}
this._debug('contentSelector', $(opts.contentSelector)[0])
$(opts.contentSelector)[0].appendChild(frag);
// previously, we would pass in the new DOM element as context for the callback
// however we're now using a documentfragment, which doesnt havent parents or children,
// so the context is the contentContainer guy, and we pass in an array
// of the elements collected as the first argument.
data = children.get();
break;
}
// loadingEnd function
opts.loading.finished.call($(opts.contentSelector)[0],opts)
// 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 () { opts.state.isDuringAjax = false; });
}
if (!opts.animate) opts.state.isDuringAjax = false; // once the call is done, we can allow it again.
callback(this,data);
},
_nearbottom: function infscr_nearbottom() {
var opts = this.options,
pixelsFromWindowBottomToBottom = 0 + $(document).height() - (opts.binder.scrollTop()) - $(window).height();
// if behavior is defined and this function is extended, call that instead of default
if (!!opts.behavior && this['_nearbottom_'+opts.behavior] !== undefined) {
return this['_nearbottom_'+opts.behavior].call(this);
}
this._debug('math:', pixelsFromWindowBottomToBottom, opts.pixelsFromNavToBottom);
// if distance remaining in the scroll (including buffer) is less than the orignal nav to bottom....
return (pixelsFromWindowBottomToBottom - opts.bufferPx < opts.pixelsFromNavToBottom);
},
// Pause / temporarily disable plugin from firing
_pausing: function infscr_pausing(pause) {
var opts = this.options;
// if behavior is defined and this function is extended, call that instead of default
if (!!opts.behavior && this['_pausing_'+opts.behavior] !== undefined) {
this['_pausing_'+opts.behavior].call(this,pause);
return;
}
// If pause is not 'pause' or 'resume', toggle it's value
if (pause !== 'pause' && pause !== 'resume' && pause !== null) {
this._debug('Invalid argument. Toggling pause value instead');
};
pause = (pause && (pause == 'pause' || pause == 'resume')) ? pause : 'toggle';
switch (pause) {
case 'pause':
opts.state.isPaused = true;
break;
case 'resume':
opts.state.isPaused = false;
break;
case 'toggle':
opts.state.isPaused = !opts.state.isPaused;
break;
}
this._debug('Paused', opts.state.isPaused);
return false;
},
// Behavior is determined
// If the behavior option is undefined, it will set to default and bind to scroll
_setup: function infscr_setup() {
var opts = this.options;
// if behavior is defined and this function is extended, call that instead of default
if (!!opts.behavior && this['_setup_'+opts.behavior] !== undefined) {
this['_setup_'+opts.behavior].call(this);
return;
}
this._binding('bind');
return false;
},
// Show done message
_showdonemsg: function infscr_showdonemsg() {
var opts = this.options;
// if behavior is defined and this function is extended, call that instead of default
if (!!opts.behavior && this['_showdonemsg_'+opts.behavior] !== undefined) {
this['_showdonemsg_'+opts.behavior].call(this);
return;
}
opts.loading.msg
.find('img')
.hide()
.parent()
.find('div').html(opts.loading.finishedMsg).animate({ opacity: 1 }, 2000, function () {
$(this).parent().fadeOut('normal');
});
// user provided callback when done
opts.errorCallback.call($(opts.contentSelector)[0],'done');
},
// grab each selector option and see if any fail
_validate: function infscr_validate(opts) {
for (var key in opts) {
if (key.indexOf && key.indexOf('Selector') > -1 && $(opts[key]).length === 0) {
this._debug('Your ' + key + ' found no elements.');
return false;
}
}
return true;
},
/*
----------------------------
Public methods
----------------------------
*/
// Bind to scroll
bind: function infscr_bind() {
this._binding('bind');
},
// Destroy current instance of plugin
destroy: function infscr_destroy() {
this.options.state.isDestroyed = true;
return this._error('destroy');
},
// Set pause value to false
pause: function infscr_pause() {
this._pausing('pause');
},
// Set pause value to false
resume: function infscr_resume() {
this._pausing('resume');
},
// Retrieve next set of content items
retrieve: function infscr_retrieve(pageNum) {
var instance = this,
opts = instance.options,
box, frag, desturl, method, condition,
pageNum = pageNum || null,
getPage = (!!pageNum) ? pageNum : opts.state.currPage;
if(typeof(opts.state.nextUrl) == 'undefined' ) {
instance._error('end');
}
beginAjax = function infscr_ajax(opts) {
// increment the current page
opts.state.currPage++;
// if we're dealing with a table we can't use DIVs
box = $(opts.contentSelector).is('table') ? $('<tbody/>') : $('<div/>');
desturl = opts.state.nextUrl;
instance._debug('heading into ajax', desturl);
/*
* Earlier the plugin was using jQuery load() method on box to retrieve page fragments
* (using url+space+selector trick and itemSelector filtering on returned document)
* box.load(url,callback) method was adding the page fragment as first child of box.
*
* so we also "simulate" that behavior. we find the nextUrl from page and then
* use append the page fragment inside box.
*
*
*/
$.ajax({
// params
url: desturl,
dataType: opts.dataType,
complete: function infscr_ajax_callback(jqXHR, textStatus) {
condition = (typeof (jqXHR.isResolved) !== 'undefined') ? (jqXHR.isResolved()) : (textStatus === "success" || textStatus === "notmodified");
if(condition) {
response = '<div>' + jqXHR.responseText + '</div>' ;
var pagerDom = $(response).find(opts.nextSelector) ;
if(pagerDom.length == 0 ) {
//not found
opts.state.nextUrl = undefined ;
} else {
opts.state.nextUrl = pagerDom.attr("href") ;
}
data = $(response).find(opts.itemSelector);
//Do the equivalent of box.load here
$(box).append(data);
instance._loadcallback(box,data) ;
} else {
instance._error('end');
}
}
});
};
// if behavior is defined and this function is extended, call that instead of default
if (!!opts.behavior && this['retrieve_'+opts.behavior] !== undefined) {
this['retrieve_'+opts.behavior].call(this,pageNum);
return;
}
// for manual triggers, if destroyed, get out of here
if (opts.state.isDestroyed) {
this._debug('Instance is destroyed');
return false;
};
// we dont want to fire the ajax multiple times
opts.state.isDuringAjax = true;
opts.loading.start.call($(opts.contentSelector)[0],opts);
},
// Check to see next page is needed
scroll: function infscr_scroll() {
var opts = this.options,
state = opts.state;
// if behavior is defined and this function is extended, call that instead of default
if (!!opts.behavior && this['scroll_'+opts.behavior] !== undefined) {
this['scroll_'+opts.behavior].call(this);
return;
}
if (state.isDuringAjax || state.isInvalidPage || state.isDone || state.isDestroyed || state.isPaused) return;
if (!this._nearbottom()) return;
this.retrieve();
},
// Toggle pause value
toggle: function infscr_toggle() {
this._pausing();
},
// Unbind from scroll
unbind: function infscr_unbind() {
this._binding('unbind');
},
// update options
update: function infscr_options(key) {
if ($.isPlainObject(key)) {
this.options = $.extend(true,this.options,key);
}
}
}
/*
----------------------------
Infinite Scroll function
----------------------------
Borrowed logic from the following...
jQuery UI
- https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js
jCarousel
- https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js
Masonry
- https://github.com/desandro/masonry/blob/master/jquery.masonry.js
*/
$.fn.infinitescroll = function infscr_init(options, callback) {
var thisCall = typeof options;
switch (thisCall) {
// method
case 'string':
var args = Array.prototype.slice.call(arguments, 1);
this.each(function () {
var instance = $.data(this, 'infinitescroll');
if (!instance) {
// not setup yet
// return $.error('Method ' + options + ' cannot be called until Infinite Scroll is setup');
return false;
}
if (!$.isFunction(instance[options]) || options.charAt(0) === "_") {
// return $.error('No such method ' + options + ' for Infinite Scroll');
return false;
}
// no errors!
instance[options].apply(instance, args);
});
break;
// creation
case 'object':
this.each(function () {
var instance = $.data(this, 'infinitescroll');
if (instance) {
// update options of current instance
instance.update(options);
} else {
// initialize new instance
instance = new $.infinitescroll(options, callback, this);
// don't attach if instantiation failed
if (!instance.failed) {
$.data(this, 'infinitescroll', instance);
}
}
});
break;
}
return this;
};
/*
* smartscroll: debounced scroll event for jQuery *
* https://github.com/lukeshumard/smartscroll
* Based on smartresize by @louis_remi: https://github.com/lrbabe/jquery.smartresize.js *
* Copyright 2011 Louis-Remi & Luke Shumard * Licensed under the MIT license. *
*/
var event = $.event,
scrollTimeout;
event.special.smartscroll = {
setup: function () {
$(this).bind("scroll", event.special.smartscroll.handler);
},
teardown: function () {
$(this).unbind("scroll", event.special.smartscroll.handler);
},
handler: function (event, execAsap) {
// Save the context
var context = this,
args = arguments;
// set correct event type
event.type = "smartscroll";
if (scrollTimeout) { clearTimeout(scrollTimeout); }
scrollTimeout = setTimeout(function () {
$.event.handle.apply(context, args);
}, execAsap === "execAsap" ? 0 : 100);
}
};
$.fn.smartscroll = function (fn) {
return fn ? this.bind("smartscroll", fn) : this.trigger("smartscroll", ["execAsap"]);
};
})(window, jQuery);
@ag-coder
Copy link

ag-coder commented Feb 4, 2013

thanks for this!
please can you update this script for jQuery 1.9.0?

@dozzed
Copy link

dozzed commented Jun 30, 2014

Thank you so much for this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment