/*
--------------------------------
infinite scroll
--------------------------------
+ https://github.com/paulirish/infinite-scroll
+ version 2.0b2.111027
+ copyright 2011 paul irish & luke shumard
+ licensed under the mit license
+ documentation: http://infinite-scroll.com/
*/
(function (window, $, undefined) {
$.infinitescroll = function infscr(options, callback, element) {
this.element = $(element);
this._create(options, callback);
};
$.infinitescroll.defaults = {
loading: {
finished: undefined,
finishedmsg: "congratulations, you've reached the end of the internet.",
img: "http://www.infinite-scroll.com/loading.gif",
msg: null,
msgtext: "loading the next set of posts...",
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
},
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) {
// if selectors from options aren't valid, return false
if (!this._validate(options)) {
return false;
}
// define options and shorthand
var opts = this.options = $.extend(true, {}, $.infinitescroll.defaults, options),
// get the relative url - everything past the domain name.
relurl = /(.*?\/\/).*?(\/.*)/,
path = $(opts.nextselector).attr('href');
// 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;
// if there's not path, return
if (!path) {
this._debug('navigation selector not found');
return;
}
// set the path to be a relative url from root.
opts.path = this._determinepath(path);
// define loading.msg
opts.loading.msg = $('
' + opts.loading.msgtext + '
');
// 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() {
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();
},
// console log wrapper
_debug: function infscr_debug() {
if (this.options && this.options.debug) {
return window.console && console.log.call(console, arguments);
}
},
// find the number to increment in the path.
_determinepath: function infscr_determinepath(path) {
var opts = this.options;
// if behavior is defined and this function is extended, call that instead of default
if (!!opts.behavior && this['_determinepath_'+opts.behavior] !== undefined) {
this['_determinepath_'+opts.behavior].call(this,path);
return;
}
if (!!opts.pathparse) {
this._debug('pathparse manual');
return opts.pathparse(path, this.options.state.currpage+1);
} else if (path.match(/^(.*?)\b2\b(.*?$)/)) {
path = path.match(/^(.*?)\b2\b(.*?$)/).slice(1);
// if there is any 2 in the url at all.
} else if (path.match(/^(.*?)2(.*?$)/)) {
// page= is used in django:
// http://www.infinite-scroll.com/changelog/comment-page-1/#comment-127
if (path.match(/^(.*?page=)2(\/.*|$)/)) {
path = path.match(/^(.*?page=)2(\/.*|$)/).slice(1);
return path;
}
path = path.match(/^(.*?)2(.*?$)/).slice(1);
} else {
// page= is used in drupal too but second page is page=1 not page=2:
// thx jerod fritz, vladikoff
if (path.match(/^(.*?page=)1(\/.*|$)/)) {
path = path.match(/^(.*?page=)1(\/.*|$)/).slice(1);
return path;
} else {
this._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.');
// get rid of isinvalidpage to allow permalink to state
opts.state.isinvalidpage = true; //prevent it from running on this page.
}
}
this._debug('determinepath', path);
return path;
},
// 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 = '' + data + '
';
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,
path = opts.path,
box, frag, desturl, method, condition,
pagenum = pagenum || null,
getpage = (!!pagenum) ? pagenum : opts.state.currpage;
beginajax = function infscr_ajax(opts) {
// increment the url bit. e.g. /page/3/
opts.state.currpage++;
instance._debug('heading into ajax', path);
// if we're dealing with a table we can't use divs
box = $(opts.contentselector).is('table') ? $('') : $('');
desturl = path.join(opts.state.currpage);
method = (opts.datatype == 'html' || opts.datatype == 'json') ? opts.datatype : 'html+callback';
if (opts.appendcallback && opts.datatype == 'html') method += '+callback'
switch (method) {
case 'html+callback':
instance._debug('using html via .load() method');
box.load(desturl + ' ' + opts.itemselector, null, function infscr_ajax_callback(responsetext) {
instance._loadcallback(box, responsetext);
});
break;
case 'html':
case 'json':
instance._debug('using ' + (method.touppercase()) + ' via $.ajax() method');
$.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");
(condition) ? instance._loadcallback(box, jqxhr.responsetext) : instance._error('end');
}
});
break;
}
};
// 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
$.data(this, 'infinitescroll', new $.infinitescroll(options, callback, this));
}
});
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"]);
};
/* 閰风珯浠g爜鏁寸悊 http://www.5icool.org */
})(window, jquery);