DropzoneJS with JAX-RS, JSF2 and Bootstrap

Recently I had to do a Drag&Drop file upload in a JSF2 application, instead of building everything from scratch I started sticking together some components which are around for a while already, namely:

  • DropzoneJS (for the drag&drop)
  • JAX-RS (for the rest service accepting the file upload)
  • Primefaces JSF2 (to refresh the existing JSF image gallery after a file upload)
  • Bootstrap

That’s how it looks like:

Gallery Screenshot

Gallery Screenshot

The JSF page with DropzoneJS

<div class="row">
 <div class="col-md-6">
 <div id="alert_placeholder"></div>
 </div>
 </div>
 <div class="row">
 <div class="refresh-gallery hidden"> <!-- this div contains a hidden link refreshing the panelGroup containing the gallery -->
 <p:commandLink id="ajax" update="imageGallery" actionListener="#{myBean.refreshImages}" style="margin-right:20px;">
 <h:outputText value="Refresh Gallery" />
 </p:commandLink>
 </div>
 <h:panelGroup layout="block" id="imageGallery"> <!-- h:panelGroup with layout="block" does render as simple div -->
 <ui:repeat var="imageId" value="#{myBean.imageIds}" varStatus="imageIdStatus">
<!-- some gallery specific code here -->
 <img src="/images/#{imageId}/200x200" alt="..."/>
<!-- some gallery specific code here -->
 </ui:repeat>
 </h:panelGroup>
 <div class="col-sm-6 col-md-2">
<div id="dz-gallery1" class="dropzone"
 data-parentId="#{myBean.id}"/>

 <div class="dropzone-preview" style="display:none"/>
 </a>
 </div>
 </div>

The JavaScript code needed to initialize DropzoneJS and integrate with JSF

 var parentId = $("div.dropzone").attr('data-parentId');
 var myDropzone = new Dropzone("div.dropzone",
 {
 url: "/images/"+parentId, //parentId is needed as in this application a gallery belongs to a specific parent object
 createImageThumbnails: false,
 addRemoveLinks: false,
 parallelUploads: 1, //my JAX-RS service does only allow one upload at a time but as DropzoneJS has internal queueing it does support dragging and dropping of multiple files at the same time
 acceptedFiles: 'image/*',
 dictDefaultMessage: '<i class="fa fa-arrow-right fa-1x"></i> #{messages['dropzone.default_message']}', //JSF i18n support
 previewsContainer: "div.dropzone-preview"
 });

 myDropzone.on("success", function(file) {
$("div.refresh-gallery a").click();
 });

 myDropzone.on("error", function(file) {
 showalert("#alert_placeholder", "#{messages['file_upload.error']} '"+file.name+"'", "danger"); //Bootstrap is used to display alerts
});</pre>
function showalert(placeholder, message, alerttype) {

var alertid = getRandomAlertId();

$(placeholder)
 .append(
 '<div id="'
 + alertid
 + '" class="alert alert-'
 + alerttype
 + ' fade in"><a class="close" data-dismiss="alert">×</a><span>'
 + message + '</span></div>')

setTimeout(function() { // this will automatically close the alert and
 // remove this if the users doesnt close it in 5 secs
 $("#" + alertid).remove();
 }, 5000);

}

And now comes the interesting part with the Java JAX-RS service integration

@Controller
@Path("/images")
class ImageController {

@POST
 @Path("/{parentObjectId}")
 @Consumes(MediaType.MULTIPART_FORM_DATA)
 public Response uploadFile(@PathParam("parentObjectId") String parentObjectId,
 @FormDataParam("file") InputStream fileInputStream,
 @FormDataParam("file") FormDataContentDisposition contentDispositionHeader)
 {
 byte[] buf = IOUtils.toByteArray(fileInputStream);

 Image img = new Image(parentObjectId, buf);
 img.persist();

 return Response.status(200).build();
 }

}

As you can see above, accepting file uploads is pretty straight forward, now let’s have a look at what we have to do to retrieve images using the same service. That can easily be achieved by adding the following method:

@GET
@Path("/{imageId}")
 @Produces("image/jpeg")
 public Response getImage(@PathParam("imageId") String imageId
 ) throws IOException {

 byte[] buf = Image.findById(imageId).getImageBytes();

return Response.ok(buf).build();
 }

If you also want to resize the image file returned, you can easily do this by adding another method:

@GET
 @Path("/{imageId}/{width}x{height}")
 @Produces("image/jpeg")
 public Response getImage(@PathParam("imageId") String imageId,
 @PathParam("width") String pWidth,
 @PathParam("height") String pHeight
 ) throws IOException {

 byte[] buf = Image.findById(imageId).getImageBytes();

int width = Integer.parseInt(pWidth);

int height = Integer.parseInt(pHeight);

 ByteArrayInputStream bais = new ByteArrayInputStream(buf);

BufferedImage img = ImageIO.read(bais);

//the following size calculation ensures that the picture is resized to fit in the defined height/width boundaries whilst still maintaining the proportions
 if(img.getHeight()/height > img.getWidth() / width) {
 width = (int) 1.0/img.getHeight()*height*img.getWidth();
 }
 else {
 height = (int) 1.0/img.getWidth()*width*img.getHeight();
 }

 java.awt.Image scaledImage = img.getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH);
 BufferedImage imageBuff = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
 imageBuff.getGraphics().drawImage(scaledImage, 0, 0, new Color(0,0,0), null);

ByteArrayOutputStream buffer = new ByteArrayOutputStream();

ImageIO.write(imageBuff, "jpg", buffer);

return Response.ok(buffer.toByteArray()).build();
 }

I hope you do like the blog post, even though I did only add very little explanations, (really sorry for that) but I’m a bit short of time at the moment.
As always any improvement suggestions are more than welcome 🙂

Advertisements

One thought on “DropzoneJS with JAX-RS, JSF2 and Bootstrap

  1. Quan Bui

    could u send me your source ( what can run ) of “DropzoneJS with JAX-RS, JSF2 and Bootstrap” project.

    Thanks a lot bro:)

    Quan Bui.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s