You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

761 lines
20 KiB

/*!
* Infinite Ajax Scroll, a jQuery plugin
* Version 1.0.2
* https://github.com/webcreate/infinite-ajax-scroll
*
* Copyright (c) 2011-2013 Jeroen Fiege
* Licensed under MIT:
* https://raw.github.com/webcreate/infinite-ajax-scroll/master/MIT-LICENSE.txt
*/
(function ($) {
'use strict';
Date.now = Date.now || function () { return +new Date(); };
$.ias = function (options)
{
// setup
var opts = $.extend({}, $.ias.defaults, options);
var util = new $.ias.util(); // utilities module
var paging = new $.ias.paging(opts.scrollContainer); // paging module
var hist = (opts.history ? new $.ias.history() : false); // history module
var _self = this;
/**
* Initialize
*
* - tracks scrolling through pages
* - remembers current page with the history module
* - setup scroll event and hides pagination element
* - loads and scrolls to previous page when we have something in our history
*
* @return self
*/
function init()
{
var pageNum;
// track page number changes
paging.onChangePage(function (pageNum, scrollOffset, pageUrl) {
if (hist) {
hist.setPage(pageNum, pageUrl);
}
// call onPageChange event
opts.onPageChange.call(this, pageNum, pageUrl, scrollOffset);
});
// setup scroll and hide pagination
reset();
// load and scroll to previous page
if (hist && hist.havePage()) {
stop_scroll();
pageNum = hist.getPage();
util.forceScrollTop(function () {
var curThreshold;
if (pageNum > 1) {
paginateToPage(pageNum);
curThreshold = get_scroll_threshold(true);
$('html, body').scrollTop(curThreshold);
}
else {
reset();
}
});
}
return _self;
}
// initialize
init();
/**
* Reset scrolling and hide pagination links
*
* @return void
*/
function reset()
{
hide_pagination();
opts.scrollContainer.scroll(scroll_handler);
}
/**
* Scroll event handler
*
* @return void
*/
function scroll_handler()
{
var curScrOffset,
scrThreshold;
curScrOffset = util.getCurrentScrollOffset(opts.scrollContainer);
scrThreshold = get_scroll_threshold();
if (curScrOffset >= scrThreshold) {
if (get_current_page() >= opts.triggerPageThreshold) {
stop_scroll();
show_trigger(function () {
paginate(curScrOffset);
});
}
else {
paginate(curScrOffset);
}
}
}
/**
* Cancel scrolling
*
* @return void
*/
function stop_scroll()
{
opts.scrollContainer.unbind('scroll', scroll_handler);
}
/**
* Hide pagination
*
* @return void
*/
function hide_pagination()
{
$(opts.pagination).hide();
}
/**
* Get scroll threshold based on the last item element
*
* @param boolean pure indicates if the thresholdMargin should be applied
* @return integer threshold
*/
function get_scroll_threshold(pure)
{
var el,
threshold;
el = $(opts.container).find(opts.item).last();
if (el.size() === 0) {
return 0;
}
threshold = el.offset().top + el.height();
if (!pure) {
threshold += opts.thresholdMargin;
}
return threshold;
}
/**
* Load the items from the next page.
*
* @param int curScrOffset current scroll offset
* @param function onCompleteHandler callback function
* @return void
*/
function paginate(curScrOffset, onCompleteHandler)
{
var urlNextPage;
urlNextPage = $(opts.next).attr('href');
if (!urlNextPage) {
if (opts.noneleft) {
$(opts.container).find(opts.item).last().after(opts.noneleft);
}
return stop_scroll();
}
if (opts.beforePageChange && $.isFunction(opts.beforePageChange)) {
if (opts.beforePageChange(curScrOffset, urlNextPage) === false) {
return;
}
}
paging.pushPages(curScrOffset, urlNextPage);
stop_scroll();
show_loader();
loadItems(urlNextPage, function (data, items) {
// call the onLoadItems callback
var result = opts.onLoadItems.call(this, items),
curLastItem;
if (result !== false) {
$(items).hide(); // at first, hide it so we can fade it in later
// insert them after the last item with a nice fadeIn effect
curLastItem = $(opts.container).find(opts.item).last();
curLastItem.after(items);
$(items).fadeIn();
}
urlNextPage = $(opts.next, data).attr('href');
// update pagination
$(opts.pagination).replaceWith($(opts.pagination, data));
remove_loader();
hide_pagination();
if (urlNextPage) {
reset();
}
else {
stop_scroll();
}
// call the onRenderComplete callback
opts.onRenderComplete.call(this, items);
if (onCompleteHandler) {
onCompleteHandler.call(this);
}
});
}
/**
* Loads items from certain url, triggers
* onComplete handler when finished
*
* @param string the url to load
* @param function the callback function
* @param int minimal time the loading should take, defaults to $.ias.default.loaderDelay
* @return void
*/
function loadItems(url, onCompleteHandler, delay)
{
var items = [],
container,
startTime = Date.now(),
diffTime,
self;
delay = delay || opts.loaderDelay;
$.get(url, null, function (data) {
// walk through the items on the next page
// and add them to the items array
container = $(opts.container, data).eq(0);
if (0 === container.length) {
// incase the element is a root element (body > element),
// try to filter it
container = $(data).filter(opts.container).eq(0);
}
if (container) {
container.find(opts.item).each(function () {
items.push(this);
});
}
if (onCompleteHandler) {
self = this;
diffTime = Date.now() - startTime;
if (diffTime < delay) {
setTimeout(function () {
onCompleteHandler.call(self, data, items);
}, delay - diffTime);
} else {
onCompleteHandler.call(self, data, items);
}
}
}, 'html');
}
/**
* Paginate to a certain page number.
*
* - keeps paginating till the pageNum is reached
*
* @return void
*/
function paginateToPage(pageNum)
{
var curThreshold = get_scroll_threshold(true);
if (curThreshold > 0) {
paginate(curThreshold, function () {
stop_scroll();
if ((paging.getCurPageNum(curThreshold) + 1) < pageNum) {
paginateToPage(pageNum);
$('html,body').animate({'scrollTop': curThreshold}, 400, 'swing');
}
else {
$('html,body').animate({'scrollTop': curThreshold}, 1000, 'swing');
reset();
}
});
}
}
function get_current_page()
{
var curScrOffset = util.getCurrentScrollOffset(opts.scrollContainer);
return paging.getCurPageNum(curScrOffset);
}
/**
* Return the active loader or creates a new loader
*
* @return object loader jquery object
*/
function get_loader()
{
var loader = $('.ias_loader');
if (loader.size() === 0) {
loader = $('<div class="ias_loader">' + opts.loader + '</div>');
loader.hide();
}
return loader;
}
/**
* Inserts the loader and does a fadeIn.
*
* @return void
*/
function show_loader()
{
var loader = get_loader(),
el;
if (opts.customLoaderProc !== false) {
opts.customLoaderProc(loader);
} else {
el = $(opts.container).find(opts.item).last();
el.after(loader);
loader.fadeIn();
}
}
/**
* Removes the loader.
*
* return void
*/
function remove_loader()
{
var loader = get_loader();
loader.remove();
}
/**
* Return the active trigger or creates a new trigger
*
* @return object trigger jquery object
*/
function get_trigger(callback)
{
var trigger = $('.ias_trigger');
if (trigger.size() === 0) {
trigger = $('<div class="ias_trigger"><a href="#">' + opts.trigger + '</a></div>');
trigger.hide();
}
$('a', trigger)
.off('click')
.on('click', function () { remove_trigger(); callback.call(); return false; })
;
return trigger;
}
/**
* @param function callback of the trigger (get's called onClick)
*/
function show_trigger(callback)
{
var trigger = get_trigger(callback),
el;
el = $(opts.container).find(opts.item).last();
el.after(trigger);
trigger.fadeIn();
}
/**
* Removes the trigger.
*
* return void
*/
function remove_trigger()
{
var trigger = get_trigger();
trigger.remove();
}
};
// plugin defaults
$.ias.defaults = {
container: '#container',
scrollContainer: $(window),
item: '.item',
pagination: '#pagination',
next: '.next',
noneleft: false,
loader: '<img src="images/loader.gif"/>',
loaderDelay: 600,
triggerPageThreshold: 3,
trigger: 'Load more items',
thresholdMargin: 0,
history : true,
onPageChange: function () {},
beforePageChange: function () {},
onLoadItems: function () {},
onRenderComplete: function () {},
customLoaderProc: false
};
// utility module
$.ias.util = function ()
{
// setup
var wndIsLoaded = false;
var forceScrollTopIsCompleted = false;
var self = this;
/**
* Initialize
*
* @return void
*/
function init()
{
$(window).load(function () {
wndIsLoaded = true;
});
}
// initialize
init();
/**
* Force browsers to scroll to top.
*
* - When you hit back in you browser, it automatically scrolls
* back to the last position. There is no way to stop this
* in a nice way, so this function does it the hard way.
*
* @param function onComplete callback function
* @return void
*/
this.forceScrollTop = function (onCompleteHandler)
{
$('html,body').scrollTop(0);
if (!forceScrollTopIsCompleted) {
if (!wndIsLoaded) {
setTimeout(function () {self.forceScrollTop(onCompleteHandler); }, 1);
} else {
onCompleteHandler.call();
forceScrollTopIsCompleted = true;
}
}
};
this.getCurrentScrollOffset = function (container)
{
var scrTop,
wndHeight;
// the way we calculate if we have to load the next page depends on which container we have
if (container.get(0) === window) {
scrTop = container.scrollTop();
} else {
scrTop = container.offset().top;
}
wndHeight = container.height();
return scrTop + wndHeight;
};
};
// paging module
$.ias.paging = function ()
{
// setup
var pagebreaks = [[0, document.location.toString()]];
var changePageHandler = function () {};
var lastPageNum = 1;
var util = new $.ias.util();
/**
* Initialize
*
* @return void
*/
function init()
{
$(window).scroll(scroll_handler);
}
// initialize
init();
/**
* Scroll handler
*
* - Triggers changePage event
*
* @return void
*/
function scroll_handler()
{
var curScrOffset,
curPageNum,
curPagebreak,
scrOffset,
urlPage;
curScrOffset = util.getCurrentScrollOffset($(window));
curPageNum = getCurPageNum(curScrOffset);
curPagebreak = getCurPagebreak(curScrOffset);
if (lastPageNum !== curPageNum) {
scrOffset = curPagebreak[0];
urlPage = curPagebreak[1];
changePageHandler.call({}, curPageNum, scrOffset, urlPage); // @todo fix for window height
}
lastPageNum = curPageNum;
}
/**
* Returns current page number based on scroll offset
*
* @param int scroll offset
* @return int current page number
*/
function getCurPageNum(scrollOffset)
{
for (var i = (pagebreaks.length - 1); i > 0; i--) {
if (scrollOffset > pagebreaks[i][0]) {
return i + 1;
}
}
return 1;
}
/**
* Public function for getCurPageNum
*
* @param int scrollOffset defaulst to the current
* @return int current page number
*/
this.getCurPageNum = function (scrollOffset)
{
scrollOffset = scrollOffset || util.getCurrentScrollOffset($(window));
return getCurPageNum(scrollOffset);
};
/**
* Returns current pagebreak information based on scroll offset
*
* @param int scroll offset
* @return array pagebreak information
*/
function getCurPagebreak(scrollOffset)
{
for (var i = (pagebreaks.length - 1); i >= 0; i--) {
if (scrollOffset > pagebreaks[i][0]) {
return pagebreaks[i];
}
}
return null;
}
/**
* Sets onchangePage event handler
*
* @param function event handler
* @return void
*/
this.onChangePage = function (fn)
{
changePageHandler = fn;
};
/**
* pushes the pages tracker
*
* @param int scroll offset for the new page
* @return void
*/
this.pushPages = function (scrollOffset, urlNextPage)
{
pagebreaks.push([scrollOffset, urlNextPage]);
};
};
// history module
$.ias.history = function ()
{
// setup
var isPushed = false;
var isHtml5 = false;
/**
* Initialize
*
* @return void
*/
function init()
{
isHtml5 = !!(window.history && history.pushState && history.replaceState);
isHtml5 = false; // html5 functions disabled due to problems in chrome
}
// initialize
init();
/**
* Sets page to history
*
* @return void;
*/
this.setPage = function (pageNum, pageUrl)
{
this.updateState({page : pageNum}, '', pageUrl);
};
/**
* Checks if we have a page set in the history
*
* @return bool returns true when we have a previous page, false otherwise
*/
this.havePage = function ()
{
return (this.getState() !== false);
};
/**
* Gets the previous page from history
*
* @return int page number of previous page
*/
this.getPage = function ()
{
var stateObj;
if (this.havePage()) {
stateObj = this.getState();
return stateObj.page;
}
return 1;
};
/**
* Returns current state
*
* @return object stateObj
*/
this.getState = function ()
{
var haveState,
stateObj,
pageNum;
if (isHtml5) {
stateObj = history.state;
if (stateObj && stateObj.ias) {
return stateObj.ias;
}
}
else {
haveState = (window.location.hash.substring(0, 7) === '#/page/');
if (haveState) {
pageNum = parseInt(window.location.hash.replace('#/page/', ''), 10);
return { page : pageNum };
}
}
return false;
};
/**
* Pushes state when not pushed already, otherwise
* replaces the state.
*
* @param obj stateObj
* @param string title
* @param string url
* @return void
*/
this.updateState = function (stateObj, title, url)
{
if (isPushed) {
this.replaceState(stateObj, title, url);
}
else {
this.pushState(stateObj, title, url);
}
};
/**
* Pushes state to history.
*
* @param obj stateObj
* @param string title
* @param string url
* @return void
*/
this.pushState = function (stateObj, title, url)
{
var hash;
if (isHtml5) {
history.pushState({ ias : stateObj }, title, url);
}
else {
hash = (stateObj.page > 0 ? '#/page/' + stateObj.page : '');
window.location.hash = hash;
}
isPushed = true;
};
/**
* Replaces current history state.
*
* @param obj stateObj
* @param string title
* @param string url
* @return void
*/
this.replaceState = function (stateObj, title, url)
{
if (isHtml5) {
history.replaceState({ ias : stateObj }, title, url);
}
else {
this.pushState(stateObj, title, url);
}
};
};
})(jQuery);