U svom prethodnom postu , postavio sam teren za izgradnju; sada je vrijeme da počnete "zaista".
<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
objekata
Ako ste navikli da razvijate API-je, upoznati ste sa funkcijom body()
; direktno vraća korisni teret, vjerovatno u JSON formatu. render()
prosljeđuje tok tehnologiji prikaza, u ovom slučaju Thymeleaf. Prihvata dva parametra:
/templates
, a prefiks je .html
; u ovom slučaju, Thymeleaf očekuje pregled na /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>
Kao što je objašnjeno u prošlosedmičnom članku, jedna od prednosti Thymeleaf-a je ta što omogućava i statičko prikazivanje datoteka i renderiranje na strani servera. Da bi magija funkcionirala, specificiram putanju na strani klijenta, tj . src
, i putanju na strani servera, tj . th:src
.
Todo
stavkeTodo
, trebalo bi postaviti/poništiti completed
atributTodo
Todo
na listu Todo
sa sledećim vrednostima:id
: izračunati ID na strani servera kao maksimum svih ostalih ID-a plus 1label
: vrijednost polja Label za label
completed
: postavljeno na false
Prvi korak je pokretanje okvira. Već smo postavili referencu za našu prilagođenu datoteku vue.js
iznad.
document.addEventListener('DOMContentLoaded', () => { //1 // The next JavaScript code snippets will be inside the block }
Sljedeći korak je da dozvolite Vueu da upravlja dijelom stranice. Što se tiče HTML-a, moramo odlučiti kojim dijelom najvišeg nivoa Vue upravlja. Možemo odabrati proizvoljan <div>
i promijeniti ga kasnije ako je potrebno.
<div id="app"> </div>
Na strani JavaScripta, kreiramo aplikaciju , prosljeđujući CSS selektor prethodnog HTML-a <div>
.
Vue.createApp({}).mount('#app');
Sljedeći korak je kreiranje Vue predloška . Vue šablon je običan HTML <template>
kojim upravlja Vue. Možete definisati Vue u Javascript-u, ali ja radije to radim na HTML stranici.
<template id="todos-app"> <!--1--> <h1>{{ title }}</h1> <!--2--> </template>
title
; ostaje da se postavi
const TodosApp = { props: ['title'], //1 template: document.getElementById('todos-app').innerHTML, }
title
, ono koje se koristi u HTML šablonu
Vue.createApp({ components: { TodosApp }, //1 render() { //2 return Vue.h(TodosApp, { //3 title: window.vueData.title, //4 }) } }).mount('#app');
render()
h()
za hiperscript kreira virtuelni čvor od objekta i njegovih svojstavatitle
vrijednošću generiranom na strani servera
Prvo sam dodao novi ugniježđeni Vue predložak za tabelu koja prikazuje Todo
. Da ne bih produžio post, izbegavaću da ga detaljno opisujem. Ako ste zainteresovani, pogledajte .
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
idTodo
completed
atribut true
Vue dozvoljava rukovanje događajima putem @
sintakse.
<input type="checkbox" :checked="todo.completed" @click="check" />
Vue poziva funkciju check()
predloška kada korisnik klikne na liniju. Ovu funkciju definiramo u parametru setup()
:
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
, kako bismo mu kasnije mogli pristupitievent
koji je pokrenuo poziv
<button class="btn btn-warning" @click="cleanup">Cleanup</button>
Na objekt TodosApp
dodajemo funkciju istog imena:
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
je mjesto gdje pohranjujemo model
U semantici Vuea, Vue model je omot oko podataka za koje želimo da budu reaktivni . Reaktivno znači dvosmjerno povezivanje pogleda i modela. Postojeću vrijednost možemo učiniti reaktivnom tako što ćemo je proslijediti metodi ref()
:
U Composition API-ju, preporučeni način za deklariranje reaktivnog stanja je korištenje funkcije
ref()
.
ref()
uzima argument i vraća ga umotanog unutar ref objekta sa svojstvom .value.
Da biste pristupili referencama u šablonu komponente, deklarirajte ih i vratite ih iz funkcije
setup()
komponente.--
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
. Nije potrebno jer nema dvosmjernog povezivanja - ne ažuriramo naslov na strani klijenta, ali radije održavam koherentno rukovanje za sve vrijednostistate
U ovom trenutku imamo reaktivni model na strani klijenta.
<tbody> <tr is="vue:todo-line" v-for="todo in todos" :key="todo.id" :todo="todo"></tr> <!--1-2--> </tbody>
Todo
objekatais
je ključan da se nosi sa načinom na koji pretraživač analizira HTML. Pogledajte za više detalja Sada možemo implementirati novu funkciju: dodati novi Todo
od klijenta. Kada kliknemo na dugme Dodaj , čitamo vrijednost polja Label , šaljemo podatke API-ju i osvježavamo model odgovorom.
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()
Todo
Na HTML strani, dodajemo dugme i vezujemo se za funkciju create()
. Isto tako, dodajemo polje Label i povezujemo ga sa modelom.
<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 vezuje funkciju create()
za HTML dugme. Poziva ga asinhrono i osvježava reaktivnu Todo
listu novom stavkom koju je vratio poziv. Isto radimo za dugme Čišćenje , da uklonimo označene Todo
objekte.
U ovom postu sam napravio prve korake u proširenju SSR aplikacije pomoću Vuea. Bilo je prilično jednostavno. Najveći problem na koji sam naišao bio je da Vue zamijeni predložak linije: nisam detaljno čitao dokumentaciju i propustio sam atribut is
.
Idi dalje: