input library (@jashkenas/inputs) not mutable; but normal views are mutable

Right now in my Observable learning curve my Observable cells are simple*; each cell represents one value assigned directly; like:

viewof library_input = text({value: 'library_input' })

Then I want to change the value of library_input. No problem, views are mutable values

viewof library_input.value = 'new library_input val 

But that doesn’t change/mutate the value of the text input! Doesn’t change the value of an input (or a text or a select).

I can assign to a “plain” view; this works the way I’d expect

viewof html_input
viewof html_input.value = 'new html_input val'

Is this expected, am I doing anything wrong, is there a better approach?

(I tried to give example here: library input vs plain view / Nate | Observable ** )


*Maybe more smaller cells is a better (more “Observable”) approach than…

**Unrelated, I like human-friendly URLs , but when I publish a new notebook before adding a title, I create URLs like this (note untitled): library input vs plain view / Nate | Observable At the moment I can’t fix this, even if I’ve added a title (markdown in first cell?), and re-published, etc…

I think you get what you want if you change:

viewof library_input.value = change_library_input ? 'changed library_input val' : 'original library_input val';

to

viewof library_input.input.value = change_library_input ? 'changed library_input val' : 'original library_input val';

(The text() gadget that you imported from @jashkenas’s notebook actually creates a <form> element containing a child element <input name=input>. We use the .input to access that input element by name.)

1 Like

Thanks @bgchen !
As you say the @jashkenas/input library wraps each control in a <form> element; aka “wrapper element”.

A few interesting notes:
These produce the same thing. I thought the parentheses may cause viewof to apply to different values. But these all produce the same result; the HTMLInputElement of the <input type="text"> named input

viewof library_input.input
(viewof library_input).input
(viewof library_input.input)

But now I’m thinking viewof applies to the values to its right, even before the dot operator (higher precedence). Maybe that’s why viewof doesn’t like parentheses to its right. Maybe viewof has highest precedence; must evaluate first. Which might explain why the above parentheses change doesn’t make a difference, and these parentheses cause error:

viewof (library_input.input)
SyntaxError: Unexpected token

Even if we access the input(s) inside each form, it may not work. I think this is a general challenge with HTML elements. For example in plain HTML/DOM APIs, changing a <input type="text"> is as simple as setting the value attribute, and getting the attribute again:

document.querySelector('[name=library_input]').value= 'new library input'
document.querySelector('[name=library_input]').value

However, other controls like <select multiple> or <input type="checkbox"> are not so easy to get/set.
For example, setting <input type="checkbox"> requires adding the checked attribute and/or setting the checked property.
Likewise, getting the value of a <select multiple> is not as easy as reading a single value attribute. Instead each child option element must be checked

While @jashkenas/input supports getValue to convert HTML state to a value but there is no concept of setValue. (Here’s how @jashkenas/inputs getValue of a <select multiple>)

getValue: input => {
  const selected = Array.prototype.filter
    .call(input.options, i => i.selected)
    .map(i => options[+i.value].value);
  return multiple ? selected : selected[0];
},

The @jashkenas/inputs strategy of wrapping input(s) in a container like a <form> is also used in linked inputs and multi-value inputs***. I cannot set the value in either of those notebook either; this doesn’t work in the multi-value input notebook:

viewof rgb.value = [ 120, 100, 90 ]

So the concept of setValue is a common challenge. One solution is to use the “more general approach … a minimal view”. The set is supported as part of the EventTarget interface provided in the View class: import {View} from "@mbostock/synchronized-views"

set value(value) {
  this._value = value;
  this.dispatchEvent({type: "input", value});
}

But then you must bind your view to your input(s):

  • addEventListener to react : when the View (EventTarget) changes -> update your input
  • listen to your input to react: when user changes the input -> update the View

Note about the “wrapper element” as I call it:

All three cases assign the property value to the wrapper element so the viewof operator can get what it needs.

1 Like

Regarding viewof: Place a debugger; statement somewhere near viewof and you’ll see that viewof varname gets replaced with $0 (or $1 etc.).

As for mutating form widgets, you might find this notebook helpful: https://beta.observablehq.com/@mootari/mutable-forms

2 Likes