/* -------------------------------- 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 = $('
loading...
' + 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);