Good Ideas

fine uploader

Come implementare un drag&drop per l’upload multiplo di immagini in Ruby on Rails

Posted by:

|

On:

|

E’ oramai una consuetudine nelle moderne applicazioni web fornire un un sistema di upload di immagini usando il drag & drop. Partendo da un caso reale che ho sviluppato, vi spiego come farlo in Ruby on Rails.

drag drop immagini

In questo piccolo tutorial faremo uso di Fine Uploader, una libreria Javascript che si occupa della gestione del drag & drop sulla pagina web. La libreria è alla versione 3.3 ed è, a mio parere, davvero molto ben sviluppata. Si può utilizzare in unione a jQuery oppure semplicemente usando le direttive native Javascript. Tra le caratteristiche principali, vi segnalo queste:

  • Compatibile con tutti i browser moderni e con IE dalla versione 7 in avanti. Per Internet Explorer chiaramente c’è un fallback su iframe, mentre per gli altri browser l’implementazione avviene tramite XMLHttpRequest che consente l’upload multiplo in AJAX con progress bar.
  • Non fa uso di Flash e jQuery è opzionale
  • Upload immediati o tramite accodamento
  • Integrazione con Twitter Bootstrap

Fine Uploader è fornito anche tramite una gemma che andremo ad aggiungere al nostro progetto.

Per gestire l’effettivo upload serverside, faremo uso di Carrierwave e di una versione modificata della classe StringIO per l’upload di file da stringhe.

Il progetto che ho sviluppato fa uso di Twitter Bootstrap quindi troverete nel codice alcune classi CSS specifiche di questo framework.

immagini caricate

Procediamo con dettagliare i pezzi di codice necessari. Iniziamo con il Gemfile:

gem "carrierwave"
gem "mini_magick"
gem "fine-uploader-rails"

Andiamo a creare questo file in config/initializers/stringiohax.rb, come descritto dal wiki di Carrierwave:

class AppSpecificStringIO < StringIO
  attr_accessor :filepath

  def initialize(*args)
    super(*args[1..-1])
    @filepath = args[0]
  end

  def original_filename
    File.basename(filepath)
  end
end

Includiamo i javascript e i css necessari per attivare Fine Uploader nel nostro progetto. Come descritto dalla pagina GitHub della gemma, includiamo la libreria javascript nel nostro application.js:

//= require fineuploader.jquery

e aggiungiamo anche il foglio di stile nel nostro CSS (application.css):

*= require fineuploader

Modello e controller Image

E’ stato creato un modello Image che detiene tutte le informazioni sull’immagine caricata. Il modello creato fa uso di un uploader, attachmentUploader, creato tramite il generatore di Carrierwave e poi modificato aggiungendo la creazione di un thumbnail chiamato :thumb, di cui faremo uso nel controller:

rails generate uploader Attachment

Il modello risultante è il seguente:

class Image < ActiveRecord::Base
  attr_accessible :name

  mount_uploader :attachment, AttachmentUploader

end

che fa riferimento a questa tabella nel database:

class CreateImages < ActiveRecord::Migration   def change     create_table :images, :force => true do |t|
      t.string   :title
      t.string   :attachment
      t.integer  :user_id

      t.timestamps
    end
  end
end

Il controller gestisce due azioni: la creazione (upload) dell’immagine e la sua eliminazione. L’unica particolarità è l’uso di AppSpecificStringIO nel metodo create. Questo è il codice:

class ImagesController < ApplicationController   respond_to :json, :html, :js   protect_from_forgery :except => :create

  # POST /images
  # POST /images.json
  def create

    file = AppSpecificStringIO.new(params[:qqfile], request.raw_post)

    @image = Image.new()
    @image.attachment =  file
    @image.user_id = current_user.id

    respond_to do |format|
      if @image.save
        format.html do
          render :text => {:success => true, :id => @image.id, :src => @image.attachment.url(:thumb)}.to_json, :layout => false
        end
        format.json do
          render json => {:success => true, :id => @image.id, :src => @image.attachment.url(:thumb)}, :layout => false
        end
      else
        format.html do
          render :text => @image.errors.to_json, :layout => false
        end
        format.json do
          render :text => @image.errors.to_json, :layout => false
        end
      end
    end
  end

  # DELETE /images/1
  # DELETE /images/1.json
  def destroy
    authorize! :destroy, @user, :message => 'Non sei autorizzato.'

    @brand = Brand.find(params[:id])
    @brand.destroy

    respond_to do |format|
      format.html { redirect_to user_path(current_user.id) }
    end
  end

end

Viste e Javascript

La parte client-side richiede semplici modifiche. Nella vista (form.html.haml) aggiungiamo l’elemento che conterrà la drop-zone per il drag & drop:

#jquery-wrapped-fine-uploader

Nella stessa vista ci sarà anche la parte relativa alla modifica delle immagini già caricate. Nel nostro caso specifico, si fa uso di simple_form e le immagini sono delle risorse annidate (nested resource) di un oggetto coupon:

        .row-fluid
          %ul.thumbnails#coupon_thumbnails
            = f.fields_for :coupons_images  do |s|
              %li.span3
                %a.thumbnail
                  = image_tag s.object.image.attachment.url(:thumb)

Poi aggiungiamo in application.js questo metodo che si occupa di attivare Fine Uploader nella pagina:

    $('#jquery-wrapped-fine-uploader').fineUploader({
        request: {
            forceMultipart:false,
            endpoint: '/images/'
        },
        text: {
            uploadButton: ' Carica una foto dal tuo computer',
            deleteButton: ' Elimina'
        },
        classes: {
            success: 'alert alert-success',
            fail: 'alert alert-error'
        },
        validation: {
            //allowedExtensions: ['jpeg', 'jpg', 'png'],
            //sizeLimit: 5351200 // 50 kB = 50 * 1024 bytes
        },
        deleteFile: {
            enabled: true,
            endpoint: '/images/'
        }
    }).on('complete', function(event, id, fileName, responseJSON) {
            if (responseJSON.success) {
                hidden = '< input type="hidden" name="coupon[coupons_images_attributes][' + Math.floor((Math.random()*1000000)+1) + '][image_id]" value="' + responseJSON.id + '" / >';
                $('#coupon_thumbnails').append('< li class="span3" >' + hidden + '< a class="thumbnail" >< img src="' + responseJSON.src + '" alt="' + fileName + '">< /a>< /li >');
            }
    }).on('onSubmitDelete', function(event, id, fileName, responseJSON) {
            if (responseJSON.success) {
            }
    });

L’unica particolarità di cui tener conto è l’opzione forceMultipart:false, indispensabile per far funzionare l’upload delle immagini via AJAX.

Conclusioni

Il sistema sviluppato si integra perfettamente con Bootstrap, creando un form drag & drop responsive e le opzioni messe a disposizione dalla libreria ci consentono di personalizzare completamente l’uploader.

Grazie a Rails e alla sua community ancora una volta siamo riusciti a creare un complesso sistema di caricamento multiplo delle immagini in breve tempo.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *