Augmenting the Client With Vue.js

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.
- Set the ID for easy binding
- Use the
title
property; it remains to be set up
On the JavaScript side, we must create the managing code.
const TodosApp = {
props: ['title'], //1
template: document.getElementById('todos-app').innerHTML,
}
- Declare the
title
property, the one used in the HTML template
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');
- Configure the component
- Vue expects the
render()
function h()
for hyperscript creates a virtual node out of the object and its properties- Initialize the
title
property with the value generated server-side
At this point, Vue displays the title.
Basic interactions
At 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
}
{{ todo.id }}
{{ todo.label }}
- Display the
Todo
id - Display the
Todo
label - Check the box if its
completed
attribute istrue
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
}
}
- Accept the
props
array, so we can later access it - Vue passes the
event
that triggered the call - Axios is a JavaScript lib that simplifies HTTP calls
- The server-side must provide an API; it’s outside the scope of this post, but feel free to check the source code.
- JSON payload
- We return all defined functions to make them accessible from HTML
Client-side model
In the previous section, I made two mistakes:
- I didn’t manage any local model
- I didn’t use the HTTP response’s call method
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
}
}
- As above
- Axios offers automated JSON conversion of the HTTP call
state
is where we store the model
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');
- Get the data set in the HTML page, via Thymeleaf, as explained above
- We change the way we set the
title
. It’s not necessary since there’s no two-way binding – we don’t update the title client-side, but I prefer to keep the handling coherent across all values - Return the refs, as per Vue’s expectations
- Look, ma, I’m using the JavaScript spread operator
- Configure the object’s attributed from the
state
At this point, we have a reactive client-side model.
On the HTML side, we use the relevant Vue attributes:
- Loop over the list of
Todo
objects - The
is
attribute is crucial to cope with the way the browser parses HTML. See Vue documentation for more details
I’ve described the corresponding template above.
Updating the model
We 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 }
}
}
- Create a reactive wrapper around the title whose scope is limited to the function
- The
create()
function proper - Append the new JSON object returned by the API call to the list of
Todo
- Reset the field’s value
- Replace the whole list when deleting; the mechanism is the same
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.
Conclusion
In 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:
Go further: