Solution 1 :

The popstate event is not cancellable, and cannot be intercepted to prevent the default. (I suppose otherwise developers would be able to prevent users from leaving their sites!).

Turbolinks handles all popstate events and has its own mechanisms for doing so. You can push to the history stack in a way that Turbolinks understands by calling:

Turbolinks
  .controller
  .pushHistoryWithLocationAndRestorationIdentifier(<YOUR_LOCATION>, Turbolinks.uuid())

So rather than thinking that you want to handle the popstate event, Turbolinks should now handle that for you, restoring the page as it was before you initialised the dialog. It’s worth noting that whilst this is a public method, it’s not documented, and therefore maybe removed in future versions.

Problem :

I’m using Turbolinks in a Rails 6.1 web app.

I understand that Turbolinks uses AJAX to replace the body and uses the history.pushState() to update the URL to match.

I’ve implemented a dialog/modal system that appends an element to the body and then shows that element using CSS with some event handlers to close the dialog and remove it when the user clicks a close button on the dialog.

The problem is on mobile device browsers, users prefer to the use back button (bottom-left arrow on Android) instead of reaching up to the top of the dialog and clicking the close button.

So I need a way to intercept the normal Turbolinks restoration event and execute some of my own Javascript on the popstate event instead of Turbolinks taking over.

I’m aware the Turbolinks docs state that restoration events can’t be canceled, but I was looking for a way I could use native JS to intercept the popstate before Turbolinks ever gets it.

I thought it should be pretty straightforward: when I show a dialog in my Javascript, I execute

window
      .history
      .pushState({ dialog: { id: "id-of-dialog" } }, null, window.location)

and then have an event handler for the popstate that closes the dialog.

document.addEventListener("popstate", (event) => {
  console.log(event.state)
  if (event.state.dialog) {
    const dialogId = event.state.dialog.id
    const dialogElement = document.getElementyById(dialogId)

    // Note: I do have some animations for closing, so the closing logic is slightly more
    // complex than this, but this shows the gist of what's going on.
    dialogElement.classList.remove("dialog-container-open")
    dialogElement.remove()

    // Does this stop Turbolinks from taking over?
    event.preventDefault()
    event.stopPropagation()
  } else {
    // Do nothing, let Turbolinks handle it
  }
})

However, in the popstate event, Turbolinks always has a state on top of the history stack and I can’t figure out how or where it’s happening, thus I can’t stop it.

Essentially, I just want to manually manage the history popstate logic when the back button is pressed to handle my dialogs. But no matter what, Turbolinks always seems to fire and refresh the page when I just want to close a dialog.

Is there a way to implement this? I’ve tried hashchange as well, but the history API seemed much more promising, since Turbolinks is built on top of that.

By

Leave a Reply

Your email address will not be published. Required fields are marked *