🏠 back to Observable

Unable to dispatch input event on non-Dom inputs

Hi, I made the following notebook as a reduction of something I am not able to do.

Based on Synchronized Inputs / Observable / Observable I was expecting to be able to set the value of synchronized inputs via a non-DOM input input.

In my example is either as a timeout after creation or via an explicit button click. Neither of those works.

Would someone know what I might be missing?

Thanks in advance!

1 Like

EDIT: you need viewof infront of the non-DOM too to do what you intend

In your version the bind works, in that the nonDOM object is updated, but the notebook UI is kinda broke because that doesn’t have the hint that the UI and value have been seperated (viewof)

Other gotcha note: generally you want bubbles: true

input.dispatchEvent(new Event("input", {bubbles:true}));
1 Like

Thanks @tomlarkworthy for the input. Unfortunately I don’t see that does change make any difference :thinking: . Applying those changes (and even after adding a viewof in () => set(viewof nonDom, ...)]) I see no updates in the main input after attempting to send updates via de nonDom input.

I updated a newer version with a couple of more callbacks to check. So it’s not an issue of doing the update inside a setTimeout since that works on dom inputs.

I am attaching a gif recording to show how the input an name are unsync on my end.

Thanks again for your time!


1 Like

oh I see what you mean. When nonDom is set, the appearance of the text updates but the underlying value does not change. maybe an event is raised but we can’t tell.

Could be an issue with bind, or the DOM input component somehow masking it. I will come back to this later coz I am AFK ATM

1 Like

I believe this is a bug in Observable’s Inspector, as dependent cells are still updated properly. For viewof cells the Inspector normally just passes through the viewof Variable value (the Element). But for EventTarget it constructs its own representation, which it then fails to update. You can see the representation update if you expand/collapse it.

/cc @mbostock


It’s not really a bug; it’s just a limitation of how the inspector works. The inspector doesn’t do deep observation of objects because that would be expensive (e.g., everything would need to be wrapped with a Proxy). Setting the value of a view doesn’t change the view (foo updates, but viewof foo is unchanged), so the inspector doesn’t run.

1 Like

@bcardiff Unfortunately the workaround is rather ugly, as it involves creating your own Inspector instance and passing in the cell name manually:

inspect = {
  const {Inspector} = await require("@observablehq/inspector");
  return (target, name) => {
    if(target instanceof Element) return target;
    if(!target instanceof EventTarget) return new Inspector(html`<div>`).fulfilled(target, name);
    const el = Object.defineProperty(html`<div>`, "value", {
      get: () => target.value,
      set: v => target.value = v,
    const i = new Inspector(el);
    i.fulfilled(target, name);
    el.addEventListener("input", () => i.fulfilled(target, name));
    return el;
function delayedInput(value, name) {  
  const res = inspect(Inputs.input("initial"), name);
  window.setTimeout(() => set(res, "value set in setTimeout"), 2000);
  return res
viewof nonDom = delayedInput("value set by the timeout", "nonDom")
1 Like

It seems I am attempting something out the expected use case. :-S
I understand if this out of scope. But from the explanation @mootari is given I thought:

Maybe I can use a DOM Input instead of a non-DOM and that might work.

function delayedInput(value) {  
  //const res = Inputs.input("initial")
  const res = Inputs.text({value: "initial"}) // now is a dom Input
  window.setTimeout(() => set(res, "value set in setTimeout"), 2000);
  return res

But it doesn’t… after the timeout happens I see the expected value in both inputs, but on other cells that references name the value is not updated.

  • name + "." will still show "initial."
    • even if I reevaluate or if I create another cell with a similar formula

A cell with (viewof name).value will show the right value only after a manual re-evaluation.

I republish the notebook just in case with this cases.

I will take a step back, maybe what I am trying to do can be approached in a different way. I was trying to have some library that will play nice with Observable’s Inputs and extend them. I will see If I can do it without Inputs.bind.


1 Like

Perhaps you could elaborate what exactly you’re trying to do?

1 Like

Sure thing :slight_smile:

I was trying to define some helpers to store inputs in local-storage with an option to store them encrypted.

Now I’m not sure why I went the route of Inputs.bind instead of setting / listening to the main input directly. That works like a charm :see_no_evil: .

Before, I was trying to use the non-Dom input in a closured, manipulate those and let Inputs.bind sync the values.

It puzzles me that there are some non obvious constraints from the user perspective regarding dom inputs and non-dom inputs on Inputs.bind … but if this is the first time is must not be that common.


The underlying issue is not pure aesthetic. The underlying value is not updated.

(viewof name).value DOES UPDATE (correct)
(name) DOES NOT UPDATE (incorrect), rerunning the cell does not help

calling (viewof name).dispatchEvent(new Event(‘input’)) DOES fixes it. So the issue is a missing event.

I see this as a composability wart with Input bind. The input events are not raised at the source so you cannot chain binds in both directions. A clearer repro:

bind is not truly symmetrical in behaviour with raising events

(see inputs/bind.js at main · observablehq/inputs · GitHub)

Personally I don’t see this as a problem, two way binding is not usually needed, there is usually a clear primary, so you can reverse the direction of the bind to remove the problem.

You may want to reread both mine and Mike’s explanation of the underlying issue.

Which is precisely what is described in detail in the docs?

yeah thats the issue here

as dependent cells are still updated properly.

was not correct. Dependant cells were not updating properly because the bind is in the wrong direction.

viewof name = Inputs.bind(Inputs.text({label: “Name”}), viewof nonDom)

Sets the text as the target and nonDom as the source. So sending events to nonDom breaks dataflow downstream of the text().

bind(target, source, …)

"When the target emits a type-appropriate event, the target ’s type-appropriate value will be applied to the source and a type-appropriate event will be dispatched on the source ; when the source emits a type-appropriate event, the source ’s type-appropriate value will be applied to the target , but no event will be dispatched , avoiding an infinite loop.

So in this use case events sent to nonDom will not be mirrored on the “name” text view.

I usually read docs in observablehq and not on github. On Synchronized Inputs / Observable / Observable I think there is no mention of the non-symmetry behaviour that is causing the underlying issue.

I think the issue can be summarised for users as: Do not capture in a name the result of the Inputs.bind. Doing so will might lead to unexpected results.

Tweaking the last example of Synchronized Inputs / Observable / Observable, by adding a viewof y = we can see that


Thanks again to everybody :bowing_man: , I will keep enjoying ObservableHQ :rocket: