Notify#
Notify lets elements on a page tell each other that something changed. nup (short for notify up) sends a signal up through the ancestors. ndown (notify down) sends it down through the descendants. A listener is an attribute that holds a callback, run when the signal reaches it. Two calls and an attribute are enough to wire a page's pieces together.
Source: Notify.js
Direction#
A button and a display next to each other are siblings, so the button cannot reach the display on its own. The signal goes up to the parent they share, and the parent sends it back down. There is no script for the widget itself. The only JavaScript is nup and ndown in the toolkit.
<div counter-onclick="() => this.ndown('counter-increment')">
<span counter-increment="() => this.textContent = +this.textContent + 1">0</span>
<button onclick="this.nup('counter-onclick')">+1</button>
</div>
Data#
A signal can carry a value. It arrives as the callback's argument, and each hop passes it along.
<div counter-onclick="(step) => this.ndown('counter-increment', step)">
<span counter-increment="(step) => this.textContent = +this.textContent + step">0</span>
<button onclick="this.nup('counter-onclick', 1)">+1</button>
<button onclick="this.nup('counter-onclick', 5)">+5</button>
</div>
Boundary#
A third argument is a selector where the walk stops. nup runs listeners up to it but not past it. ndown will not descend into it. It is only needed when a widget is nested inside another of the same kind, so each keeps its signals to itself.
<button onclick="this.nup('counter-onclick', 1, '[counter-space]')">+1</button>
Ancestor context#
The control and the package never name each other. A control sends its change up as a small patch. The space owns the state and is its only writer. It merges the patch into its configurator-val-* attributes with attrs, then signals its dependents down to re-check. The package reads those attributes and decides on its own whether to stay available. Every parameter uses the same channel, so adding a second control needs no new wiring. Each change goes through the space before it is stored. That one handler is where a value could later be vetoed, or the configuration's rules enforced.
<div configurator-space configurator-val-engine="standard"
configurator-onchange="(change) => this
.attrs('configurator-val', change)
.ndown('configurator-refresh')">
<select onchange="this.nup('configurator-onchange', { engine: this.value })">
<option value="standard" selected>Standard engine</option>
<option value="performance">Performance engine</option>
</select>
<label configurator-refresh="() =>
this.one('input').disabled =
this.up('[configurator-space]').attrs('configurator-val').engine === 'standard'
">
<input type="checkbox" disabled> Sport package
</label>
</div>
Clear#
A Clear button empties fields elsewhere in the form. The click goes up to the form as one signal. The form passes it down, and each field clears itself. There is no module: the whole feature is three short attributes.
<div form-space form-reset="() => this.ndown('field-clear')">
<label>Name <input field-clear="() => this.value = ''" value="John Doe"></label>
<label>City <input field-clear="() => this.value = ''" value="London"></label>
<button onclick="this.nup('form-reset')">Clear</button>
</div>