visit
This post is part of a series comparing different ways to implement asynchronous requests on the client to augment the latter. So far, I described the process with Vue.js and Alpine.js. Both are similar from the developers' point of view: they involve JavaScript.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <!--1-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId> <!--1-->
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId> <!--1-->
<version>0.52</version>
</dependency>
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>htmx.org</artifactId> <!--2-->
<version>2.0.1</version>
</dependency>
</dependencies>
<script th:src="@{/webjars/htmx.org/dist/htmx.js}" src="//cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js"></script> <!--1-->
HTMX implements a radical approach that is different from traditional AJAX frameworks. They force you to develop an HTTP API that accepts and returns JSON. With HTMX, you return HTML fragments instead. HTMX uses it to replace the DOM elements that you configured. Hence, you need to write neither JavaScript nor deal with JSON and serialization of entities.
-------------------- APP --------------------
| index.html |
| |
| ---------------- TABLE ---------------- |
| | table.html | |
| | | |
| | ------------- LINES ------------- | |
| | | lines.html | | |
| | | | | |
| | --------------------------------- | |
| --------------------------------------- |
---------------------------------------------
I'll split the HTML page into these fragments. Because we render them via Thymeleaf, we can split each into their dedicated file for a cleaner separation. At initial load time, we use Thymeleaf's replace
directive; we use HTMX for asynchronous client-side interactions.
<tbody id="lines">...</tbody> <!--1-->
<button class="btn btn-warning"
hx-trigger="click" <!--2-->
hx-delete="/htmx/todo:cleanup" <!--3-->
hx-target="#lines"> <!--4-->
Cleanup
</button>
lines
DOM elementclick
eventDELETE
HTTP request to the URLlines
DOM element with it
Note that there's no explicit JavaScript involved, not a single line of code. HTMX takes care of it.
On the server side, the code is the following:
fun htmx(todos: MutableList<Todo>) = router {
DELETE("/htmx/todo:cleanup") {
todos.removeIf { it.completed } //1
ok().render("htmx/lines", mapOf("todos" to todos)) //2
}
}
render()
function, instead of body()
for API calls. Because of our previous file split, we can render only the needed HTML fragment. It uses Thymeleaf for any necessary server-side rendering.
Adding a new todo follows the same principle, but the DOM element is the whole table to reset the label
value. If interested in the complete, look at .
todo
HTMX offers the hx-vals
for the JSON payload. However, the URL is different for each row as we want to include the ID in the path. We must generate it server-side with Thymeleaf. TIL: Thymeleaf can manage any HTML attribute prefixed with th:
: it will process the value as usual and write the attribute's name unprefixed.
<input type="checkbox"
th:checked="${todo.completed}" <!--1-->
hx-trigger="click" <!--2-->
th:hx-patch="'/htmx/todo/' + ${todo.id}" <!--3-->
hx-vals='js:{"checked": event.target.checked}' /> <!--4-->
todo
is completedclick
eventsPATCH
request to the server, with Thymeleaf having replaced the id
with the value in the HTML previously
To go further: