Skip to content

Blogging On Rails

Everything on Rails!

Don’t accidentally walk away from your Active Storage Uploads with Stimulus.js

I’ve recently been using Active Storage, and it has a really neat feature called Direct Uploads. Active Storage asks your app for a URL for uploading each file, and then proceeds to upload the file directly to your storage service, without having to tax your app server’s processor. But if you’ve selected a lot of files, or your file is huge, or both, you may accidentally leave the page before those files are uploaded, causing the whole upload batch to fail. Thankfully, we can setup our form, like in the previous form example, to prevent our customer from accidentally leaving the page while uploading files.

How?

The browser fires off a event every time it’s about to change the current page called beforeunload that we’ll use to prevent losing an unsaved form. We’ll also listen for Turbolinks turbolinks:before-visit event for page transitions inside our app. By intercepting and handling these events, and keep tracking of uploads by listening to active storage DOM events, the controller can put up an alert, making sure the user actually wants to leave the page.

Getting Started

I’m assuming you’ve setup your page to properly pull in Stimulus Controllers. The Stimulus Handbook has instructions if you’re new and need to get started.

I’m also assuming you have Active Storage configured with your preferred storage service. You can easily get started with the built in disk service.

HTML

Let’s add the Stimulus annotations to our form. The <form> tag is going to have the controller, and the window event listening actions. We will also listen for two Active Storage events, direct-uploads:start and direct-uploads:end. When uploading starts, we will change the uploading data attribute on the form to true, and then change it back to false when uploading is done.

I’m working with a simple ActiveRecord PhotoAlbum model which has a name, description, and is set to have may photos through Active Storage.

<%= form_for @photo_album, remote: true, data: { controller: 'photo-albums',  action: 'beforeunload@window->photo-albums#leavingPage turbolinks:before-visit@window->photo-albums#leavingPage direct-uploads:start@window->photo-albums#startDirectUploads  direct-uploads:end@window->photo-albums#endDirectUploads', 'photo-albums-uploading': "false"  } do |form| %>
  <%= form.text_field :name, :placeholder => "Album Name" %>
  <%= form.text_area :description, :placeholder => "Album Description", :rows => 4 %>
  
  <%= form.file_field :photos, multiple: true, direct_upload: true %>
 
  <%= form.submit %>
<% end %>

Stimulus Controller

Our controller has the three actions, and uses an uploading data attribute to keep track of whether to prevent the browser from changing the page.

The uploading attribute always starts as false. When a direct-uploads:start event happens, it is set it to true and when a direct-uploads:end event happens, it is set to false. If uploading is true when the a user tries to navigate from the page, the controller attempts to stop them. The turbolinks:before-visit uses a confirm message and the beforeunload event uses the browser’s native alert to make sure that the user wants to leave the page.

Here is the code for the photo_albums_controller.js:

import { Controller } from "stimulus"

const LEAVING_PAGE_MESSAGE = "You have attempted to leave this page and you are uploading files. If you leave, you will need to reupload these files.  Are you sure you want to exit this page?"

export default class extends Controller {

  startDirectUploads(event) {
    this.setUploading("true")
  }

  endDirectUploads(event) {
    this.setUploading("false")
  }

  leavingPage(event) {
    if (this.isUploadingFiles()) {
      if (event.type == "turbolinks:before-visit") {
        if (!window.confirm(LEAVING_PAGE_MESSAGE)) { 
          event.preventDefault()
        }
      } else {
        event.returnValue = LEAVING_PAGE_MESSAGE;
        return event.returnValue;
      }
    } 
  }

  setUploading(changed) {
    this.data.set("uploading", changed)
  }

  isUploadingFiles() {
    return this.data.get("uploading") == "true";
  }
}

In Closing

Now you can pick 20 or so photos, and not have to worry about accidentally closing a tab or leaving the page while they are still uploading. I hope this helps you build a great user experience.

Feel free to leave a comment or question below.

Want To Learn More?

Try out some more of my Stimulus.js Tutorials.

Make Interactivity Default 

Make your web app interactive now with easy to implement and simple to add HOTWire integrations. 

Enter your email and get a free sample of my HOTWire Tutorials ebook.

We won’t send you spam. Unsubscribe at any time.

Leave a Reply

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