Attract
The @attract attribute intercepts a <form> submit and sends a fetch request instead. A cached <template> can be used to render an optimistic preview while the server processes the request.
import Attractive from "attractivejs";
import { attract } from "attractivejs/attract";
Attractive.activate({ extendWith: [attract] });
@attract attribute
@attract on a <form> (or a container that propagates to child forms) intercepts submit and issues a fetch. The form’s action and method are used as-is.
<form action="/messages" method="post" @attract>
<input name="author" />
<input name="body" />
<button>Submit</button>
</form>
On a container, @attract propagates to child <form> elements:
<div @attract>
<form action="/messages" method="post">
<input name="body" />
<button>Submit</button>
</form>
</div>
| Data attribute | Purpose |
|---|---|
data-attract-template |
ID of <template> to clone for optimistic render |
data-attract-target |
Target element ID or CSS selector |
data-attract-position |
Insertion position: append (default), prepend, replace, before, after
|
Feedback states on the form element:
| State | Attribute |
|---|---|
| Busy | data-attract-busy |
| Success | data-attract-success="true" |
| Error | data-attract-error="true" |
Mapping response data to elements
Place attract-field="key" on elements inside an attract <template>. When the template renders with JSON data, the matching key’s value is applied to the element based on its type:
| Element | Behavior |
|---|---|
<input type="checkbox"> / <input type="radio">
|
element.checked = !!value |
<input> / <textarea> / <select>
|
element.value = value |
<option> |
element.selected = !!value |
| Everything else (div, span, h1, p, etc.) | element.textContent = value |
<template id="message">
<div class="message">
<strong attract-field="author"></strong>
<p attract-field="body"></p>
</div>
</template>
JSON response format
The response is an action hash or an actions array. Each action describes a DOM operation.
data is an object (single item) or an array (multiple items, each rendered independently):
{
"action": "prepend",
"target": "messages",
"template": "message",
"data": { "author": "Cameron", "body": "Hello" }
// "data": [
// { "author": "Cameron", "body": "Hello" },
// { "author": "Kendall", "body": "Hi" }
// ]
}
Actions without data work too. The target field specifies the element:
{ "action": "remove", "target": "spinner" }
{ "action": "setAttribute#id=42", "target": "new-message" }
Multiple actions in one response:
{
"actions": [
{ "action": "replace", "target": "new-message", "template": "message", "data": { "author": "Cameron", "body": "Hello!" } },
{ "action": "remove", "target": "spinner" },
{ "action": "setAttribute#id=42", "target": "new-message" }
]
}
Actions execute sequentially in the order they appear.
Any registered action (built-in, custom or from an addon) can be used.
For validation errors, the server returns an errors object. Each key maps to a form field by name using the native Validity API:
{
"errors": { "body": "Cannot be empty" }
}
Attract request headers
Every attract request sends two headers:
-
Attract: true— identifies the request as coming from an attract client -
Accept: application/vnd.attract+json— signals the expected JSON response format
The client validates the response Content-Type contains json before processing. Non-JSON responses are treated as errors.
The headers are sent on three request paths:
-
Form submissions via
@attract -
Generic request actions (
post,patch,put) -
GET request action (
get)
Attract added actions
These actions work from both @action="…" in HTML and from JSON responses:
| Action | What it does |
|---|---|
append |
Clone template, append to target |
prepend |
Clone template, prepend to target |
replace |
Clone template, replace target element |
before |
Clone template, insert before target |
after |
Clone template, insert after target |
remove |
Remove target element(s) |
The actions can be imported individually and used without the full addon. Register the ones you need via addActions:
import Attractive from "attractivejs";
import { append, remove } from "attractivejs/attract/actions";
Attractive.activate({ addActions: { append, remove } });
Or register all of them via the default export:
import actions from "attractivejs/attract/actions";
Attractive.activate({ addActions: actions });
<ul id="messages"></ul>
<button @action="append#message" @target="messages">Add</button>
<template id="message">
<div class="message">
<strong>Cameron</strong>
<p>Hello</p>
</div>
</template>