This might be easier to understand if we look at the code that Observable produces:
{
name: "test_text_mutate",
inputs: ["viewof test_text"],
value: (function($0) {
return($0.value = 'bar')
})
}
Here, $0 is the viewof test_text cell, which in turn is a DOM node with a “special” value property. Let’s take a look at what makes it special:
- Let’s say we have an input
<input type=text>. Per the HTML specification, this DOM element already comes with avalueproperty. - When we enter something into the text field, each keystroke will trigger an “input” event.
- We can use this input with the
viewofmacro to have both a cell with our DOM element, and another internal cell that only contains the value:viewof mytext = html`<input type=text>` // Produces two Runtime Variables: // 1. "viewof mytext" (the DOM element) // 2. "mytext" (the value of the DOM element's `value` property) - Because we used the
viewofmacro, Observable wraps our cell in some code that listens to these events and automatically updates the internal value Variable (and in turn all dependent cells). - We can modify the DOM element’s
valueproperty from the outside, by accessing the viewof cell’s value, i.e., the DOM element:(viewof mytext).value = "a new value" - We can see the text inside the input change, but no “input” event is triggered, and as a result Observable has no idea that the value was changed.
Luckily Javascript allows us to define a getter and, more importantly, a setter for a property – that is, a function that is called whenever some code accesses the property (reading or changing it):
-
Internally we still need to access and change the input’s property directly, so we cannot simply define a getter or setter directly for the input. Instead, we wrap it, e.g.:
viewof mytext = html`<div><input type=text></div>` -
Observable will now listen to input events on the div instead of the input, and also check for a value property on the div. The input events “bubble up” and thus can still be registered on the div, but div’s don’t have a
valueproperty! -
Let’s define one:
viewof mytext = Object.defineProperty( // The object that we want to add the property to. // Note that this is also the return value of defineProperty()! html`<div><input type=text></div>`, // the name of the property "value", // The definition: { get() { // "this" is the div, .firstChild the input. return this.firstChild.value; }, set(value) { // Assign the new value, like we did earlier. this.firstChild.value = value; // Let Observable know about the change. // (Note that manually dispatched events, by default, do *not* bubble.) this.dispatchEvent(new Event("input")); }, } )
With that modification in place, let’s again assign a value and watch what happens:
(viewof mytext).value = "a new value"
- The setter is invoked and receives the string “a new value”.
- The setter modifies the
valueon the input element. - The setter dispatches an “input” event.
- Observable notices the input event and updates the “mytext” variable.