Dynamic form elements. Demo with adding items. Now... how do I remove elements?

Hello,

I wanted to manipulate some data with an arbitrary amount of inputs. I also want several versions of these controls within the notebook so I have a requirement to bind the controls together.

You can see it here: dynamically modify form controls / Will Jessup / Observable

inputsForm() will render the inputs and I can add inputs to this. It works just fine.

now, how do I add a button on each control to remove that control? I can’t get it working. There is always some circular definition errors when I try to modify controls from within each control.

You can do this with the view literal there is an example under “dynamic arrays” of controls.

Tom, thanks for the reply! I’ve seen your work before and this is really awesome stuff… I approached this notebook trying to understand more of what you were doing and build up that intuition myself.

I’m still wondering if you can help me in my current notebook. What is really required to have a “remove” button on each slider? Is my mental model for the rest of it wrong? etc. Thank you!

Here’s a better example of the problem: more dynamic form controls / Will Jessup / Observable

Just uncomment out.appendChild( removeButton(i) ) on line 3 in the first cell.

Later in the notebook you can see the removeButton, when called on it’s own works just fine.

How do I resolve this?

build up that intuition myself.

great to hear! Its a long journey but worth it!

An interesting ability of a views is that they can get around circular dependencies. Dataflow is strictly unidirectional, whereas with a view you can backwrite into it from anywhere. So a graph of views can be setup with a cycles in the update graph.

Instead of initializing your controls views with addSlider in one step, initialize it to undefined. This means it is not resolving and downstream cells depending on the inner view value will not start.

viewof controls = Inputs.input(undefined)

Then, by depending on the “viewof control” you can run a piece of code after the view has initialized (even though the inner value hasn’t). That initialization code can setup the inner value and kick off dataflow with dispatchEvent

initializeControls = {
  viewof controls.value = Array.from({length: 5}).map((a,i) => control(undefined, i))
  viewof controls.dispatchEvent(new Event('input', {bubbles: true}))
}

It’s important to understand “viewof controls” is a totally different dataflow variable to “controls”, hence it can run even though controls is not setup. Also, when controls does change (e.g. by addSlider), “viewof controls” does not run so that initialization block executes just once.

To summarize, you can break a circular dependency by replacing a dataflow link with a programmatic dispatchEvent. (or bind)

here is my version https://observablehq.com/d/e5513a2f45c21b96

This is the thing I needed! Wow, thank you Tom!

1 Like