Which scope a piece of DOM was rendered under.
The problem this solves
Most of a component's DOM is rendered against the component's own scope, and
an executable binding on it — an event handler, a directive — can be resolved
against that scope whenever it fires. Some of it is not. A <@for> body is
rendered once per item, against a scope carrying that item and its index; a
scoped slot is rendered against the props the parent passed. In both cases the
text interpolated into the markup was resolved correctly at render time, and
the handler attached to it was not: handlers are bound in one pass over the
component's element, with the component's scope, long after the derived scope
that produced the row has gone out of scope in the renderer.
The visible result was that @click="select(item.id)" — the form the events
guide documents — threw Cannot read property "id" of undefined, because by
the time the click arrived nothing remembered which row it came from.
What is stored, and where
The scope itself, on the root element of the subtree it produced. Resolution
walks up from the element the handler is on and takes the nearest one, which
gives nesting for free: an inner <@for> stamps a scope derived from the
outer one, so a handler in the inner row finds the inner scope and reads both
loop variables through it.
Storing the scope rather than a copy of its bindings is what keeps a reused row correct. The list manager recycles DOM nodes, so the element a handler is attached to may have held a different item a moment ago; re-stamping on every create-or-patch means the answer is always the item the element holds now.
Why a property rather than a WeakMap
A WeakMap keyed by node would be tidier, but resolution walks ancestors on every dispatched event and a property read is the cheaper of the two by enough to matter on a large list. The property is non-enumerable so it does not appear in a serialised node, a diff or a trace.
- Source:
Members
(inner, constant) SCOPE_PROPERTY :string
The property holding a subtree's scope.
Named for what it is rather than for the first thing that needed it. Scoped
slots got here first and called it __avenx_slot_scope; lists, defer blocks
and error fallbacks all need exactly the same thing, and a list row carrying
something called a slot scope reads as a bug to whoever finds it next.
Type:
- string
- Source:
Methods
(static) clearScope(node)
Removes a scope stamp.
Called when a node is returned to the list manager's pool, so a pooled node cannot hand a stale item to anything that reads it before it is re-stamped.
Parameters:
| Name | Type | Description |
|---|---|---|
node |
Node | The node to clear. |
- Source:
(static) findScope(node) → {object|null}
The scope an element's bindings should resolve against.
Walks ancestors and returns the nearest stamp, so the innermost enclosing derived scope wins. Returns null when the element sits in ordinary component DOM, which means "resolve against the component scope" to every caller.
Parameters:
| Name | Type | Description |
|---|---|---|
node |
Node | null | Where to start looking. |
- Source:
Returns:
The nearest enclosing scope, or null.
- Type
- object | null
(static) stampScope(node, scope) → {Node}
Records the scope a subtree was rendered under.
Safe to call repeatedly on the same node: a recycled list row is re-stamped with the item it now holds.
Parameters:
| Name | Type | Description |
|---|---|---|
node |
Node | The root of the rendered subtree. |
scope |
object | null | The scope it was rendered against. |
- Source:
Returns:
The node, for chaining.
- Type
- Node