hotwired / turbo

The speed of a single-page web application without having to write any JavaScript
https://turbo.hotwired.dev
MIT License
6.65k stars 421 forks source link

Stimulus substitute for window.beforeunload #485

Open spand opened 2 years ago

spand commented 2 years ago

Hi,

We attempted to use turbo:before-visit to prevent navigation for pages with "unsaved" content. Looking at the documentation again it is clear that this will not work for all cases:

turbo:before-visit fires before visiting a location, except when navigating by history. Access the requested location with event.detail.url. Cancel this event to prevent navigation.

It kind of sucks that there is no stimulus substitute for window.beforeunload ie. An event handler that works for all cases as beforeunload does for non-SPA websites

I assume that before-visit is implemented via. the normal link navigation and this is the reason it is a poor substitute for beforeunload. It seems however (and some quick googling seems to indicate) that one could hack a beforeunload on top of both before-visit and popstate (and on cancel reconstruct the history to match pre popstate).

This kind of leaves me at the end here asking why this has not been implemented. Has it been attempted and failed or has it simply not yet been attempted/done ?

Kind regards

leehericks commented 2 years ago

I'm working on this for preventing navigation during active storage file uploads.

beforeunload@window works testing to close the tab after the direct upload starts.

It's rather the problem of turbo:before-visit@document (nor @window) not working, possibly because it's in a turbo frame?

@spand in your case you could make a dirty_controller.js with a boolean value dirty, default false, and find a way to listen on the form elements for changes.

html

<form data-controller="uploading" data-action="direct-uploads:start->uploading#start direct-uploads:end->uploading#end beforeunload@window->uploading#confirmCancel turbo:before-visit@document->uploading#confirmCancel" enctype="multipart/form-data" action="..." accept-charset="UTF-8" method="post">
...
</form>

uploading_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static values = {
    uploading: { type: Boolean, default: false },
    confirmCancelMessage: { type: String, default: "Leave during uploading?" }
  }

  start(event) {
    this.uploadingValue = true
  }

  end(event) {
    this.uploadingValue = false
  }

  confirmCancel(event) {
    if (this.isUploading) {
      event.preventDefault()

      if (window.confirm(this.confirmCancelMessageValue)) {
        event.detail.resume()
      }
    }
  }

  get isUploading() {
    return this.hasUploadingValue && this.uploadingValue == true
  }
}
spand commented 2 years ago

@leehericks No, your example will not fix the problem. The issue is that there is no way to prevent unloading the page on navigating by history as the issue says.

leehericks commented 2 years ago

@spand sorry you're right, my miss! I guess we both have a problem here of similar nature.

leehericks commented 2 years ago

@spand but to be honest, your whole issue talks about window.beforeunload. You don't really mention navigating by history at all.

It kind of sucks that there is no stimulus substitute for window.beforeunload

My stimulus example runs fine for window.beforeunload and prompts the user when trying to close the window.

So maybe you should re-write your issue.

collimarco commented 9 months ago

I also need to auto-save same content (I already have an autosave stimulus controller), before any other action:

I see that this issue is quite old. Have you found any reliable solution?

collimarco commented 9 months ago

Related: https://github.com/hotwired/turbo/issues/934