Module: lib/core/renderer/bindingScope

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