This post is part of a series comparing different ways to implement asynchronous requests on the client, which is colloquially known as AJAX. I dedicated the previous post to Vue.js; I'll dedicate this one to Alpine.js - not to be confused with Alpine Linux.
I'll follow the same structure as previously.
Here's the setup, server- and client-side.
Here is how I integrate Thymeleaf and Alpine.js in the POM:
<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>alpinejs</artifactId> <!--2-->
<version>3.14.1</version>
</dependency>
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>axios</artifactId> <!--1-->
<version>1.7.3</version>
</dependency>
</dependencies>
It's similar to Vue's setup.
Here's the code on the HTML side:
<script th:src="@{/webjars/axios/dist/axios.js}" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script> <!--1-->
<script th:src="@{/webjars/alpinejs/dist/cdn.js}" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js" defer></script> <!--2-->
<script th:src="@{/alpine.js}" src="../static/alpine.js"></script> <!--3-->
<script th:inline="javascript">
/*<![CDATA[*/
window.alpineData = { <!--4-->
title: /*[[${ title }]]*/ 'A Title',
todos: /*[[${ todos }]]*/ [{ 'id': 1, 'label': 'Take out the trash', 'completed': false }]
}
/*]]>*/
</script>
As for the POM, it's the same code for Alpine as for Vue.
We want to implement the same features as for Vue.
The first step is to bootstrap the framework. We already added the link to our custom alpine.js
file above.
document.addEventListener('alpine:init', () => { //1
Alpine.data('app', () => ({ //2
// The next JavaScript code snippets will be inside the block
}))
})
alpine:init
event is triggered; the triggering event is specific to Alpine.app
We now set the app
id on the HTML side.
<div id="app">
</div>
Until now, it's very similar to Vue.js, a straight one-to-one mapping.
Unlike Vue.js, Alpine doesn't seem to have templates. The official UI components are not free. I found an Open Source approach, but it's unavailable on WebJars.
Let's implement the check of the complete checkbox.
Here's the HTML code:
<input type="checkbox" :checked="todo.completed" @click="check(todo.id)"> <!--1-->
<input type="checkbox" :checked="todo.completed" @click="check" /> <!--2-->
The code is very similar, with the difference that Alpine allows passing parameters.
On the Javascript side, we must define the function, and that's all:
Alpine.data('app', () => ({
check(id) {
axios.patch(`/api/todo/${id}`, {checked: event.target.checked})
}
}))
You might wonder where the todo
above comes from. The answer is: from the local model.
We initialize it in the app
or to be more precise, we initialize the list:
Alpine.data('app', () => ({
title: window.alpineData.title, //1
todos: window.alpineData.todos, //2
}))
title
even if it's read-onlytodos
list; at this point, it's read-only but we are going to update it the next sectionIn this section, we will implement adding a new Todo
.
Here's the HTML snippet:
<form>
<div class="form-group row">
<label for="new-todo-label" class="col-auto col-form-label">New task</label>
<div class="col-10">
<input type="text" id="new-todo-label" placeholder="Label" class="form-control" x-model="label" /> <!--1-->
</div>
<div class="col-auto">
<button type="button" class="btn btn-success" @click="create()">Add</button> <!--2-->
</div>
</div>
</form>
x-model
defines a model and binds the label
property defined in app
The related code is the following:
Alpine.data('app', () => ({
label: '', //1
create() {
axios.post('/api/todo', {label: this.label}).then(response => { //2
this.todos.push(response.data) //3
}).then(() => {
this.label = '' //4
})
}
}))
label
propertyPOST
request with the label
value as the JSON payloadTodo
label
valueAlpine is very similar to Vue, with the notable difference of the lack of templating; components are only available via a price. All other features have an equivalent.
I may need to be corrected because the documentation is less extensive. Also, Vue is much more popular than Alpine.
The complete source code for this post can be found on GitHub:
To go further: