Lab 1: Server-side rendering with Spring Boot and JTE
Last updated
Last updated
This lab aims to build a simple user management application with Spring Boot and htmx.
We are using JTE as the server-side template language.
We want to display a table of users like this:
First, navigate to the UserController
in de.tschuehly.easy.spring.auth.user
in the lab-1
folder and change it to the following.
(1): We define a constant for the USER_TABLE_BODY_ID
and the MODAL_CONTAINER_ID
(2): We create a new @GetMapping
to the /
path.
(3): Inside the method, we call the userService.findAll()
function and add it to the MVC model as easyUserList
attribute.
(4): We return the string UserManagement
. This is the reference to the View we want to render.
Spring MVC will look for templates in the src/main/jte
directory as we use the jte-spring-boot-starter-3.
You can find all the templates we need already there. We now need to fill them with life.
We start with the UserManagement.jte
template.
As you can see, a barebones html structure is already in place with CSS and htmx referenced.
We need to import all static variables defined in UserController with the @import
JTE syntax at the top of the file and we add the easyUserList we defined earlier in the model as a parameter to the template with @param
after the imports.
There is already a basic HTML Table in place, that we need to fill with our data.
We replace the table body element with the following:
(1): We set the id of the tbody
to USER_TABLE_BODY_ID
to reference it statically from other places.
(2): We loop over the easyUserList with the @for
JTE syntax
(3): Then in the loop body we call the userRow.jte template with the @template
syntax and pass the user
loop variable into the template.
We also add an empty <div>
after the </body>
element with the id
set to MODAL_CONTAINER_ID
to show a modal
Your UserManagement.jte
template should now look like this:
In the UserRow.jte
template we define an EasyUser parameter and a local variable with the exclamation mark JTE expression: !{var name = value}
at the top of the file.
We then add the uuid
of the user as id to the <tr>
element and add a <td>
element for the uuid, username and password and display the value of the user.
We can see all currently defined users if we start the application and navigate to http://localhost:8080.
Lab-1 Checkpoint 1
If you are stuck you can resume at this checkpoint with:
git checkout tags/lab-1-checkpoint-1 -b lab-1-c1
Next, we want to edit a user that has already been created and change his username.
First, we need to create an HTTP endpoint that will display the EditUserForm
. Add this to the UserController
:
(1): We create a UserForm record that represents the user.
(2): We create a static constant GET_EDIT_USER_MODAL
for the HTTP Endpoint. This makes it easy to understand what controller mappings htmx sends requests to.
(3): We retrieve the data from the datastore and add it to the model, using the record we just defined.
In the UserRow.jte
we add a new <td>
element and create a button element.
(1): hx-get="${URI(GET_EDIT_USER_MODAL,uuid)}
creates an HTTP get request to /user/edit/{uuid}
when the button element is clicked.
We use the HtmxUtil.URI()
method, that creates a UriTemplate
and fills it with the variables we pass.
(2): hx-target="#${MODAL_CONTAINER_ID}"
tells HTMX to swap the response body with the element where the id equals modalContainer
. We created this earlier in the UserManagement.jte
template
In the corresponding EditUserForm.jte
template we display the values in a <form>
element:
Restart the application, navigate to http://localhost:8080, and click the edit button. You should now see the modal popup and the values of the user displayed.
Lab-1 Checkpoint 2
If you are stuck you can resume at this checkpoint with:
git checkout tags/lab-1-checkpoint-2 -b lab-1-c2
We now want to change a value, save it to the datastore and display the updated value in the table. We create a new endpoint in the UserController.java
.
We create a new endpoint and call the userService.saveUser()
endpoint, then we add the new user value to the model and return the UserRow template:
(1): We create a constant POST_SAVE_USER
(2): We create a new @PostMapping
endpoint
(3): We save the user and add it to the model
(4): We return the UserRow.jte
template
We then change the EditUserForm.jte
template:
We import the POST_SAVE_USER
constant at the top of the file with @import
(1): We create an HTTP POST
request to the POST_SAVE_USER
endpoint by adding a button with an hx-post
attribute.
We can also place the hx-post
attribute on the form, as the button would trigger a form submit, that htmx catches.
If you restart the app now you will see that the table value is not updated.
Instead, the table row is rendered inside the button, because htmx by default swaps the innerHTML of the element that created the request.
To fix this, we can return htmx attributes as HTTP Response headers in the UserController
(1): We add HX-Retarget = #user-${user.uuid}
to target the table row <tr> element that contains the user we just edited.
(2): HX-Reswap = outerHTML
tells htmx to swap the whole table row.
(3): HX-Trigger = close-modal
tells htmx to trigger a JavaScript event close-modal
in the browser when the HTTP response is received.
We now listen to the MODAL_CONTAINER_ID
event in theUserManagement.jte
template, by adding a hx-on
attribute.
(1): We can use hx-on
and the JTE $unsafe
syntax to set the innerHTML to null and remove the modal.
If we click the Save User
button and go to Chrome DevTools we see Hypermedia as the Engine of Application State (HATEOAS) in action.
The new application state after saving the user is transferred via HTML to the browser, and the new row also includes the link to get the modal it was just called from.
Lab-1 Checkpoint 3
If you are stuck you can resume at this checkpoint with:
git checkout tags/lab-1-checkpoint-3 -b lab-1-c3
This is the end of lab 1. If you are ahead of the others you can do the bonus exercise below
As the next step, we want to create a new User.
To start we create a new endpoint GET_CREATE_USER_MODAL
where we return the CreateUserForm
template in the UserController
We replace the <tfoot>
element of the UserManagement
, adding a button element that creates a GET request to the GET_CREATE_USER_MODAL
endpoint and targeting the MODAL_CONTAINER_ID
We now need to create a POST_CREATE_USER
endpoint in the UserController
:
It follows the same pattern as the POST_SAVE_USER
endpoint.
(1): We target the <body>
element using HX-Retarget: #USER_TABLE_BODY_ID
(2): We use HX-Reswap: afterbegin
to insert the response's content as the target element's first child.
(3): We trigger the CLOSE_MODAL_EVENT
(4): Finally we return the UserRow.jte
template.
Then we create a <form>
element in the CreateUserForm.jte
template:
(1): This time we have the hx-post attribute on the <form>
element
(2): We trigger the HTTP request with the <button type="submit">
After restarting the application you should be able to create a new user and when saving the new user they should be displayed as the first item of the table:
Lab-1 Checkpoint 4 If you are stuck you can resume at this checkpoint with:
git checkout tags/lab-1-checkpoint-4 -b lab-1-c4
This was Lab 1, you should now feel confident to use server-side rendering with Spring Boot and JTE and be able to create an interactive application using htmx.