router icon indicating copy to clipboard operation
router copied to clipboard

Cancel navigateBack from canDeactivate

Open TonyLugg opened this issue 7 years ago • 33 comments

I'm submitting a bug report

  • Library Version: aurelia-router: 1.3.0

  • Operating System: Windows 10

  • Node Version: 6.11.3

  • NPM Version: 5.3.0

  • JSPM OR Webpack AND Version Webpack 3.5.5 (using CLI)

  • Browser: all

  • Language: TypeScript 2.5.2

Current behavior: I load the app, which loads "home" route/view. Press a button and use router.navigate("second") to go to second page. Enter some data and press back button (on page) which executes router.navigateBack(). Because data has changed, display a message box (Aurelia dialog) from canDeactivate() to ask for user confirmation (same behavior with window.confirm()), and return false from canDeactivate(). Navigation stops. Repeat from pressing back button (on page), backward navigation continues and navigates right out of the app as if it has navigated back twice.

https://gist.github.com/TonyLugg/f4a7fa39c9d82f5de0af3b0bc8e01850

Expected/desired behavior: Each time false is returned from canDeactivate, navigateBack() should be stopped.

If I press a different button and perform navigate("someOtherRoute") and return false from canDeactivate it works correctly with multiple navigation attempts.

TonyLugg avatar Oct 03 '17 15:10 TonyLugg

Just wondering if this has any attention. It is causing me a bit of grief.

TonyLugg avatar Oct 14 '17 21:10 TonyLugg

@TonyLugg Thanks for your detailed report.

Running gist here: https://gist.run/?id=f4a7fa39c9d82f5de0af3b0bc8e01850

davismj avatar Oct 25 '17 09:10 davismj

Hello!

Any update on this bug?

gama410 avatar Jan 05 '18 12:01 gama410

I'm going to want to start looking at this for 1.6.0, potentially a March release if all goes well.

davismj avatar Jan 05 '18 15:01 davismj

The core issue is that router.navigateBack() calls history.back(), which is a purposefully opaque API. When the cancel is processed, it is not forwarded to the history and so the history believes that the navigation back was successful.

davismj avatar Aug 01 '18 06:08 davismj

I'm not familiar with the implementation details but wouldn't it be logical to call history.back only after candeactivate returns true? It seems weird to me that we navigate back before checking if we are allowed to.

gama410 avatar Aug 01 '18 07:08 gama410

It's not a straightforward switch. history.back() is used to change the url, which kicks off the navigation pipeline including canDeactivate. This strategy makes sense as well because if we just read the previous state and navigated to it, we'd be adding a new entry into the history, not navigating backwards in it.

I've done some experiments calling history.forward when a navigateBack() fails, but it seems that calling the two in rapid succession causes them to fail to function properly. See this gist: https://gist.run/?id=6e306fa6d5c9675e8ea55fe5f495be2f

davismj avatar Aug 01 '18 08:08 davismj

I've found a number of related bugs. Specifically, if you start at #/first and navigate to #/second via <a href="#/second">, and if first returns false from canDeactivate(), a new history item would be appended for the current route.

Some early tests show this solving this issue and most of the related issues I've uncovered:


  function restorePreviousLocation(router) {
    var previousLocation = router.history.previousLocation;
    if (previousLocation) {
      Promise.resolve().then(() => history.go(router.isNavigatingBack ? 1 : -1));
    } else if (router.fallbackRoute) {
      router.navigate(router.fallbackRoute, { trigger: true, replace: true });
    } else {
      logger.error('Router navigation failed, and no previous location or fallbackRoute could be restored.');
    }
  }

I need to do additional testing to with pushState and with child routers.

davismj avatar Aug 01 '18 09:08 davismj

This strategy is not a good one because it enters an infinite loop when the user navigates back (or forwards) more than one entry.

I think the only and right way to handle this is using sessionStorage. I'm going to give that a try.

davismj avatar Aug 03 '18 08:08 davismj

It has been a year since I reported this issue. Any update on expected fix date?

TonyLugg avatar Oct 02 '18 15:10 TonyLugg

I did spend a fair amount of time trying to fix it, but unfortunately due to limitations on the browser detailed above I was not able to make sufficient progress. I'd be happy to accept a PR from a community member who thinks they may have a solution.

davismj avatar Oct 02 '18 15:10 davismj

Thanks for the update. We will avoid using CanDeactivate.

TonyLugg avatar Oct 02 '18 15:10 TonyLugg

@TonyLugg @davismj I think I could put together a PR if it's of interest.

jwx avatar Oct 06 '18 00:10 jwx

@jwx @davismj It is of great interest. Thank you.

TonyLugg avatar Oct 06 '18 01:10 TonyLugg

I'd really like to see it too. I'd be happy to work with you on it. It's tougher than it seems. If you open up a preliminary PR we can collaborate on it as well.

davismj avatar Oct 06 '18 01:10 davismj

I'll take a look later this weekend. Just to be clear (since I haven't really used the "exit" cycle steps): canDeactivate only triggers when you're navigating (either through link or history) to a different page within the same Aurelia application, right? Since navigation is done via browser location, if I go from my-aurelia-app.com/#/some-page to not-an-aurelia-app.com/elsewhere the exit cycle steps can't run due to the nature of the browser switching to the new site immediately. Right?

jwx avatar Oct 06 '18 08:10 jwx

Note that this doesn't in any way prevent a user from leaving the Aurelia application; it only deals with navigation within the application.

jwx avatar Oct 08 '18 14:10 jwx

Also, I'd really appreciate some help in testing this with pushstate, other browsers than Chrome, some more use cases etc. @davismj @TonyLugg

jwx avatar Oct 08 '18 14:10 jwx

I'm happy to help with testing. However, I am a .NET/WPF with TFS guy turned Aurelia, so the whole git workflow is a bit foreign to me. If you can help me setup the test environment I will make the effort.

TonyLugg avatar Oct 08 '18 15:10 TonyLugg

Thanks @TonyLugg! I've made two extra branches to simplify testing so just installing

npm install --save git://github.com/jwx/history-browser.git#history-index-test
npm install --save git://github.com/jwx/router.git#candeactivate-history-bugfix-test

in a project should do.

jwx avatar Oct 08 '18 16:10 jwx

OK, I will give this a try. Are they ready now?

TonyLugg avatar Oct 08 '18 16:10 TonyLugg

Yeah, should be.

jwx avatar Oct 08 '18 16:10 jwx

@jwx I have tested your fix, both backward and forward, with cancellation and without, with user confirmation and without, including multiple history items deep. It works perfectly! Great work!

@davismj When can we get it released in a router update?

TonyLugg avatar Oct 09 '18 16:10 TonyLugg

Hello @davismj ! I'm checking my opened issues at work and noticed I still have this issue opened. I'm surprised the fix from @jwx has not been merged, did you encounter some problems with it?

gama410 avatar Jan 09 '19 14:01 gama410

@gama410 This is on me. I was asked to create some thorough tests for this and have so far been coming up short. I hope to have it fixed before the end of the week.

jwx avatar Jan 09 '19 14:01 jwx

Ok great! Thanks for your work!

gama410 avatar Jan 09 '19 14:01 gama410

Hi @davismj @jwx - Any update on this ?

timmyktom avatar Feb 06 '19 04:02 timmyktom

Would also love to see this implemented, any update on when this could be merged? @davismj @jwx

tomasbillborn avatar Feb 19 '19 10:02 tomasbillborn

It works in the basic case but edge cases are not handled, e.g. when a route is not found. See here for the status of the PR: https://github.com/aurelia/router/pull/621

davismj avatar Feb 19 '19 10:02 davismj

Is there any viable workaround for this issue? I need to ask user about leaving the large form with unsaved data.

sousekd avatar Nov 19 '20 20:11 sousekd