Overview of This Lesson

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

Thymeleaf is a template engine that allows you to build HTML pages that can be filled with dynamic data. For example, you might want to look up a list of books in a database by a certain author, and list those books in an HTML table or an unordered list on the HTML page. The list of books might be different each time the database query is made, you only need to worry about displaying one table row or one list item for each book, and which fields you want to display. This kind of page is easy to create using Thymeleaf.

In this lesson we'll learn the basics of Thymeleaf with a simple demonstration. Later, we'll look at more of the details of Thymeleaf syntax and use it to create more comprehensive applications.

Pre-Requisites

Before doing this lesson, make sure you've completed the lessons on controllers and handlers, request parameters, and dependency injection.

Helpful Resources

What is Thymeleaf?

Thymeleaf is a template engine. It allows you to take normal HTML code and add special attributes to make your pages display dynamic content. You use HTML and Thymeleaf to create a "template" and then Thymeleaf fills in the data for you as it's processed on the server. The plain-text/html response is then sent back to the client with all your normal structure, styling, and data. There is just so much stuff you can do with Thymeleaf, so this is really just an overview of the most common things you might want to do. Feel free to explore the documentation and the tutorials to find out what else you can do!

Thymeleaf also makes use of Spring Expression Language, or SpEL in addition to a huge number of special attributes you can add right into the HTML elements you're already familiar with.

For example, the following code displays a paragraph element containing the values of an Inventory object's data members:

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

Don't worry too much about what's going on in that code, you'll learn what it all means over the next couple of lessons. But you can probably guess a lot already!

The values with the ${ } are called Thymeleaf Expressions or variable expressions. These expressions evaluate to something, depending on what the expression is. These expressions usually go inside special Thymeleaf attributes that are prefixed with th:.

We'll explore all the details of variable expressions in the next lesson on Thymeleaf Syntax.

Model Attributes

In the lesson on request paramters we learned how to send data from an input form into the Controller. The next logical question might be "How can we send data from the controller to an ouput page?" In other words, how can we add stuff to the response that goes back to the client? For example, if the user uses an HTML page with a form to select a genre of book, we would send that genre input to the controller (in the request) and tell the controller to look up all the books with that genre in the database. Once we retrieve the list of books we want to send that list along with the response back to the client so that the user can see the list of books nicely formatted on an HTML page.

We need two things to do this:

  1. Model attributes where we store the list of books.
  2. A Thymeleaf template page to format the list of books so that it's presentable.

First we'll talk about Model attributes, and then in the next next few lessons section we'll look at Thymeleaf in detail.

What is a Model Attribute?

The org.springframework.ui.Model is an object that is used to store attributes as key-value pairs (it's actually a type of Map - recall from Java 2 that a Map is a Collection object of key-value pairs), so that those attributes can be used in a single request-response cycle. Attributes can be objects or primitive data. The attribute's key is a String value that must be unique (different from all the other attribute keys currently stored in the Model) and the value can be a primitive or any kind of object. Examples:

Recall that HTTP is stateless: let's say you go to the SLATE page and log in with your credentials:

  1. When you submit the login form, your browser makes a request and sends along your credentials with the request.
  2. The server validates your credentials and sends back a response page that lists all the courses you have access to this term.
  3. You click on the PROG32758 course and your browser sends a new request to the server, requesting the main page for that course.
  4. Because HTTP is stateless, the server doesn't remember the previous requests, so it doesn't remember you. It uses cookies and sessions to figure out who you are and if you're allowed to access the requested page.
  5. For example, the code on the server might have to check the session data to see who you are, and then run a query to see if you're authorized to view the requested page.

In a Spring application, we can set attributes when a request is being processed so that those attributes can be accessed by the the response and added to the output page. Later you'll earn to use Session attributes to do the same thing across different request-response cycles!

The Model is where we store request attributes: these attributes are accessible during a single request/response cycle. So any attributes you add to the Model when processing the request are accessible when the response is created and sent back to the client.

You can store attributes in the Model object using code in your Controller (generally, in a handler method) and then you can access those attributes on an HTML output page!

To access the Model in a handler method, just add a Model parameter to your method: Spring automatically injects the Model it has in the IOCC!


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

  // add an attribute to the model
  model.addAttribute("cat", "Sydney");
  return "page.html";
}

In this example, we're adding a String object to the Model. The attribute has a key of "cat" and the attribute's value is a String object with the value "Sydney".

Here's another example:


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

  Book b = new Book();
  b.setIsbn(12345678);
  b.setTitle("A Good Book");
  b.setAuthor("Someone Interesting");
  b.setPrice(9.99);

  // add an attribute to the model
  model.addAttribute("book", b);
  return "page.html";
}

In this example, we're constructing a Book instance, setting some of its members, and then adding a Model attribute that has the key "book". The attribute's value is the book object we created.

Go ahead and try it out:

  1. Start up a new project, add the dependencies for Spring Web, Dev Tools, Thymeleaf and Lombok.
  2. Add a Controller to the .controllers package.
  3. Add a .beans package and put one of your beans in there. I'm going to use the Book bean in my example.
  4. Add an index.html page to the /templates directory and edit the <title> value. Add a heading of your choice.
  5. In the <html lang="en"> element, add the Thymeleaf namespace:
    <html lang="en" xmlns:th="http://www.thymeleaf.org">

Let's pause for a moment and examine the code you added to the <html> element:

Now go to your controller and add a handler for the index page.

  1. Instantiate a Book object (or instantiate whatever class you chose for your bean).
  2. Add your book object to the Model as an attribute.
  3. Add a second model attribute that's a String. Use any key and any String value you like.
	@GetMapping("/")
public String index(Model model) {
                            
    Book b = new Book(123456789, "Cheese Stories", 
        "Wendi Jollymore", 29.95, 1);
    model.addAttribute("book", b);

    model.addAttribute("cat", "Sydney");

    return "index.html";
}

So storing attributes is easy, now how do we access them in our output pages? For this we use Thymeleaf. We'll start with a quick example:

Add the following to your index.html page, under your heading:

<p th:text="${book}">my new book</p>
<p th:text="${book.title}">a book title</p>
<p th:text="${cat}">a cat's name</p>

Replace "book" and "cat" with the names of your two attributes, if you used different ones!

The th:text="" attribute inside the <p> elements is a Thymeleaf attribute. You should assign it a string value containing the innerHTML content you'd like inside the element. You can use th:text on most HTML elements.

In all three lines of HTML code I've assigned th:text a value using the ${ } characters. This is a Thymeleaf Expression. This expression is evaluated, and the result is assigned to the element's innerHTML as follows:

Each <p> element also contains some text: This is fallback text: if for some reason the Thyemleaf expression can't be evaluated or the code can't be parsed, the <p> elements will show that fallback text, instead. This is a best-practice of web development: always provide fallback code if possible.

Request Parameter Object

What if you want to access form input values and request parameters on the page that's coming back as the response? You can! Thymeleaf gives you access to the HttpServletRequest object (which holds your request parameters, among many other things). Request parameters can be accessed via the implicit object called param.

The param object is a map of all the request parameters as key-value pairs in the query string. To access one of the request parameters in your output page, just use the ${param.key} where "key" is the parameter name! Remember that the key corresponds to the name attribute of the form input field.

For example, if I had a form <input> with attribute name="firstName", you can access the input value from the request as ${param.firstName}.

Give it a try: add a form to your index page:

<form action="/submit">
  <p><label for="stuff">Enter Stuff:
    <input type="text" id="stuff" name="stuff">
  </label></p>
  <p><input type="submit"></p>
</form>

Below your form, add another paragraph element to display the value of your form's input field:

<p th:text="${param.stuff}">stuff</p>

In your controller, add another handler method to process your form. The handler should just load the index page (yes, you can totally do that), and nothing else.

@GetMapping("/submit")
public String submit() {
                    
    return "index.html";
}

Save all your changes and test out your page: reload https://localhost:8080.

You should still see your book and string attirbutes on the page, but now you'll also see your form.

the index page with the small form
The new index page

Go ahead and enter a value in the input field, and click the Submit button.

I like things has been typed into the form field
Add some text to the form input field

The controller will execute the handler for the form's submit action. This will cause the index.html page to reload. On the page, you should now see your input text below the form.

the input text appears below the form
The index page after form submission

Note that you no longer see your first two attributes for the book and the String. Recall that HTTP is stateless: when you make a request, the server processes that request, and then returns a response. Once the server sends the response, it forgets who you are and what you were doing, it doesn't need to remember those details. When the form is submitted, a new request is made, the server processes that request and responds with the index page again. It doesn't remember any of the details of the previous request.

The Model and its attributes has scope, just like program variables and methods have. The Model has what we refer to as Request/Response Level scope, or simply just Request scope: This means that the model is only "alive" or accessible/visible during a single Request-Response cycle. once that cycle has completed and the client loads the Reponse, the Model "dies" or is no longer accessible.

There are other levels of scope (for example, application-level scope) and there are other ways to keep data alive or accessible between request-response cycles (for example, using sessions). We'll learn about these things later in the course.

Exercise

Start a new project and add Spring Web, DevTools, Lombok, and Thymeleaf dependencies. Copy your Inventory bean from the previous lesson over to this project.

Add an index page with a form that asks the user to enter their name. Add a controller that maps to the root and loads the index page.

Add an output page (e.g. output.html) and give it a title and heading.

In your controller, add a handler method that receives the user's input for the name field on the form. Also, construct an instance of the Inventory class and give it some data for the id, item name, and quantity.

Store the inventory object in the model.

On your output page, display the inventory object as a String, then display the item id, name, and quantity separately, then also display the user's name that they entered on the form.

Test your application!