This tutorial will demonstrate how to integrate DropzoneJS with Spring MVC. I'll cover in detail starting from creating a simple Spring MVC project, uploading files using DropzonJS and saving them in the server side, storing the files information in the database, downloading the files. The technologies used:
- Spring MVC 4.0.3 as a web framework
- Maven 3.1.1 as a build tool and for dependency management
- Hibernate 4.2.7.Final as an ORM tool
- Tomcat 7 as a web server
Let's get started and have some fun using DropzoneJS.
Objective:
The primary goal is to depict how to upload files using DropzoneJS. I'll also touch some of the customizing features. We'll then store the uploaded files information into database. We'll also see how to download those files.
Snapshots:
If you're curious to know what it will look like at the end, the following pictures are for you. For the image files, DropzoneJS displays a nice preview too.
1. Home Screen
2. Dragging the file over the region to attach
3. Listing out the attached files
4. Uploading completed
5. View All page with download option for each file
Project Setup
Let's begin setting up the project for eclipse. I would prefer setting up from scratch so that you understand everything happening behind the scenes. I created the the following hierarchical directory structure, springmvc-dropzonejs being the root directory.
Now, add the following pom.xml right under the root
springmvc-dropzonejs directory.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>np.com.mshrestha</groupId>
<artifactId>springmvc-dropzonejs</artifactId>
<name>photo-gallery</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<spring-version>4.0.3.RELEASE</spring-version>
<hibernate-version>4.2.7.Final</hibernate-version>
<mysql-version>5.1.27</mysql-version>
<apache-connection-pooling-version>1.4</apache-connection-pooling-version>
</properties>
<dependencies>
<!-- Spring
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- Servlet
-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Apache
Commons FileUpload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- Common IO
-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<!-- Jackson JSON Processor
-->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate-version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate-version}</version>
</dependency>
<!-- Apache's
Database Connection Pooling -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>${apache-connection-pooling-version}</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>${apache-connection-pooling-version}</version>
</dependency>
<!-- MySQL
-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Tomcat
7 plugin -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.0</version>
</plugin>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
</plugins>
</build>
</project>
Now, open the
command prompt, go to the root directory and then type
mvn eclipse:eclipse. This will generate the Eclipse configuration files so that eclipse recognizes the root directory as a project directory. For instance, you'll see the files
.classpath
,
.project
created under the root directory.
The above
pom.xml
contains dependencies for Spring, Servlet, Apache File I/O and Jackson JSON processor. You can also see the Tomcat 7 plugin.
Now, we're ready to import the project in eclipse. You can import the project
either as 'General -> Existing Projects into Workspace' or as 'Maven
-> Existing Maven Projects'.
Let's create the following java source packages under the 'src/java/main' directory.
- np.com.mshrestha.dropzonetest.model
- np.com.mshrestha.dropzonetest.dao
- np.com.mshrestha.dropzonetest.dao.impl
- np.com.mshrestha.dropzonetest.service
- np.com.mshrestha.dropzonetest.service.impl
- np.com.mshrestha.dropzonetest.controller
Please note that just to upload the files to server, we don't need any model classes, daos, services. Only controller is sufficient for that. Having said that, in this example we're going to store the uploaded file information in the database so that we can retrieve back the information.
Deployment Descriptor: web.xml
Now, let's create another folder 'WEB-INF' under the webapp
directory and add the deployment descriptor web.xml
under WEB-INF. The web.xml defines root spring container and the heart of the spring MVC - 'DispatcherServlet
'.
web.xml ( path: /webapp/WEB-INF/web.xml )
<?xml version="1.0" encoding="UTF-8"?>
<web-app
version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app.xsd">
<!-- The definition of the Root Spring Container shared by
all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<!-- Creates the
Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- Processes
application requests -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/servlet-context.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
The above web.xml simply defines the dispatcher servlet and the two configuration xmls. I intentionally created those two config xmls to let you know that we can initialize parameters in two ways. This will be needed if you have multiple servlets and filters that shares common parameters. You can define only one servlet config xml and put all of contents from both xmls into the one. The tag
<context-param> defines parameters that are shared by all servlets and filters whereas
<init-param> defines parameters that are accessible by only that
<servlet> inside which it is placed. We also defined the DispatcherServlet which is the default request handler for all the requests as defined by the pattern "/" in
<url-pattern>.
Let's create the two context configuration files alongside web.xml under the WEB-INF directory.
servlet-context.xml ( path: /webapp/WEB-INF/servlet-context.xml )
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd"
default-lazy-init="true">
<!--
DispatcherServlet Context: defines this servlet's request-processing
infrastructure -->
<!-- Enables the
Spring MVC @Controller programming model -->
<annotation-driven />
<context:component-scan base-package="np.com.mshrestha.dropzonetest.controller"
/>
<!-- Defines a
resolver implementation bean. It gets applied to all requests handled by that
DispatcherServlet -->
<beans:bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
/>
<!-- Handles
HTTP GET requests for /web-resources/** by efficiently serving up static
resources in the ${webappRoot}/resources/ directory -->
<resources mapping="/web-resources/**"
location="/resources/" />
<beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<beans:property name="useTrailingSlashMatch"
value="true" />
</beans:bean>
<!-- Resolves
views selected for rendering by @Controllers to .jsp resources in the
/WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix"
value="/WEB-INF/views/" />
<beans:property name="suffix"
value=".jsp" />
</beans:bean>
</beans:beans>
In the above, it sets the property
useTrailingSlashMatch of the bean
RequestMappingHandlerMapping true. This is to match to URLs irrespective of the presence of a trailing slash. When it's true, the controller method mapped to "/listFiles" also matches to "/listFiles/".
Also, it defines the
CommonsMultipartResolver bean, an implementation class of
MultipartResolver.
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
We needed to define it explicitly because there is no default resolver implementation used for Spring DispatcherServlets, as an application might choose to parse its multipart requests itself. To define an implementation, create a bean with the id "multipartResolver" in a DispatcherServlet's application context. Such a resolver gets applied to all requests handled by that DispatcherServlet.
root-context.xml ( path: /webapp/WEB-INF/root-context.xml )
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- Root
Context: defines shared resources visible to all other web components -->
<context:component-scan base-package="np.com.mshrestha.dropzonetest"
/>
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:database.properties</value>
</list>
</property>
</bean>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource"
ref="dataSource" />
<property name="packagesToScan">
<list>
<value>np.com.mshrestha.dropzonetest.model</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">false</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory"
ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"
/>
</beans>
The above root-context.xml looks pretty straight-forward. The database property file looks like as follows:
database.properties ( path: /src/main/resources/database.properties )
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/dropzone_test?createDatabaseIfNotExist=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;autoReconnect=true
jdbc.username=root
jdbc.password=root
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
Persistence
Let's create a pojo class as a model. We're going to use this class and corresponding table to store the uploaded file information in the database. Let's define a class UploadedFile
under the package np.com.mshrestha.dropzonetest.model
. The class corresponds to the table uploaded_file.
UploadedFile.java
package np.com.mshrestha.dropzonetest.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "uploaded_file")
public class UploadedFile {
private Long id;
private String name;
private String location;
private Long size;
private String type;
@Id
@GeneratedValue(strategy =
GenerationType.AUTO)
public Long getId() {
return id;
}
@Column(nullable = false)
public String getName() {
return name;
}
@Column(nullable = false)
public String getLocation()
{
return location;
}
@Column()
public Long getSize() {
return size;
}
@Column(nullable = false)
public String getType() {
return type;
}
// setter methods...
}
Note that we don't store the file itself in the database. Instead, we only store its name and location.
Dao Layer
It's time to work on DAO layer. Let's define an interface FileUploadDao and the implementation class FileUploadDaoImpl in their respective packages. The methods and implementations don't need any explanations.
FileUploadDao.java
package np.com.mshrestha.dropzonetest.dao;
import java.util.List;
import np.com.mshrestha.dropzonetest.model.UploadedFile;
public interface FileUploadDao {
List<UploadedFile> listFiles();
UploadedFile getFile(Long id);
UploadedFile saveFile(UploadedFile
uploadedFile);
}
FileUploadDaoImpl.java
package np.com.mshrestha.dropzonetest.dao.impl;
import java.util.List;
import np.com.mshrestha.dropzonetest.dao.FileUploadDao;
import np.com.mshrestha.dropzonetest.model.UploadedFile;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class FileUploadDaoImpl implements FileUploadDao {
@Autowired
private SessionFactory sessionFactory;
public List<UploadedFile>
listFiles() {
return getSession().createCriteria(UploadedFile.class).list();
}
public UploadedFile getFile(Long id) {
return (UploadedFile)
getSession().get(UploadedFile.class, id);
}
public UploadedFile saveFile(UploadedFile
uploadedFile) {
return (UploadedFile)
getSession().merge(uploadedFile);
}
private Session getSession() {
Session sess = getSessionFactory().getCurrentSession();
if (sess == null) {
sess = getSessionFactory().openSession();
}
return sess;
}
private SessionFactory
getSessionFactory() {
return sessionFactory;
}
}
Service Layer
So, for the service layer, just like we did in Dao Layer, let's create an interface FileUploadService and the implementation class FileUploadServiceImpl in their respective packages.
FileUploadService.java
package np.com.mshrestha.dropzonetest.service;
import java.util.List;
import np.com.mshrestha.dropzonetest.model.UploadedFile;
public interface FileUploadService {
List<UploadedFile> listFiles();
UploadedFile getFile(Long id);
UploadedFile saveFile(UploadedFile uploadedFile);
}
FileUploadServiceImpl.java
package np.com.mshrestha.dropzonetest.service.impl;
import java.util.List;
import np.com.mshrestha.dropzonetest.dao.FileUploadDao;
import np.com.mshrestha.dropzonetest.model.UploadedFile;
import np.com.mshrestha.dropzonetest.service.FileUploadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class FileUploadServiceImpl implements FileUploadService {
@Autowired
private FileUploadDao dao;
@Transactional(readOnly = true)
public
List<UploadedFile> listFiles() {
return dao.listFiles();
}
@Transactional(readOnly = true)
public UploadedFile
getFile(Long id) {
return dao.getFile(id);
}
@Transactional
public UploadedFile
saveFile(UploadedFile uploadedFile) {
return dao.saveFile(uploadedFile);
}
}
So far, we've dealt everything needed for Service layer and DAO layer. I'm assuming you have no problem understanding the usage of the various annotations. Now, let's take a look into the Web Layer.
Presentation ( Web ) Layer
Controller
Now we're going to create a controller that handles all the requests from the DispatcherServlet. Let's create the controller FileUploadController under the package np.com.mshrestha.dropzonetest.controller.
FileUploadController.java
package np.com.mshrestha.dropzonetest.controller;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import np.com.mshrestha.dropzonetest.model.UploadedFile;
import np.com.mshrestha.dropzonetest.service.FileUploadService;
import org.apache.commons.io.FileUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import
org.springframework.web.multipart.MultipartHttpServletRequest;
@Controller
public class FileUploadController {
@Autowired
private FileUploadService
uploadService;
@RequestMapping("/")
public String home() {
// will be resolved to /views/fileUploader.jsp
return "fileUploader";
}
@RequestMapping(value
= "/upload", method =
RequestMethod.POST)
public @ResponseBody
List<UploadedFile>
upload(MultipartHttpServletRequest request,
HttpServletResponse
response) throws IOException {
// Getting uploaded
files from the request object
Map<String,
MultipartFile> fileMap = request.getFileMap();
// Maintain a list
to send back the files info. to the client side
List<UploadedFile>
uploadedFiles = new ArrayList<UploadedFile>();
// Iterate through
the map
for (MultipartFile
multipartFile : fileMap.values()) {
// Save the file to
local disk
saveFileToLocalDisk(multipartFile);
UploadedFile
fileInfo = getUploadedFileInfo(multipartFile);
// Save the file
info to database
fileInfo
= saveFileToDatabase(fileInfo);
// adding the file
info to the list
uploadedFiles.add(fileInfo);
}
return uploadedFiles;
}
@RequestMapping(value
= { "/list" })
public String
listBooks(Map<String, Object> map) {
map.put("fileList",
uploadService.listFiles());
// will be resolved to /views/listFiles.jsp
return "/listFiles";
}
@RequestMapping(value
= "/get/{fileId}", method =
RequestMethod.GET)
public void
getFile(HttpServletResponse response, @PathVariable Long fileId) {
UploadedFile
dataFile = uploadService.getFile(fileId);
File file = new
File(dataFile.getLocation(), dataFile.getName());
try {
response.setContentType(dataFile.getType());
response.setHeader("Content-disposition", "attachment;
filename=\""
+
dataFile.getName() + "\"");
FileCopyUtils.copy(FileUtils.readFileToByteArray(file),
response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
private void
saveFileToLocalDisk(MultipartFile multipartFile)
throws IOException,
FileNotFoundException {
String
outputFileName = getOutputFilename(multipartFile);
FileCopyUtils.copy(multipartFile.getBytes(),
new FileOutputStream(
outputFileName));
}
private UploadedFile
saveFileToDatabase(UploadedFile uploadedFile) {
return
uploadService.saveFile(uploadedFile);
}
private String
getOutputFilename(MultipartFile multipartFile) {
return
getDestinationLocation() + multipartFile.getOriginalFilename();
}
private UploadedFile
getUploadedFileInfo(MultipartFile multipartFile)
throws IOException {
UploadedFile
fileInfo = new UploadedFile();
fileInfo.setName(multipartFile.getOriginalFilename());
fileInfo.setSize(multipartFile.getSize());
fileInfo.setType(multipartFile.getContentType());
fileInfo.setLocation(getDestinationLocation());
return fileInfo;
}
private String
getDestinationLocation() {
return "D:/uploaded-files/";
}
}
upload(): This method is called when the files are uploaded to server. The method is pretty simple and understandable. Otherwise, the comments will help you to understand. As you can see, the method parameters are little different than the usual controller methods. The interface
MultipartHttpServletRequest
extends HttpServletRequest and MultipartRequest. It provides additional methods for dealing with multipart content within a servlet request, allowing to access uploaded files. And it's implementation comes from the MultipartResolver bean called
CommonsMultipartResolver that we defined in servlet-context.xml.
getFile(): The method returns the requested file in the downloadable format. Note that we need to add the header '
Content-disposition' to the response to make the file downloadable.
The rest of the methods should be effortless to understand.
JSPs
We're going to create two JSP pages, one, the homepage, for uploading the files while another is for viewing all the uploaded files in the servers which will have download option too.
fileUploader.jsp ( path: /webapp/WEB-INF/views/fileUploader.jsp )
<%@
taglib uri="http://java.sun.com/jsp/jstl/core"
prefix="c"%>
<!DOCTYPE
html>
<html>
<head>
<meta charset="utf-8" name="viewport"
content="width=device-width,
initial-scale=1">
<title>Spring MVC + Dropzone.js Example</title>
<link rel="stylesheet" type="text/css"
href='<c:url value="/web-resources/libs/bootstrap-3.1.1/css/bootstrap.min.css"/>'>
<link rel="stylesheet" type="text/css"
href='<c:url value="/web-resources/libs/bootstrap-dialog/css/bootstrap-dialog.min.css"/>'>
<link rel="stylesheet" type="text/css"
href='<c:url value="/web-resources/css/style.css"/>'>
</head>
<body>
<div class="container">
<div class="panel
panel-default">
<div class="panel-heading
text-center">
<h3>Spring MVC +
Dropzone.js Example</h3>
</div>
<div class="panel-body">
<div>
<form id="dropzone-form"
class="dropzone"
enctype="multipart/form-data">
<div class="dz-default dz-message file-dropzone text-center
well col-sm-12">
<span class="glyphicon
glyphicon-paperclip"></span> <span>
To
attach files, drag and drop here</span><br>
<span>OR</span><br>
<span>Just Click</span>
</div>
<!-- this is
were the previews should be shown. -->
<div class="dropzone-previews"></div>
</form>
<hr>
<button id="upload-button"
class="btn btn-primary">
<span class="glyphicon
glyphicon-upload"></span> Upload
</button>
<a class="btn
btn-primary pull-right" href="list"> <span
class="glyphicon glyphicon-eye-open"></span> View All Uploads
</a>
</div>
</div>
</div>
</div>
<script type="text/javascript"
src='<c:url value="/web-resources/libs/jquery/jquery-2.1.1.js"/>'></script>
<script type="text/javascript"
src='<c:url value="/web-resources/libs/bootstrap-3.1.1/js/bootstrap.js"/>'></script>
<script type="text/javascript"
src='<c:url value="/web-resources/libs/bootstrap-dialog/js/bootstrap-dialog.min.js"/>'></script>
<script type="text/javascript"
src='<c:url value="/web-resources/libs/dropzone.js"/>'></script>
<script type="text/javascript"
src='<c:url value="/web-resources/js/app.js"/>'></script>
</body>
</html>
It's quite easy to integrate DropzoneJS. It starts with the <form> tag with class="dropzone". That's pretty much of it. Dropzone will find all form elements with the class
dropzone, automatically attach itself to it, and upload files dropped into it to the specified action attribute. The only additional thing to do is to initialize the Dropzone configuration. The configuration is stored under the
Dropzone.options
object. It looks something like this:
Dropzone.options.dropzoneForm = {
url : "/form/action/url",
autoProcessQueue
: false,
uploadMultiple
: true,
maxFilesize
: 256, //
MB
previewsContainer : ".dropzone-previews",
init : function() {
// handling events
}
}
I've configured that in a separate JS file which we'll see in few minutes later. That's all we have to do for setting up DropzoneJS. Isn't that cool?
For each uploading file, Dropzone generates some HTML elements with dropzone classes. This is to display the file specific information like file name, file size, image preview, upload progress, success indicator, error indicator, error messages. And the best part here is they are easily customizable. The generated HTML is as follows:
<div class="dz-preview
dz-file-preview">
<div class="dz-details">
<div class="dz-filename">
<span data-dz-name></span>
</div>
<div class="dz-size"
data-dz-size></div>
<img data-dz-thumbnail
/>
</div>
<div class="dz-progress">
<span class="dz-upload"
data-dz-uploadprogress></span>
</div>
<div class="dz-success-mark">
<span>✔</span>
</div>
<div class="dz-error-mark">
<span>✘</span>
</div>
<div class="dz-error-message">
<span data-dz-errormessage></span>
</div>
</div>
We can access the HTML of the file preview in any of the events with
file.previewElement
.
Dropzone also creates a
<div> element with
class="dz-default dz-message" with default message "Drop files to upload (or click)". As depicted in the above JSP page, we can override the default message by putting the
<div> element with
class="dz-message" inside the dropzone form. This
<div> element is the actual drop zone where we can drop the files or simply click to open the file chooser dialog.
Also, I've added an effect such that if a file is dragged over the message
<div>, it outlines the
<div> so that we can know the file is over the correct region to drop.
Also, we've created an empty
<div> element with
class="dropzone-previews". I've configured this div in the dropzone configuration as a container for all the dropzone generated preview elements. This is done by adding
previewsContainer : ".dropzone-previews" in the dropzone configuration.
Moreover, I've customized the default progress bar too. In the dropzone configuration, the progress bar has been customized to show the progress percentage. It is done inside dropzone's
uploadprogress event.
The following is the JS file I created and placed it under the '/webapp/resources/js/' directory.
app.jsp ( path: /webapp/resources/js/app.js )
$(document).ready(function() {
$(".file-dropzone").on('dragover', handleDragEnter);
$(".file-dropzone").on('dragleave', handleDragLeave);
$(".file-dropzone").on('drop', handleDragLeave);
function handleDragEnter(e) {
this.classList.add('drag-over');
}
function handleDragLeave(e) {
this.classList.remove('drag-over');
}
//
"dropzoneForm" is the camel-case version of the form id "dropzone-form"
Dropzone.options.dropzoneForm
= {
url : "upload", // not required if the <form> element has action attribute
autoProcessQueue
: false,
uploadMultiple
: true,
maxFilesize :
256, //
MB
parallelUploads
: 100,
maxFiles : 100,
addRemoveLinks
: true,
previewsContainer
: ".dropzone-previews",
// The setting up
of the dropzone
init : function() {
var myDropzone = this;
// first set
autoProcessQueue = false
$('#upload-button').on("click", function(e) {
myDropzone.processQueue();
});
// customizing the default progress bar
this.on("uploadprogress", function(file, progress) {
progress
= parseFloat(progress).toFixed(0);
var progressBar =
file.previewElement.getElementsByClassName("dz-upload")[0];
progressBar.innerHTML
= progress + "%";
});
// displaying the uploaded files information in a Bootstrap dialog
this.on("successmultiple", function(files,
serverResponse) {
showInformationDialog(files,
serverResponse);
});
}
}
function showInformationDialog(files,
objectArray) {
var responseContent = "";
for (var i = 0; i <
objectArray.length; i++) {
var infoObject =
objectArray[i];
for ( var infoKey in infoObject) {
if
(infoObject.hasOwnProperty(infoKey)) {
responseContent
= responseContent + " " + infoKey + " -> " +
infoObject[infoKey] + "<br>";
}
}
responseContent
= responseContent + "<hr>";
}
// from the library bootstrap-dialog.min.js
BootstrapDialog.show({
title : '<b>Server
Response</b>',
message
: responseContent
});
}
});
That's all I have for the dropzone integration. The only remaining part for this tutorial is to see another page that lists out all the uploaded files in the server. The page will have a download button for each file listed. Let's have a look.
listFiles.jsp ( path: /webapp/WEB-INF/views/listFiles.jsp )
<%@
taglib uri="http://java.sun.com/jsp/jstl/core"
prefix="c"%>
<!DOCTYPE
html>
<html>
<head>
<meta charset="utf-8" name="viewport"
content="width=device-width,
initial-scale=1">
<title>Spring MVC + Dropzone.js Example</title>
<link rel="stylesheet" type="text/css"
href='<c:url value="/web-resources/libs/bootstrap-3.1.1/css/bootstrap.min.css"/>'>
<link rel="stylesheet" type="text/css"
href='<c:url value="/web-resources/libs/bootstrap-dialog/css/bootstrap-dialog.min.css"/>'>
<link rel="stylesheet" type="text/css"
href='<c:url value="/web-resources/css/style.css"/>'>
</head>
<body>
<div class="container">
<div class="panel
panel-default">
<div class="panel-heading
text-center">
<h3>Spring MVC +
Dropzone.js Example</h3>
</div>
<div class="panel-body">
<a class="btn
btn-primary" href="${pageContext.request.contextPath}">
<span class="glyphicon
glyphicon-chevron-left"></span> Go Back
</a>
<br>
<h4>List of All Uploaded
Files</h4>
</div>
<table class="table
table-hover table-condensed">
<thead>
<tr>
<th width="5%">S.N</th>
<th width="40%">File Name</th>
<th width="20%">File Type</th>
<th width="15%">File Size</th>
<th width="10%">Actions</th>
</tr>
</thead>
<tbody>
<c:forEach items="${fileList}"
var="dataFile" varStatus="loopCounter">
<tr>
<td><c:out value="${loopCounter.count}"
/></td>
<td><c:out value="${dataFile.name}"
/></td>
<td><c:out value="${dataFile.type}"
/></td>
<td><c:out value="${dataFile.size}"</td>
<td>
<a class="btn
btn-primary" href="${pageContext.request.contextPath}/get/${dataFile.id}">
<span class="glyphicon
glyphicon-download"></span> Download
</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
<script type="text/javascript"
src='<c:url value="/web-resources/libs/jquery/jquery-2.1.1.js"/>'>
</script>
<script type="text/javascript"
src='<c:url value="/web-resources/libs/bootstrap-3.1.1/js/bootstrap.js"/>'>
</script>
</body>
</html>
Download
The source code is available in
GitHub. You can either download the zip or directly import the project in eclipse from the following location:
Running the application
Now, running the application is quite simple enough.
- Open the Command Prompt
- Go to the root project directory ( springmvc-dropzonejs )
- Run the mvn command to download all dependent JARs.
- Type mvn and hit <Enter>.
- Run Tomcat server
- Go to the browser and enter the following URL:
- http://localhost:8080/springmvc-dropzonejs/
- The port number might be different in your case. Please have a look at the tomcat log in console for that.
Questions???
Feel free to shoot me an email or comment below if you have any questions or confusions.