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

Track unique history location states #14011

Merged
merged 1 commit into from
Jan 21, 2017
Merged

Track unique history location states #14011

merged 1 commit into from
Jan 21, 2017

Conversation

bcardarella
Copy link
Contributor

@bcardarella bcardarella commented Aug 3, 2016

This PR will allow the HistoryLocation API to keep track of unique
state UUIDs for each state that is pushed/replaced. This change should not
break backwards compatibility and can allow for other libraries to use
this information to set proper scroll position.

The path alone does not provide enough information. For example, if you
visit page A, scroll down, then click on a link to page B, then click on
a link back to page A. Your actual browser history stack is [A, B, A].
Each of those nodes in the history should have their own unique scroll
position. In order to record this position we need an ID that is unique
for each node in the history.

@@ -150,7 +151,8 @@ export default EmberObject.extend({
@param path {String}
*/
pushState(path) {
let state = { path: path };
let id = guidFor(`${path}-${new Date()}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think using guidFor on a string is very useful, if the idea is that all new states have a unique id I think this may be easier/better:

let state = { path, id: ++stateCounter };

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I figured that was hairy. I'll go about the stateCounter

@bcardarella
Copy link
Contributor Author

@rwjblue updated

@@ -33,6 +33,7 @@ export default EmberObject.extend({
set(this, 'location', get(this, 'location') || window.location);

this._popstateHandler = undefined;
this.stateCounter = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems private, this._stateCounter would be preferable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@mixonic
Copy link
Member

mixonic commented Aug 8, 2016

@bcardarella this also adds public API to getState in the form of id, correct? Can you add some documentation to getState about the shape of the object being returned?

Generally seems reasonable.

@@ -150,7 +151,8 @@ export default EmberObject.extend({
@param path {String}
*/
pushState(path) {
let state = { path: path };
let id = `${this.stateCounter++}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this just be the integer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like I had a good reason to force it to a string. Let me go back and see why I did that. It might not be necessary now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something that I can handle upstream. Moving to just int

@bcardarella
Copy link
Contributor Author

@mixonic updated per your suggestions

@rwjblue
Copy link
Member

rwjblue commented Aug 12, 2016

There is some question here if feature flagging and/or an RFC is needed here. I think the quick summary is that If this is introducing public API, then we should flag it (and potentially create a small and quick RFC for it)...

@bcardarella
Copy link
Contributor Author

The counter itself is private as @mixonic suggested. The unique value added to the history states is not an API

@rwjblue
Copy link
Member

rwjblue commented Aug 13, 2016

OK, so no new public API is being added here then. c/d?

@bcardarella
Copy link
Contributor Author

That's what I believe, but of course not my call ;)

@mixonic
Copy link
Member

mixonic commented Aug 21, 2016

@bcardarella yeah the return value from a public API is of course at best public API itself, or at the worst intimate. For modern things that are properties but private API we've started using symbols and non-enumerable getters, but that obviously hasn't propagated through the whole codebase.

The use-case you described seems reasonable, but a socialized RFC would help gather other use cases that may be related. It would be good to hear from some other community members.

If you can draft something I'm happy to help edit and champion this with you 🙇

@bcardarella
Copy link
Contributor Author

@mixonic finally got around to this: emberjs/rfcs#186

@stefanpenner
Copy link
Member

thanks @bcardarella !

@stefanpenner
Copy link
Member

stefanpenner commented Dec 6, 2016

After reading through the RFC and then commenting on it, i realized I believe this can already be implemented via map or WeakMap, but maybe we should talk about it on the RFC itself, no changes to ember required

@krisselden
Copy link
Contributor

@stefanpenner session state is serialized for browser crash recovery including the history state, so if you don't map the id to session storage or the sequence your app will be broken if someone chooses to recover their session after a crash.

In fact this PR should really store the id sequence start in session storage.

@bcardarella
Copy link
Contributor Author

@stefanpenner @krisselden should this conversation take place in the RFC?

@krisselden
Copy link
Contributor

@bcardarella I'll copy & paste it

@stefanpenner
Copy link
Member

@stefanpenner session state is serialized for browser crash recovery including the history state, so if you don't map the id to session storage or the sequence your app will be broken if someone chooses to recover their session after a crash.

In fact this PR should really store the id sequence start in session storage.

@krisselden ya, i came to this conclusion in the linked discussion.

@homu
Copy link
Contributor

homu commented Jan 17, 2017

☔ The latest upstream changes (presumably #14821) made this pull request unmergeable. Please resolve the merge conflicts.

@@ -150,7 +155,8 @@ export default EmberObject.extend({
@param path {String}
*/
pushState(path) {
let state = { path: path };
let id = this._stateCounter++;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should update this to use a UUID more details here

@bcardarella
Copy link
Contributor Author

@stefanpenner updated

Copy link
Member

@rwjblue rwjblue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RFC for this was just merged (:tada:), so now this is ready for some more work.

Basic steps to land are:

  • add new feature to feature.json with null value
  • feature flag API changes (if (isFeatureEnabled('new-feature-name') { } etc)
  • feature flag tests

@bcardarella
Copy link
Contributor Author

@rwjblue I wrapped the private scope _uuid function like so:

if (isFeatureEnabled('ember-unique-location-history-state')) {
  function _uuid() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r, v;
      r = Math.random() * 16 | 0;
      v = c === 'x' ? r : r & 3 | 8;
      return v.toString(16);
    });
  }
}

but am getting the following error:

18:3   error  Move function declaration to program root  no-inner-declarations

How should I resolve?

@rwjblue
Copy link
Member

rwjblue commented Jan 21, 2017

I think generally we do something like this:

let _uuid;
if (isFeatureEnabled('ember-unique-location-history-state')) {
  _uuid = function _uuid() { /*  implementation here */ };
}

@bcardarella
Copy link
Contributor Author

All set

This PR will allow the HistoryLocation API to keep track of unique
state IDs for each state that is pushed/replaced. This change should not
break backwards compatibility and can allow for other libraries to use
this information to set proper scroll position.

The path alone does not provide enough information. For example, if you
visit page A, scroll down, then click on a link to page B, then click on
a link back to page A. Your actual browser history stack is [A, B, A].
Each of those nodes in the history should have their own unique scroll
position. In order to record this position we need an ID that is unique
for each node in the history.

Moved from stateCounter to uuid

Set under Feature Flag
@rwjblue rwjblue merged commit 13c26ed into emberjs:master Jan 21, 2017
@bcardarella bcardarella deleted the bc-unique-id-for-history-states branch January 21, 2017 19:47
briangonzalez pushed a commit to DockYard/ember-router-scroll that referenced this pull request Jan 24, 2017
Now that emberjs/ember.js#14011 has landed
it would be best to align this library with the new implementation in
anticipation of deprecating the RouterScroll for Ember 2.13 and
eventually removing it in Ember 3.0.
@locks locks mentioned this pull request Mar 13, 2017
18 tasks
@knownasilya
Copy link
Contributor

Can this be used in 2.14 or is the addon by DollarShaveClub still necessary?

@bcardarella
Copy link
Contributor Author

The addon is still necessary as it makes use of the token for the scroll positioning. If you have a use outside of the addon this functionality is in the HistoryLocation class in 2.14

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants