Module: lib/core/renderer/program/bindings

What each render-program op does to the DOM.

One function per op kind, each writing to exactly one node. These are the leaves of the new architecture: everything above them exists to arrange for the right one of these to run with the right value, and nothing below them touches the DOM.

Matching the string renderer's semantics

These replace a pipeline that escaped a value into HTML, parsed that HTML, and diffed the result into the document. Anything the round trip did incidentally is behaviour applications now depend on, so it is reproduced here deliberately rather than rediscovered as a bug report:

  • Escaping. The old path escaped a value and the parser unescaped it, so the text that reached the document was the value verbatim. Writing node.data directly produces the same string and cannot be mis-escaped, because it never becomes markup at all.
  • null renders as nothing, not as the string "null".
  • SafeHtml in a {{ }} interpolation is inserted as markup. The old path skipped escaping for it; a text write would have shown the tags. Text ops therefore check for it and hand over to the raw path.
  • Boolean attributes. disabled="false" removed the attribute and set the property; anything else set both.
  • URL attributes were sanitised on every parsed tree, so they are sanitised on every write here.

Where the security boundary sits

Nowhere in this file is a value turned into markup unless the op is raw or the value is a SafeHtml -- the same two doors the string renderer had, and no new ones. Expressions are still evaluated by the AST evaluator through the callback these functions are given; this module receives values, never source.

Source:

Members

(inner, constant) KEY_MODIFIERS :Map:.<string:, string:>

Key names the @keydown.enter family of modifiers filters on.

Type:
  • Map:.<string:, string:>
Source:

(inner, constant) VALUE_PROPERTY_TAGS :Set:.<string:>

Elements whose value attribute stops driving their value once a user has typed into them.

Type:
  • Set:.<string:>
Source:

(inner, constant) escaper :HtmlEscaper

The escaper the string renderer used, reused rather than reimplemented.

data-ax-html escapes a plain value and only lets a SafeHtml through. A second escaping implementation here could drift from that one, and the direction it would drift in is "escapes less".

Type:
Source:

Methods

(static) applyAttribute(element, name, value)

Sets an attribute from a whole-value expression.

Parameters:
Name Type Description
element Element

The bound element.

name string

The attribute name.

value any

The evaluated value.

Source:

(static) applyAttributeParts(element, name, value)

Sets an attribute assembled from literal and expression parts.

Parameters:
Name Type Description
element Element

The bound element.

name string

The attribute name.

value string

The already-joined value.

Source:

(static) applyBoolean(element, name, value)

Sets or removes a boolean attribute, and mirrors it onto the property.

Parameters:
Name Type Description
element Element

The bound element.

name string

The attribute name.

value any

The evaluated value.

Source:

(static) applyClass(element, value, binding)

Applies a class binding, removing only the classes it previously added.

Parameters:
Name Type Description
element Element

The bound element.

value any

A string of class names, or an object of name to flag.

binding object

Per-binding state, holding the previous class list.

Source:

(static) applyHtml(element, value)

Replaces an element's inner HTML from an expression.

Parameters:
Name Type Description
element Element

The bound element.

value any

The evaluated value.

Source:

(static) applyProp(element, name, value) → {boolean}

Stores an evaluated prop for a child component mounted at this element.

The value is held on the element rather than pushed straight into the child, because at the moment a prop op first runs the child does not exist yet -- the parent's tree is still detached. The owner reads these when it mounts or refreshes its children.

Values are kept as values. The string path carried props through data-props-* attributes and re-evaluated them from source on every render of the parent, which meant every prop of every child was re-evaluated whenever anything in the parent changed.

Parameters:
Name Type Description
element Element

The child's mount point.

name string

The prop name.

value any

The evaluated value.

Source:
Returns:

True when the stored value changed.

Type
boolean

(static) applyRaw(anchor, value, binding)

Replaces the markup a raw binding owns.

A raw binding owns a range rather than a node: one expression can produce any number of elements. The compiler's text marker stays in the document as an anchor, and the nodes the binding inserted are tracked so the next evaluation can remove exactly those and nothing else. Clearing by emptying the parent would take siblings that belong to other bindings.

Parameters:
Name Type Description
anchor Text

The anchor node the compiler reserved.

value any

The evaluated value.

binding object

Per-binding state holding the current range.

Source:

(static) applyShow(element, value, binding)

Toggles an element's visibility, preserving its authored display value.

Parameters:
Name Type Description
element Element

The bound element.

value any

The evaluated value; truthiness decides.

binding object

Per-binding state, holding the authored display.

Source:

(static) applyStyle(element, value, binding)

Applies an inline style binding, removing only the properties it set.

data-ax-style has been documented public API for as long as the directive has existed, and applied nothing: the string renderer never implemented it, so the E2E suite pinned it as a known gap. It is implemented here because the compiled path is where a directive's behaviour now lives, and leaving it unimplemented would have carried a documented no-op into the new architecture.

Only the properties this binding wrote are cleared on change, so a style the author wrote in the template's own style attribute survives.

Parameters:
Name Type Description
element Element

The bound element.

value any

A CSS text string, or an object of property to value.

binding object

Per-binding state, holding the previous properties.

Source:

(static) applyText(node, value, binding)

Writes a value into a dynamic text node.

Parameters:
Name Type Description
node Text

The text node the compiler reserved.

value any

The evaluated value.

binding object

Per-binding state, used to manage raw ranges.

Source:

(static) attachEvent(element, op, run)

Attaches a declared event handler to an element.

The string renderer re-read data-ax-event off the DOM and re-bound every handler on every update, because a diff could have replaced the node the listener was on. A compiled element is created once and never replaced, so one addEventListener at mount is both correct and the whole cost.

Modifiers are applied here rather than inside the handler body so the compiled statement stays exactly what the author wrote.

Parameters:
Name Type Description
element Element

The element carrying the handler.

op object

The event op, with its name and modifiers.

run function

Runs the compiled handler.

Source:

(static) reportBindingError(op, error)

Reports a binding that threw, without taking the rest of the update with it.

Parameters:
Name Type Description
op object

The op that failed.

error Error

What went wrong.

Source:

(inner) asText(value) → {string}

Converts an evaluated value to the text it renders as.

Parameters:
Name Type Description
value any

The evaluated value.

Source:
Returns:

The text, with null and undefined rendering as nothing.

Type
string

(inner) clearRawRange(binding)

Removes the nodes a raw binding previously inserted.

Parameters:
Name Type Description
binding object

Per-binding state.

Source:

(inner) isBooleanOff(value) → {boolean}

Values that mean "this boolean attribute is off".

Deliberately not JavaScript falsiness. The string renderer decided by looking at the rendered attribute text, where the only off value was the literal "false" -- so 0 was on. Matching that keeps disabled="{{ count }}" behaving as it does today. Empty and null are added because they reached the old path as an empty attribute value, which read as on: an attribute bound to nothing being present is a bug rather than a semantic worth carrying forward.

Parameters:
Name Type Description
value any

The evaluated value.

Source:
Returns:

True when the attribute should be absent.

Type
boolean

(inner) syncValueProperty(element, name, value)

Mirrors an attribute onto the DOM property where the two can diverge.

For a form control the value attribute is the default value: once the user has typed, the property and the attribute are independent, and setting the attribute alone leaves what the user sees unchanged. Two-way binding depends on this -- a state change made in code has to reach the control the user has already touched. The string renderer did the same thing in its attribute patch, for the same reason.

Parameters:
Name Type Description
element Element

The bound element.

name string

The attribute just written.

value string

The value written.

Source:

(inner) toCssProperty(name) → {string}

Converts a camelCase style property name to its CSS spelling.

Parameters:
Name Type Description
name string

The property name as written.

Source:
Returns:

The hyphenated name.

Type
string