Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Built in modifier test and README #215

Merged
merged 10 commits into from
Dec 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<ul class="list">
<li></li>
<li></li>
<div {{in-viewport onEnter=(fn this.onEnter artwork) onExit=this.onExit scrollableArea=".list"}}>
List sentinel
</div>
</ul>
```

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
Expand Down Expand Up @@ -476,4 +496,4 @@ We're grateful to these wonderful contributors who've contributed to `ember-in-v
<a href="https://github.com/hybridmuse"><img src="https://avatars0.githubusercontent.com/u/27986936?v=4" title="hybridmuse" width="80" height="80"></a>
<a href="https://github.com/csand"><img src="https://avatars2.githubusercontent.com/u/142865?v=4" title="csand" width="80" height="80"></a>

[//]: contributor-faces
[//]: contributor-faces
80 changes: 40 additions & 40 deletions addon/services/in-viewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
};
}

/**
Expand Down
17 changes: 16 additions & 1 deletion tests/acceptance/infinity-test.js
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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');

Expand Down
3 changes: 2 additions & 1 deletion tests/acceptance/integration-test.js
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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');
});

Expand Down
49 changes: 37 additions & 12 deletions tests/dummy/app/controllers/infinity-built-in-modifiers.js
Original file line number Diff line number Diff line change
@@ -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'];

Expand All @@ -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 });
}
}
10 changes: 8 additions & 2 deletions tests/dummy/app/controllers/infinity-modifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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) => {
Expand Down
82 changes: 41 additions & 41 deletions tests/dummy/app/templates/components/dummy-artwork.hbs
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
{{#if this.actualArtwork}}

{{#if this.isUserMonogram}}

<svg class="dummy-artwork__image" width="100%" height="100%" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="a" x1="50%" y1="0%" x2="50%" y2="100%"><stop offset="0%" stop-color="#A5ABB8"/><stop offset="100%" stop-color="#848993"/></linearGradient></defs><rect width="100%" height="100%" fill="url(#a)"/><text x="320" y="50%" dy="0.35em" font-size="250" fill="#fff" text-anchor="middle" font-weight="500">{{this.userInitials}}</text></svg>

{{else if this.isFallbackArtwork}}

<img
class="dummy-artwork__image {{this.elementId}}"
src={{this.fallbackSrc}}
height={{this.adjustedHeight}}
width={{this.adjustedWidth}}
alt={{alt}}>

{{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 --}}
<img
class="dummy-artwork__image {{this.elementId}}"
sizes={{this.mediaQueries}}
src="{{this.rootURL}}assets/1x1.gif"
data-srcset={{this.srcset}}
height={{this.adjustedHeight}}
width={{this.adjustedWidth}}
style={{this.imgBgColor}}
alt={{alt}}>

{{else}}

{{!-- responsive images --}}
{{!-- `sizes` must come before `src` and `srcset` to avoid double downloads --}}
<img
class="dummy-artwork__image {{this.elementId}}"
sizes={{this.mediaQueries}}
src="{{this.rootURL}}assets/1x1.gif"
srcset={{this.srcset}}
height={{this.adjustedHeight}}
width={{this.adjustedWidth}}
style={{this.imgBgColor}}
alt={{alt}}>

{{/if}}
{{#if this.isUserMonogram}}

<svg class="dummy-artwork__image" width="100%" height="100%" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="a" x1="50%" y1="0%" x2="50%" y2="100%"><stop offset="0%" stop-color="#A5ABB8"/><stop offset="100%" stop-color="#848993"/></linearGradient></defs><rect width="100%" height="100%" fill="url(#a)"/><text x="320" y="50%" dy="0.35em" font-size="250" fill="#fff" text-anchor="middle" font-weight="500">{{this.userInitials}}</text></svg>

{{else if this.isFallbackArtwork}}

<img
class="dummy-artwork__image {{this.elementId}}"
src={{this.fallbackSrc}}
height={{this.adjustedHeight}}
width={{this.adjustedWidth}}
alt={{alt}}>

{{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 --}}
<img
class="dummy-artwork__image {{this.elementId}}"
sizes={{this.mediaQueries}}
src="{{this.rootURL}}assets/1x1.gif"
data-srcset={{this.srcset}}
height={{this.adjustedHeight}}
width={{this.adjustedWidth}}
style={{this.imgBgColor}}
alt={{alt}}>

{{else}}

{{!-- responsive images --}}
{{!-- `sizes` must come before `src` and `srcset` to avoid double downloads --}}
<img
class="dummy-artwork__image {{this.elementId}}"
sizes={{this.mediaQueries}}
src="{{this.rootURL}}assets/1x1.gif"
srcset={{this.srcset}}
height={{this.adjustedHeight}}
width={{this.adjustedWidth}}
style={{this.imgBgColor}}
alt={{alt}}>

{{/if}}

{{/if}}

Expand Down
23 changes: 13 additions & 10 deletions tests/dummy/app/templates/infinity-built-in-modifiers.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,35 @@
{{link-to "exit" (query-params direction="exit")}}
</nav>
{{#if (eq this.direction "both")}}
<ul>
<ul class="infinity-container">
{{#each this.models as |artwork i|}}
<li
{{in-viewport
onEnter=(action "didEnterViewport" artwork i)
onExit=(action "didExitViewport" artwork i)
}}
class="infinity-item infinity-item-{{i}}"
>
{{dummy-artwork artwork=artwork artworkProfile="dummy"}}
</li>
{{/each}}
<div
class="sentinel"
{{in-viewport onEnter=(action "didEnterViewport") onExit=(action "didExitViewport") viewportTolerance=this.viewportTolerance scrollableArea=".infinity-container"}}
></div>
</ul>
{{else if (eq this.direction "enter")}}
<ul>
{{#each this.models as |artwork i|}}
<li {{in-viewport onEnter=(action "didEnterViewport" artwork i)}}>
{{#each this.models as |artwork|}}
<li>
{{dummy-artwork artwork=artwork artworkProfile="dummy"}}
</li>
{{/each}}
<div class="sentinel" {{in-viewport onEnter=(action "didEnterViewport" artwork i)}}></div>
</ul>
{{else if (eq this.direction "exit")}}
<ul>
{{#each this.models as |artwork i|}}
<li {{in-viewport onExit=(action "didExitViewport" artwork i)}}>
{{#each this.models as |artwork|}}
<li>
{{dummy-artwork artwork=artwork artworkProfile="dummy"}}
</li>
{{/each}}
<div class="sentinel" {{in-viewport onExit=(action "didExitViewport")}}></div>
</ul>
{{/if}}
{{/if}}