Forms Edit in Place

Edit in Place

Turns any element into an inline editor via wf-edit-in-place.js . Click (or double-click) any marked cell to open a floating input or textarea that overlays the cell exactly, matching its size. Confirms with Enter, cancels with Escape. Changes can be POSTed to a server endpoint with an optimistic update and automatic rollback on error. Tab and arrow keys navigate between cells; a cancelable validation event lets you block saves client-side.

Quick Start

Import the module once. All elements with data-wf-edit-in-place are enhanced automatically on DOMContentLoaded . Mark individual cells with data-wf-editable . Without a data-wf-url the component fires wf:edit-change instead of posting to a server.

Name Role Department
Alice Martin Designer Product
Bob Chen Engineer Platform
Carol White Manager Operations
HTML
<!-- Auto-init -->
<table class="wf-table wf-table-bordered" data-wf-edit-in-place>
  <tbody>
    <tr>
      <td data-wf-editable>Alice Martin</td>
      <td data-wf-editable>Designer</td>
    </tr>
  </tbody>
</table>

<!-- Manual init -->
<table id="my-table"></table>
<script type="module">
  import { WojoEditInPlace } from './wf-edit-in-place.js'
  const eip = new WojoEditInPlace(document.getElementById('my-table')).init()
</script>

AJAX Save

Add data-wf-url to enable server persistence. When the user confirms a change, the cell is updated immediately (optimistic); then the value is POSTed to the endpoint. A non-2xx response causes the cell to roll back to its original value, and the editor shows an error state.

Use data-wf-extra on each cell to merge extra fields (like a row ID) into the request body. The server may return a data.value property to override the canonical display text after save.

Alice Martin Designer
Jeff Dalton Developer
HTML
<!-- Container-level URL: all cells POST here -->
<table class="wf-table" data-wf-edit-in-place
       data-wf-url="/api/update-cell">
  <tbody>
    <tr>
      <td data-wf-editable
          data-wf-field="name"
          data-wf-extra='{"id": 1}'>Alice Martin</td>
      <td data-wf-editable
          data-wf-field="role"
          data-wf-extra='{"id": 2}'>Designer</td>
    </tr>
  </tbody>
</table>

<!-- Per-cell URL override -->
<td data-wf-editable
    data-wf-url="/api/users/42/name"
    data-wf-field="value">Alice Martin</td>
PHP
// Minimal success
echo json_encode(['success' => true]);

// Override the displayed value after save
echo json_encode(['success' => true, 'data' => ['value' => $sanitizedName]]);

// Signal an error - cell rolls back automatically
http_response_code(422);
echo json_encode(['success' => false, 'message' => 'Name is required']);

Double-click Trigger

Set data-wf-trigger="dblclick" on the container to require a double click to activate the editor. Useful when single-click is already used for row selection or navigation.

Product SKU Price
Wireless Headphones WH-1000XM5 $349.00
USB-C Hub HB-C7PRO $79.99
Laptop Stand LS-ALUM-SLV $59.00
HTML
<table class="wf-table" data-wf-edit-in-place
       data-wf-trigger="dblclick">
  <tbody>
    <tr>
      <td data-wf-editable>Wireless Headphones</td>
      <td data-wf-editable>$349.00</td>
    </tr>
  </tbody>
</table>

Non-table Elements

The container does not have to be a table. Any element can host editable children - spans, paragraphs, list items, card fields, or definition terms. The floating editor matches the bounding box of whichever element is activated.

Alice Martin Lead Designer Berlin, Germany
HTML
<div data-wf-edit-in-place class="wf-table-list">
    <label>First name:</label>
    <span data-wf-editable>Alice</span>
</div>

Multiline

Control the editor type with data-wf-multiline on the container: auto (default) switches to a textarea when cell content wraps or overflows; true always uses a textarea; false always uses a single-line input. Individual cells can override the container default with their own data-wf-multiline . The textarea row height is set with data-wf-rows (default 3).

Title Description
Onboarding Guide Step-by-step walkthrough for new team members covering tooling, process, and first-week goals.
Release Notes Changelog for v2.4.0 including performance improvements and three bug fixes in the payment module.
HTML
<!-- Always textarea, 4 rows -->
<table class="wf-table" data-wf-edit-in-place
       data-wf-multiline="true"
       data-wf-rows="4">
  <tbody>
    <tr>
      <!-- Per-cell override: force single-line input -->
      <td data-wf-editable data-wf-multiline="false">Onboarding Guide</td>
      <!-- Inherits textarea from container -->
      <td data-wf-editable>Long description...</td>
    </tr>
  </tbody>
</table>

Validation

The wf:edit-validate event fires each time the editor value changes. Call e.preventDefault() in the handler to put the editor into an error state and block the save. The next keystroke that passes without calling preventDefault() clears the error automatically.

JS
const table = document.querySelector('#my-table')

// Block save on empty value
table.addEventListener('wf:edit-validate', (e) => {
  if (!e.detail.value.trim()) {
    e.preventDefault()
  }
})

// Block save when below minimum length
table.addEventListener('wf:edit-validate', (e) => {
  if (e.detail.value.trim().length < 3) {
    e.preventDefault()
  }
})

Data Attributes

Container attributes

Place these on the element marked with data-wf-edit-in-place .

Attribute Values Description
data-wf-edit-in-place - Required. Marks the container; auto-initializes on DOMContentLoaded.
data-wf-url URL string AJAX endpoint for all cells. Omit for client-side-only mode.
data-wf-field string POST body key for the new value. Default: "value".
data-wf-silent string Whether to suppress the success response from the server.
data-wf-method POST · PUT · PATCH HTTP method used for AJAX saves. Default: POST.
data-wf-trigger click · dblclick Activation event. Default: "click".
data-wf-multiline auto · true · false Editor type. auto detects per-cell wrapping. Default: "auto".
data-wf-rows integer Textarea row height when multiline is active. Default: 3.
data-wf-navigate true · false Enable Tab and arrow-key navigation between cells. Default: true.
data-wf-csrf-header string CSRF header name. Token is read from <meta name="csrf-token">. Default: "X-CSRF-Token".
Show More

Cell attributes

Place these on individual editable elements. Cell-level values override the container.

Attribute Values Description
data-wf-editable - Required. Marks the element as an edit target.
data-wf-url URL string Per-cell AJAX URL overrides the container value.
data-wf-field string Per-cell POST field name overrides the container value.
data-wf-silent string Whether to suppress the success response from the server.
data-wf-extra JSON string Extra fields merged into the POST body. E.g. {"id": 42}.
data-wf-multiline true · false Per-cell editor type override.

Events & API

All events bubble from the container element. The component instance is stored on el._wfEditInPlace .

Events

Event Cancelable detail Description
wf:edit-start No { cell, value } Cell enters edit mode.
wf:edit-validate Yes { cell, value } Fires on each value change. preventDefault() blocks the save.
wf:edit-change No { cell, value, oldValue } Value confirmed, no AJAX URL configured.
wf:edit-save No { cell, value, oldValue, data } AJAX save succeeded. data is the parsed JSON response.
wf:edit-error No { cell, error, value, oldValue } AJAX save failed. Cell rolls back to oldValue.
wf:edit-cancel No { cell } Edit canceled via Escape or .cancel().

Methods

Method Description
eip.edit(cell) Open the editor on a specific cell element programmatically.
eip.cancel() Cancel the active edit without saving. Cell text is restored.
eip.destroy() Remove all event listeners and detach the floating editor element.
JS
const table = document.querySelector('#my-table')

table.addEventListener('wf:edit-start',  (e) => console.log('editing',   e.detail.cell))
table.addEventListener('wf:edit-change', (e) => console.log('changed',   e.detail.value))
table.addEventListener('wf:edit-save',   (e) => console.log('saved',     e.detail.data))
table.addEventListener('wf:edit-error',  (e) => console.error('error',   e.detail.error))
table.addEventListener('wf:edit-cancel', (e) => console.log('cancelled'))

// Programmatic control
const eip = table._wfEditInPlace
const firstCell = table.querySelector('[data-wf-editable]')

eip.edit(firstCell)  // open editor on a specific cell
eip.cancel()         // cancel without saving
eip.destroy()        // teardown