\ In my previous post, I laid the ground to build upon; now is the time to start "for real".
\ I heard a lot of Vue.js. Additionally, a friend who transitioned from developer to manager told me good things about Vue, which further piqued my interest. I decided to have a look at it: it will be the first "lightweight" JavaScript framework I'll study - from the point of view of a newbie, which I am.
Laying out the workI explained WebJars and Thymeleaf in the last post. Here's the setup, server- and client-side.
Server-sideHere is how I integrate both in the POM:
\
\ I'm using the Kotlin Router and Bean DSLs on the Spring Boot side:
\
fun vue(todos: List\ If you're used to developing APIs, you're familiar with the body() function; it returns the payload directly, probably in JSON format. The render() passes the flow to the view technology, in this case, Thymeleaf. It accepts two parameters:
\
Here's the code on the HTML side:
\
\ As explained in last week's article, one of Thymeleaf's benefits is that it allows both static file rendering and server-side rendering. To make the magic work, I specify a client-side path, i.e., src, and a server-side path, i.e., th:src.
\
The Vue codeNow, let's dive into the Vue code.
We want to implement several features:
\
The first step is to bootstrap the framework. We have already set up the reference for our custom vue.js file above.
\
document.addEventListener('DOMContentLoaded', () => { //1 // The next JavaScript code snippets will be inside the block }\ The next step is to let Vue manage part of the page. On the HTML side, we must decide which top-level part Vue manages. We can choose an arbitrary
\
\ On the JavaScript side, we create an app, passing the CSS selector of the previous HTML
\
Vue.createApp({}).mount('#app');\ At this point, we launch Vue when the page loads, but nothing visible happens.
\ The next step is to create a Vue template. A Vue template is a regular HTML managed by Vue. You can define Vue in Javascript, but I prefer to do it on the HTML page.
\ Let's start with a root template that can display the title.
\
\ On the JavaScript side, we must create the managing code.
\
const TodosApp = { props: ['title'], //1 template: document.getElementById('todos-app').innerHTML, }\ Finally, we must pass this object when we create the app:
\
Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');\ At this point, Vue displays the title.
Basic interactionsAt this point, we can implement the action when the user clicks on a checkbox: it needs to be updated in the server-side state.
First, I added a new nested Vue template for the table that displays the Todo. To avoid lengthening the post, I'll avoid describing it in detail. If you're interested, have a look at the source code.
\ Here's the starting line template's code, respectively JavaScript and HTML:
\
const TodoLine = { props: ['todo'], template: document.getElementById('todo-line').innerHTML }\ Vue allows event handling via the @ syntax.
\
\ Vue calls the template's check() function when the user clicks on the line. We define this function in a setup() parameter:
\
const TodoLine = { props: ['todo'], template: document.getElementById('todo-line').innerHTML, setup(props) { //1 const check = function (event) { //2 const { todo } = props axios.patch( //3 `/api/todo/${todo.id}`, //4 { checked: event.target.checked } //5 ) } return { check } //6 } }In the previous section, I made two mistakes:
\
\ We will do that by implementing the next feature, which is the cleanup of completed tasks.
\ We now know how to handle events via Vue:
\
\ On the TodosApp object, we add a function of the same name:
\
const TodosApp = { props: ['title', 'todos'], components: { TodoLine }, template: document.getElementById('todos-app').innerHTML, setup() { const cleanup = function() { //1 axios.delete('/api/todo:cleanup').then(response => { //1 state.value.todos = response.data //2-3 }) } return { cleanup } //1 } }\ In Vue's semantics, the Vue model is a wrapper around data that we want to be reactive. Reactive means two-way binding between the view and the model. We can make an existing value reactive by passing it to the ref() method:
\
In Composition API, the recommended way to declare reactive state is using the ref() function.
\ ref() takes the argument and returns it wrapped within a ref object with a .value property.
\ To access refs in a component's template, declare and return them from a component's setup() function.
\ Let's do it:
\
const state = ref({ title: window.vueData.title, //1-2 todos: window.vueData.todos, //1 }) createApp({ components: { TodosApp }, setup() { return { ...state.value } //3-4 }, render() { return h(TodosApp, { todos: state.value.todos, //5 title: state.value.title, //5 }) } }).mount('#app');\ At this point, we have a reactive client-side model.
\ On the HTML side, we use the relevant Vue attributes:
\
I've described the corresponding template above.
Updating the modelWe can now implement a new feature: add a new Todo from the client. When clicking on the Add button, we read the Label field value, send the data to the API, and refresh the model with the response.
\ Here's the updated code:
\
const TodosApp = { props: ['title', 'todos'], components: { TodoLine }, template: document.getElementById('todos-app').innerHTML, setup() { const label = ref('') //1 const create = function() { //2 axios.post('/api/todo', { label: label.value }).then(response => { state.value.todos.push(response.data) //3 }).then(() => { label.value = '' //4 }) } const cleanup = function() { axios.delete('/api/todo:cleanup').then(response => { state.value.todos = response.data //5 }) } return { label, create, cleanup } } }\ On the HTML side, we add a button and bind to the create() function. Likewise, we add the Label field and bind it to the model.
\
\ Vue binds the create() function to the HTML button. It does call it asynchronously and refreshes the reactive Todo list with the new item returned by the call. We do the same for the Cleanup button, to remove checked Todo objects.
\ Note that I didn't intentionally implement any error-handling code to avoid making the code more complex than necessary. I'll stop here as we gained enough insights for a first experience.
ConclusionIn this post, I took my first steps in augmenting an SSR app with Vue. It was pretty straightforward. The biggest issue I encountered was for Vue to replace the line template: I didn't read the documentation extensively and missed the is attribute.
\ However, I had to write quite a few lines of JavaScript, though I used Axios to help me with HTTP calls and didn't manage errors.
\ In the next post, I'll implement the same features with Alpine.js.
\ The complete source code for this post can be found on GitHub:
https://github.com/ajavageek/compare-frontends?embedable=true
Go further:
\
All Rights Reserved. Copyright , Central Coast Communications, Inc.