Skip to content

Commit

Permalink
Feature: Target Element for scroll top (#132)
Browse files Browse the repository at this point in the history
* Add support for targetElement config

* defaults

* set default to target element for scrollTop

* add basic acceptance test WIP

* modify config in dummy route

* link to images correctly

* rework index

* update env

* comment out targetElement for tests

* update app-scheduler to 0.2.2

* add some comments

* take name out of test

* update readme

* update name in test

* add scrollY assertion to first test

* use nested route...oops revert

* update readme

* update imports in readme

* basic assertion

* restart build

* restart again

* add a few more tests

* update based on comments

* add version 0.7.0 to pkg.json
  • Loading branch information
snewcomer authored and yowainwright committed May 17, 2018
1 parent 1efa8ee commit 948f25d
Show file tree
Hide file tree
Showing 18 changed files with 308 additions and 65 deletions.
31 changes: 14 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ 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.
If you need to scroll to the top of an area that generates a vertical scroll bar, you can specify the id of an element of the scrollable area. 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'
};

If you want to scroll to a target element on the page, you can specify the id or class of the element on the page. This is particularly useful if instead of scrolling to the top of the window, you want to scroll to the top of the main content area (that does not generate a vertical scrollbar).

ENV['routerScroll'] = {
targetElement: '#main-target-element' // or .main-target-element
};

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
Expand Down Expand Up @@ -84,23 +90,14 @@ historySupportMiddleware: true,
This location type inherits from Ember's `HistoryLocation`.

4. Tests
4. If using old style QUnit tests. If tests based on [RFC](https://github.com/emberjs/rfcs/pull/232), you can ignore this.
In your router and controller tests, add `'service:router-scroll'` and `'service:scheduler'` as dependencies in the `needs: []` block:

```js
//{your-app}}/tests/unit/routes/{{your-route}}.js
needs:[ 'service:router-scroll', 'service:scheduler' ],
```

### 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'
};
```
## Issues with nested routes

### Before:
Expand All @@ -124,9 +121,9 @@ Add `preserveScrollPosition` as a queryParam in the controller for the route tha
Example:

```javascript
import Ember from 'ember';
import Controller from '@ember/controller';
export default Ember.Controller.extend({
export default Controller.extend({
queryParams: [
'preserveScrollPosition',
],
Expand Down Expand Up @@ -154,9 +151,9 @@ In this example we have `preserveScrollPosition` initially set to false so that
Example:

```javascript
import Ember from 'ember';
import Controller from '@ember/controller';
export default Ember.Controller.extend({
export default Controller.extend({
queryParams: ['filter'],
preserveScrollPosition: false,
Expand All @@ -175,9 +172,9 @@ export default Ember.Controller.extend({
If your controller is changing the preserveScrollPosition property, you'll probably need to reset `preserveScrollPosition` back to the default behavior whenever the controller is reset. This is not necceary on routes where `preserveScrollPosition` is always set to true.
```javascript
import Ember from 'ember';
import Router from '@ember/routing/route';
export default Ember.Route.extend({
export default Route.extend({
resetController(controller) {
controller.set('preserveScrollPosition', false);
}
Expand Down
11 changes: 7 additions & 4 deletions addon/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default Mixin.create({
}
},

updateScrollPosition (transitions) {
updateScrollPosition(transitions) {
const lastTransition = transitions[transitions.length - 1];

let routerPath
Expand All @@ -58,12 +58,15 @@ export default Mixin.create({
} else {
scrollPosition = get(this, 'service.position');
}
const scrollElement = get(this, 'service.scrollElement');

const preserveScrollPosition = get(lastTransition, 'handler.controller.preserveScrollPosition');

if (!preserveScrollPosition) {
if ('window' === scrollElement) {
const scrollElement = get(this, 'service.scrollElement');
const targetElement = get(this, 'service.targetElement');

if (targetElement) {
window.scrollTo(scrollPosition.x, scrollPosition.y);
} else if ('window' === scrollElement) {
window.scrollTo(scrollPosition.x, scrollPosition.y);
} else if ('#' === scrollElement.charAt(0)) {
const element = document.getElementById(scrollElement.substring(1));
Expand Down
32 changes: 28 additions & 4 deletions addon/services/router-scroll.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Service from '@ember/service';
import { getWithDefault, computed, set, get } from '@ember/object';
import { typeOf } from '@ember/utils';
import { assert } from '@ember/debug';
import { getOwner } from '@ember/application';

export default Service.extend({
Expand All @@ -10,23 +11,39 @@ export default Service.extend({
}),

scrollElement: 'window',
targetElement: null,
delayScrollTop: false,

init(...args) {
this._super(...args);
this._loadConfig();
set(this, 'scrollMap', {});
set(this, 'scrollMap', { default: { x: 0, y: 0 }});
set(this, 'key', null);
},

update() {
const scrollElement = get(this, 'scrollElement');
const targetElement = get(this, 'targetElement');
const scrollMap = get(this, 'scrollMap');
const key = get(this, 'key');
let x;
let y;

if ('window' === scrollElement) {
if (targetElement) {
if (get(this, 'isFastBoot')) {
return;
}

let element = document.querySelector(targetElement);
if (element) {
x = element.offsetLeft;
y = element.offsetTop;

// if we are looking to where to transition to next, we need to set the default to the position
// of the targetElement on screen
set(scrollMap, 'default', { x, y });
}
} else if ('window' === scrollElement) {
x = window.scrollX;
y = window.scrollY;
} else if ('#' === scrollElement.charAt(0)) {
Expand Down Expand Up @@ -54,19 +71,26 @@ export default Service.extend({
set(this, 'key', stateUuid); // eslint-disable-line ember/no-side-effects
const key = getWithDefault(this, 'key', '-1');

return getWithDefault(scrollMap, key, { x: 0, y: 0 });
return getWithDefault(scrollMap, key, scrollMap.default);
}).volatile(),

_loadConfig() {
const config = getOwner(this).resolveRegistration('config:environment');

if (config && config.routerScroll && config.routerScroll.scrollElement) {
if (config && config.routerScroll) {
const scrollElement = config.routerScroll.scrollElement;
const targetElement = config.routerScroll.targetElement;

assert('You defined both scrollElement and targetElement in your config. We currently only support definining one of them', !(scrollElement && targetElement));

if ('string' === typeOf(scrollElement)) {
set(this, 'scrollElement', scrollElement);
}

if ('string' === typeOf(targetElement)) {
set(this, 'targetElement', targetElement);
}

const delayScrollTop = config.routerScroll.delayScrollTop;
if (delayScrollTop === true) {
set(this, 'delayScrollTop', true);
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ember-router-scroll",
"version": "0.6.1",
"version": "0.7.0",
"description": "Scroll to top with preserved browser history scroll position",
"keywords": [
"ember-addon",
Expand Down Expand Up @@ -62,7 +62,7 @@
"test:all": "ember try:each"
},
"dependencies": {
"ember-app-scheduler": "^0.2.0",
"ember-app-scheduler": "^0.2.2",
"ember-cli-babel": "^6.6.0",
"ember-getowner-polyfill": "^2.0.1"
},
Expand Down
42 changes: 41 additions & 1 deletion tests/acceptance/basic-functionality-test.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,55 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit, click, currentURL } from '@ember/test-helpers';
import { visit, click, currentURL, triggerEvent } from '@ember/test-helpers';
import config from 'dummy/config/environment';

module('Acceptance | basic functionality', function(hooks) {
setupApplicationTest(hooks);

hooks.beforeEach(function() {
document.getElementById('ember-testing-container').scrollTop = 0;
});
hooks.afterEach(function() {
config['routerScroll'] = {};
});

test('The application should work when loading a page and clicking a link', async function(assert) {
config['routerScroll'] = {
scrollElement: '#ember-testing-container'
}

await visit('/');

// testing specific
let container = document.getElementById('ember-testing-container');
assert.equal(container.scrollTop, 0);

await document.getElementById('monster').scrollIntoView(false);
await triggerEvent(window, 'scroll');

assert.ok(container.scrollTop > 0);

await click('a[href="/next-page"]');

assert.equal(currentURL(), '/next-page');
});

test('The application should work when loading a page and clicking a link to target an element to scroll to', async function(assert) {
config['routerScroll'] = {
scrollElement: '#target-main'
}

await visit('/target');

// testing specific
let container = document.getElementById('ember-testing-container');
assert.equal(container.scrollTop, 0);

await document.getElementById('monster').scrollIntoView(false);
assert.ok(container.scrollTop > 0);

await click('a[href="/target-next-page"]');

assert.equal(currentURL(), '/target-next-page');
});
});
2 changes: 2 additions & 0 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const Router = EmberRouter.extend(RouterScroll, {

Router.map(function () {
this.route('next-page');
this.route('target');
this.route('target-next-page');
});

export default Router;
4 changes: 4 additions & 0 deletions tests/dummy/app/routes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Route from '@ember/routing/route';

export default Route.extend({
});
4 changes: 4 additions & 0 deletions tests/dummy/app/routes/target-next-page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Route from '@ember/routing/route';

export default Route.extend({
});
4 changes: 4 additions & 0 deletions tests/dummy/app/routes/target.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Route from '@ember/routing/route';

export default Route.extend({
});
16 changes: 13 additions & 3 deletions tests/dummy/app/styles/app.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
@import url(https://fonts.googleapis.com/css?family=VT323);

body {
margin: 0;
}

#death-star {
position:absolute;
margin: 1550px auto 0 60%;
Expand Down Expand Up @@ -52,6 +56,12 @@ div {
margin: 12% 10% -200px 10%;
}

#nav {
height: 44px;
background-color: white;
color: black;
}

.depth {
font-size: 26px;
}
Expand All @@ -74,6 +84,6 @@ div {
.left {
margin: 300px 0 0 10%;
}
.right {
margin: 300px 0 0 75%;
}
.right {
margin: 300px 0 0 75%;
}
6 changes: 3 additions & 3 deletions tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div id="intro">
Click on the submarine to launch!
</div>
{{#link-to 'next-page'}}<img src="./images/yellow_submarine.gif" id="submarine">{{/link-to}}
{{#link-to 'next-page'}}<img src="/images/yellow_submarine.gif" alt="Submarine" id="submarine">{{/link-to}}
<div class="depth left">
— 500 fathoms
</div>
Expand Down Expand Up @@ -33,6 +33,6 @@
<div class="depth left">
— 5000 fathoms
</div>
<img src="./images/tentacle-monster.png" id="monster">
<img src="/images/tentacle-monster.png" alt="Monster" id="monster">
</div>
{{outlet}}
{{outlet}}
4 changes: 2 additions & 2 deletions tests/dummy/app/templates/next-page.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
Using the back button will take you to your previous scroll position.
</span>
</div>
{{#link-to 'application'}}<img src="./images/yellow_submarine.gif" class="flip" id="submarine">{{/link-to}}
{{#link-to 'application'}}<img src="/images/yellow_submarine.gif" alt="Submarine" class="flip" id="submarine">{{/link-to}}
<img src="./images/death-star.png" id="death-star">
<div class="depth right">
— Orbit -500 km
Expand Down Expand Up @@ -42,4 +42,4 @@
— Orbit -5000 km
</div>
</div>
</div>
</div>
Loading

0 comments on commit 948f25d

Please sign in to comment.