Reputation: 267010
I have a working version of the active-storage example using s3 found here:
https://edgeguides.rubyonrails.org/active_storage_overview.html
Now I want to be able to perform the file upload not when I finishing filling the form but immediately after the user selects a file to upload. Actually in my case I have a wysiwyg editor that has a on drop event that fires
var myCodeMirror = CodeMirror.fromTextArea(post_body, {
lineNumbers: true,
dragDrop: true
});
myCodeMirror.on('drop', function(data, e) {
var file;
var files;
// Check if files were dropped
files = e.dataTransfer.files;
if (files.length > 0) {
e.preventDefault();
e.stopPropagation();
file = files[0];
console.log('File: ' + file.name);
console.log('File: ' + file.type);
return false;
}
});
So is there, since the file drop triggers this event, for me to then send this to active-storage somehow so it will start uploading the file to S3 right away?
Upvotes: 5
Views: 2749
Reputation: 71
The main problem of the topic is - you cannot Import DataUpload in java script section of the form. But we can create object ImmediateUploader as follow:
Global Java script part
upload/uploader.js
import { DirectUpload } from "@rails/activestorage"
export default class Uploader {
constructor(file, url) {
this.file = file
this.url = url
this.directUpload = new DirectUpload(this.file, this.url, this)
}
upload() {
return new Promise((resolve, reject) => {
this.directUpload.create((error, blob) => {
if (error) {
// Handle the error
reject(error)
} else {
// Add an appropriately-named hidden input to the form
// with a value of blob.signed_id
resolve(blob)
}
})
})
}
}
upload/index.js
import Uploader from './uploader.js'
export default {
upload (file, url) {
const uploader = new Uploader(file, url)
return uploader.upload()
}
}
application.js
window.ImmediateUploader = require('./upload');
Form part
Now we can use ImmediateUploader to upload selected files directly to active storage and update images after load without commit:
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<div class="row">
<img id="avatar" class="centered-and-cropped" width="100" height="100" style="border-radius:50%" src="<%= url_for(user.photo) %>">
<button type="button" class="btn" onclick="event.preventDefault(); document.getElementById('user_photo').click()">Change avatar</button>
</div>
<%= f.file_field :photo, direct_upload: true, class: "hiddenfile" %>
</div>
<div class="form-actions">
<%= f.button :submit, t(".update"), class: 'btn btn-primary' %>
</div>
<% end %>
<% content_for :js do %>
<script>
const input = document.querySelector('input[type=file]')
input.addEventListener('change', (event) => {
Array.from(input.files).forEach(file => uploadFile(file))
// clear uploaded files from the input
input.value = null
})
const uploadFile = (file) => {
// your form needs the file_field direct_upload: true, which
// provides data-direct-upload-url
const url = input.dataset.directUploadUrl;
ImmediateUploader.default.upload (file, url)
.then(blob => {
// get blob.signed_id and add it to form values to submit form
const hiddenField = document.createElement('input')
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("value", blob.signed_id);
hiddenField.name = input.name
document.querySelector('form').appendChild(hiddenField)
// Update new avatar Immediately
document.getElementById('avatar').src = '/rails/active_storage/blobs/' + blob.signed_id + '/' + blob.filename;
// Update photo in Database
axios.post('/users/photo', { 'photo': blob.signed_id }).then(response => {});
});
}</script>
<% end %>
Controller:
class RegistrationController < Devise::RegistrationsController
def update
super
@user = current_user
@user.avatar = url_for(@user.photo.variant(resize_to_limit: [300, 300]).processed) if @user.photo.attached?
@user.save
end
def updatephoto
@photo = params[:photo]
@user = current_user
@user.photo = @photo
@user.save
@user = current_user
@user.avatar = url_for(@user.photo.variant(resize_to_limit: [300, 300]).processed) if @user.photo.attached?
@user.save
end
end
Upvotes: 0
Reputation: 7434
Active Storage exposes the DirectUpload
JavaScript class which you can use to trigger a file upload directly from the client-side.
You can leverage this for integrations with third-party plugins (e.g. Uppy, Dropzone) or with your own custom JS code.
DirectUpload
The first thing you need to do is make sure that AWS S3 is set up to handle direct uploads. This requires ensuring your CORS configuration is set up properly.
Next, you simply instantiate an instance of the DirectUpload
class, passing it the file to upload and the upload URL.
import { DirectUpload } from "activestorage"
// your form needs the file_field direct_upload: true, which
// provides data-direct-upload-url
const input = document.querySelector('input[type=file]')
const url = input.dataset.directUploadUrl
const upload = new DirectUpload(file, url)
upload.create((error, blob) => {
// handle errors OR persist to the model using 'blob.signed_id'
})
See full documentation here: https://edgeguides.rubyonrails.org/active_storage_overview.html#integrating-with-libraries-or-frameworks
The DirectUpload#create
method initiates the upload to S3 and returns with an error or the uploaded file blob.
Assuming there are no errors, the last step is to persist the uploaded file to the model. You can do this using blob.signed_id
and putting it into a hidden field somewhere on the page OR with an AJAX request to update your model.
In the case above, to start the direct upload on the drop simply put the code above into the drop
handler.
Something like this:
myCodeMirror.on('drop', function(data, e) {
// Get the file
var file = e.dataTransfer.files[0];
// You need a file input somewhere on the page...
const input = document.querySelector('input[type=file]')
const url = input.dataset.directUploadUrl
// Instantiate the DirectUploader object
const upload = new DirectUpload(file, url)
// Upload the file
upload.create((error, blob) => { ... })
});
If you are just using the asset pipeline and not using a JavaScript bundler tool, then you create instances of the DirectUpload
class like this
const upload = new ActiveStorage.DirectUpload(file, url)
Upvotes: 6