Events are not firing smoothly

Hello,

I’m wondering why the events in this notebook are not working smoothly. Why don't these events work smoothly? / Will Jessup / Observable

The issue is caused by this line:

//THIS NEXT LINE IS THE PROBLEM, COMMENT ME OUT
viewof data.dispatchEvent(new Event(‘input’))

Thank you.

Yeah, so the problem is that when data dataflows, the formInput function cell reevaluates (take note of the grey bar flickering). This in turn causes the UI cells to reevaluate too. Thus, its like deleting the whole UI and recreating it. So the users slider state is lost (including the mouse handler).

For smooth UI, you do not want dataflow to recreate the whole UI from scratch, so references to the variable data should be avoided. Note that ‘viewof data’ is a different variable which is not updated often, so Inputs.bind(ui, viewof data) is ok, and this lets you sync data without a dataflow dependancy. See Synchronized Inputs / Observable / Observable

To fix your example:-

inputsForm = () => html`
  ${Inputs.bind(Inputs.range([0, 255]), viewof data.value[0])}
  ${Inputs.bind(Inputs.range([0, 255]), viewof data.value[1])}`

Under the hood (inputs/bind.js at main · observablehq/inputs · GitHub)

bind is calling addEventListener() to passed in args, so it is event driven like dataflow still. Note the function formInput no longer reevaluates, because it no longer has a data dependancy on “data”. Instead it is dependant on “viewof data” which is setup only once.

1 Like

Tom, I tried the approach you recommended but

inputsForm = () => html`
  ${Inputs.bind(Inputs.range([0, 255]), viewof data.value[0])}
  ${Inputs.bind(Inputs.range([0, 255]), viewof data.value[1])}

will throw an TypeError: Cannot read properties of undefined (reading 'type') error.

Hmm, this is my notebook, what is different?

1 Like

My mistake, I only added the viewof keyword and didn’t change the target to from data[0] to data.value[0]

This works!

aah cool. Yeah, “viewof data” is the outer presentation (often DOM) variable. It’s property “value” is the inner data variable. Because a view holds a data variable, I sometimes mentally model views as pointers (but they also do other things so that description is not sufficient).

1 Like

Well, I think I finally got this together: Form with composite data and some controls / Will Jessup / Observable

There is a Composite value that’s updated any time any of the inputs update. It’s powered by manually creating oninput functions on each input that will drive the composite value:

setData = (a) => {
  a.map(div => div.oninput = () => set(viewof data, a)) //when each control updates, update `data`
  set(viewof data, a) //update `data`
}

This is the big part that you helped me solve above.

  • The inputs are all bound together
  • I can have as many views of the controls as I want.
  • I can add, remove, etc. the controls pretty easily.

This feels like some serious duct tape :smile: but it works …

Here is how I would do it

One thing I have noticed is that you have separated out your “controls” vs your “data” as two distinct views, but this distinction is already part of the viewof machinery. The outer viewof variable (the view) should be a DOM node and is a UI. The inner value should be a pure data objects like a number.

So “viewof data” is the UI. and “data” is the value if we are being idiomatic.

1 Like

How would I duplicate the view and have the duplicated view bound to the original in your example? Something like: https://observablehq.com/d/33a8042c58f2657c

Controls being separate from Data was a leftover of our previous thread with the circular events. As you called out it’s not needed here! Updated: Form with composite data and some controls / Will Jessup / Observable

Removing data also removes the reactive hack of defining the inputsForm() with controls, inputsForm() for reactivity.

Thanks Tom!

When I want to instantiate multiple UIs I switch to a builder pattern, which by convention I always pass in an options arg (so I can add features without breaking old version).

If we want one view linked to another you can use Inputs.bind. I put this in the options but you could do it outside too.

I updated my notebook with an example.

1 Like

Thanks again Tom.

I tried your updated example and it suffers from the event stuttering when using the controls generated by the builder.

lol. Ooops. Yeah, that kind of makes sense as its replacing the whole array every bind which implies replacing the whole DOM every time. This should be work

Inputs.bind(viewof linked.elements, viewof values.elements)

but it doesn’t because of a bug so I will investigate that later…

1 Like

Tom, Also I found a bug in my own example when removing data — then clicking “add item” doesn’t refresh the inputs cell, so it won’t show the new input(s).

The reason for both data and controls is just that data is a copy of controls so that I can update the inputsForm when an item is added. The inputsForm needs to be bound to something that’s not controls or it ends up in the render loop / replacing DOM problem.

Updated so that at least it works again: Form with composite data and some controls / Will Jessup / Observable

Looking forward to seeing your version or if you have ideas for how to fix the reactivity loop in mine without needing the duplicate of the controls in data. Thank you.

2 Likes