diff options
Diffstat (limited to 'catalog-ui/app/third-party/ng-infinite-scroll/src/infinite-scroll.coffee')
-rw-r--r-- | catalog-ui/app/third-party/ng-infinite-scroll/src/infinite-scroll.coffee | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/catalog-ui/app/third-party/ng-infinite-scroll/src/infinite-scroll.coffee b/catalog-ui/app/third-party/ng-infinite-scroll/src/infinite-scroll.coffee new file mode 100644 index 0000000000..cf2f90fc19 --- /dev/null +++ b/catalog-ui/app/third-party/ng-infinite-scroll/src/infinite-scroll.coffee @@ -0,0 +1,209 @@ +angular.module('infinite-scroll', []) + .value('THROTTLE_MILLISECONDS', null) + .directive 'infiniteScroll', [ + '$rootScope', '$window', '$interval', 'THROTTLE_MILLISECONDS', +($rootScope, $window, $interval, THROTTLE_MILLISECONDS) -> + scope: + infiniteScroll: '&' + infiniteScrollContainer: '=' + infiniteScrollDistance: '=' + infiniteScrollDisabled: '=' + infiniteScrollUseDocumentBottom: '=', + infiniteScrollListenForEvent: '@' + + link: (scope, elem, attrs) -> + windowElement = angular.element($window) + + scrollDistance = null + scrollEnabled = null + checkWhenEnabled = null + container = null + immediateCheck = true + useDocumentBottom = false + unregisterEventListener = null + checkInterval = false + + height = (elem) -> + elem = elem[0] or elem + + if isNaN(elem.offsetHeight) then elem.document.documentElement.clientHeight else elem.offsetHeight + + offsetTop = (elem) -> + if not elem[0].getBoundingClientRect or elem.css('none') + return + + elem[0].getBoundingClientRect().top + pageYOffset(elem) + + pageYOffset = (elem) -> + elem = elem[0] or elem + + if isNaN(window.pageYOffset) then elem.document.documentElement.scrollTop else elem.ownerDocument.defaultView.pageYOffset + + # infinite-scroll specifies a function to call when the window, + # or some other container specified by infinite-scroll-container, + # is scrolled within a certain range from the bottom of the + # document. It is recommended to use infinite-scroll-disabled + # with a boolean that is set to true when the function is + # called in order to throttle the function call. + handler = -> + if container == windowElement + containerBottom = height(container) + pageYOffset(container[0].document.documentElement) + elementBottom = offsetTop(elem) + height(elem) + else + containerBottom = height(container) + containerTopOffset = 0 + if offsetTop(container) != undefined + containerTopOffset = offsetTop(container) + elementBottom = offsetTop(elem) - containerTopOffset + height(elem) + + if(useDocumentBottom) + elementBottom = height((elem[0].ownerDocument || elem[0].document).documentElement) + + remaining = elementBottom - containerBottom + shouldScroll = remaining <= height(container) * scrollDistance + 1 + + if shouldScroll + checkWhenEnabled = true + + if scrollEnabled + if scope.$$phase || $rootScope.$$phase + scope.infiniteScroll() + else + scope.$apply(scope.infiniteScroll) + else + if checkInterval then $interval.cancel checkInterval + checkWhenEnabled = false + + # The optional THROTTLE_MILLISECONDS configuration value specifies + # a minimum time that should elapse between each call to the + # handler. N.b. the first call the handler will be run + # immediately, and the final call will always result in the + # handler being called after the `wait` period elapses. + # A slimmed down version of underscore's implementation. + throttle = (func, wait) -> + timeout = null + previous = 0 + later = -> + previous = new Date().getTime() + $interval.cancel(timeout) + timeout = null + func.call() + + return -> + now = new Date().getTime() + remaining = wait - (now - previous) + if remaining <= 0 + $interval.cancel(timeout) + timeout = null + previous = now + func.call() + else timeout = $interval(later, remaining, 1) unless timeout + + if THROTTLE_MILLISECONDS? + handler = throttle(handler, THROTTLE_MILLISECONDS) + + scope.$on '$destroy', -> + container.unbind 'scroll', handler + if unregisterEventListener? + unregisterEventListener() + unregisterEventListener = null + if checkInterval + $interval.cancel checkInterval + + # infinite-scroll-distance specifies how close to the bottom of the page + # the window is allowed to be before we trigger a new scroll. The value + # provided is multiplied by the container height; for example, to load + # more when the bottom of the page is less than 3 container heights away, + # specify a value of 3. Defaults to 0. + handleInfiniteScrollDistance = (v) -> + scrollDistance = parseFloat(v) or 0 + + scope.$watch 'infiniteScrollDistance', handleInfiniteScrollDistance + # If I don't explicitly call the handler here, tests fail. Don't know why yet. + handleInfiniteScrollDistance scope.infiniteScrollDistance + + # infinite-scroll-disabled specifies a boolean that will keep the + # infnite scroll function from being called; this is useful for + # debouncing or throttling the function call. If an infinite + # scroll is triggered but this value evaluates to true, then + # once it switches back to false the infinite scroll function + # will be triggered again. + handleInfiniteScrollDisabled = (v) -> + scrollEnabled = !v + if scrollEnabled && checkWhenEnabled + checkWhenEnabled = false + handler() + + scope.$watch 'infiniteScrollDisabled', handleInfiniteScrollDisabled + # If I don't explicitly call the handler here, tests fail. Don't know why yet. + handleInfiniteScrollDisabled scope.infiniteScrollDisabled + + # use the bottom of the document instead of the element's bottom. + # This useful when the element does not have a height due to its + # children being absolute positioned. + handleInfiniteScrollUseDocumentBottom = (v) -> + useDocumentBottom = v + + scope.$watch 'infiniteScrollUseDocumentBottom', handleInfiniteScrollUseDocumentBottom + handleInfiniteScrollUseDocumentBottom scope.infiniteScrollUseDocumentBottom + + # infinite-scroll-container sets the container which we want to be + # infinte scrolled, instead of the whole window. Must be an + # Angular or jQuery element, or, if jQuery is loaded, + # a jQuery selector as a string. + changeContainer = (newContainer) -> + if container? + container.unbind 'scroll', handler + + container = newContainer + if newContainer? + container.bind 'scroll', handler + + changeContainer windowElement + + if scope.infiniteScrollListenForEvent + unregisterEventListener = $rootScope.$on scope.infiniteScrollListenForEvent, handler + + handleInfiniteScrollContainer = (newContainer) -> + # TODO: For some reason newContainer is sometimes null instead + # of the empty array, which Angular is supposed to pass when the + # element is not defined + # (https://github.com/sroze/ngInfiniteScroll/pull/7#commitcomment-5748431). + # So I leave both checks. + if (not newContainer?) or newContainer.length == 0 + return + + if newContainer.nodeType && newContainer.nodeType == 1 + newContainer = angular.element newContainer + else if typeof newContainer.append == 'function' + newContainer = angular.element newContainer[newContainer.length - 1] + else if typeof newContainer == 'string' + newContainer = angular.element document.querySelector newContainer + + if newContainer? + changeContainer newContainer + else + throw new Error("invalid infinite-scroll-container attribute.") + + scope.$watch 'infiniteScrollContainer', handleInfiniteScrollContainer + handleInfiniteScrollContainer(scope.infiniteScrollContainer or []) + + # infinite-scroll-parent establishes this element's parent as the + # container infinitely scrolled instead of the whole window. + if attrs.infiniteScrollParent? + changeContainer angular.element elem.parent() + + # infinte-scoll-immediate-check sets whether or not run the + # expression passed on infinite-scroll for the first time when the + # directive first loads, before any actual scroll. + if attrs.infiniteScrollImmediateCheck? + immediateCheck = scope.$eval(attrs.infiniteScrollImmediateCheck) + + checkInterval = $interval (-> + if immediateCheck + handler() + $interval.cancel checkInterval + ) +] +if typeof module != "undefined" && typeof exports != "undefined" && module.exports == exports + module.exports = 'infinite-scroll' |