Skip to content

Commit ab8974a

Browse files
committed
refactoring: clipTop, clipBottom, adjustPadding, adjustScrollTop
1 parent f3ed72d commit ab8974a

File tree

1 file changed

+66
-123
lines changed

1 file changed

+66
-123
lines changed

src/ui-scroll.coffee

Lines changed: 66 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,12 @@ angular.module('ui.scroll', [])
6969
wrapper.scope.$destroy()
7070
]
7171

72-
Buffer = (itemName, $scope, linker, datasource)->
72+
Buffer = (itemName, $scope, linker, datasource, bufferSize)->
7373

7474
buffer = Object.create Array.prototype
7575

76+
buffer.size = bufferSize
77+
7678
# inserts wrapped element in the buffer
7779
# the first argument is either operation keyword (see below) or a number for operation 'insert'
7880
# for insert the number is the index for the buffer element the new one have to be inserted after
@@ -108,20 +110,20 @@ angular.module('ui.scroll', [])
108110
buffer.splice buffer.indexOf(arg1), 1
109111
removeElementAnimated arg1
110112

111-
#clears the buffer
112-
buffer.clear = ->
113-
buffer.remove(0, buffer.length)
113+
reset = ->
114114
buffer.eof = false
115115
buffer.bof = false
116116
buffer.first = 1
117117
buffer.next = 1
118118

119-
buffer.clear()
120-
121-
buffer.minIndex = -> datasource.minIndex || 0
122-
123-
buffer.maxIndex = -> datasource.maxIndex || 0
119+
# clears the buffer
120+
buffer.clear = ->
121+
buffer.remove(0, buffer.length)
122+
reset()
124123

124+
reset()
125+
buffer.minIndex = -> datasource.minIndex || 1
126+
buffer.maxIndex = -> datasource.maxIndex || 1
125127
buffer
126128

127129
Padding = (template) ->
@@ -146,31 +148,24 @@ angular.module('ui.scroll', [])
146148
viewport.css({'overflow-y': 'auto', 'display': 'block'})
147149

148150
topPadding = null
149-
150151
bottomPadding = null
151152

152153
bufferPadding = -> viewport.outerHeight() * Math.max(0.1, +padding || 0.1) # some extra space to initiate preload
153154

154155
viewport.createPaddingElements = (template) ->
155-
156156
topPadding = new Padding template
157-
element.before topPadding
158-
#viewport.topPadding = -> topPadding.height.apply(topPadding, arguments)
159-
160157
bottomPadding = new Padding template
158+
element.before topPadding
161159
element.after bottomPadding
162-
#viewport.bottomPadding = -> bottomPadding.height.apply(bottomPadding, arguments)
163160

164161
viewport.bottomDataPos = ->
165162
(viewport[0].scrollHeight ? viewport[0].document.documentElement.scrollHeight) - bottomPadding.height()
166163

167164
viewport.topDataPos = -> topPadding.height()
168165

169-
viewport.bottomVisiblePos = ->
170-
viewport.scrollTop() + viewport.outerHeight()
166+
viewport.bottomVisiblePos = -> viewport.scrollTop() + viewport.outerHeight()
171167

172-
viewport.topVisiblePos = ->
173-
viewport.scrollTop()
168+
viewport.topVisiblePos = -> viewport.scrollTop()
174169

175170
viewport.insertElement = (e, sibling) -> insertElement(e, sibling || topPadding)
176171

@@ -181,78 +176,64 @@ angular.module('ui.scroll', [])
181176

182177
viewport.clipBottom = ->
183178
# clip the invisible items off the bottom
184-
bottomHeight = 0
185179
overage = 0
186-
180+
calculateAverageItemHeight()
181+
overageBottom = viewport.outerHeight() + viewport.averageItemHeight * (buffer.size)
187182
for i in [buffer.length-1..0]
188183
item = buffer[i]
189-
itemTop = item.element.offset().top
190-
newRow = rowTop isnt itemTop
191-
rowTop = itemTop
192-
itemHeight = item.element.outerHeight(true) if newRow
193-
if (viewport.bottomDataPos() - bottomHeight - itemHeight > viewport.bottomVisiblePos() + bufferPadding())
194-
bottomHeight += itemHeight if newRow
195-
overage++
196-
buffer.eof = false
197-
else
198-
break if newRow
184+
if item.element.offset().top > overageBottom
199185
overage++
200-
186+
else break
201187
if overage > 0
202-
#viewport.bottomPadding(viewport.bottomPadding() + bottomHeight)
203188
buffer.remove(buffer.length - overage, buffer.length)
204189
buffer.next -= overage
190+
viewport.adjustPadding()
205191

206192
viewport.shouldLoadTop = ->
207193
!buffer.bof && (viewport.topDataPos() > viewport.topVisiblePos() - bufferPadding())
208194

209195
viewport.clipTop = ->
210196
# clip the invisible items off the top
211-
topHeight = 0
212197
overage = 0
198+
heightIncrement = 0
199+
calculateAverageItemHeight()
200+
overageTop = (-1) * viewport.averageItemHeight * buffer.size
213201
for item in buffer
214-
itemTop = item.element.offset().top
215-
newRow = rowTop isnt itemTop
216-
rowTop = itemTop
217-
itemHeight = item.element.outerHeight(true) if newRow
218-
if (viewport.topDataPos() + topHeight + itemHeight < viewport.topVisiblePos() - bufferPadding())
219-
topHeight += itemHeight if newRow
220-
overage++
221-
buffer.bof = false
222-
else
223-
break if newRow
202+
if item.element.offset().top < overageTop
203+
heightIncrement += item.element.outerHeight()
224204
overage++
205+
else break
225206
if overage > 0
226-
#viewport.topPadding(viewport.topPadding() + topHeight)
227207
buffer.remove(0, overage)
228208
buffer.first += overage
229-
230-
viewport.adjustPadding = (heightIncrement) ->
231-
if (buffer.length > 0)
232-
elementHeight = (buffer[buffer.length-1].element.offset().top +
233-
buffer[buffer.length-1].element.outerHeight(true) -
234-
buffer[0].element.offset().top) / buffer.length
235-
topPaddingHeight = (buffer.first - buffer.minIndex()) * elementHeight
236-
#if !buffer.bof
237-
#topPaddingHeight += bufferPadding()
238-
topPadding.height topPaddingHeight
239-
bottomPaddingHeight = (buffer.maxIndex() - buffer.next) * elementHeight
240-
if !buffer.eof
241-
bottomPaddingHeight += bufferPadding()
242-
bottomPadding.height bottomPaddingHeight
243-
console.log "top=#{topPaddingHeight} bottom=#{bottomPaddingHeight}"
244-
if (heightIncrement)
245-
# adjust padding to prevent it from visually pushing everything down
246-
if topPadding.height() >= heightIncrement
247-
# if possible, reduce topPadding
248-
topPadding.height(topPadding.height() - heightIncrement)
249-
else
250-
# if not, increment scrollTop
251-
viewport.scrollTop(viewport.scrollTop() + heightIncrement)
252-
209+
viewport.adjustPadding()
210+
viewport.adjustScrollTop { height: heightIncrement, clipTop: true }
211+
212+
calculateAverageItemHeight = () ->
213+
viewport.averageItemHeight = 0
214+
return if not buffer.length
215+
viewport.averageItemHeight = (buffer[buffer.length-1].element.offset().top +
216+
buffer[buffer.length-1].element.outerHeight(true) -
217+
buffer[0].element.offset().top) / buffer.length
218+
219+
viewport.adjustPadding = () ->
220+
return if not buffer.length
221+
calculateAverageItemHeight()
222+
topPadding.height (buffer.first - buffer.minIndex()) * viewport.averageItemHeight
223+
bottomPadding.height (buffer.maxIndex() - buffer.next + 1) * viewport.averageItemHeight
224+
225+
viewport.adjustScrollTop = (options) ->
226+
return if not options or not options.height
227+
# edge case 1, scroll up from the very top position
228+
if options.prepend and viewport.scrollTop() < options.height
229+
viewport.scrollTop viewport.scrollTop() + options.height
230+
# edge case 2, scroll down from the very bottom position
231+
if options.clipTop and options.height - bottomPadding.height() > 0
232+
viewport.scrollTop viewport.scrollTop() + options.height - bottomPadding.height()
253233

254234
viewport
255235

236+
256237
Adapter = ($attr, viewport, buffer, adjustBuffer) ->
257238

258239
this.isLoading = false
@@ -299,25 +280,10 @@ angular.module('ui.scroll', [])
299280
buffer.insert 'prepend', item
300281
adjustBuffer()
301282

302-
if $attr.topVisible
303-
setTopVisible = $parse($attr.topVisible).assign
304-
else
305-
setTopVisible = ->
306-
307-
if $attr.topVisibleElement
308-
setTopVisibleElement = $parse($attr.topVisibleElement).assign
309-
else
310-
setTopVisibleElement = ->
311-
312-
if $attr.topVisibleScope
313-
setTopVisibleScope = $parse($attr.topVisibleScope).assign
314-
else
315-
setTopVisibleScope = ->
316-
317-
if $attr.isLoading
318-
setIsLoading = $parse($attr.isLoading).assign
319-
else
320-
setIsLoading = ->
283+
setTopVisible = if $attr.topVisible then $parse($attr.topVisible).assign else ->
284+
setTopVisibleElement = if $attr.topVisibleElement then $parse($attr.topVisibleElement).assign else ->
285+
setTopVisibleScope = if $attr.topVisibleScope then $parse($attr.topVisibleScope).assign else ->
286+
setIsLoading = if $attr.isLoading then $parse($attr.isLoading).assign else ->
321287

322288
this.loading = (value) ->
323289
this.isLoading = value
@@ -375,7 +341,7 @@ angular.module('ui.scroll', [])
375341

376342
pending = []
377343

378-
buffer = new Buffer(itemName, $scope, linker, datasource)
344+
buffer = new Buffer(itemName, $scope, linker, datasource, bufferSize)
379345

380346
viewport = new Viewport(buffer, element, controllers, $attr.padding)
381347

@@ -411,8 +377,6 @@ angular.module('ui.scroll', [])
411377
reload = ->
412378
dismissPendingRequests()
413379
buffer.clear()
414-
#viewport.topPadding(0)
415-
#viewport.bottomPadding(0)
416380
adjustBuffer ridActual
417381

418382
adapter.reload = reload
@@ -444,51 +408,35 @@ angular.module('ui.scroll', [])
444408
toBePrepended = []
445409
toBeRemoved = []
446410

447-
bottomPos = viewport.bottomDataPos()
411+
getPreSibling = (i) -> if i > 0 then buffer[i-1].element else undefined
412+
448413
for wrapper, i in buffer
449414
switch wrapper.op
450415
when 'prepend' then toBePrepended.unshift wrapper
451416
when 'append'
452-
if (i == 0) # the first item in buffer is to be appended, therefore the buffer was empty
453-
keepFetching = insertWrapperContent(wrapper) || keepFetching
454-
else
455-
keepFetching = insertWrapperContent(wrapper, buffer[i-1].element) || keepFetching
417+
keepFetching = insertWrapperContent(wrapper, getPreSibling(i)) || keepFetching
456418
wrapper.op = 'none'
457419
when 'insert'
458-
if (i == 0)
459-
promises = promises.concat (viewport.insertElementAnimated wrapper.element)
460-
else
461-
promises = promises.concat (viewport.insertElementAnimated wrapper.element, buffer[i-1].element)
420+
promises = promises.concat (viewport.insertElementAnimated wrapper.element, getPreSibling(i))
462421
wrapper.op = 'none'
463422
when 'remove' then toBeRemoved.push wrapper
464423

465424
for wrapper in toBeRemoved
466425
promises = promises.concat (buffer.remove wrapper)
467426

468-
# for anything other than prepend adjust the bottomPadding height
469-
#viewport.bottomPadding(Math.max(0,viewport.bottomPadding() - (viewport.bottomDataPos() - bottomPos)))
470-
471427
if toBePrepended.length
472-
bottomPos = viewport.bottomDataPos()
428+
adjustPaddingSettings = { height: 0, prepend: true }
473429
for wrapper in toBePrepended
474430
keepFetching = insertWrapperContent(wrapper) || keepFetching
475431
wrapper.op = 'none'
476-
477-
viewport.adjustPadding viewport.bottomDataPos() - bottomPos
478-
479-
###
480-
# adjust padding to prevent it from visually pushing everything down
481-
if viewport.topPadding() >= heightIncrement
482-
# if possible, reduce topPadding
483-
viewport.topPadding(viewport.topPadding() - heightIncrement)
484-
else
485-
# if not, increment scrollTop
486-
viewport.scrollTop(viewport.scrollTop() + heightIncrement)
487-
###
432+
adjustPaddingSettings.height += wrapper.element.height()
488433

489434
# re-index the buffer
490435
item.scope.$index = buffer.first + i for item,i in buffer
491436

437+
viewport.adjustPadding()
438+
viewport.adjustScrollTop(adjustPaddingSettings)
439+
492440
# schedule another adjustBuffer after animation completion
493441
if (promises.length)
494442
$q.all(promises).then ->
@@ -506,9 +454,7 @@ angular.module('ui.scroll', [])
506454
else
507455
if viewport.shouldLoadTop()
508456
enqueueFetch(rid, false)
509-
if pending.length == 0
510-
adapter.calculateProperties()
511-
viewport.adjustPadding()
457+
adapter.calculateProperties() if not pending.length
512458

513459
adjustBufferAfterFetch = (rid) ->
514460
# We need the item bindings to be processed before we can do adjustment
@@ -523,12 +469,11 @@ angular.module('ui.scroll', [])
523469
# BTW there will always be at least 1 element in the pending array because bottom is fetched first
524470
enqueueFetch(rid, false) if keepFetching || pending[0]
525471
pending.shift()
526-
if pending.length == 0
472+
if not pending.length
527473
adapter.loading(false)
528474
adapter.calculateProperties()
529475
else
530476
fetch(rid)
531-
viewport.adjustPadding()
532477

533478
fetch = (rid) ->
534479
#log "Running fetch... #{{true:'bottom', false: 'top'}[direction]} pending #{pending.length}"
@@ -542,7 +487,6 @@ angular.module('ui.scroll', [])
542487
return if (rid and rid isnt ridActual) or $scope.$$destroyed
543488
if result.length < bufferSize
544489
buffer.eof = true
545-
#viewport.bottomPadding(0)
546490
#log 'eof is reached'
547491
if result.length > 0
548492
viewport.clipTop()
@@ -565,7 +509,6 @@ angular.module('ui.scroll', [])
565509
return if (rid and rid isnt ridActual) or $scope.$$destroyed
566510
if result.length < bufferSize
567511
buffer.bof = true
568-
#viewport.topPadding(0)
569512
#log 'bof is reached'
570513
if result.length > 0
571514
viewport.clipBottom() if buffer.length

0 commit comments

Comments
 (0)