I mit tidligere indlæg lagde jeg grunden til at bygge videre på; nu er det tid til at starte "for alvor".
<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> <!--2--> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> <!--3--> <version>0.52</version> </dependency> <dependency> <groupId>org.webjars.npm</groupId> <artifactId>vue</artifactId> <!--4--> <version>3.4.34</version> </dependency> </dependencies>
fun vue(todos: List<Todo>) = router { //1 GET("/vue") { ok().render("vue", mapOf("title" to "Vue.js", "todos" to todos)) //2-3 } }
Todo
objekter
Hvis du er vant til at udvikle API'er, er du bekendt med body()
-funktionen; det returnerer nyttelasten direkte, sandsynligvis i JSON-format. render()
sender flowet til visningsteknologien, i dette tilfælde Thymeleaf. Den accepterer to parametre:
/templates
og præfikset er .html
; i dette tilfælde forventer Thymeleaf en visning på /templates/vue.html
<script th:src="@{/webjars/axios/dist/axios.js}" src="//cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script> <!--1--> <script th:src="@{/webjars/vue/dist/vue.global.js}" src="//cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script> <!--2--> <script th:src="@{/vue.js}" src="../static/vue.js"></script> <!--3--> <script th:inline="javascript"> /*<![CDATA[*/ window.vueData = { <!--4--> title: /*[[${ title }]]*/ 'A Title', todos: /*[[${ todos }]]*/ [{ 'id': 1, 'label': 'Take out the trash', 'completed': false }] }; /*]]>*/ </script>
Som forklaret i sidste uges artikel, er en af Thymeleafs fordele, at den tillader både statisk filgengivelse og gengivelse på serversiden. For at få magien til at virke, specificerer jeg en sti på klientsiden, dvs. , src
, og en sti på serversiden, dvs. th:src
.
Todo
elementerTodo
gennemført, skal det aktivere/deaktivere den completed
attributTodo
Todo
til listen over Todo
med følgende værdier:id
: Server-side beregnet ID som det maksimale af alle andre ID'er plus 1label
: værdien af feltet Label for label
completed
: indstillet til false
Det første skridt er at bootstrap rammen. Vi har allerede oprettet referencen til vores tilpassede vue.js
-fil ovenfor.
document.addEventListener('DOMContentLoaded', () => { //1 // The next JavaScript code snippets will be inside the block }
Det næste trin er at lade Vue administrere en del af siden. På HTML-siden skal vi beslutte, hvilken del på øverste niveau Vue administrerer. Vi kan vælge en vilkårlig <div>
og ændre den senere, hvis det er nødvendigt.
<div id="app"> </div>
På JavaScript-siden opretter vi en app , der passerer CSS-vælgeren for den tidligere HTML <div>
.
Vue.createApp({}).mount('#app');
Det næste trin er at oprette en Vue -skabelon . En Vue-skabelon er en almindelig HTML <template>
der administreres af Vue. Du kan definere Vue i Javascript, men jeg foretrækker at gøre det på HTML-siden.
<template id="todos-app"> <!--1--> <h1>{{ title }}</h1> <!--2--> </template>
title
egenskaben; det mangler at blive sat op
const TodosApp = { props: ['title'], //1 template: document.getElementById('todos-app').innerHTML, }
title
egenskaben, den der bruges i HTML-skabelonen
Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');
render()
-funktionenh()
for hyperscript opretter en virtuel node ud af objektet og dets egenskabertitle
med den værdigenererede serverside
Først tilføjede jeg en ny indlejret Vue-skabelon til tabellen, der viser Todo
. For at undgå at forlænge indlægget, vil jeg undgå at beskrive det i detaljer. Hvis du er interesseret, så tag et kig på .
const TodoLine = { props: ['todo'], template: document.getElementById('todo-line').innerHTML }
<template id="todo-line"> <tr> <td>{{ todo.id }}</td> <!--1--> <td>{{ todo.label }}</td> <!--2--> <td> <label> <input type="checkbox" :checked="todo.completed" /> </label> </td> </tr> </template>
Todo
-id'etTodo
-etikettencompleted
attribut er true
Vue tillader hændelseshåndtering via @
-syntaksen.
<input type="checkbox" :checked="todo.completed" @click="check" />
Vue kalder skabelonens check()
funktion, når brugeren klikker på linjen. Vi definerer denne funktion i en 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 } }
props
-arrayet, så vi senere kan få adgang til detevent
, der udløste opkaldet
<button class="btn btn-warning" @click="cleanup">Cleanup</button>
På TodosApp
objektet tilføjer vi en funktion af samme navn:
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 } }
state
er hvor vi opbevarer modellen
I Vues semantik er Vue-modellen en indpakning omkring data, som vi ønsker skal være reaktive . Reaktiv betyder tovejsbinding mellem udsigten og modellen. Vi kan gøre en eksisterende værdi reaktiv ved at overføre den til ref()
metoden:
I Composition API er den anbefalede måde at erklære reaktiv tilstand på at bruge
ref()
-funktionen.
ref()
tager argumentet og returnerer det pakket ind i et ref-objekt med en .value-egenskab.
For at få adgang til refs i en komponents skabelon skal du deklarere og returnere dem fra en komponents
setup()
-funktion.--
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');
title
på. Det er ikke nødvendigt, da der ikke er nogen tovejsbinding - vi opdaterer ikke titlen på klientsiden, men jeg foretrækker at holde håndteringen sammenhængende på tværs af alle værdierstate
På dette tidspunkt har vi en reaktiv model på klientsiden.
<tbody> <tr is="vue:todo-line" v-for="todo in todos" :key="todo.id" :todo="todo"></tr> <!--1-2--> </tbody>
Todo
objekteris
-attributten er afgørende for at klare den måde, browseren analyserer HTML på. Se for flere detaljer Vi kan nu implementere en ny funktion: tilføje en ny Todo
fra klienten. Når vi klikker på knappen Tilføj , læser vi Label- feltets værdi, sender dataene til API'et og opdaterer modellen med svaret.
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()
funktionTodo
På HTML-siden tilføjer vi en knap og binder til create()
-funktionen. Ligeledes tilføjer vi feltet Label og binder det til modellen.
<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" v-model="label" /> </div> <div class="col-auto"> <button type="button" class="btn btn-success" @click="create">Add</button> </div> </div> </form>
Vue binder funktionen create()
til HTML-knappen. Det kalder det asynkront og opdaterer den reaktive Todo
liste med det nye element, der returneres af opkaldet. Vi gør det samme for knappen Oprydning for at fjerne afkrydsede Todo
objekter.
I dette indlæg tog jeg mine første skridt i at udvide en SSR-app med Vue. Det var ret ligetil. Det største problem, jeg stødte på, var, at Vue skulle erstatte linjeskabelonen: Jeg læste ikke dokumentationen grundigt og savnede is
-attributten.
Gå videre: