Data and forms
Fetching data from a server
Fetch data, submit a form, delete a record. Normally that turns into fetch,
then, HTML assembled by hand and error handling repeated on every screen. In Voodoo
it is one attribute.
The blocks here do not run live the way they do on the other pages: they would need a real server on the other side. The URLs are made up — swap them for addresses on your own API.
The five verbs
<button v-get="/api/users" v-target="#list">Load</button>
<div id="list"></div>
There are v-get, v-post, v-put, v-patch and
v-delete. Each element has a natural trigger: a <form> fires on
submit, a field fires on change, and everything else fires on
click.
The URL can be fixed or an expression:
<button v-delete="'/api/users/' + u.id"
v-confirm="Delete user?"
v-toast-success="User deleted!">Delete</button>
What happens to the response
If the server returns HTML, it goes straight into the target. If it returns JSON, Voodoo turns it into readable HTML: a list of objects becomes a table, an object becomes a definition list, and every value is escaped along the way.
If you would rather control the shape, point at a <template> on the page
itself:
<button v-get="/api/users" v-target="#list" v-template="#row">Load</button>
<template id="row">
<li><strong>{ name }</strong> <small>{ email }</small></li>
</template>
<ul id="list"></ul>
And if you want no HTML at all, only the data in state, v-as writes into the
variable and the rest of the page reacts the way it would to any other change:
<div v-data="{ users: [] }">
<button v-get="/api/users" v-as="users">Load</button>
<ul>
<li v-for="u in users" :key="u.id">{ u.name }</li>
</ul>
</div>
Where the result is placed
v-target is the selector of the element that receives it. Without it, the target is
the element itself. v-swap decides how:
<button v-get="/api/comments?page=2" v-target="#comments" v-swap="append">
Load more
</button>
Accepted modes: innerHTML (the default), outerHTML,
replace, textContent, beforebegin,
afterbegin, beforeend, afterend, append,
prepend, delete and none.
Other triggers
<div v-get="/api/status" v-poll="5s" v-target="#status"></div>
<div v-load-visible="/api/comments"></div>
<input v-search="/api/products" v-param="q" v-target="#results" v-debounce="300">
<div v-get="/api/banner" v-trigger="visible"></div>
v-pollrepeats at a fixed interval and pauses when the tab is not visible.v-load-visiblefetches when the element gets close to the viewport.-
v-searchsearches while the user types, withv-paramfor the parameter name andv-min-lengthfor the minimum number of characters.
Loading, error, and what to do afterwards
<button v-get="/api/report"
v-target="#output"
v-loading="#spinner"
v-disable-loading
v-on-success="$toast.success('Done!')"
v-on-error="console.warn($detail)">
Generate
</button>
<div id="spinner">Generating the report...</div>
While the request is in flight the element gets the class v-loading and the
attribute aria-busy="true" — both give CSS and screen readers a hook with no extra
code. A new request from the same element cancels the previous one if it is still pending, which
settles the repeated click on its own.
v-resource: data, loading and error in one line
For when you want the request state available in the HTML, and not only the result:
<div v-resource="products: /api/products">
<p v-if="products.loading">Loading...</p>
<p v-else-if="products.error">Failed: { products.error.message }</p>
<ul v-else>
<li v-for="p in products.data" :key="p.id">{ p.name }</li>
</ul>
<button @click="products.reload()">Refresh</button>
</div>
The syntax is name: url. The object it creates has data,
loading, error, loaded, reload() and
set(). It accepts v-method, v-params,
v-cache, v-retry, v-timeout, v-json-path,
v-poll and v-manual, which holds the first fetch back until you call
reload().
Supporting attributes
| Attribute | What it is for |
|---|---|
v-body | The request body, as an object or an expression |
v-params | Query parameters |
v-headers | Extra headers |
v-cache | Keeps the response for the time given |
v-retry | Tries again when it fails |
v-timeout | Gives up after the time given |
v-json-path | Takes one piece out of the response, such as data.items |
v-redirect | Navigates to a URL when it succeeds |
v-scroll-to | Scrolls to an element after the swap |
v-loading-class | The class applied during the request |
v-on-complete | An expression run at the end, success or not |
v-offline-queue | Holds the request while there is no network and sends it when the connection is back |
The same thing, in JavaScript
const users = await V.http.get('/api/users', { params: { page: 2 }, cache: 60000 });
await V.http.post('/api/users', { name: 'Ann' });
await V.http.upload('/api/files', formData, { onProgress: (p) => console.log(p) });
V.http.setBaseURL('https://api.example.com');
V.http.setToken('my-jwt');
V.http.interceptors.response.use((r) => r);
The full surface — interceptors, cache, cancellation, HttpError and upload with
progress — is in the HTTP client reference.
Methods that write carry the token from <meta name="csrf-token"> in the
X-CSRF-TOKEN header automatically. If your server uses another header name, adjust
it with a request interceptor.
v-confirm on the same element as an HTTP verb asks twice. Use
v-confirm alongside v-click, or ask for the confirmation inside the
expression itself with $confirm(...).