Overview of This Lesson

Refresh this page because I am probably still making changes to it.

You've learned that one of the controller's jobs in an MVC application is to facilitate communication between the view and the model. For example, when the user updates the inventory quantity on an HTML form, the controller needs to make sure the model (the inventory object) gets updated as well so that the inventory object's quantity reflects the changes made on the view.

This can be achieved in a Spring Boot application by binding, or connecting, the form to the Inventory object.

Selection Expressions

To learn about form binding, we need to learn about a couple of new bits of Thymeleaf syntax. So far in Thymeleaf we've learned variable expressions ${ }, for evaluating expressions, and URL expressions @{ }, for referencing files and URLs.

There are also selection expressions *{ }

A selection expression evaluates a specific expression on a selected object. Selection expressions are used in conjunction with the th:object attribute.

The th:object Attribute

The th:object attribute selects an object that can then be referred to elsewhere in the element. For example, assuming the object variable inv has been saved as a model attribute (e.g. model.addAttribute("inv", inventoryObject);) that contains an Inventory object:

<div th:object="${inv}">
  <b>ID:</b> <span th:text="*{itemId}">item id</span>
  <b>Name:</b> <span th:text="*{itemName}">item name</span>
</div>

In this example, the DIV element contains the th:object attribute which "selects" the inv object. Inside the DIV, the SPAN tags are displaying data members of that inv object using selection expressions. For example, *{itemId} refers to the currently selected object's itemId member and *{itemName} refers to the currently selected object's itemName member. Since the currently selected object is ${inv}, both of these SPAN elements are equivalent to:

<div>
  <b>ID:</b> <span th:text="${inv.itemId}">item id</span>
  <b>Name:</b> <span th:text="${inv.itemName}">item name</span>
</div>

At first this may not seem very impressive: what's the big deal if it's just different syntax? But the power in using th:object along with a selection expression *{ } comes with form binding.

Form Binding

Form binding allows you to bind or connect an object to a form. This not only makes it easy to access the object's properties on the form, but makes it super easy to work with: when the user fills in the object's details on the form, the object's properties are automatically populated with the form inputs! So when the form is submitted to the server, it comes with an object already instantiated and populated with your form's input data!

Let's do an example: I've created a quick Player bean that uses Lombok. Add a Player class to your .beans package and copy my Player bean code over to yours. The Player class models a player in a sports league. Mine is for Canadian hockey teams in the NHL but you can use whatever sports league you want for yours, you can even make one up if you want!

Notice that the Player class contains a constant array of the Canadian NHL teams. Feel free to modify the team names and cities to whatever you like, football/soccer, cricket, basketball, baseball, Quidditch, underwater basket weaving, anything!

When we assign a player to a specific team, we'll store the index of that team's name to the team integer data member. For example, if my player is on the Montreal Canadiens, then that player's team member is set to the value 2.

Now create a form so that a user can add a player to a sports team in the league. Start off with adding fields for the first name, last name, and the jersey number. For the team input, we want to allow the user to select a team by name from a drop-down list:

<select name="teamName"> ... </select>
teams array loaded into selection list,
                     upon selection the value is the index
Load TEAMS array into SELECT list. A selected item will use the array index as the value.

If you're short on time or doing this during a live class, you can copy and paste this incomplete form:

We could easily just go ahead and type an <option> element for each team, but sometimes the teams change. For example, the Winnipeg Jets became the Atlanta Thrashers in 1997 and then became the Winnipeg Jets again in 2011. So if we change a team name, remove a team, or add a team, we'll have to remember to do it in both the Player class AND in this form, and that's not good design.

For now, we'll leave the array with the Player class (but later we'll try it with an enumeration, and eventually this will be a table in a database).

But how do we access the TEAM array from our Player class in our HTML code? Easy!!

If we stored an instance of Player in as a Model attribute, we could access it here in our form. For example, if we had this in our model:

model.addAttribute("player", new Player());

We could then access the Player object's TEAMS array in our HTML.

bind the player object in the model to the form
We need to bind the player object in the model to the form so we can populate the SELECT element.

Go to your controller and add a handler method that loads the main form page. Add a Model parameter. Then, in the method body, construct a new default player object and add it as a model attribute "player". Then return the name of the form page.

@GetMapping("/")
public String index(Model model) {
  model.addAttribute("player", new Player());
  return "index.html";
}

In this code, we're creating an empty Player object and adding it to the model so that we can access it on the form. This is going to allow us to use that player object's TEAMS array to populate our drop down list.

First, we have to tell our form what object it should work with. We'll tell it to work with the player object we have stored as a model attribute:

<form method="post" action="/player" th:object="${player}">
...
</form>

Now we create our SELECT list, and create an OPTION element for each item in our array using the th:each. We want to iterate through the TEAMS array in player, which is part of the player object we selected in the form's th:object attribute:

<option th:each="t : *{TEAMS}"></option>

This says, "iterate through the TEAMS member of the selected object (which is player) and each element of the TEAMS member is referred to as t".

For our OPTION elements, we want the value to be set to the array index of TEAMS, because Player.team is an integer:

<option th:each="t,stat : *{TEAMS}" th:value="${stat.index}"></option>

We have to add the stat loop status variable so that we can access the loop index. We used index instead of count because index is 0-based, so it matches perfectly with the array indexes.

We set the th:value property to the value of stat.index.

So the first option element will have a value of 0, the second option element a value of 1, etc. Matching the array perfectly.

Lastly, we need some text to appear in this option element. Let's set it to the actual array element value!

<option th:each="t,stat : *{TEAMS}" th:value="${stat.index}"
th:text="${t}">...</option>
each piece of the option element's code
How the option element is configured.

That's it! If you now run your program and load your form page, you should see the drop down list of teams!

<form method="post" action="/player" th:object="${player}">
  <p><label for="firstName">First Name:
      <input type="text" id="firstName" name="firstName" required>
    </label>
    <br>
    <label for="lastName">Last Name:
      <input type="text" id="lastName" name="lastName"  required>
    </label>
    <br>
    <label for="number">Jersey Number: 
        <input type="number" min="0" id="number" name="number">
    </label>
  </p>
  <p><label for="team">Select a Team:
    <select name="team" id="team">
      <option th:each="t,stat : *{TEAMS}" th:value="${stat.index}" 
        th:text="${t}">team</option>
    </select>
    </label>
  </p>
  <p><input type="submit"></p>
</form>

@ModelAttribute Annotation

Basically what we've done so far is we bound our form to an object stored in the model attribute. You should be wondering, if we can set up a model attribute in our controller and send it to a form, could we not also take the object that is bound to a form and send it to the controller?

For example, if we were to edit the inputs on the form, could we not tell Spring to automatically use those inputs to update our player object? If we could, it would make things easier. Previously, in the Inventory example, we did this:

@PostMapping("/output")
public String output(Model model, @RequestParam long itemId,
    @RequestParam String itemName, @RequestParam int quantity) {

    Inventory inv = new Inventory(id, itemName, quantity);
    model.addAttribute("invItem", inv);

    return "output.html";
}

With form binding, we can cut down on some of this code.

We've already bound the object to the form, but we now need to bind each field to one of the object's data members. For example, we want to bind the player first name text input field to player.first name, and the player last name input field to player.last name, etc.

We do this using the th:field attribute on each input element.

The th:field attribute binds the input field on the form with a member of the same name in the selected object e.g. th:object="${theObject}"

So for example, if your form has th:object="${inv}" then th:field="*{itemName}" refers to ${inv.itemName}.

diagram of individual field inputs bound to individual data members of Player
Each input field can be bound to a specific data member belonging to the form's bound object.

In the handler method that processes the form, we want to be able to now load the output page, and display the newly added player on that page. How can we access the player object that is bound to the form, can we access it from the model as an attribute?

@GetMapping("/")
public String index(Model model) {

    // set up an empty player object in the model 
    // so we can use it on the form
    model.addAttribute("player", new Player());

    return "index.html";
}

@PostMapping("/player")
public String player(Model model) {

    // test: what's in here? nothing
    System.out.println(model.getAttribute("player"));
    return "player.html";
}

This code will display "null" for the player attribute and, if you've already coded your player.html output page with Thymeleaf, show several Thymeleaf errors.

Why? The Model attributes are at what we call "request level" attributes: they are only alive or accessible during the current request-response cycle. What's happening is:

  1. We go to the index page: this begins a request to load index.html.
  2. The controller handler method index() executes and adds a new Player object to the Model.
  3. That Player object is bound to the form: the form is linked to the Player object and the form input fields are linked to the Player data members. So the server builds the index.html file using the Player's data.
  4. The server sends back the response of the index.html source so that it can be rendered in the browser.
  5. At this point, the server is done with the Model data for this request, so that model data is deleted.
  6. Next, we enter form data and click the Submit button: a new request is made to the URL http://localhost:8080/player.
  7. The query string in the request consists of all the form input data.
  8. HTTP is stateless, so the server has no memory of the model attributes from the previous request - it deleted the previous model attributes after the previous response was sent back to the client. Therefore, there is no Player object in the model.
animation of a single request response cycle
How a single request/response cycle works

So the "player" model attribute was only alive during the first request. All model attributes are cleared from the model at the end of the previous request.

But this is easy to fix.

In the controller's handler method that processes the form, you can access the updated model attribute easily using a @ModelAttribute annotation on a parameter:

@PostMapping("/player")
    public String player(Model model, @ModelAttribute Player player) {
}

@ModelAttribute retrieves an object from the request source and stores it in the parameter. The parameter variable name should be the same as the model attribute, but if you don't want to do this, you can set the model attribute key name just like you do with @RequestParam:

@ModelAttribute(name="player") Player newPlayer

or

@ModelAttribute("player") Player newPlayer

In our example, the @ModelAttribute annotation, when attached to the handler method parameter Player player, retrieves the player object that is bound to the form with th:object. So this grabs the player object we bound our form to, with all the input data updated!

If you add that parameter, you should now be able to see your newly added player:

@PostMapping("/player")
public String player(Model model, @ModelAttribute Player player) {

    // test: what's in here? the player from the form!
    System.out.println(player);

    return "player.html";
}

What's cool about @ModelAttribute is that if there is no Player object hanging around, it will just instantiate a brand new, default Player!

Now we can finish our output page player.html:

<div th:object="${player}">
#<span th:text="*{number}">n</span> 
Name: <span th:text="*{lastName}">last</span>, 
<span th:text="*{firstName}">first</span>
<br>
Team: <span th:text="*{team}">team name</span>
</div>
output from the player example
Output from the Player example

We use th:object on the DIV element to indicate that we'd like to use the stored player object called "player". For each SPAN, we use a selection expression to select each data member from the stored player object.

It would be nicer if we could display the team name instead of the team index. That's possible, just access *{TEAMS[team]} This uses the Player.team integer value as an index to the TEAMS array:

Team: <span th:text="*{TEAMS[team]}">team name</span>

Using an Enumeration

An even better solution to the teams list problem is to use an Enumeration. Add a new .repositories package to your project and add the Teams enumeration. You can copy my code that I've already created.

You will also need this new version of the Player class: this one uses the Teams enum for the team data member, instead of an integer, and there is no longer a need for the array member.

Let's modify our drop-down list to use the enumeration instead of an array in the Player class.

In our SELECT element, we don't need to change th:field because the data member for the team has the same name, only the type of data is different, but that doesn't matter to th:field: it's only concerned with the name of the member that this select should be bound to.

For each option, we're now iterating through an enumeration. The enumeration is now in a separate class, the team data is not inside the Player class, so we can't access it using the player object.

Instead, we can use the T operator. The T operator is actually part of Spring Expression language, and it allows you to access a class directly. You specify the fully qualified name of the class after the T operator:

T(ca.sheridancollege.jollymor.week4.repositories.Teams)

(make sure you use the package name for your project)

This segment refers to the class Teams, as opposed to an instance or object. This comes in handy when you need to access the static method of a class: as you recall, you invoke a static method on the name of the class, not on an instance.

Recall also that you can access the array of enum constants inside an enum by using the values() method. For example, Teams.values() returns an array of Team enum constants. So to iterate through the Teams enum, we use the array expression

${T(ca.sheridancollege.jollymor.week4.repositories.Teams).values()}

(use your own enumeration's package name)

Therefore, our th:each now becomes:

th:each="t : ${T(ca.sheridancollege.jollymor.week4.repositories.Teams).values()}"

Now, because Player.team is a Team enum, the value of a selected option must be an enum. We can use the Team enum constant name as the value, and Spring will automatically convert that string into an actual Team enum using the enum's built-in valueOf() method:

th:value="${t.name()}"

The option's text value can remain the same:

th:text="${t}"

Since t is the current item in a th:each, and we're iterating Team enum constants, then t represents each enum in Teams.

You'll notice the toString() in the Team enums returns the city name followed by a space, followed by the team name, which is exactly what we want to display in our list. ${t} will automatically invoke the toString() and display the return value in the OPTION element's inner HTML.

So in summary, we now have:

<select name="team" id="team" th:field="*{team}">
<option th:each="t : ${T(ca.sheridancollege.jollymor.week4.repositories.Teams).values()}" 
th:value="${t.name()}"  th:text="${t}">team</option>
</select>

Save and load your HTML page to make sure it still works. Examine the page source for the select element and make sure it's set up correctly:

We don't need to change anything in our controller.

Now the output page: we need to change our output so that the team name from the enum is being displayed for this player's team:

Team: <span th:text="*{team}">team name</span>

That's it! Because *{team} references the player's team member, and that team member is of the enum type Team, that will automatically invoke the toString(), which as we just said earlier, returns the city name followed by the team name.

Add a new player and make sure it shows up correctly on the screen.

Exercises

1. Create your Dream Team!
Many serious sports fans like to create dream teams made up of players from different teams. Modify your players program:

Try your program. Add a new player and submit it. You should see your player in the table, but your player data is also still populating the form fields. If we wanted to edit the player, this would be great, but we don't! We want to add a new player, so it would be better if the form fields were cleared (or reset to the player default values, where appropriate). Why does the information appear in the form fields? Think about the answer to that question.

Add one line of code to your handler method (the one that processes the form) to fix this, so that the fields are cleared/reset.

Now restart/relaunch the project (this will clear your dream team list so you can start over with a new list). Add a new player: you should now see the player in the table, and the fields are cleared and ready for you to add a new one. Add a few more players and watch them appear in your table!

another like above with more players
Below the form, show a list of new players that have been added.

2. Generate a List of Books Start a new project and copy over your latest Book bean.

Add a Genre enumeration to a .repositories package of your project:

Create an enum called Genre to contain the different genres of books. Make sure you include a field for the "display name" so the user doesn't have to see the genre names in upper-case letters. Adjust your bean's genre data member so that it stores a Genre enum value.

Create a form newBook.html that contains fields that allows a user to add an instance of the Book bean. Genre can be selected in a drop-down list.

new book form
Form for adding a new Book, the drop-down list uses the Genre enumeration

new book form
Form for adding a new Book

Add controller methods (3) to:

  1. Load the form page newBook.html.
  2. Process the form data:
    • Add the new book to a list of books.
    • Load the view books.html that shows a list of all the books added.
  3. Clear the list of books:
    • Empty the list.
    • Load the view books.html output page.

Create the books.html file to show the output.

output page with list of books
List of books in an HTML table on the output page

In my screen shots, you'll see that my Submit button and my 2 links are both styled to look like buttons. Feel free to copy and edit the CSS:

.wsjbtn {
  background-color: teal; 
  border: 1px solid navy;
  box-shadow: 4px 4px 2px grey;
  border-radius: 8px;
  color: white;
  margin: 1px;
  padding: 10px;
}

.wsjbtn:hover {
  background-color: white;
  color: teal;
}