Skip to content

Commit

Permalink
delayScrollTop config + schedule in render queue (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
snewcomer authored and RobbieTheWagner committed Apr 24, 2018
1 parent a87ca1a commit 2101a93
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 24 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@

> Scroll to page top on transition, like a non-SPA website. An alternative scroll behavior for Ember applications.
## Installation

```
ember install ember-router-scroll
```

### Options
You can specify the id of an element for which the scroll position is saved and set. Default is `window` for using the scroll position of the whole viewport. You can pass an options object in your application's `config/environment.js` file.

```javascript
ENV['routerScroll'] = {
scrollElement: '#mainScrollElement'
};

Moreover, if your route breaks up render into multiple phases, you may need to delay scrollTop functionality until after the First Meaningful Paint using `delayScrollTop: true` in your config. `delayScrollTop` defaults to `false`.

```javascript
ENV['routerScroll'] = {
delayScrollTop: true
};
```

## A working example
See [demo](https://dollarshaveclub.github.io/router-scroll-demo/) made by [Jon Chua](https://github.com/Chuabacca/).

Expand Down
23 changes: 16 additions & 7 deletions addon/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Mixin from '@ember/object/mixin';
import { get, computed } from '@ember/object';
import { inject } from '@ember/service';
import { getOwner } from '@ember/application';
import Mixin from '@ember/object/mixin'
import { get, computed } from '@ember/object'
import { inject } from '@ember/service'
import { getOwner } from '@ember/application'
import { scheduleOnce } from '@ember/runloop'

export default Mixin.create({
scheduler: inject('scheduler'),
Expand All @@ -25,9 +26,17 @@ export default Mixin.create({

if (get(this, 'isFastBoot')) { return; }

this.get('scheduler').scheduleWork('afterContentPaint', () => {
this.updateScrollPosition(transitions);
});
const delayScrollTop = get(this, 'service.delayScrollTop')

if (!delayScrollTop) {
scheduleOnce('render', this, () => this.updateScrollPosition(transitions))
} else {
// as described in ember-app-scheduler, this addon can be used to delay rendering until after First Meaningful Paint.
// If you loading your routes progressively, this may be a good option to delay scrollTop until the remaining DOM elements are painted.
this.get('scheduler').scheduleWork('afterContentPaint', () => {
this.updateScrollPosition(transitions)
})
}
},

updateScrollPosition (transitions) {
Expand Down
6 changes: 6 additions & 0 deletions addon/services/router-scroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default Service.extend({
}),

scrollElement: 'window',
delayScrollTop: false,

init(...args) {
this._super(...args);
Expand Down Expand Up @@ -66,6 +67,11 @@ export default Service.extend({
if ('string' === typeOf(scrollElement)) {
set(this, 'scrollElement', scrollElement);
}

const delayScrollTop = config.routerScroll.delayScrollTop;
if (delayScrollTop === true) {
set(this, 'delayScrollTop', true);
}
}
}
});
59 changes: 42 additions & 17 deletions tests/unit/mixins/router-scroll-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,32 @@ module('mixin:router-scroll', function(hooks) {
const subject = RouterScrollObject.create({
isFastBoot: false,
scheduler: getSchedulerMock(),
updateScrollPosition() {
service: {
delayScrollTop: false,
},
updateScrollPosition () {
assert.ok(true, 'it should call updateScrollPosition.');
done();
},
});

run(() => {
subject.didTransition();
});
});

test('when the application is not FastBooted with delayScrollTop', (assert) => {
assert.expect(1);

const done = assert.async();
const RouterScrollObject = EmberObject.extend(RouterScroll);
const subject = RouterScrollObject.create({
isFastBoot: false,
scheduler: getSchedulerMock(),
service: {
delayScrollTop: true,
},
updateScrollPosition () {
assert.ok(true, 'it should call updateScrollPosition.');
done();
},
Expand Down Expand Up @@ -122,36 +147,36 @@ module('mixin:router-scroll', function(hooks) {
});

run(() => {
subject.didTransition(getTransitionsMock('Hello/#World', false, false))
done()
})
})
subject.didTransition(getTransitionsMock('Hello/#World', false, false));
done();
});
});

test('Ensure correct internal router intimate api is used: _router', (assert) => {
assert.expect(1)
const done = assert.async()
assert.expect(1);
const done = assert.async();

const elem = document.createElement('div')
elem.id = 'World'
document.body.insertBefore(elem, null)
const elem = document.createElement('div');
elem.id = 'World';
document.body.insertBefore(elem, null);
window.scrollTo = (x, y) =>
assert.ok(x === elem.offsetLeft && y === elem.offsetTop, 'Scroll to called with correct offsets')
assert.ok(x === elem.offsetLeft && y === elem.offsetTop, 'Scroll to called with correct offsets');

const RouterScrollObject = EmberObject.extend(RouterScroll)
const RouterScrollObject = EmberObject.extend(RouterScroll);
const subject = RouterScrollObject.create({
isFastBoot: false,
scheduler: getSchedulerMock(),
service: {
position: null,
scrollElement: 'window',
},
})
});

run(() => {
subject.didTransition(getTransitionsMock('Hello/#World', false, true))
done()
})
})
subject.didTransition(getTransitionsMock('Hello/#World', false, true));
done();
});
});

test('Update Scroll Position: URL has nothing after an anchor', (assert) => {
assert.expect(1);
Expand Down

0 comments on commit 2101a93

Please sign in to comment.