Code Monkey home page Code Monkey logo

active_storage_drag_and_drop's Introduction

Active Storage Drag and Drop

Gem Version Build Status Test Coverage Maintainability Yard Docs JavaScript Style Guide

Provides a form helper to make it easy to make drag and drop file upload fields that work with Rails' ActiveStorage.

Demo

Table of Contents

Installation

Add this line to your application's Gemfile:

gem 'active_storage_drag_and_drop'

And then execute:

$ bundle

Or install it yourself as:

$ gem install active_storage_drag_and_drop

Include the styles in your application css:

/*
 *= require active_storage_drag_and_drop
 */

Asset Pipeline - Rails < 6

Include active_stroage_drag_and_drop.js in your application's JavaScript bundle.

//= require active_storage_drag_and_drop

Webpacker - Rails >= 6

Use yarn to add the js package as a dependency:

$ yarn add active_storage_drag_and_drop

Import the start function from the bundle and run it:

import ActiveStorageDragAndDrop from 'active_storage_drag_and_drop'

ActiveStorageDragAndDrop.start()

Usage

Add an ActiveStorage attachment to your model:

class Message < ApplicationRecord
  has_one_attached :image
end

or add multiple ActiveStorage attachments to your model:

class Message < ApplicationRecord
  has_many_attached :images
end

Call the method drag_and_drop_file_field on your model's form:

= form_with model: @message, local: true do |form|
  = form.drag_and_drop_file_field :images
  = form.submit

The first parameter is a symbol representing the method of the ActiveStorage attachment and an optional second parameter sets the text on the drag and drop zone.

form.drag_and_drop_file_field :images, 'Drag and drop images here!'

The content of the dropzone can also be passed as a block of ERB or HAML:

= form.drag_and_drop_file_field :images, disabled: false do
  %i.far.fa-images
  Drag images here!

Strong Parameters

In your controller you can permit the params like so:

# single file upload
params.permit(:message).require(:image)
# multiple upload
params.permit(:message).require(images: [])

Options

Options for the nested file field can be passed as key value pairs. The helper accepts the same options as the rails file_field form helper ActionView::Helpers::FormHelper#file_field

form.drag_and_drop_file_field :images, nil, disabled: true

Validation

Like with the ActionView::Helpers::FormHelper#file_field you can pass a list of acceptable mime-types for client-side validation:

form.drag_and_drop_file_field :images, nil, accept: 'image/png, image/jpeg, image/gif, image/tiff'

An additional size_limit option can be passed which provides validation on the maximum acceptable filesize in bytes:

form.drag_and_drop_file_field :images, nil, size_limit: 5_000_000 # 5MB upper limit on file size

When one of these errors occurs by default a JavaScript 'dnd-upload:error' event is dispatched and an alert detailing the validation error appears. To override this default behaviour listen for the event and call preventDefault() on the event.

JavaScript Events

Event name Event target Event data (event.detail) Description
dnd-uploads:start <form> None All of the uploads begin either by submission of the form or a call to processUploadQueue()
dnd-upload:initialize <input> { id, file, iconContainer } Dispatched for every file before the UI elements representing the file are added to the dropzone, default can be prevented to add your own UI.
dnd-upload:placeholder <input> { id, file, iconContainer } Paints the UI for each file that has been queued for attachment but hasn't been attached yet (e.g. after a failed validation in the form) can be prevented as above.
dnd-upload:start <input> { id, file, iconContainer } An upload is starting.
dnd-upload:before-blob-request <input> { id, file, iconContainer, xhr } Before making a request to your application for upload metadata.
dnd-upload:before-storage-request <input> { id, file, iconContainer, xhr } Before making a request to store a file.
dnd-upload:progress <input> { id, file, iconContainer, progress } Called as requests to store files progress. Default UI sets the width of the direct-upload__progress element.
dnd-upload:cancel <input> { id, file, iconContainer } A user triggered the cancelation of an upload, the upload is removed from the queue and the preventable default UI removes matching upload icons from the iconContainer.
dnd-upload:error <input> { id, file, iconContainer, error } An error occurred pertaining to a specific file. The default can be prevented to supply your own UI for errors.
dnd-upload:end <input> { id, file, iconContainer } An upload has ended. Default can be prevented to supply your own UI for the end of an upload.
dnd-uploads:error <form> { error } An error occurred unrelated to a specific file. The default can be prevented to supply your own UI for errors.
dnd-uploads:end <form> None All uploads have ended.

To override the default behaviour of any of these events catch them with an event listener and call preventDefault() on the event:

document.addEventListener('dnd-upload:error', function (event) {
  # do something…
  event.preventDefault()
})

Upload Asynchronously

To asynchronously trigger uploading without form submission, import the processUploadQueue function and pass the form containing the uploads and a callback function as arguments:

import { processUploadQueue } from 'active_storage_drag_and_drop'

var callback = function(error) {
  if (error) {
    // …handle error…
  } else {
    // …do your stuff
  }
}

processUploadQueue(form, callback)

Custom Upload Icons

To customise how the upload icons for each file are added to the DOM you pass an iconPainter option to the start function e.g:

import { start } from 'active_storage_drag_and_drop'

start({
  iconPainter (iconContainer, id, file, complete) {
    iconContainer.insertAdjacentHTML('beforeend', `
    <li data-direct-upload-id="${id}" class="direct-upload">
      <div class="direct-upload__progress" style="width: ${progress}"></div>
      ${file.name}
      <button class="direct-upload__remove">X</button>
    </li>
    `)
  }
})

The arguments that iconPainter receives are:

Parameter Type Description
iconContainer HTMLElement The element to insert the upload icon markup into.
id string A unique identifier which will be used for any future UI events on this upload.
file File The object representing the file and its metadata.
complete boolean Is true if the upload is finished and false otherwise.
This is the default implementation:
export function paintDefaultUploadIcon (iconContainer, id, file, complete) {
  const uploadStatus = (complete ? 'complete' : 'pending')
  const progress = (complete ? 100 : 0)
  iconContainer.insertAdjacentHTML('beforeend', `
  <div data-direct-upload-id="${id}">
    <div class="direct-upload direct-upload--${uploadStatus}">
      <div class="direct-upload__progress" style="width: ${progress}%"></div>
      <span class="direct-upload__filename">${file.name}</span>
      <span class="direct-upload__filesize">${fileSizeSI(file.size)}</span>
    </div>
    <a href='remove' class='direct-upload__remove'>X</a>
  </div>
  `)
}

All of the built in UI that manipulates these icons can be overriden by attaching to their event and preventing the default behaviour. But to take advantage of the built in ui events though you should follow these rules:

  • The root level element must have a data-direct-upload-id attribute set to the id passed to paintUploadIcon.
  • The progress section consists of a parent with the class direct-upload and a direct child with the class direct-upload__progress.
  • The element that will cancel the upload when clicked has the class direct-upload__remove.

Development

Install yarn to manage js dependencies. After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

Use yarn dev to build JavaScript files automatically on change. Use with gem 'active_storage_drag_and_drop', path: [local-gem-repo] to develop and debug the gem in place in a rails app. You can run the JS tests with yarn test and you can see the coverage as well as the tests by running yarn cover.

After making changes to JavaScript run yarn build before committing changes to transpile the JavaScript for production.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/marinosoftware/active_storage_drag_and_drop. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the ActiveStorageDragAndDrop project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

active_storage_drag_and_drop's People

Contributors

daveokeeffe avatar dependabot[bot] avatar iangrantmarino avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

active_storage_drag_and_drop's Issues

Direct uploads?

Does this automatically handle direct uploads: true? or is there a way to set it?

Title attribute on error not populated on validation error

Hi!

When I set the accept or file_size option, the validation of the selected file works fine, but I see in the DOM that there is a class added to the direct-upload container, direct-upload--error and that a Title with a explanation is set. Unfortunately, the title has the text: TypeError: Error.captureStackTrace is not a function. This does not seem right. Could you have a look?

Adding css to yarn version and removing the need to install the gem

Could I suggest adding the css to the yarn version and removing the need to install the gem all together? Currently, I have copied the css file from the git repo because I have moved away from using the assets pipeline and am using webpack to serve all my assets. With that said, I am still forced to bundle the gem version to handle a few ruby dependencies.

I am really loving this gem. Great job.
Thank you

Would like to improve keyboard accessibility

I noticed the browser file upload starts on clicking the drag-drop box.

I would like to open this file upload box with a keyboard after tabbing to the box. I was not on the best way to do this though.

Unable to upload new files in edit action with Cocoon

When I remove an attachment & then save the record; I cannot add anymore attachments to the cocoon field. The file doesn't save the second time around. I don't know exactly why that is happening. Can you please look into it?

Upload without form submit

Hi,

I'm trying to trigger file uploads as soon as they're dropped into the dndzone, and I'm just not succeeding. I'm hoping you can help. I'm sure I'm just misunderstanding something. Here's my code:

const form = document.getElementsByClassName('form-with-dnd');

function TriggerProcessUploadQueue(form, event) {
    console.log(event.detail);

    //Start the upload

    let callback = function(error) {
        if (error) {
            console.log('An error was found:');
            console.log(error);
            console.log('This upload will be aborted');
        } else {
            console.log("No errors were detected with this item");
            console.log("Let's upload it now...");
        }
    };

    const uploadEvent = document.createEvent('Event');
    uploadEvent.initEvent('dnd-uploads:process-upload-queue', true, true);
    uploadEvent.detail = { callback };
    form.dispatchEvent(uploadEvent);
}

document.addEventListener('dnd-upload:initialize', function (event) {
    console.log('This item was dropped on the box, or selected from dialog:');
    TriggerProcessUploadQueue(form[0], event);
});

When I step through the code in Chrome Dev Tools I find that the file is present in the detail attribute of event (demonstrated by console.log(event.detail)) but at the below block of code in ujs.js, the nextUpload.current_uploaders.length is equal to 0:

function processUploadQueue (event) {
  const form = event.target
  const { callback } = event.detail
  const nextUpload = new UploadQueueProcessor(form)
  if (nextUpload.current_uploaders.length > 0) {
    nextUpload.start(error => {
      if (error) {
        callback(error)
      } else {
        callback()
      }
    })
  } else {
    callback()
  }
}

I can confirm the form being passed to UploadQueueProcessor is the correct form DOM element.

I'm certain I must be overlooking something obvious, as it all seems to tie together nicely!

Two or more similar block gone wrong

I has 5 similar forms in one page, each has 1 drag_and_drop_file_field. when attach is selected their labels appear only at first form, but attach is correctly upload.
perhaps, cause they all have identical ID on input field and ignore html_input options

Rails 6 support

Want to use this gem with Rails 6 but unable to do so as current version is dependent on rails (~> 5.2.2.1)

Any idea if there are plans to upgrade?

Persist on form rerender

Is the file_field supposed to persist on a form rerender, for example when any validation for the form object fails and the form gets redisplayed?

Uploading bar doesn't work

Before or after i click to submit my form, loading bar doesn't fill up. Most scarry part is when i click submit. My cloud storage makes a url place for it but doesn't upload there. Form gets unresponsive and stuck on same page

Weird issue that comes up with cocoon nested fields

When I remove an attachment & then save the record; I cannot add anymore attachments to the cocoon field. The file doesn't save the second time around. I don't know exactly why that is happening. Can you please look into it?

Droped file not showing

Hi I try to use your gem in a rails 7 project,
when I drop the files in the drop zone, they are not listed in the box... but they are there because if I save the files are linked with the new created object.

Do you have any clue for me?

style placeholder attachments when validation failed - event 'dnd-upload:placeholder' not firing

hi thank you for this gem. I know this is explained in the doc but i am having some trouble. i had a quick question regarding how the flow works when user submits a form that is invalid which is returned with validation errors. I see acdd saves the attachments previously uploaded but not saved to database yet which is great. But i want to be able to style how these look. I was able to do style when they first upload with paintUploadIcon. But when the page re-renders with errors that event does not fire. I also tried document.addEventListener('dnd-upload:placeholder', function (event) { debugger event.preventDefault() }) but this does not fire when the page re-renders. Also when the placeholder attachments are added i cant seem to click the X icon to remove the attachment. Any help would be greatly appreciated.

Uncaught Error: Can only initialize form controller with a form element.

Getting the above-mentioned error when I try to use active storage drag and drop inside a bootstrap modal via the javascript implementation for rendering. However, using the pure html implementation of the bootstrap modal seems to work fine. Need some help regarding the implementation.

Invalid Authenticity Token when using in remote form

Hi, thank you for this great gem. I was having an issue when submitting a remote rails form along with drag_and_drop_file_field

  1. Steps to reproduce
    Create rails form with remote set to true, add <%=f.drag_and_drop_file_field :documents, 'Drag and drop images here!'%> as form field. Submit form.
  2. Expected behaviour
    Submitting form not receiving CSRF error and processing request as js
  3. Actual behaviour
    Submitting form returns 'Can't verify CSRF token authenticity.' error and processing request as html
  4. System configuration
    Rails version: 'Rails 5.2.3'
    Ruby version: 'ruby 2.6.3p62'

Doesn't play nice with Turbolinks?

First off thanks for this library!

Looks like the JS for this library assumes the target element is already present on the page, at the time application.js is executed -- however this doesn't work with Turbolinks as application.js will only get executed on the very first request, and so if the dnd-target is not present until a later navigation, the library doesn't get activated.

Worse so, since the start status is cached, even attempts to force-call start() to bind after a turbolinks:load doesn't work.

Would be great to get some better Turbolinks integration given its been the default in Rails 5 and up...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.