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.
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.