Search code examples
ruby-on-railsamazon-s3rails-activestorage

How to trigger the file upload on the client side and not on form submission?


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?


Solution

  • Triggering uploads from the client-side

    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.

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

    Uploading a file on drop

    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) => { ... })
    });
    

    Using the asset pipeline

    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)