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

Source: https://clayjs.com/docs/dom-helpers/

ClayJS ships three optional DOM [satellites](https://clayjs.com/docs/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 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](https://clayjs.com/docs/offline/) (`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](https://clayjs.com/docs/regions/) 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`.

> [!WARNING]
> 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 |

```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>
```

> [!WARNING]
> 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

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

```js
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();
});
```
