<!-- Basic click: POSTs action=clearSession -->
<button type="button"
data-wf-ajax-action="clearSession"
data-wf-url="/api/action">
<i class="icon trash"></i> Clear Session Log
</button>
<!-- Remove a parent element from the DOM on success -->
<div id="banner" class="wf-message wf-message-info">
<div class="wf-message-body">Heads up!</div>
</div>
<button type="button"
data-wf-ajax-action="dismissBanner"
data-wf-url="/api/action"
data-wf-parent="#banner"
data-wf-complete="remove">
Dismiss
</button>
Ajax Action
Fire-and-forget AJAX POST handler for any interactive element. Add data-wf-ajax-action to a button, select, or input and the component handles the request, shows a WojoToast from the server response, and optionally mutates the DOM — all without writing JavaScript.
Simple Buttons
Add data-wf-ajax-action and data-wf-url to any button. The component POSTs the action name and shows the JSON response as a WojoToast . The button is disabled while the request is in flight. Use data-wf-parent + data-wf-complete="remove" to remove a DOM element on success.
Change Triggers
A <select> with data-wf-ajax-action defaults to change trigger mode automatically. The current value is sent in the POST body under the key set by data-wf-value-key (default value ). Use data-wf-action-from-value to use the selected value as the action name itself. Works with both native selects and wf-select .
<!-- Triggers on change — sends action + current value -->
<select
data-wf-ajax-action="setStatus"
data-wf-url="/api/action"
data-wf-value-key="status"
data-wf-params='{"id":42}'>
<option value="draft">Draft</option>
<option value="published">Published</option>
<option value="archived">Archived</option>
</select>
<!-- wf-select uses the same change event underneath -->
<select data-wf-select
data-wf-ajax-action="setSort"
data-wf-url="/api/action"
data-wf-value-key="sort">
<option value="name">Name</option>
<option value="date">Date</option>
</select>
<!-- Use selected option value as the action name -->
<select data-wf-ajax-action="placeholder"
data-wf-action-from-value
data-wf-url="/api/action">
<option value="publish">Publish</option>
<option value="archive">Archive</option>
</select>
Input Validation
Use data-wf-value-source="#selector" on a click-triggered button to read the value from a separate input field and include it in the POST body. This is the pattern for promo codes, invite keys, coupon fields, or any “type something, press a button” validation flow. The value is sent under the key specified by data-wf-value-key (default: value ). The standard JSON response drives the toast automatically.
<!-- Button reads value from the input and POSTs: action=validatePromo & promo_code=SAVE20 -->
<div class="wf-input-group">
<input type="text" id="promo-input" class="wf-input" placeholder="Promo code">
<button type="button"
data-wf-ajax-action="validatePromo"
data-wf-url="/api/action"
data-wf-value-source="#promo-input"
data-wf-value-key="promo_code">Apply</button>
</div>
<!-- Works with any input — API key, invite code, license key… -->
<input type="text" id="license-key" class="wf-input" placeholder="xxxx-xxxx-xxxx">
<button type="button"
data-wf-ajax-action="activateLicense"
data-wf-url="/api/license"
data-wf-value-source="#license-key"
data-wf-value-key="key"
data-wf-parent="#license-result"
data-wf-complete="innerHTML">Activate</button>
<div id="license-result"></div>
// Valid code — toast + optional DOM update
{ "type": "positive", "message": "20% discount applied!" }
// Invalid code — toast only
{ "type": "negative", "message": "Invalid or expired promo code." }
// Valid + inject result HTML into data-wf-parent
{ "type": "positive", "message": "Code accepted.", "html": "<p>Discount: −$12.00</p>" }
DOM Mutations
Use data-wf-parent to target a DOM element and data-wf-complete to choose what happens to it on success. The server can also override the strategy by returning a complete key in the JSON response.
<div id="notification-1">
<span>New comment on your post</span>
<!-- Replace inner HTML with server-returned HTML -->
<button type="button"
data-wf-ajax-action="markRead"
data-wf-url="/api/action"
data-wf-params='{"id":1}'
data-wf-parent="#notification-1"
data-wf-complete="innerHTML">
Mark read
</button>
<!-- Remove the parent element entirely -->
<button type="button"
data-wf-ajax-action="removeItem"
data-wf-url="/api/action"
data-wf-params='{"id":1}'
data-wf-parent="#notification-1"
data-wf-complete="remove">
<i class="icon cancel"></i>
</button>
</div>
Mutation strategies
Values for data-wf-complete and their effect on the data-wf-parent element. The server response can override the strategy by including a complete key.
| Value | Effect on data-wf-parent |
|---|---|
| append | Insert response.html as last child |
| prepend | Insert response.html as first child |
| replace / replaceWith / update | Replace the element entirely (outerHTML) |
| innerHTML | Replace inner content, keep the element |
| insert | Append or prepend based on data-wf-mode |
| remove | Remove the element from the DOM |
| reload | Reload the page after 800 ms |
| redirect | Navigate to the URL specified by data-wf-redirect-url |
| highlight | Add .wf-highlight CSS class to the element |
Extra Params & Child Updates
Use data-wf-params to merge a JSON object into the POST body alongside the action name. Use data-wf-children to update additional DOM elements from keyed values in the server response — useful for counters, labels, or badges that reflect the new server state.
<!-- data-wf-params: extra JSON merged into POST body -->
<button type="button"
data-wf-ajax-action="sortItems"
data-wf-url="/api/action"
data-wf-params='{"ids":"3,1,4,1,5"}'>
Save order
</button>
<!-- data-wf-children: update counter badge from response field -->
<button type="button"
data-wf-ajax-action="toggleFavorite"
data-wf-url="/api/action"
data-wf-params='{"id":42}'
data-wf-children='{"#fav-count":{"html":"html"}}'>
<i class="icon heart"></i> Toggle Favourite
</button>
<span id="fav-count">—</span>
<!-- Server returns: { "type":"positive", "html": 37 } -->
<!-- → #fav-count innerHTML becomes "37" -->
Input Filter
A text input with data-wf-filter-target automatically switches to filter trigger mode. Matching runs client-side against text from data-wf-filterable attributes (or the item's textContent as fallback) — no server round-trip for each keystroke. Accent characters are normalized before comparison.
| Component | Category |
|---|---|
| Accordion | Data Display |
| Buttons | Forms |
| Carousel | Media |
| Dropdown | Navigation |
| Toast | Feedback |
| Tooltip | Feedback |
<input type="search"
data-wf-ajax-action="filterRows"
data-wf-filter-target="#filterTable"
data-wf-filter-item="tr"
data-wf-filter-text-selector="[data-wf-filterable]"
data-wf-filter-min-chars="2">
<table>
<tbody id="filterTable">
<tr>
<td data-wf-filterable="Accordion">Accordion</td>
<td>Data Display</td>
</tr>
<tr>
<td data-wf-filterable="Buttons">Buttons</td>
<td>Forms</td>
</tr>
</tbody>
</table>
Live Search
Add data-wf-live-url and data-wf-live-action to the same filter input to also send a debounced server request. Results are rendered in an absolutely positioned panel below the input. The local filter and live search run simultaneously — the table updates client-side instantly while the server panel populates asynchronously.
| Label | Category |
|---|---|
| Actions | Forms |
| Archive | State |
| Publish | State |
<!-- Combine local filter + live search on the same input -->
<input type="search"
data-wf-ajax-action="filterRows"
data-wf-filter-target="#filterTable"
data-wf-filter-min-chars="2"
data-wf-live-url="/api/action"
data-wf-live-action="liveSearch"
data-wf-value-key="query">
Server response for live search
Return html as a pre-built HTML string, or a results array of strings for an auto-generated wf-list .
// Pre-built HTML string (full control)
{ "type": "info", "html": "<ul class='wf-list'><li>Actions</li></ul>" }
// Results array — auto-wrapped in a wf-list
{ "type": "info", "results": ["Actions", "Archive", "Publish"] }
// Empty — hides the live panel
{ "type": "info", "html": "" }
File Download
If the server responds with a Content-Disposition: attachment header the component automatically triggers a browser file download instead of parsing JSON. No extra attributes are needed — the response header drives the behavior. The filename is read directly from the header (RFC 5987 filename*=UTF-8''… form takes precedence over the plain filename= form).
<!-- No special attribute needed — the Content-Disposition header drives download -->
<button type="button"
data-wf-ajax-action="exportCsv"
data-wf-url="/api/action"
data-wf-params='{"type":"users"}'>
<i class="icon download"></i> Export Users CSV
</button>
Server-side (PHP)
Set Content-Disposition: attachment and stream any content type. The component reads the filename from the header automatically.
case 'exportCsv':
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="users-export.csv"');
header('Cache-Control: no-cache, no-store, must-revalidate');
$out = fopen('php://output', 'w');
fputcsv($out, ['ID', 'Name', 'Email', 'Role', 'Status']);
foreach ($users as $row) {
fputcsv($out, $row);
}
fclose($out);
exit; // do not echo JSON
Data Attributes
| Attribute | Default | Description |
|---|---|---|
| data-wf-ajax-action="name" | — | Required. Marks an element as a trigger. Value sent as action in the POST body. |
| data-wf-url="/path" | — | Required. POST endpoint URL. |
| data-wf-method="GET" | — | Required for GET requests. HTTP method. Default: POST. |
| data-wf-trigger | click | Trigger mode. Accepts click · change · input · filter · <select> defaults to change ; text inputs with data-wf-filter-target default to filter . |
| data-wf-params='{"k":"v"}' | — | JSON object merged into the POST body. |
| data-wf-value-key | value | POST body key used for the current control value on change-triggered elements. |
| data-wf-action-from-value | — | Use the current control value as action in the POST body instead of the attribute value. |
| data-wf-send-value="false" | true | Disable auto-sending the current control value for change-triggered elements. |
| data-wf-value-source="#sel" | — | CSS selector of an input or textarea to read the value from when a button is clicked. Lets a button send data from a separate field (e.g. promo code input + validate button). Pair with data-wf-value-key to control the POST body key (default: value ). |
| data-wf-value-key | value | POST body key used when sending a value via data-wf-value-source. |
| data-wf-parent="#sel" | — | CSS selector of the DOM element to mutate on success. |
| data-wf-complete | — | DOM mutation strategy applied to data-wf-parent . See the Mutation Strategies table above. |
| data-wf-mode | append | Insert direction for the insert strategy. Accepts append or prepend . |
| data-wf-children='{"#el":{"k":"html"}}' | — | Update additional DOM elements from keyed server response fields. html sets innerHTML; any other value sets the element's value property. |
| data-wf-csrf-header | X-CSRF-Token | CSRF header name. Token read from <meta name="csrf-token"> . Set to an empty string to disable. |
| data-wf-loader-class | — | Optional CSS class applied to the loader target before sending and removed on completion. |
| data-wf-loader-target | — | Element that receives the loader class. Accepts a CSS selector, self , or parent . |
| data-wf-filter-target="#sel" | — | Container element whose children are filtered in filter mode. |
| data-wf-filter-item | children | Selector for items inside the filter container (e.g. tr , li , .row ). Defaults to direct children. |
| data-wf-filter-text-selector | [data-wf-filterable] | Selector is used to collect searchable text from each item. Falls back to the item's textContent . |
| data-wf-filter-min-chars | 0 | Minimum number of characters before filtering is applied. |
| data-wf-filter-hide-class | wf-hidden | CSS class toggled on non-matching items. Also sets the hidden attribute. |
| data-wf-live-url="/path" | — | Endpoint for live search requests fired on input. Results panel appears below the input field. |
| data-wf-live-action | — | Action name sent in the live search POST body. |
Events & API
| Event | Cancelable | detail | Description |
|---|---|---|---|
| wf:ajax-action-before | Yes | { url, body, trigger } | Fires before the fetch request. Call e.preventDefault() to cancel. |
| wf:ajax-action-success | No | { data, trigger } | Fires on a 2xx response after the toast and DOM mutation are applied. |
| wf:ajax-action-error | No | { error, trigger } | Fires on a network error or non-2xx response. |
| wf:ajax-action-filter | No | { query, visible, total, trigger, target } | Fires after each local filter passes. Use visible and total to update a result counter. |
| wf:ajax-action-download | No | { filename, trigger } | Fires after a file download is triggered from an attachment response. |
| wf:ajax-action-live | No | { data, query, trigger, panel } | Fires after live search results are rendered into the panel. |
Event listeners
All events dispatch on the trigger element and bubble to document .
import './wf-ajax-action.js'
// Cancel a request before it is sent
document.addEventListener('wf:ajax-action-before', e => {
const { url, body, trigger } = e.detail
if (!confirm('Are you sure?')) e.preventDefault()
})
// Inspect the server response
document.addEventListener('wf:ajax-action-success', e => {
console.log('response', e.detail.data)
})
// Handle fetch / HTTP errors
document.addEventListener('wf:ajax-action-error', e => {
console.error('error', e.detail.error)
})
// Update a "N results" counter after each filter pass
document.addEventListener('wf:ajax-action-filter', e => {
const { visible, total, query } = e.detail
document.getElementById('result-count').textContent =
query ? `${visible} of ${total}` : total
})
// Act on live search results
document.addEventListener('wf:ajax-action-live', e => {
const { query, data, panel } = e.detail
console.log('live results for', query, data)
})