clay.js
  1. ClayJS
  2. Reference
  3. API
  4. DOM helpers: clay-dom, clay-events and All

DOM helpers: clay-dom, clay-events and All

Reference for the clay-dom.js, clay-events.js, and all.js satellites, including how to load them and every method and attribute they add.

ClayJS ships three optional DOM satellites for writing compact, DOM-first scripts and inline handlers: clay-dom.js, clay-events.js, and all.js. Each loads from its own classic <script> tag and works with or without clay.js, in both edit mode and view mode.

Loading the satellites#

Load each satellite with a classic <script src="..."> tag using the same version prefix as clay.js. Never load them with type="module".

HTML
<script src="https://clayjs.com/v1/clay.js"></script>
<script src="https://clayjs.com/v1/clay-dom.js"></script>
<script src="https://clayjs.com/v1/clay-events.js"></script>
<script src="https://clayjs.com/v1/all.js"></script>

Each tag registers a readiness promise on window.clay.loaded:

Script tagReadiness promiseWhat it adds
clay-dom.jsclay.loaded.domElement prototype helpers (el, nearest, val, text, exec, cycle, cycleAttr, show, hide) and clay.dom
clay-events.jsclay.loaded.eventsSix declarative event attributes (onclickaway, onclickchildren, onclone, onmutation, onglobalmutation, onrender)
all.jsclay.loaded.allwindow.All and clay.All collection helper

If you call a satellite from an inline script immediately after its tag, await its readiness promise first (await clay.loaded.dom). If you use the offline build (clay.standalone.js), all three satellites are already bundled inside it, so omit the extra <script> tags.

clay-dom.js#

clay-dom.js adds relative lookup and manipulation helpers to Element.prototype and HTMLElement.prototype, plus content-filtering helpers on clay.dom.

Element prototype properties and methods#

  • elem.nearest.NAME and elem.el.NAME: Getter proxies that search outward from elem for the nearest element matching [NAME], .NAME (an attribute or class named NAME). The search checks elem itself, then its descendants, previous siblings, next siblings, and finally parent and ancestor subtrees. Returns the found Element or null.
  • elem.val.NAME: Getter and setter for the nearest [NAME], .NAME element. On <input>, <select>, and <textarea>, it reads or writes .value; on any other element, it calls getAttribute("NAME") or setAttribute("NAME", value). Setting is a no-op if nothing matches.
  • elem.text.NAME: Getter and setter for innerText on the nearest [NAME], .NAME element. Setting is a no-op if nothing matches.
  • elem.exec.NAME(): Finds the nearest [NAME], .NAME element, reads its NAME attribute as JavaScript code, and executes it with this bound to that element. Returns undefined when nothing matches or the attribute is empty.
  • elem.cycleAttr(order = 1, setAttr, lookupAttr = setAttr): Collects all unique values of lookupAttr across the document, sorts them alphabetically, and sets setAttr on elem to the next (1) or previous (-1) value.
  • elem.cycle(order = 1, attr): Finds all [attr] elements in the document, deduplicates them by textContent, sorts them with localeCompare, and replaces elem in the DOM with a cloneNode(true) copy of the next (1) or previous (-1) element.
  • elem.show() and elem.hide(): show() sets elem.style.display = "" and hide() sets elem.style.display = "none". Both return elem.
HTML
<div class="card">
  <h3 class="title">Draft</h3>
  <button type="button" onclick="this.text.title = 'Published'">Publish</button>
  <button type="button" data-status="todo" onclick="this.cycleAttr(1, 'data-status')">Next status</button>
</div>

clay.dom functions#

  • clay.dom.createContentView(context, options): Builds an inert clone of context with elements matching a region capability removed. options accepts { capability = "data", exclude = null, inherit = true, provenance = null } (the default "data" strips no-data and editor-ui). Returns { root, document, capability, selector, cloneToLive, liveToClone, original(node), cloneOf(node), query(selector, queryRoot = root), text(node = root), html(node = root), clone(node = root) }.
  • clay.dom.cleanContentClone(node, options): Shorthand for clay.dom.createContentView(node, options).root.

Because elem.nearest.NAME (and el, val, and text) searches siblings and cousins when a child match is missing, calling it inside a list item that lacks .NAME will read or overwrite a neighboring item’s field. For record-scoped lookups, use elem.closest("[data-id]").querySelector(...). Also note that show() and hide() write inline style attributes that are saved to disk; use clay-options.js (show-when:) or a runtime <style> tag inside clay="editor-ui" for tab-local visibility.

clay-events.js#

clay-events.js waits for DOMContentLoaded (and for clay.loaded.dom if clay-dom.js is present on the page) and activates six HTML attributes:

AttributeWhen it runsthis inside handler
onclickaway="code"Every click on document outside the elementThe element with onclickaway
onclickchildren="code"A click inside a child of the element (ignores clicks on the parent itself)The clicked direct child (child.parentNode === parent)
onclone="code"Whenever cloneNode is called on the element or an ancestor (unless window.__preventOnclone is set); on deep clones, also moves textarea[data-value] into textContentThe newly cloned element
onmutation="code"An observed DOM mutation inside the element’s subtree (runs as an async function body; ignores the element’s own insertion)The element with onmutation
onglobalmutation="code"Any observed DOM mutation in the document, debounced by 200 ms (also accepts legacy alias onpagemutation)The element with the attribute
onrender="code"Once at page load and again (debounced by 200 ms) whenever the element is inserted into the DOMThe rendered element
HTML
<nav onclickchildren="for (const tab of this.parentElement.children) tab.removeAttribute('aria-current'); this.setAttribute('aria-current', 'page')">
  <button type="button" aria-current="page">Board</button>
  <button type="button">Archive</button>
</nav>

<div class="menu" onclickaway="this.hidden = true">...</div>

Guard every DOM write inside onmutation and onglobalmutation: a handler that writes into the subtree it watches triggers another mutation and loops forever, even when setting identical text. Always check first: if (this.textContent !== next) this.textContent = next. Also note that onclone does not fire on template.content.cloneNode(true) because template.content is a DocumentFragment; clone template.content.firstElementChild.cloneNode(true) instead.

all.js (All)#

all.js publishes window.All (and clay.All), a zero-dependency Proxy wrapper around element collections.

Selecting elements#

  • All(selectorOrElements, contextSelector?): Accepts a CSS selector string, an Element, Document, or an array of Element/Document nodes (anything else throws a TypeError). When contextSelector is passed, All searches within matching context elements (including any context element that matches selectorOrElements itself) or filters a passed element array to nodes inside those contexts.
  • All.NAME shorthand: Accessing any unknown property on All queries [NAME], .NAME (for example, All.card returns All("[card], .card")). Because All.name and All.length are built-in Function properties, pass a selector string for those names: All("[name]").

Chaining, properties, and events#

An All collection behaves like an array (.length, numeric indexing cards[0], for...of, and Array.prototype methods) while forwarding DOM properties, methods, and events across every matched element:

  • Property and method fan-out: Setting a property such as All(".card").className = "card open" writes to every matched element. Accessing .style, .classList, .dataset, or calling DOM methods chains across all elements and stays a safe no-op when the collection is empty.
  • Event binding (on* methods):
    • All(".btn").onclick(fn): Attaches a click listener to each element.
    • All("#board").onclick(".card", fn): Delegated listener; invokes fn with this set to the closest .card.
    • All("#board").onclick({ ".add": addCard, ".remove": removeCard }): Map of delegated selectors to handlers.

Collection methods#

MethodDescription
eq(index)Returns a new All collection with only the element at index (negative indices count from the end; out-of-range returns an empty All set).
at(index)Returns the raw Element at index, or undefined.
prop({ key: value })Assigns each property in the object to every element and returns the collection.
css({ prop: value })Assigns each inline style property to every element and returns the collection.
pluck(attr)Returns a plain array of attr values across the collection.
unique()Returns a new All collection with duplicate elements removed.
sortBy(fn | attrName)Sorts elements using fn(el) or the string value of attribute attrName (pass a function to compare numbers).
All.use({ properties, methods })Extends All with custom properties and methods and returns All.
JavaScript
await clay.loaded.all;

const ids = All(".card").pluck("data-id"); // ["1", "2", "3"]
All(".card").eq(0).classList.add("active");
All("#list").onclick(".remove", function () {
  this.closest("li").remove();
});