Skip to content

Back Button Support? #52

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

Closed
ajoslin opened this issue Mar 17, 2013 · 18 comments
Closed

Back Button Support? #52

ajoslin opened this issue Mar 17, 2013 · 18 comments

Comments

@ajoslin
Copy link
Contributor

ajoslin commented Mar 17, 2013

It would be absolutely amazing if we could detect when the back button is pressed, using window.onpopstate on newer browsers.

Then once ngAnimate is in, we could have transitions between pages that look different depending on forward or back (eg github file browser, any mobile app).

I'd be willing to work on this, has anyone else done anything with this?

@ksperling
Copy link
Contributor

Yes that would be neat. Maybe transitionTo() should have an 'options' object as a third parameter that can contain flags like 'this is a backwards transition', or properties that e.g. ngAnimate could look at to decide what type of animation to use etc.

@0x-r4bbit
Copy link
Contributor

And what about older browser? Should one then load a polyfill (or something like that) to also get a smooth transition, even if window.onpopstate === undefined?

Or would it be more like graceful degradation and there'll be just no transition? Which browsers does ui-router support anyway?

@ksperling
Copy link
Contributor

Well, back button support is already working in the sense that going back and fowards through history causes the correct state transition. I haven't looked at how (if) $location uses onpopstate internally, but as I see it this would essentially be a case of passing along a 'direction' hint to $state.transitionTo().

I'd think most work for this would actually have to happen in $location, to be able to determine if a location change is 'forward', 'backward', or unknown direction.

@jeme
Copy link
Contributor

jeme commented Apr 5, 2013

@ajoslin The alternation of sliding left or right in git-hubs file browser doesn't depend on using the back button, it depends on what direction you navigate in the file tree...

If you navigate down the tree it slides left
If you navigate up the tree it slides right

What you experience as different behavior on "back button" is properly because you often navigate down (left slide) but then you uses the back button to step up (right slide)... but if you instead click on the parent in the breadcrumbs, it slides right, if you click the back-button after than, it slides left because your now going down the tree.

That is not to say that this wouldn't be a neat feature to add, but looking around the web, I couldn't even figure out if this is possible for browsers that support the history API, but for those that doesn't, it is certainly not unless we wan't to make awful workarounds.

@ksperling
Copy link
Contributor

For history API, we could include a numeric value in the state and increase it on each forward transition. On a popstate we can then compare that number to the current one.

For legacy we could either not bother, or keep our own history (just an array of the URLs visisted within the current page, capped to some limit) and then when a state change is about to happen via $location, see if the URL we're going to is the last one we've been at.

@jeme
Copy link
Contributor

jeme commented Apr 6, 2013

@ksperling Problem with the legacy approach is that if the user actually navigates to the same link (not by the back button), it's a forward navigation... E.g. he clicks "/page1" then clicks "/page2" then clicks "/page1" again.

If we base it on an "URL" history, that would see it as a "back"...

@timkindberg
Copy link
Contributor

Seems like the same thing would happen if you went page2, page1, then page2. I'm kind of thinking this feature is better handled with some logic of where the user is coming from compared against where they are going instead of relying on history which can be completely messed up if the user travels directly to a page.

@timkindberg
Copy link
Contributor

As a user I could attach a depth to states; so root would be 0 then children of root would be 1.0, 1.1, 1.2, etc. then children of those would be 2.0, 2.1, 2.2, etc. Then in ngAnimate if the state I'm going to has a higher depth number than the state I'm coming from (meaning its deeper or a its sibling that comes after) then play the "forward" animation otherwise play "backward".

@legomind
Copy link

legomind commented May 1, 2013

@timkindberg I think you have the right idea there. That logic would allow detection of peer-to-peer navigation as well.

@ksperling
Copy link
Contributor

As basic requirement to support any of this ng-animate / $animator needs the ability to pass a custom "context" object for the transition through to the actual animation function, or ideally even the ability to choose between a set of animations declaratively.

@juanmarinbear
Copy link

I am currently working on a website that could use this feature. I have six sections. If you go from section 1 to 6, you quickly go through the other 4. However, when you press back, history saves the last section you went through, 5. So instead of going back to 1 (your origin) you go back to 5. I will work on capturing the "Back" press event and do some logic on it but would love a cleaner solution.

@mwager
Copy link

mwager commented Dec 8, 2015

What about adding a variable to the "event" parameter of "stateChangeStart/stateChangeSuccess" which tells the developer if the event was triggered programmatically or not?

Something like:

$rootScope.$on('$stateChangeStart', function(event) {
  1. $state.go 'some_where' -> event.isTriggeredProgrammatically === true
  2. back button clicked -> event.isTriggeredProgrammatically === false

@cudasteve
Copy link

Here's my use case. I think it's one of the more common ones. I'm listening for $stateChangeStart to prompt for "are you sure?" to prevent leaving unsaved edits.

If the user clicks a link to leave the page, using $stateChangeStart, I can prompt them, get their response, and cancel the navigation if the user dictates. Then if they click the link again and this time say "OK I'd like to leave" I can just let them go through.

However, if we try that same workflow with the back button things are a little different. When they hit the back button, you can see the URL change to the toState. After prompting I can call event.preventDefault() to prevent them and UI-router will put the correct URL back, but the page they were originally trying to go back to gets removed from the browser's history. So if they hit back again and accept this time, it'll send them back two pages.

@mwager's suggestion would allow me to get around this relatively nicely by allowing me to essentially check for whether they hit the back button or not. If they did, I can just call history.pushState({}, ''), which will quickly re-save the page they were trying to go to (because the URL has already changed) before event.preventDefault() pushes the original state back up on top.

For now, I'm probably going to compare $state.href(fromState, fromParams) with $location.path() to determine if the back button was pressed. That should get me by but isn't very elegant.

....if that makes any sense.

It'd be really nice to have a "here's how you prevent the user from leaving a page with unsaved edits" best practice writeup for UI-Router that covers all the ways to leave a page state-change, link-click, back button, refresh button, and close button.

...oh and shoot what about the FORWARD button??? I can't think of anything to help with that.

And there's even less hope when the user selects an item from the history rather than just pushing the back button.

@cudasteve
Copy link

FYI, in case anyone needs something like this in the future, this is what my final solution looks like that accounts for link-clicking, state changes, back button, reload button, and close button. The other two (forward button and history item selection) are a lot harder I think.

    // State change, link clicking, and back button
    $scope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
        if (!$scope.noSave  && hasChanged()) {
            if (confirm("Are you sure?\nUnsaved changes will be lost.")) {
                $scope.noSave = true;  // so subsequent redirects in this same state change don't ask again
            } else {
                if ($state.href(fromState, fromParams) !== $location.path()) {
                    // Clicked back button
                    history.pushState({}, '');  // Requires HTML5 pushState API
                }
                event.preventDefault();
            }
        }
    });

    // Refresh and close buttons
    $window.onbeforeunload = function () {
        return hasChanged() ? 'Unsaved changes will be lost.' : null;
    };
    $scope.$on('$destroy', function() {
        $window.onbeforeunload = undefined;
    });

@floyd-may
Copy link

This StackOverflow answer seems to be far more complex than what @cudasteve has posted:

http://stackoverflow.com/a/22402285/170407

Is the SO answer overkill?

@cudasteve
Copy link

@floyd-may I think the SO answer is answering something slightly different than my solution. My solution is just a way to implement "are you sure?" prompts when leaving states using the back button. My solution assumes the previous states have already been added to the browser's history via URL changes, whereas the SO answer seems to be about how to leverage the back button for navigation even without URL changes.

And neither of those things are the full scope of what I think is being discussed in this issue anyway.

@amypellegrini
Copy link

My case involves 404 redirection on certain routes. When a user (for whatever reason not relevant here) gets a 404 page, going "back" navigates to the same route that redirected to 404, which ends up again in a 404 page. I've tried to prevent pushing state for these cases, so the URL is not recorded in history, but fur some reason $state.go('notFound', {}, { location: false }) is not doing the trick, I'm not sure if this is actually a bug.

@stale
Copy link

stale bot commented Jan 24, 2020

This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs.

This does not mean that the issue is invalid. Valid issues
may be reopened.

Thank you for your contributions.

@stale stale bot added the stale label Jan 24, 2020
@stale stale bot closed this as completed Feb 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests