Skip to content

Commit e7313d1

Browse files
committed
added upwards scroll logic
1 parent 72d3606 commit e7313d1

File tree

1 file changed

+135
-13
lines changed

1 file changed

+135
-13
lines changed

src/infinite-ajax-scroll.js

Lines changed: 135 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import {scrollHandler} from "./event-handlers";
77
import Emitter from "tiny-emitter";
88
import {getDistanceToFold, getRootRect, getScrollPosition} from "./dimensions";
99
import {nextHandler} from './next-handler';
10+
import {prevHandler} from './prev-handler';
1011
import Pagination from './pagination';
1112
import Spinner from './spinner';
1213
import Logger from './logger';
1314
import Paging from './paging';
1415
import Trigger from './trigger';
1516
import {appendFn} from './append';
17+
import {prependFn} from './prepend';
1618
import * as Events from './events';
1719
import ResizeObserverFactory from './resize-observer';
1820
import Prefill from "./prefill";
@@ -36,21 +38,29 @@ export default class InfiniteAjaxScroll {
3638
}
3739

3840
this.nextHandler = nextHandler;
41+
this.prevHandler = prevHandler;
3942

4043
if (this.options.next === false) {
4144
this.nextHandler = function() {}
4245
} else if (typeof this.options.next === 'function') {
4346
this.nextHandler = this.options.next;
4447
}
4548

49+
if (this.options.prev === false) {
50+
this.prevHandler = function() {}
51+
} else if (typeof this.options.prev === 'function') {
52+
this.prevHandler = this.options.prev;
53+
}
54+
4655
this.resizeObserver = ResizeObserverFactory(this, this.scrollContainer);
4756
this._scrollListener = throttle(scrollHandler, 200).bind(this);
4857

4958
this.ready = false;
5059
this.bindOnReady = true;
5160
this.binded = false;
5261
this.paused = false;
53-
this.pageIndex = this.sentinel() ? 0 : -1;
62+
this.pageIndexPrev = 0;
63+
this.pageIndex = this.pageIndexNext = this.sentinel() ? 0 : -1;
5464

5565
this.on(Events.HIT, () => {
5666
if (!this.loadOnScroll) {
@@ -60,6 +70,14 @@ export default class InfiniteAjaxScroll {
6070
this.next();
6171
});
6272

73+
this.on(Events.TOP, () => {
74+
if (!this.loadOnScroll) {
75+
return;
76+
}
77+
78+
this.prev();
79+
});
80+
6381
this.on(Events.SCROLLED, this.measure);
6482
this.on(Events.RESIZED, this.measure);
6583

@@ -74,6 +92,11 @@ export default class InfiniteAjaxScroll {
7492
// prefill/measure after all plugins are done binding
7593
this.on(Events.BINDED, this.prefill.prefill.bind(this.prefill));
7694

95+
this.hitFirst = this.hitLast = false;
96+
97+
this.on(Events.LAST, () => this.hitLast = true);
98+
this.on(Events.FIRST, () => this.hitFirst = true);
99+
77100
let ready = () => {
78101
if (this.ready) {
79102
return;
@@ -132,6 +155,10 @@ export default class InfiniteAjaxScroll {
132155
}
133156

134157
next() {
158+
if (this.hitLast) {
159+
return;
160+
}
161+
135162
if (!this.binded) {
136163
if (!this.ready) {
137164
return this.once(Events.BINDED, this.next);
@@ -142,23 +169,46 @@ export default class InfiniteAjaxScroll {
142169

143170
this.pause();
144171

145-
const pageIndex = this.pageIndex + 1;
172+
const pageIndex = this.pageIndexNext + 1;
146173

147-
this.emitter.emit(Events.NEXT, {pageIndex: this.pageIndex + 1});
174+
this.emitter.emit(Events.NEXT, {pageIndex: this.pageIndexNext + 1});
148175

149176
return Promise.resolve(this.nextHandler(pageIndex))
150177
.then((hasNextUrl) => {
151-
this.pageIndex = pageIndex;
178+
this.pageIndexNext = pageIndex;
152179

153180
if (!hasNextUrl) {
154181
this.emitter.emit(Events.LAST);
155-
156-
return;
157182
}
158183

159184
this.resume();
160185
}).then(() => {
161-
this.emitter.emit(Events.NEXTED, {pageIndex: this.pageIndex});
186+
this.emitter.emit(Events.NEXTED, {pageIndex: this.pageIndexNext});
187+
});
188+
}
189+
190+
prev() {
191+
if (!this.binded || this.hitFirst) {
192+
return;
193+
}
194+
195+
this.pause();
196+
197+
const pageIndex = this.pageIndexPrev - 1;
198+
199+
this.emitter.emit(Events.PREV, {pageIndex: this.pageIndexPrev - 1});
200+
201+
return Promise.resolve(this.prevHandler(pageIndex))
202+
.then((hasPrevUrl) => {
203+
this.pageIndexPrev = pageIndex;
204+
205+
this.resume();
206+
207+
if (!hasPrevUrl) {
208+
this.emitter.emit(Events.FIRST);
209+
}
210+
}).then(() => {
211+
this.emitter.emit(Events.PREVED, {pageIndex: this.pageIndexPrev});
162212
});
163213
}
164214

@@ -272,6 +322,48 @@ export default class InfiniteAjaxScroll {
272322
});
273323
}
274324

325+
/**
326+
* @param {array<Element>} items
327+
* @param {Element|null} parent
328+
*/
329+
prepend(items, parent) {
330+
let ias = this;
331+
parent = parent || ias.container;
332+
333+
let event = {
334+
items,
335+
parent,
336+
prependFn
337+
};
338+
339+
ias.emitter.emit(Events.PREPEND, event);
340+
341+
let executor = (resolve) => {
342+
window.requestAnimationFrame(() => {
343+
const first = ias.first();
344+
const scrollPositionStart = getScrollPosition(this.scrollContainer);
345+
const topStart = first.getBoundingClientRect().top + scrollPositionStart.y;
346+
347+
Promise.resolve(event.prependFn(event.items, event.parent, ias.first()))
348+
.then(() => {
349+
const scrollPositionEnd = getScrollPosition(this.scrollContainer);
350+
const topEnd = first.getBoundingClientRect().top + scrollPositionEnd.y;
351+
352+
let deltaY = topEnd - topStart;
353+
354+
this.scrollContainer.scrollTo(scrollPositionEnd.x, deltaY);
355+
})
356+
.then(() => {
357+
resolve({items, parent});
358+
});
359+
});
360+
};
361+
362+
return (new Promise(executor)).then((event) => {
363+
ias.emitter.emit(Events.PREPENDED, event);
364+
});
365+
}
366+
275367
sentinel() {
276368
const items = $(this.options.item, this.container);
277369

@@ -282,6 +374,16 @@ export default class InfiniteAjaxScroll {
282374
return items[items.length-1];
283375
}
284376

377+
first() {
378+
const items = $(this.options.item, this.container);
379+
380+
if (!items.length) {
381+
return null;
382+
}
383+
384+
return items[0];
385+
}
386+
285387
pause() {
286388
this.paused = true;
287389
}
@@ -298,9 +400,15 @@ export default class InfiniteAjaxScroll {
298400
this.loadOnScroll = false;
299401
}
300402

403+
/**
404+
* @deprecated replaced by distanceBottom
405+
*/
301406
distance(rootRect, sentinel) {
302-
const _rootRect = rootRect || getRootRect(this.scrollContainer);
407+
return this.distanceBottom(rootRect, sentinel);
408+
}
303409

410+
distanceBottom(rootRect, sentinel) {
411+
const _rootRect = rootRect || getRootRect(this.scrollContainer);
304412
const _sentinel = sentinel || this.sentinel();
305413

306414
const scrollPosition = getScrollPosition(this.scrollContainer);
@@ -313,8 +421,14 @@ export default class InfiniteAjaxScroll {
313421
return distance;
314422
}
315423

424+
distanceTop() {
425+
const scrollPosition = getScrollPosition(this.scrollContainer);
426+
427+
return scrollPosition.y - this.negativeMargin;
428+
}
429+
316430
measure() {
317-
if (this.paused) {
431+
if (this.paused || (this.hitFirst && this.hitLast)) {
318432
return;
319433
}
320434

@@ -330,12 +444,20 @@ export default class InfiniteAjaxScroll {
330444
return;
331445
}
332446

333-
const sentinel = this.sentinel();
447+
if (!this.hitFirst) {
448+
let distanceTop = this.distanceTop();
334449

335-
let distance = this.distance(rootRect, sentinel);
450+
if (distanceTop <= 0) {
451+
this.emitter.emit(Events.TOP, {distance: distanceTop});
452+
}
453+
}
336454

337-
if (distance <= 0) {
338-
this.emitter.emit(Events.HIT, {distance});
455+
if (!this.hitLast) {
456+
let distanceBottom = this.distanceBottom(rootRect, this.sentinel());
457+
458+
if (distanceBottom <= 0) {
459+
this.emitter.emit(Events.HIT, {distance: distanceBottom});
460+
}
339461
}
340462
}
341463

0 commit comments

Comments
 (0)