Upload progress bar on Rails using Uppy
This is how I added an upload progress bar to a standard Rails form using Uppy, stimulus and turbo streams.
1. Add uppy js and css
yarn add uppy
In your stylesheet (SASS here):
@import @uppy/core/dist/style.css
@import @uppy/dashboard/dist/style.css
2. Update the form
Replace the file input with two placeholders (HAML here):
= simple_form_for @document, url: url, data: {controller: 'forms--document'} do |f|
- if @document.errors.any?
.alert.alert-danger
%ul
- @document.errors.full_messages.each do |e|
%li= e
=# f.input :attachment, as: :file, hint: label # commented out, we use uppy now
#uppy-dashboard-container
= submit_tag "Save", class: 'btn btn-default'
3. Create stimulus controller to start uppy
Something like this, that binds uppy to the containing form:
import { Controller } from "@hotwired/stimulus"
import Uppy from '@uppy/core'
import Form from '@uppy/form'
import Dashboard from '@uppy/dashboard'
import XHRUpload from '@uppy/xhr-upload'
export default class extends Controller {
connect() {
const uppy = new Uppy({
restrictions: {
minNumberOfFiles: 0,
maxNumberOfFiles: 1
},
autoProceed: false // if true, the upload will start immediately after a file is selected
});
uppy.use(Form, {
target: this.element
});
uppy.use(Dashboard, {
inline: true,
target: '#uppy-dashboard-container',
hideUploadButton: false
})
uppy.use(XHRUpload, {
endpoint: this.element.action,
method: this.element.method,
formData: true,
fieldName: 'document[attachment]',
responseType: 'text',
getResponseData(responseText, response) {
// process the server response. In this case it's turbo_stream elements
return responseText;
}
})
uppy.on('upload-success', (file, response) => {
// append the response to the body. Turbo will pick them up
$(this.element).append(response.body);
})
this.uppy = window.uppy = uppy; // useful for testing
}
disconnect() {
window.uppy = null;
this.uppy.close();
}
}
The complicated part was using the turbo stream response. Uppy expects JSON, so we tell it explicitly that it’s text, and we wrap it in a hash, that we then use to append to the body.
4. Bonus: capybara test helper
Thanks to GoRails:
def upload_file_with_uppy(file)
input = page.driver.evaluate_script Capybara::Selenium::Node::Html5Drag::ATTACH_FILE
input.set_file(file)
page.driver.execute_script <<~JS, find("#uppy-dashboard-container"), input
var el = arguments[0],
input = arguments[1],
file = input.files[0];
window.uppy.addFile({
name: file.name,
type: file.type,
data: file
})
JS
find("button.uppy-StatusBar-actionBtn--upload").click
end