Part 4: Back to the frontend
Since we need to be able to store documents before we can test how our retrieval methods work, let's start with implementing an upload page. Note that you of course can implement a dummy method that stores some test documents in the database, but for the sake of this tutorial we'll just go ahead and start implementing the upload functionality.
Let's create another jsp page called "upload.jsp" in the views folder of our webapp.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --> <meta name="description" content=""> <meta name="author" content=""> <title>File Manager</title> <!-- Bootstrap core CSS --> <link href="<c:url value="resources/dist/css/bootstrap.min.css" />" rel="stylesheet"> <!-- Custom styles for this template --> <link href="resources/navbar.css" rel="stylesheet"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <div class="container"> <!-- Static navbar --> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">File Manager</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav"> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="">Logout</a></li> </ul> </div> <!--/.nav-collapse --> </div> <!--/.container-fluid --> </nav> <form:form method="post" modelAttribute="documentForm" enctype="multipart/form-data" action="${pageContext.servletContext.contextPath}/performupload"> <div class="form-group"> <label for="title">Title</label> <form:input class="form-control" path="title" placeholder="Title"></form:input> </div> <div class="form-group"> <label for="file">Select file to upload</label> <form:input type="file" path="file"></form:input> </div> <button type="submit" class="btn btn-default">Submit</button> </form:form> </div> <!-- /container --> <!-- Bootstrap core JavaScript ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="resources/dist/js/bootstrap.min.js"></script> </body> </html>
The important part here is the form:
<form:form method="post" modelAttribute="documentForm" enctype="multipart/form-data" action="${pageContext.servletContext.contextPath}/performupload"> <div class="form-group"> <label for="title">Title</label> <form:input class="form-control" path="title" placeholder="Title"></form:input> </div> <div class="form-group"> <label for="file">Select file to upload</label> <form:input type="file" path="file"></form:input> </div> <button type="submit" class="btn btn-default">Submit</button> </form:form>
We are using Spring's form tags to generate the input fields ("form:input') and the general form ("form:form"). Note the two attribute "modelAttribute" and "action". Their values will be important when we now implement the controller layer.
Model attribute and controller
The value you specify in in the "modelAttribute" attribute, specifies what attribute should be used to transfer the submitted data. The "action" attribute specifies what method should be called. Let's create a new controller for our upload page called "UploadController". We need to autowire the DocumentService (because we want to store submitted documents) and the controller will have two methods: one to prepare and show the upload page, the other to handle the submission of a new document.
The prepare and show method is rather simple. It should return the jsp page that should be rendered for the upload page (upload.jsp). Your class should look like this:
package edu.asu.diging.tutorial.spring.web; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import edu.asu.diging.tutorial.spring.service.impl.DocumentService; @Controller public class UploadController { @Autowired private DocumentService documentService; @RequestMapping(value = "upload") public String showUploadPage(Model model) { return "upload"; } }
We should now see the upload page, right? Try it out by starting up your server and going to http://localhost:8080/fileManager/upload.
Did it work? Probably not. Most likely, you will see an error message similar to this:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'documentForm' available as request attribute
What Spring tells us here is that it is trying to find an object that it can use to bind the form to. Specifically, it tries to find an object named "documentForm", because in the modelAttribute of the form we specified "documentForm". So, what object should we give Spring? It should be an object that has the properties we want to submit through the form, namely "title" and "file" (we specified those in the path attributes of the "form:input" tags.
<form:input class="form-control" path="title" placeholder="Title"></form:input>
and
<form:input type="file" path="file"></form:input>
Let's create a class that we can use to back our form. First, create a new package called "forms" in our web package. In there create a new class called "UploadForm" and specify two fields: title and file:
package edu.asu.diging.tutorial.spring.web.forms; import org.springframework.web.multipart.MultipartFile; public class UploadForm { private MultipartFile file; private String title; public MultipartFile getFile() { return file; } public void setFile(MultipartFile file) { this.file = file; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
Now, add the following line to your "showUploadPage" method in the UploadController before the return statement:
model.addAttribute("documentForm", new UploadForm());
Restart the server and reload the upload page. You should now see something like this:
Next, we need implement a method to submit the form to and handle the submission. In the form we specified we would submit the form to
${pageContext.servletContext.contextPath}/performupload
which will resolve to fileManager/performupload (if you named your app "fileManager").
So, let's create another method in our UploadController and annotate it with a @RequestMapping with value "performupload". You can simply inject the UploadForm into the method and Spring will handover the filled UploadForm when the method is called. Your method should look something like this:
@RequestMapping(value = "performupload", method = RequestMethod.POST) public String performUpload(UploadForm documentForm, Model model) { MultipartFile multipartFile = documentForm.getFile(); // still need to handle the file documentService.storeDocument(documentForm.getTitle(), "anonymous", 1); return "redirect:files"; }
Note that in line 6, we specify "anonymous" as the uploader, since we do not have authentication yet. Also, note the "redirect" keyword in the returned String ("redirect:files"). This keyword tell Spring that instead of trying to render the specified jsp page, it should redirect to the "files" location of our webapp, which will call the FileListController method annotated with "files".
Restart your server and try uploading a file. You should be redirected to the "files" page after your upload and see a new entry.
Adding a link
Now, all that's left to do here, is adding a button on our file list page so that we can easily get to the upload page. Add the following lines over your table:
<p> <a class="btn btn-default" href="upload">Upload new document</a> </p>
You should be able to navigate from your list page to the upload page and upload a new file easily.
Note that so far we haven't handle the uploaded files yet, which means files are not actually stored yet. We'll take a look at that in the next section.
To be continued.