Forms Ajax Action

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.

Heads up! Click Dismiss Banner above — this element is removed from the DOM after the server responds.
HTML
<!-- 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>

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 .

HTML
<!-- 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.

HTML
<!-- 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>
JSON Response
// 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: &minus;$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.

New comment on your post — Getting started with Wojo Framework
Your export is ready — users-2026-04.csv
New member registered — jane@example.com
HTML
<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.

Total favourites:
HTML
<!-- 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
HTML
<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
HTML
<!-- 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 .

JSON
// 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).

HTML
<!-- 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.

PHP
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 .

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 .

JS
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)
})