1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
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'
|