Reference

HTTP client

Two layers: the directives, which cover most cases without a line of JavaScript, and the full client in V.http. The guide on fetching data teaches you to use it; this page lists everything.

Directive triggers

Each verb fires on the element's natural trigger:

ElementTrigger
<form>submit
<input>, <select>, <textarea>change, or click on buttons
anything elseclick

v-trigger replaces the default:

<div v-get="/api/status" v-trigger="load">...</div>
<div v-get="/api/banner" v-trigger="visible">...</div>
<input v-get="/api/search" v-trigger="keyup" v-debounce="300">
<div v-get="/api/feed" v-trigger="visible.repeat">...</div>
<button v-get="/api/x" v-trigger="click.once">Only once</button>

Special triggers: load and ready fire on mount; visible and revealed fire when the element gets close to the screen. The modifiers accepted in the text are once and repeat.

v-target and v-swap

ModeWhat it does
innerHTMLReplaces the content of the target. Default
outerHTML, replaceReplaces the whole target
textContentWrites it as plain text
beforebeginInserts before the target
afterbegin, prependInserts as the first child
beforeend, appendInserts as the last child
afterendInserts after the target
deleteRemoves the target
noneLeaves the DOM alone

The inserted HTML is walked by Voodoo, so it can bring new directives with it.

Handling the response

AttributeWhat it does
v-as="name"Stores the response in the scope instead of writing to the DOM
v-json-path="data.items"Cuts out a piece of the response first
v-template="#row"Renders each item with a <template> from the page

Inside the template you have item (the whole object), index and every key of the object as a variable of its own.

v-search

AttributeDefault
v-paramthe name of the input, or q
v-debounce300 ms
v-min-length0

v-resource

FieldWhat it is
dataThe body of the response, or null
loadingtrue while the request runs
error{ name, message }, or null
loadedtrue after the first success
reload()Runs the request again
set(value)Replaces the data locally, useful for optimistic updates

The syntax is name: url. With no name, the resource is called resource. It accepts v-method, v-params, v-cache, v-retry, v-timeout, v-json-path, v-poll and v-manual, which does not fetch on mount and waits for reload().

Loading, notifications and callbacks

AttributeWhat it does
v-loadingSelector of an element that stays hidden until the request starts
v-loading-classClass applied during the request. Default v-loading
v-disable-loadingDisables the button while the request runs
v-toast-successSuccess notification
v-toast-errorError notification. Without it, the server's error becomes the message
v-on-successExpression run on success. Has $el, data and response
v-on-errorExpression run on error. Has $el, error and message
v-on-completeExpression run every time, at the end
v-redirectNavigates to the URL after success
v-scroll-toScrolls smoothly to the selector after success

During the request the element also gets aria-busy="true".

Network

AttributeWhat it does
v-paramsObject that becomes the query string
v-bodyExpression that becomes the body of the request
v-headersObject of headers
v-cacheKeeps the response for a while. GET only. Accepts 30s, 5m
v-retryExtra attempts on network failure and 5xx errors
v-timeoutTime until it aborts
v-offline-queueStores the request while the browser is offline and sends it later

Events

<div v-get="/api/x"
     @voodoo:before-request="console.log('going')"
     @voodoo:success="console.log($detail.data)"
     @voodoo:error="console.log($detail.message)"
     @voodoo:complete="console.log('done')">
</div>
Automatic cancellation

A new request from the same element cancels the previous one if it is still pending. That solves the classic search-as-you-type race, with no configuration at all.

The V.http client

const users   = await V.http.get('/api/users');
const created = await V.http.post('/api/users', { name: 'Ann' });
await V.http.put('/api/users/1', { name: 'Bea' });
await V.http.patch('/api/users/1', { active: false });
await V.http.delete('/api/users/1');
await V.http.head('/api/users');

The shortcuts return the data only. For the full response, use request:

const response = await V.http.request({ url: '/api/users', method: 'GET' });
response.data;
response.status;
response.headers.get('x-total');
response.ok;
response.raw;     // the original Response

Options

OptionWhat it does
paramsObject that becomes the query string. Null and empty values are dropped
headersHeaders of the request
timeoutMilliseconds until it aborts. Default 30000. 0 turns it off
retryExtra attempts on network failure and 5xx. The wait doubles on each round
retryDelayInitial wait between attempts. Default 500 ms
cacheMilliseconds of cache. GET only
signalAbortSignal to cancel
credentialsDefault same-origin
responseTypeauto, json, text, blob, arrayBuffer, formData
offlineQueueStores the request while offline and sends it again when it comes back

Body

Objects become JSON with the right header. FormData, Blob, URLSearchParams, ArrayBuffer and text pass through as they are.

Errors

try {
  await V.http.get('/api/x');
} catch (err) {
  if (err instanceof V.HttpError) {
    err.status;            // 0 when there was no response
    err.response?.data;    // the body of the error
    err.isNetworkError;    // true on network failure, timeout or cancellation
    err.config;            // the configuration used
  }
}

4xx errors are not retried. 5xx errors and network failures respect retry.

Interceptors

const off = V.http.interceptors.request.use((config) => {
  config.headers = { ...config.headers, 'X-Tenant': currentTenant() };
  return config;
});

V.http.interceptors.response.use((response) => response);

V.http.interceptors.error.use((error) => {
  if (error.status === 401) location.assign('/login');
});

off();  // removes the request interceptor

Interceptors can be asynchronous. The request one has to return the configuration, and the response one has to return the response.

Authentication and default headers

V.http.setBaseURL('https://api.example.com');
V.http.setToken('my-jwt');                   // Authorization: Bearer my-jwt
V.http.setToken('key', 'Token');             // Authorization: Token key
V.http.setToken(null);                       // removes it
V.http.setHeader('Accept-Language', 'en');
V.http.setHeader('Accept-Language', null);   // removes it

Every request already carries X-Requested-With: XMLHttpRequest. On methods that write, the CSRF token is read from <meta name="csrf-token"> and sent in X-CSRF-TOKEN. The names are configurable in V.http.defaults.csrfMeta and V.http.defaults.csrfHeader.

Upload with progress

const data = new FormData();
data.append('file', input.files[0]);

await V.http.upload('/api/files', data, {
  onProgress: (percent, sent, total) => {
    bar.style.width = percent + '%';
  }
});

It uses XMLHttpRequest underneath, because that is the only thing that reports real upload progress.

Server-Sent Events and streaming

const source = V.http.sse('/api/events', {
  message: (data) => console.log(data),
  error: (e) => console.warn('connection dropped', e)
});
source.close();

await V.http.stream('/api/logs', (line) => console.log(JSON.parse(line)));

In SSE, JSON is parsed automatically when possible. stream delivers line by line, which works well for NDJSON and long responses.

Cache

await V.http.get('/api/config', { cache: 300000 });
V.http.clearCache();                // clears everything
V.http.clearCache('/api/products'); // clears whatever contains the text
V.http.clearCache(/^GET \/api\//);  // clears by pattern

Offline queue

await V.http.post('/api/orders', order, { offlineQueue: true });
await V.http.flushOfflineQueue();   // forces the resend

Requests with offlineQueue: true fired while the browser is offline are stored in localStorage and sent again when the connection comes back. The immediate response is synthetic, with status: 0 and statusText: 'offline-queued', handle that case in the interface, or the user will think the operation is already finished.