- ClayJS
- Reference
- API
- 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".
<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 tag | Readiness promise | What it adds |
|---|---|---|
clay-dom.js | clay.loaded.dom | Element prototype helpers (el, nearest, val, text, exec, cycle, cycleAttr, show, hide) and clay.dom |
clay-events.js | clay.loaded.events | Six declarative event attributes (onclickaway, onclickchildren, onclone, onmutation, onglobalmutation, onrender) |
all.js | clay.loaded.all | window.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.NAMEandelem.el.NAME: Getter proxies that search outward fromelemfor the nearest element matching[NAME], .NAME(an attribute or class namedNAME). The search checkselemitself, then its descendants, previous siblings, next siblings, and finally parent and ancestor subtrees. Returns the foundElementornull.elem.val.NAME: Getter and setter for the nearest[NAME], .NAMEelement. On<input>,<select>, and<textarea>, it reads or writes.value; on any other element, it callsgetAttribute("NAME")orsetAttribute("NAME", value). Setting is a no-op if nothing matches.elem.text.NAME: Getter and setter forinnerTexton the nearest[NAME], .NAMEelement. Setting is a no-op if nothing matches.elem.exec.NAME(): Finds the nearest[NAME], .NAMEelement, reads itsNAMEattribute as JavaScript code, and executes it withthisbound to that element. Returnsundefinedwhen nothing matches or the attribute is empty.elem.cycleAttr(order = 1, setAttr, lookupAttr = setAttr): Collects all unique values oflookupAttracross the document, sorts them alphabetically, and setssetAttronelemto the next (1) or previous (-1) value.elem.cycle(order = 1, attr): Finds all[attr]elements in the document, deduplicates them bytextContent, sorts them withlocaleCompare, and replaceselemin the DOM with acloneNode(true)copy of the next (1) or previous (-1) element.elem.show()andelem.hide():show()setselem.style.display = ""andhide()setselem.style.display = "none". Both returnelem.
<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 ofcontextwith elements matching a region capability removed.optionsaccepts{ capability = "data", exclude = null, inherit = true, provenance = null }(the default"data"stripsno-dataandeditor-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 forclay.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:
| Attribute | When it runs | this inside handler |
|---|---|---|
onclickaway="code" | Every click on document outside the element | The 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 textContent | The 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 DOM | The rendered element |
<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, anElement,Document, or an array ofElement/Documentnodes (anything else throws aTypeError). WhencontextSelectoris passed,Allsearches within matching context elements (including any context element that matchesselectorOrElementsitself) or filters a passed element array to nodes inside those contexts.All.NAMEshorthand: Accessing any unknown property onAllqueries[NAME], .NAME(for example,All.cardreturnsAll("[card], .card")). BecauseAll.nameandAll.lengthare built-inFunctionproperties, 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 aclicklistener to each element.All("#board").onclick(".card", fn): Delegated listener; invokesfnwiththisset to the closest.card.All("#board").onclick({ ".add": addCard, ".remove": removeCard }): Map of delegated selectors to handlers.
Collection methods#
| Method | Description |
|---|---|
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. |
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();
});