Skip to content

04 Creating Books

Julia Damerow edited this page Feb 18, 2022 · 4 revisions

Now, that we have a book class as well as all the backend code to create and retrieve books, let's add a page to create new books.

The Controller

First, let's create a controller that backs the page we are about to create. Typically, there should be one controller per page. Our page will just be a form to enter a title and author. In the web package, add the following class:

package edu.asu.diging.springaction.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.springaction.core.model.impl.BookImpl;
import edu.asu.diging.springaction.core.service.BookManager;

@Controller
public class AddBookController {

    @Autowired
    private BookManager bookManager;
    
    @RequestMapping(value="admin/book/add", method=RequestMethod.GET)
    public String show(Model model) {
        model.addAttribute("book", new BookImpl());
        return "admin/books/add";
    }
}

There are three annotations here:

  • @Controller: We've seen this one before. It tells Spring that this is a controller class. It will be scanned for @RequestMapping annotations.
  • @RequestMapping: This annotation tells Spring that when a GET request to admin/book/add comes in (e.g. when you go to http://localhost:8080/springtoaction/admin/book/add), then the method show should be called.
  • @Autowired: This one shouldn't be new either. It tells Spring that we want an instance of a class that implements the interface BookManager.

As you can see, the show method returns a string "admin/books/add". Like in the HomeController, this is the path to the template that should be rendered.

The Thymeleaf Template

First create a folder books in src/main/webapp/WEB-INF/views/admin. Then create a file called add.html inside the books folder. Add the following to the file:

<html layout:decorate="~{layouts/main}">
  <head>
    <title>Library App</title>
  </head>
  <body>
   <div layout:fragment="content">

    <form action="#" th:action="@{/admin/book/add}" method="POST" th:object="${book}">
        <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
        
        <label>Title: </label>
        <input th:field="*{title}" class="form-control input-sm"></input>
        <p class="text-danger"><th:errors path="title"/></p>
        
        <label>Author: </label>
        <input th:field="*{author}" class="form-control input-sm"></input>
        <p class="text-danger"><th:errors path="author"/></p>
    
        <div style="padding-top: 20px;">
        <button class="btn btn-sm btn-primary pull-right">Add Book</button>
        </div>
    </form>
   </div>
  </body>
</html>

This template will create a simple form with two input fields (title and author) and a button to submit the form. The attribute th:object="${book}" of the form tag tells Thymeleaf that the form is backed by that object. Whenever we use the star-notation within the form (e.g. th:field="*{authors}"), Thymeleaf will now look for the attribute on the book object.

Add Books

Now, let's see if we can see our work! Start the server and go to http://localhost:8080/spring-to-action/admin/book/add. You should see a login page. Login with your admin account. You should now see the form.

If you hit the "Add Book" button, however, you will get a "HTTP Status 405 – Method Not Allowed" error. This happens because our controller only defines a method for GET requests but we are making a POST request. So, let's add a method to handle our POST requests.

@Controller
public class AddBookController {

    @Autowired
    private BookManager bookManager;
    
    @RequestMapping(value="admin/book/add", method=RequestMethod.GET)
    public String show(Model model) {
        model.addAttribute("book", new BookImpl());
        return "admin/books/add";
    }
    
    @RequestMapping(value="admin/book/add", method=RequestMethod.POST)
    public String add(@ModelAttribute("book") BookImpl book) {
        bookManager.store(book.getAuthor(), book.getTitle());
        return "redirect:/admin/book/add";
    }
}

Now, if you click the "Add Book" button, you should be redirected to the add book form and a new book should be stored in the database. In the second method we added, you can see another @RequestMapping annotation that tells Spring the add method should be called when POST requests to admin/book/add come in. It maps the values in the form to a new BookImpl object and after storing the book in the database the user is redirected to /admin/book/add.

Book Listing

Before we move on to borrowing books, let's add all books to our home page. Open the HomeController and modify it like this:

@Controller
public class HomeController {
    
    @Autowired
    private BookManager bookManager;
    
    @RequestMapping(value = "/")
    public String home(Model model) {
        model.addAttribute("books", bookManager.all());
        return "home";
    }
}

You can see that we are now retrieving all books and putting them into the model. Now, we just need to show them on the home.html page. Remove the jumbotron and add a list of books instead:

<html layout:decorate="~{layouts/main}">
  <head>
    <title>Authority Matcher</title>
  </head>
  <body>
   <div layout:fragment="content">
      <ul class="list-group">
        <li th:each="book : ${books}" class="list-group-item clearfix">
            <i>[[${book.title}]]</i> by <b>[[${book.author}]]</b>
            </div>
        </li>
    </ul>
   </div>`
  </body>
</html>

You can see that there is a loop iterating over all books in the model attribute "books": <li th:each="book : ${books}" class="list-group-item clearfix">. This will create a new li element for each book. You might also notice that instead of using th:text, we use [[${book.title}]]. This notation is an alternative to the th:text attribute. Anything inside the square brackets will be interpreted by Thymeleaf.

Tips & Tricks

Redeploying your App

If you stopped your server for some reason, you do not have to go back to the context menu of your project and choose "Run on Server". You can simply open the server tab of Eclipse (Window > Show View > Other...) and right-click on your server to start, stop or restart it.

Modifying Thymeleaf Pages

When you edit a Thymeleaf template, you do not restart the server to see your changes. Simply reload the page. If the page shows up blank (all of it or partially), that typically means you have an error in your template. Thymeleaf stops rendering a page when it encounters an error. You can find Thymeleaf's documentation here.