Hook into Safari back button click

45 Views Asked by At

We have a survey in a single-page app. Because it's a survey, we want to allow users to go back but not forward in history. That is, if they change an answer in the "past" it may affect questions in the future, so the rule is: you can go time travel backwards, but only progress forward in real time.

Because of the nature of this, using history.pushState and popstate handlers isn't quite what we want. In effect, we want an event listener on the browser's back button, while choosing to prevent or allow the "natural" browser's functionality.

I've tried a few solutions, and they do pretty much everything I want them to, except they don't seem to work in Safari. Safari is the new IE, apparently. I've tried these basic code snippets:

window.history.pushState(null, null, document.URL);
window.addEventListener('popstate', () => {
  // This is simplified, obviously...
  if (onFirstPageOfSurvey) {
    // beacuse we start by pushing on the history stack, we have to go back
    // 2 steps to allow the user to go back to the actual previous page.
    history.back(2);
  } else {
    // pushing right back on to the history after popping should 
    // keep the page from navigating away (just not in safari)
    window.history.pushState(null, null, document.URL)
  }
})

I've tried a number of variations of the above, but can't possibly list them all.

This one seems a bit dated, but does still work in Chrome, but again not Safari

function noBack() {
  window.history.forward()
}
noBack()
window.onload = noBack
window.onpageshow = function (evt) {
  if (evt.persisted) noBack()
}
window.onunload = function () {
  void 0
}

Any help is appreciated!


I know this is a touchy UX subject. The goal is basically to allow the back button to go back within the app, and then if they back out all the way, let the browser history take over and actually go back a page. We're not trying to "trap" the users; on the contrary, we've found that users tend to assume that they can use the back button and are pretty upset once they've gotten well into the survey, and then end up on the page before the survey, and basically lose everything.

1

There are 1 best solutions below

0
thinnerwhiterduke On

Posting to SO was a sort of rubber duck for me...I thought of a few things while writing and actually have a solution at this point.

I think the problem is that Safari doesn't like an immediate pushState. Upon closer inspection, navigating from home => sruvey results in my Chrome history looking like (top is most recent):

  1. survey
  2. survey
  3. home

...entry #2 being the initial navigation, and entry #1 (most recent) being the result of the first pushState. In Safari, the exact same navigation just looks like

  1. survey
  2. home

The solution:

Defer pushState until navigating through the app. Declare a flag so that it's only called once. I'm getting the sense it might need to be tied to a user event in Safari. Using a setTimeout on page load did not work.

Simplified example:

window.addEventListener('popstate', () => {
  if (onFirstPageOfSurvey) {
    history.back(2);
  } else {
    window.history.pushState(null, null, document.URL)
  }
})
let backButtonControlled = false;
function navigateThroughSurvey(surveyState) {
  // Do things with surveyState that we're not concerned about right now...
  if (!backButtonControlled) {
    window.history.pushState(null, null, document.URL);
    backButtonControlled = true;
  }
}