Skip to content

Instantly share code, notes, and snippets.

@manekinekko
Last active August 10, 2016 20:51
Show Gist options
  • Save manekinekko/304f5d74d05f857064f091ec61661442 to your computer and use it in GitHub Desktop.
Save manekinekko/304f5d74d05f857064f091ec61661442 to your computer and use it in GitHub Desktop.
ngInfiniteScroll is a directive for AngularJS to evaluate an expression when the bottom of the directive's element approaches the bottom of the browser window, which can be used to implement infinite scrolling.
/* ng-infinite-scroll - v1.1.2 - 2014-05-21 */
/* original code: https://github.com/sroze/ngInfiniteScroll/issues/69#issuecomment-51181292 */
var mod;
mod = angular.module('infinite-scroll', []);
mod.value('THROTTLE_MILLISECONDS', null);
mod.value('THROTTLE_DISTANCE', 20);
mod.directive('infiniteScroll', [
'$rootScope', '$window', '$timeout', 'THROTTLE_MILLISECONDS', 'THROTTLE_DISTANCE', function($rootScope, $window, $timeout, THROTTLE_MILLISECONDS, THROTTLE_DISTANCE) {
return {
scope: {
infiniteScroll: '&',
infiniteScrollContainer: '=',
infiniteScrollDistance: '=',
infiniteScrollDisabled: '=',
infiniteScrollUseDocumentBottom: '=',
infiniteScrollHorizontal: '=',
infiniteScrollCallMax: '='
},
link: function(scope, elem, attrs) {
var changeContainer, checkWhenEnabled, container, handleInfiniteScrollContainer, handleInfiniteScrollDisabled, handleInfiniteScrollDistance, handleInfiniteScrollUseDocumentBottom, handleInfiniteScrollHorizontal, handler, immediateCheck, scrollDistance, scrollEnabled, throttle, useDocumentBottom, scrollHorizontal;
$window = angular.element($window);
scrollDistance = null;
scrollEnabled = null;
checkWhenEnabled = null;
container = null;
immediateCheck = true;
useDocumentBottom = false;
var handleInfiniteScrollCallMax; // attr
var infiniteScrollCallMax = NaN; // default: no limit
var scrollCallCounter = 0; // internal counter
handler = function() {
var containerBottom, containerTopOffset, elementBottom, remaining, shouldScroll;
if (container === $window) {
if (scrollHorizontal) {
containerBottom = container.width() + container.scrollLeft();
elementBottom = elem.offset().left + elem.width();
} else {
containerBottom = container.height() + container.scrollTop();
elementBottom = elem.offset().top + elem.height();
}
} else {
if (scrollHorizontal) {
containerBottom = container.width();
} else {
containerBottom = container.height();
}
containerTopOffset = 0;
if (container.offset() !== void 0) {
if (scrollHorizontal) {
containerTopOffset = container.offset().left;
} else {
containerTopOffset = container.offset().top;
}
}
if (scrollHorizontal) {
elementBottom = elem.offset().left - containerTopOffset + elem.width();
} else {
elementBottom = elem.offset().top - containerTopOffset + elem.height();
}
}
if (useDocumentBottom) {
if (scrollHorizontal) {
elementBottom = $(document).width();
} else {
elementBottom = $(document).height();
}
}
remaining = elementBottom - containerBottom;
if (scrollHorizontal) {
shouldScroll = remaining <= container.width() * scrollDistance + 1;
} else {
shouldScroll = remaining <= container.height() * scrollDistance + 1;
}
if(remaining > THROTTLE_DISTANCE) {
scrollCallCounter = 0;
}
if( !isNaN(infiniteScrollCallMax) && scrollCallCounter >= infiniteScrollCallMax) {
shouldScroll = false
}
if (shouldScroll) {
checkWhenEnabled = true;
if (scrollEnabled) {
scrollCallCounter += 1;
if (scope.$$phase || $rootScope.$$phase) {
return scope.infiniteScroll();
} else {
return scope.$apply(scope.infiniteScroll);
}
}
} else {
return checkWhenEnabled = false;
}
};
throttle = function(func, wait) {
var later, previous, timeout;
timeout = null;
previous = 0;
later = function() {
var context;
previous = new Date().getTime();
$timeout.cancel(timeout);
timeout = null;
func.call();
return context = null;
};
return function() {
var now, remaining;
now = new Date().getTime();
remaining = wait - (now - previous);
if (remaining <= 0) {
clearTimeout(timeout);
$timeout.cancel(timeout);
timeout = null;
previous = now;
return func.call();
} else {
if (!timeout) {
return timeout = $timeout(later, remaining);
}
}
};
};
if (THROTTLE_MILLISECONDS != null) {
handler = throttle(handler, THROTTLE_MILLISECONDS);
}
scope.$on('$destroy', function() {
return container.off('scroll', handler);
});
handleInfiniteScrollHorizontal = function (v) {
scrollHorizontal = v;
};
scope.$watch('infiniteScrollHorizontal', handleInfiniteScrollHorizontal);
handleInfiniteScrollHorizontal(scope.infiniteScrollHorizontal);
handleInfiniteScrollCallMax = function (v) {
if(v) {
infiniteScrollCallMax = v;
}
};
scope.$watch('infiniteScrollCallMax', handleInfiniteScrollCallMax);
handleInfiniteScrollCallMax(parseInt(scope.infiniteScrollCallMax || NaN, 10));
handleInfiniteScrollDistance = function(v) {
return scrollDistance = parseInt(v, 10) || 0;
};
scope.$watch('infiniteScrollDistance', handleInfiniteScrollDistance);
handleInfiniteScrollDistance(scope.infiniteScrollDistance);
handleInfiniteScrollDisabled = function(v) {
scrollEnabled = !v;
if (scrollEnabled && checkWhenEnabled) {
checkWhenEnabled = false;
return handler();
}
};
scope.$watch('infiniteScrollDisabled', handleInfiniteScrollDisabled);
handleInfiniteScrollDisabled(scope.infiniteScrollDisabled);
handleInfiniteScrollUseDocumentBottom = function(v) {
return useDocumentBottom = v;
};
scope.$watch('infiniteScrollUseDocumentBottom', handleInfiniteScrollUseDocumentBottom);
handleInfiniteScrollUseDocumentBottom(scope.infiniteScrollUseDocumentBottom);
changeContainer = function(newContainer) {
if (container != null) {
container.off('scroll', handler);
}
container = typeof newContainer.last === 'function' && newContainer !== $window ? newContainer.last() : newContainer;
if (newContainer != null) {
return container.on('scroll', handler);
}
};
changeContainer($window);
handleInfiniteScrollContainer = function(newContainer) {
if ((!(newContainer != null)) || newContainer.length === 0) {
return;
}
newContainer = angular.element(newContainer);
if (newContainer != null) {
return changeContainer(newContainer);
} else {
throw new Exception("invalid infinite-scroll-container attribute.");
}
};
scope.$watch('infiniteScrollContainer', handleInfiniteScrollContainer);
handleInfiniteScrollContainer(scope.infiniteScrollContainer || []);
if (attrs.infiniteScrollParent != null) {
changeContainer(angular.element(elem.parent()));
}
if (attrs.infiniteScrollImmediateCheck != null) {
immediateCheck = scope.$eval(attrs.infiniteScrollImmediateCheck);
}
return $timeout((function() {
if (immediateCheck) {
return handler();
}
}), 0);
}
};
}
]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment