diff --git a/README.md b/README.md
index 09717dcd..7381796a 100644
--- a/README.md
+++ b/README.md
@@ -257,7 +257,27 @@ Note if you want to disable right and left in-viewport triggers, set these value
### Modifiers
-Using with [Modifiers](https://blog.emberjs.com/2019/03/06/coming-soon-in-ember-octane-part-4.html) is easy. Note, modifiers currently only works to watch entering the viewport.
+Using with [Modifiers](https://blog.emberjs.com/2019/03/06/coming-soon-in-ember-octane-part-4.html) is easy.
+
+You can either use our built in modifier `{{in-viewport}}` or a more verbose, but potentially more flexible generic modifier. Let's start with the former.
+
+1. Use `{{in-viewport}}` modifier on target element
+2. Ensure you have a callbacks in context for enter and/or exit
+3. `options` are optional - see [Advanced usage (options)](#advanced-usage-options)
+
+```hbs
+
+
+
+
+ List sentinel
+
+
+```
+
+This modifier is useful for a variety of scenarios where you need to watch a sentinel. With template only components, functionality like this is even more important! If you have logic that currently uses the `did-insert` modifier to start watching an element, try this one out!
+
+If you need more than our built in modifier...
1. Install [@ember/render-modifiers](https://github.com/emberjs/ember-render-modifiers)
2. Use the `did-insert` hook inside a component
@@ -476,4 +496,4 @@ We're grateful to these wonderful contributors who've contributed to `ember-in-v
-[//]: contributor-faces
\ No newline at end of file
+[//]: contributor-faces
diff --git a/addon/services/in-viewport.js b/addon/services/in-viewport.js
index 177e0491..2f720a04 100644
--- a/addon/services/in-viewport.js
+++ b/addon/services/in-viewport.js
@@ -56,48 +56,48 @@ export default class InViewport extends Service {
* @void
*/
watchElement(element, configOptions = {}, enterCallback, exitCallback) {
- if (get(this, 'viewportUseIntersectionObserver')) {
- if (!get(this, 'observerAdmin')) {
- this.startIntersectionObserver();
- }
- const observerOptions = this.buildObserverOptions(configOptions);
-
- scheduleOnce('afterRender', this, () => {
- // create IntersectionObserver instance or add to existing
- this.setupIntersectionObserver(
- element,
- observerOptions,
- enterCallback,
- exitCallback
- );
- });
- } else {
- if (!get(this, 'rafAdmin')) {
- this.startRAF();
- }
- scheduleOnce('afterRender', this, () => {
- // grab the user added callbacks when we enter/leave the element
- const {
- enterCallback = noop,
- exitCallback = noop
- } = this.getCallbacks(element) || {};
- // this isn't using the same functions as the mixin case, but that is b/c it is a bit harder to unwind.
- // So just rewrote it with pure functions for now
- startRAF(
- element,
- configOptions,
- enterCallback,
- exitCallback,
- this.addRAF.bind(this, element.id),
- this.removeRAF.bind(this, element.id)
- );
- });
+ if (get(this, 'viewportUseIntersectionObserver')) {
+ if (!get(this, 'observerAdmin')) {
+ this.startIntersectionObserver();
+ }
+ const observerOptions = this.buildObserverOptions(configOptions);
+
+ scheduleOnce('afterRender', this, () => {
+ // create IntersectionObserver instance or add to existing
+ this.setupIntersectionObserver(
+ element,
+ observerOptions,
+ enterCallback,
+ exitCallback
+ );
+ });
+ } else {
+ if (!get(this, 'rafAdmin')) {
+ this.startRAF();
}
+ scheduleOnce('afterRender', this, () => {
+ // grab the user added callbacks when we enter/leave the element
+ const {
+ enterCallback = noop,
+ exitCallback = noop
+ } = this.getCallbacks(element) || {};
+ // this isn't using the same functions as the mixin case, but that is b/c it is a bit harder to unwind.
+ // So just rewrote it with pure functions for now
+ startRAF(
+ element,
+ configOptions,
+ enterCallback,
+ exitCallback,
+ this.addRAF.bind(this, element.id),
+ this.removeRAF.bind(this, element.id)
+ );
+ });
+ }
- return {
- onEnter: this.addEnterCallback.bind(this, element),
- onExit: this.addExitCallback.bind(this, element)
- };
+ return {
+ onEnter: this.addEnterCallback.bind(this, element),
+ onExit: this.addExitCallback.bind(this, element)
+ };
}
/**
diff --git a/tests/acceptance/infinity-test.js b/tests/acceptance/infinity-test.js
index d4a5f19c..9b6a7e1d 100644
--- a/tests/acceptance/infinity-test.js
+++ b/tests/acceptance/infinity-test.js
@@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
-import { find, findAll, visit, waitFor, waitUntil } from '@ember/test-helpers';
+import { find, findAll, visit, settled, waitFor, waitUntil } from '@ember/test-helpers';
module('Acceptance | infinity-scrollable', function(hooks) {
setupApplicationTest(hooks);
@@ -25,6 +25,21 @@ module('Acceptance | infinity-scrollable', function(hooks) {
assert.equal(findAll('.infinity-svg').length, 20);
});
+ test('works with in-viewport modifier', async function(assert) {
+ await visit('/infinity-built-in-modifiers');
+
+ assert.equal(findAll('.infinity-item').length, 10, 'has items to start');
+ document.querySelector('.infinity-item-9').scrollIntoView(false);
+
+ await waitUntil(() => {
+ return findAll('.infinity-item').length === 20;
+ }, { timeoutMessage: 'did not find all items in time' });
+
+ await settled();
+
+ assert.equal(findAll('.infinity-item').length, 20, 'after infinity has more items');
+ });
+
test('ember-in-viewport works with classes', async function(assert) {
await visit('/infinity-class');
diff --git a/tests/acceptance/integration-test.js b/tests/acceptance/integration-test.js
index 7a9b5516..e49fe84d 100644
--- a/tests/acceptance/integration-test.js
+++ b/tests/acceptance/integration-test.js
@@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
-import { find, visit, waitFor } from '@ember/test-helpers';
+import { find, settled, visit, waitFor } from '@ember/test-helpers';
module('Acceptance | Intersection Observer', function(hooks) {
setupApplicationTest(hooks);
@@ -15,6 +15,7 @@ module('Acceptance | Intersection Observer', function(hooks) {
await visit('/');
+ await settled();
assert.ok(find('.my-component.top.start-enabled.active'), 'component is active');
});
diff --git a/tests/dummy/app/controllers/infinity-built-in-modifiers.js b/tests/dummy/app/controllers/infinity-built-in-modifiers.js
index a4f67e23..1de63d9f 100644
--- a/tests/dummy/app/controllers/infinity-built-in-modifiers.js
+++ b/tests/dummy/app/controllers/infinity-built-in-modifiers.js
@@ -1,4 +1,6 @@
import Controller from '@ember/controller';
+import { later } from '@ember/runloop';
+import { action, set, get } from '@ember/object';
const images = ['jarjan', 'aio___', 'kushsolitary', 'kolage', 'idiot', 'gt'];
@@ -14,18 +16,41 @@ const models = [
})
];
-export default Controller.extend({
- queryParams: ['direction'],
- direction: 'both',
+export default class BuiltIn extends Controller {
+ queryParams = ['direction'];
+ direction = 'both';
- models,
+ init() {
+ super.init(...arguments);
- actions: {
- didEnterViewport(/*artwork, i, element*/) {
- // console.log('enter', { artwork, i, element });
- },
- didExitViewport(/*artwork, i, element*/) {
- // console.log('exit', { artwork, i, element });
- }
+ this.models = models;
+ set(this, 'viewportTolerance', {
+ bottom: 300
+ });
}
-});
+
+ @action
+ didEnterViewport(/*artwork, i, element*/) {
+ const arr = Array.apply(null, Array(10));
+ const newModels = [...arr.map(() => {
+ return {
+ bgColor: '0790EB',
+ url: `https://s3.amazonaws.com/uifaces/faces/twitter/${images[(Math.random() * images.length) | 0]}/128.jpg`
+ }
+ })];
+
+ return new Promise((resolve) => {
+ later(() => {
+ const models = get(this, 'models');
+ models.push(...newModels);
+ set(this, 'models', Array.prototype.slice.call(models));
+ resolve();
+ }, 0);
+ });
+ }
+
+ @action
+ didExitViewport(/*artwork, i, element*/) {
+ // console.log('exit', { artwork, i, element });
+ }
+}
diff --git a/tests/dummy/app/controllers/infinity-modifier.js b/tests/dummy/app/controllers/infinity-modifier.js
index 8a1e11d6..9b2aaf98 100644
--- a/tests/dummy/app/controllers/infinity-modifier.js
+++ b/tests/dummy/app/controllers/infinity-modifier.js
@@ -7,7 +7,10 @@ const images = ["jarjan", "aio___", "kushsolitary", "kolage", "idiot", "gt"];
const arr = Array.apply(null, Array(10));
const models = [...arr.map(() => {
- return { bgColor: 'E8D26F', url: `https://s3.amazonaws.com/uifaces/faces/twitter/${images[(Math.random() * images.length) | 0]}/128.jpg` }
+ return {
+ bgColor: 'E8D26F',
+ url: `https://s3.amazonaws.com/uifaces/faces/twitter/${images[(Math.random() * images.length) | 0]}/128.jpg`
+ }
})];
export default Controller.extend({
@@ -17,7 +20,10 @@ export default Controller.extend({
infinityLoad() {
const arr = Array.apply(null, Array(10));
const newModels = [...arr.map(() => {
- return { bgColor: '0790EB', url: `https://s3.amazonaws.com/uifaces/faces/twitter/${images[(Math.random() * images.length) | 0]}/128.jpg` }
+ return {
+ bgColor: '0790EB',
+ url: `https://s3.amazonaws.com/uifaces/faces/twitter/${images[(Math.random() * images.length) | 0]}/128.jpg`
+ }
})];
return new Promise((resolve) => {
diff --git a/tests/dummy/app/templates/components/dummy-artwork.hbs b/tests/dummy/app/templates/components/dummy-artwork.hbs
index 7fcca077..11b9f28b 100644
--- a/tests/dummy/app/templates/components/dummy-artwork.hbs
+++ b/tests/dummy/app/templates/components/dummy-artwork.hbs
@@ -1,46 +1,46 @@
{{#if this.actualArtwork}}
- {{#if this.isUserMonogram}}
-
-
-
- {{else if this.isFallbackArtwork}}
-
-
-
- {{else if this.lazyLoad}}
- {{!-- if we are lazyloading an image, we want to still show the image src if it turns out the client has no scripting --}}
- {{!-- `sizes` must come before `src` and `data-srcset` to avoid double downloads --}}
-
-
- {{else}}
-
- {{!-- responsive images --}}
- {{!-- `sizes` must come before `src` and `srcset` to avoid double downloads --}}
-
-
- {{/if}}
+ {{#if this.isUserMonogram}}
+
+
+
+ {{else if this.isFallbackArtwork}}
+
+
+
+ {{else if this.lazyLoad}}
+ {{!-- if we are lazyloading an image, we want to still show the image src if it turns out the client has no scripting --}}
+ {{!-- `sizes` must come before `src` and `data-srcset` to avoid double downloads --}}
+
+
+ {{else}}
+
+ {{!-- responsive images --}}
+ {{!-- `sizes` must come before `src` and `srcset` to avoid double downloads --}}
+
+
+ {{/if}}
{{/if}}
diff --git a/tests/dummy/app/templates/infinity-built-in-modifiers.hbs b/tests/dummy/app/templates/infinity-built-in-modifiers.hbs
index 303d8cf4..887db7be 100644
--- a/tests/dummy/app/templates/infinity-built-in-modifiers.hbs
+++ b/tests/dummy/app/templates/infinity-built-in-modifiers.hbs
@@ -5,32 +5,35 @@
{{link-to "exit" (query-params direction="exit")}}
{{#if (eq this.direction "both")}}
-