Upload multiple et asynchrone de fichiers

J’avoue avoir pas mal galéré à trouver mon bonheur car l’upload de fichiers en AJAX étant relativement récent, on trouve de nombreux exemples obsolètes sur la toile qui se basent sur une iframe… Ou ne fonctionnent pas avec Jersey coté serveur ^^
Trève de blabla, passons au code.

Premièrement, ajouter un champ input acceptant l’ajout de plusieurs fichiers :

 <input id="files" multiple="multiple" name="file[]" type="file">

Ensuite, uploadons les fichiers en utilisant les « FormData » d’HTML5 :

var upload = function (file) {
      var data = new FormData();

      data.append('name', file.name);
      data.append('file', file);

      $.ajax({
         url:'/photo',
         data:data,
         cache:false,
         contentType:false,
         processData:false,
         type:'POST'
      }).error(function () {
                 alert("unable to upload " + file.name);
              })
              .done(function (data, status) {
                 doSomethingUseful(data);
              });
   };

   function multiUpload(files) {
      for (var i = 0; i < files.length; i++) {
         // Only upload images
         if (/image/.test(files[i].type)) {
            upload(files[i]);
         }
      }
   }

   $(document).ready(function () {
      $("#files").change(function (e) {
         multiUpload(e.target.files);
      })
   });

Coté serveur, avec Jersey, il faut inclure le module « multipart » :

<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-multipart</artifactId>
    <version>1.13</version>
</dependency>

Ensuite le code est plutôt simple :

import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;

import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;
import java.io.IOException;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;

@Controller
@Path("/photo")
public class PhotoResource extends AbstractResource {
    private static final Logger LOG = Logger.getLogger(PhotoResource.class);

    @Inject
    private FileRepository fileRepository;

    @GET
    @Produces("image/png")
    @Path("/{photoId}")
    public byte[] photo(@PathParam("photoId") String photoId) {
        try {
            return fileRepository.get(photoId);
        } catch (IOException e) {
            LOG.warn("When get photo id : " + photoId, e);
            throw ResourceException.notFound();
        }
    }

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.TEXT_PLAIN)
    public String addPhoto(@FormDataParam("file") byte[] photo,
                           @FormDataParam("file") FormDataContentDisposition fileDetail) {
        String photoId = null;
        try {
            photoId = fileRepository.save(photo);
        } catch (IOException e) {
            LOG.error("unable to add photo", e);
            throw ResourceException.error(e);
        }
        return photoId;
    }
}

Et pour s’amuser, stockons les fichiers dans Mongodb grace à GridFS :

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSInputFile;
import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.bson.types.ObjectId;
import org.jongo.Jongo;
import org.springframework.stereotype.Repository;

@Repository
public class FileRepository {

    private Jongo jongo;
    private GridFS files;

    @Inject
    public FileRepository(Jongo jongo) {
        this.jongo = jongo;
    }

    @PostConstruct
    public void afterPropertiesSet() throws Exception {
        files = new GridFS(jongo.getDatabase());
    }

    /**
     * Save a file and return the corresponding id
     */
    public String save(byte[] file) {
        GridFSInputFile savedFile = this.files.createFile(file);
        savedFile.save();
        return savedFile.getId().toString();
    }

    /**
     Return the file
    */
    public byte[] get(String fileId) throws IOException {
        return IOUtils.toByteArray(files.findOne(new ObjectId(fileId)).getInputStream());
    }
}
}

Et si vous voulez faire du drag and drop, il suffit d’inclure ce plugin jQuery : drop.js et de faire comme ceci :

 $(document).ready(function () {
         $('body').dropArea();

         $('body').bind('drop', function (e) {
            e.preventDefault();
            e = e.originalEvent;
            multiUpload(e.dataTransfer.files);
         });
      });

Sources :

Leave a Reply