diff --git a/README.md b/README.md index 1715eab..f035b80 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ https://zestia.github.io/ember-simple-infinite-scroller ## Example ```handlebars - + {{#each things as |thing|}} ... {{/each}} @@ -64,13 +64,13 @@ Required. Fired when the the element has been scrolled to the specified `@percen Optional. By default the scroll position of the component's own DOM element is monitored. You can use this argument to change the element, to monitor the document for example. -#### `@down` +#### `@percentDown` -Optional. The distance that has to be scrolled down before the load more action is fired. +Optional. The distance that has to be scrolled down before the load more action is fired. 100% means the bottom. -#### `@up` +#### `@percentUp` -Optional. The distance that has to be scrolled up before the load more action is fired. +Optional. The distance that has to be scrolled up before the load more action is fired. 0% means the top. #### `@debounce` diff --git a/addon/components/infinite-scroller.gjs b/addon/components/infinite-scroller.gjs index a757f8c..2e005f2 100644 --- a/addon/components/infinite-scroller.gjs +++ b/addon/components/infinite-scroller.gjs @@ -4,6 +4,7 @@ import { debounce, cancel } from '@ember/runloop'; import { tracked } from '@glimmer/tracking'; import { modifier } from 'ember-modifier'; import { action } from '@ember/object'; +import { isPresent } from '@ember/utils'; const { round } = Math; const UP = 'UP'; const DOWN = 'DOWN'; @@ -13,7 +14,7 @@ export default class InfiniteScrollerComponent extends Component { @tracked scrollState = {}; @tracked lastScrollState = {}; - debug; + debug = true; scroller; debounceId; @@ -30,12 +31,28 @@ export default class InfiniteScrollerComponent extends Component { return this.args.debounce ?? 100; } - get percent() { - return this.args.percent ?? 100; - } - get shouldLoadMore() { - return this.scrollState.reachedBottom && !this.isLoading; + if (this.isLoading) { + return false; + } + + if ( + isPresent(this.args.percentDown) && + this.scrollState.direction === DOWN && + this.scrollState.percentScrolled >= this.args.percentDown + ) { + return true; + } + + if ( + isPresent(this.args.percentUp) && + this.scrollState.direction === UP && + this.scrollState.percentScrolled <= this.args.percentUp + ) { + return true; + } + + return false; } get normalisedScrollerElement() { @@ -100,8 +117,6 @@ export default class InfiniteScrollerComponent extends Component { } this.scrollState = this._getScrollState(); - - this._debug(); } _debug() { @@ -124,11 +139,10 @@ export default class InfiniteScrollerComponent extends Component { const clientHeight = element.clientHeight; const isScrollable = scrollHeight > clientHeight; const bottom = scrollHeight - clientHeight; - const percent = this.percent; const percentScrolled = round((scrollTop / bottom) * 100); - const reachedBottom = percentScrolled >= percent; const scrollingDown = element.scrollTop > this.lastScrollState.scrollTop; - const direction = scrollingDown ? DOWN : UP; + const scrollingUp = element.scrollTop < this.lastScrollState.scrollTop; + const direction = scrollingDown ? DOWN : scrollingUp ? UP : null; return { isScrollable, @@ -136,9 +150,7 @@ export default class InfiniteScrollerComponent extends Component { clientHeight, scrollTop, bottom, - percent, percentScrolled, - reachedBottom, direction }; } diff --git a/tests/dummy/app/_controllers/things.js b/tests/dummy/app/_controllers/things.js index c9b68f8..44fb754 100644 --- a/tests/dummy/app/_controllers/things.js +++ b/tests/dummy/app/_controllers/things.js @@ -8,28 +8,37 @@ export default class ThingsController extends Controller { @injectController('application') appController; @tracked page = 1; - @tracked things; + @tracked things = this._generateThingsForPage(1); perPage = 10; - constructor() { - super(...arguments); - this.things = this._generateThings(); - } - @action - handleLoadMore() { + handleLoadMore(direction) { return new Promise((resolve) => { later(() => { - this.page++; - this.things = [...this.things, ...this._generateThings()]; + if (direction === 'DOWN' && this.page < 10) { + this.page++; + this.things = [ + ...this.things, + ...this._generateThingsForPage(this.page) + ]; + } + + if (direction === 'UP' && this.page > 1) { + this.page--; + this.things = [ + ...this._generateThingsForPage(this.page), + ...this.things + ]; + } + resolve(); }, this.appController.loadDelay); }); } - _generateThings() { - const start = this.things ? this.things.length + 1 : 0; - const end = this.page * this.perPage; + _generateThingsForPage(page) { + const start = (page - 1) * 10 + 1; + const end = page * this.perPage; return generateThings(start, end); } } diff --git a/tests/dummy/app/controllers/example-5.js b/tests/dummy/app/controllers/example-5.js new file mode 100644 index 0000000..3858423 --- /dev/null +++ b/tests/dummy/app/controllers/example-5.js @@ -0,0 +1,12 @@ +import ThingsController from 'dummy/_controllers/things'; +import { modifier } from 'ember-modifier'; +import { tracked } from '@glimmer/tracking'; + +export default class extends ThingsController { + @tracked page = 4; + @tracked things = this._generateThingsForPage(5); + + scrollIntoView = modifier((element) => element.scrollIntoView(), { + eager: false + }); +} diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js index 7aadde3..b6956bc 100644 --- a/tests/dummy/app/router.js +++ b/tests/dummy/app/router.js @@ -13,4 +13,5 @@ Router.map(function () { this.route('example-2'); this.route('example-3'); this.route('example-4'); + this.route('example-5'); }); diff --git a/tests/dummy/app/routes/application.js b/tests/dummy/app/routes/application.js deleted file mode 100644 index 72f0d5e..0000000 --- a/tests/dummy/app/routes/application.js +++ /dev/null @@ -1,3 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class ApplicationRoute extends Route {} diff --git a/tests/dummy/app/routes/example-1.js b/tests/dummy/app/routes/example-1.js deleted file mode 100644 index ba9ac3b..0000000 --- a/tests/dummy/app/routes/example-1.js +++ /dev/null @@ -1,3 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class Example1Route extends Route {} diff --git a/tests/dummy/app/routes/example-2.js b/tests/dummy/app/routes/example-2.js deleted file mode 100644 index f66cba4..0000000 --- a/tests/dummy/app/routes/example-2.js +++ /dev/null @@ -1,3 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class Example2Route extends Route {} diff --git a/tests/dummy/app/routes/example-3.js b/tests/dummy/app/routes/example-3.js deleted file mode 100644 index 0f2f5f2..0000000 --- a/tests/dummy/app/routes/example-3.js +++ /dev/null @@ -1,3 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class Example3Route extends Route {} diff --git a/tests/dummy/app/routes/example-4.js b/tests/dummy/app/routes/example-4.js deleted file mode 100644 index 177d41e..0000000 --- a/tests/dummy/app/routes/example-4.js +++ /dev/null @@ -1,3 +0,0 @@ -import Route from '@ember/routing/route'; - -export default class Example4Route extends Route {} diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index 27327cc..a6c0041 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -17,6 +17,10 @@ Example 4 +| + + Example 5 +

+ + + {{#if scroller.isLoading}} +
+ Loading... +
+ {{/if}} + {{#each this.things as |thing|}} +
+ {{thing.name}} +
+ {{/each}} +
\ No newline at end of file diff --git a/tests/dummy/app/utils/generate-things.js b/tests/dummy/app/utils/generate-things.js index c60e54f..974a3de 100644 --- a/tests/dummy/app/utils/generate-things.js +++ b/tests/dummy/app/utils/generate-things.js @@ -1,7 +1,5 @@ -import { A as emberA } from '@ember/array'; - export default function (start, end) { - const array = emberA(); + const array = []; for (let i = start; i <= end; i += 1) { array.push({ diff --git a/tests/integration/components/infinite-scroller-test.gjs b/tests/integration/components/infinite-scroller-test.gjs index e81ed4a..92ea45f 100644 --- a/tests/integration/components/infinite-scroller-test.gjs +++ b/tests/integration/components/infinite-scroller-test.gjs @@ -48,29 +48,15 @@ module('infinite-scroller', function (hooks) { assert.dom('.infinite-scroller').exists('has an appropriate class name'); }); - test('scrollable attr', async function (assert) { - assert.expect(2); - - await render(); - - assert.dom('.infinite-scroller').hasAttribute('data-scrollable', 'true'); - - await render(); - - assert.dom('.infinite-scroller').hasAttribute('data-scrollable', 'false'); - }); - test('load more action (success)', async function (assert) { assert.expect(3); await render(