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

How to add go back action? #43

Open
aryelgois opened this issue Jul 6, 2021 · 8 comments
Open

How to add go back action? #43

aryelgois opened this issue Jul 6, 2021 · 8 comments

Comments

@aryelgois
Copy link
Contributor

I would like to add an in-app back button that should go back in the history, but if it would leave my site it should call history.replaceState to a fallback instead.

For example, if I click on a link to example.com/products/123, the in-app back button would work as router.route('/products', true), but if I was already at any page in that site and navigated to that route, the button would work as history.back().

The falback uri would be different depending on the page and should be specified, but it could default to /.

@lukeed
Copy link
Owner

lukeed commented Jul 6, 2021

Navaid will still listen/react when you call history.back()

@aryelgois
Copy link
Contributor Author

Thanks for the response!

Navaid will still listen/react when you call history.back()

I noticed that :)

But if I enter my site and call that function, it may do nothing, because there is no history, or it may take me to the previous site (in a different domain) or a blank page. I would like to know how to avoid leaving my site with this in-app back button.

Something like:

function goBack (fallback) {
  if (canGoBack) { // how do I get this?
    // the previous history is in my site
    history.back()
  } else {
    // there is no history or it would leave my site
    router.route(fallback, true)
  }
}

@lukeed
Copy link
Owner

lukeed commented Jul 7, 2021

You should use some sort of state/boolean flag when your application boots up, so that you know it's on the page. The history API doesn't let you see any forward/previous URLs, so there's no way to "ask" what the last URL was and then parse it.

var startAt;
function onAppInit() {
  // ...
  startAt = history.length || 0;
}

function goBack(fallback) {
  if (history.length - startAt > 0) {
    // ...
  }
}

If you don't want to do this, you have to look for a Navaid side effect. For example, if (and only if) you call router.listen, then Navaid creates a custom history.push property.

function goBack() {
  if (!!history.push) { /* ... */ }
  // or
  if (history.push === 'push') { /* ... */ }
}

However, the issue with this is that it's (a) set immediately and (b) if this goBack runs immediately, it may do weird things, especially in the unlikely event your user is coming from a different site that was also using Navaid lol

@aryelgois
Copy link
Contributor Author

Storing the initial state like this won't work because once the window goes to the previous history entry, either by clicking the native back button or calling history.back(), the check if (history.length - startAt > 0) { history.back() } ... might fail since the length stays the same but you're not at the latest entry.

I was checking this stackoverflow post and I tried to use the beforeunload "hack", but I got an annoying leaving page pop-up on Firefox.

The best answer for me on that post is https://stackoverflow.com/a/62658623, that puts a flag in the history itself.

It worked great, but I had to wrap navaid's route() to cover the case that you are at the first history entry in the site and replace it with another page, because navaid always stores the path in the history.state, but seems to never use it.

Is there a way for navaid supporting custom state to be passed in the first parameter here? Or even better handle this state internally and expose a back()function..

@lukeed
Copy link
Owner

lukeed commented Jul 8, 2021

It will only stay the same if you use replaceState (or router.route('..', true)) which – generally – shouldn't be done. Not sure of your use case exactly, but replaceState in a SPA setting is usually used for login/logout forced redirects.

Even then, you can save the initial history.length and save the initial URL loaded.

var startLen, startPath;
function onAppInit() {
  // ...
  startLen = history.length || 0;
  startPath = location.pathname;
}

function goBack(fallback) {
  if (history.length - startLen > 0 || location.pathname !== startPath) {
    // ...
  }
}

Sorry but I don't want to add anything, but mostly because I don't think it needs to be added. A library shouldn't need to accomodate every use case, especially when it builds on top of platform primitives. This more-or-less boils down to a simple, application-specific if condition

@aryelgois
Copy link
Contributor Author

It's okay, I understand.

or router.route('..', true)

Wow, I didn't think I could do that. At first I thought it would go to the parent path of the current route, but it seems to be going to /, is this right?

I am planning to maintain a fork with the changes I need just because I will be using it in multiple projects.

@lukeed
Copy link
Owner

lukeed commented Jul 9, 2021

No problem re: forking :)

Ah sorry, the router.route('..', true) was meant to be "..." to indicate "any value".
Was just trying to say that passing true for the 2nd argument will use replaceState under the hood. Either way, because of replacing, the history.length doesn't change because you're just changing the last entry instead of appending to it. That's why the location.pathname comparison comes into play.

@aryelgois
Copy link
Contributor Author

Oh, I see.

In my case, I had to use replaceState because router.route('...', true) overrides the history.state and I need to keep a flag in there when calling goBack() (see https://stackoverflow.com/a/62658623)

If route()did accept an optional state, I would have:

// setup flag before listen() to avoid double on() handle
history.replaceState({ root: true }, '', location.pathname)

router.listen()

function goBack (fallback) {
  if (history.state && history.state.root) {
    router.route(fallback || '/', true, { root: true })
  } else {
    history.back()
  }
}

Maybe this is a use case for #6 :D

Of course, this does not cover the SPA setting with redirects for login, because the route() does not keep the current state, so I had to wrap it:

function navigate (uri, replace) {
  if (replace && history.state && history.state.root) {
    history.replaceState({ root: true }, '', uri)
  } else {
    router.route(uri, replace)
  }
}

It still fails if I call this navigate() or click in a link with the same path as the current one. And since in one of my projects the router has a base path, I also had to take that into account with my current workaround.

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

No branches or pull requests

2 participants