Forked from rjha/jquery.infinitescroll.hacked.js
Last active
December 13, 2015 18:08
-
-
Save ag-coder/4953029 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
-------------------------------- | |
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 | |
============================================================= | |
+ jquery 1.9.1 fix | |
*/ | |
(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.dispatch.apply(context, args); | |
}, execAsap === "execAsap" ? 0 : 100); | |
} | |
}; | |
$.fn.smartscroll = function (fn) { | |
return fn ? this.bind("smartscroll", fn) : this.trigger("smartscroll", ["execAsap"]); | |
}; | |
})(window, jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment