Why does adding text to an HTML that contains a button break it's viewof?

This button is reactive so other cells can see when it’s clicked.

viewof makeColours = html`<button>Generate some colours</button>`

This button is is not reactive and other cells don’t see when it’s clicked.

viewof makeColours = html`<button>Generate some colours</button>
 <i>this can take a few seconds to respond</i>`

How can I get the button to be reactive with extra html text.

The reason why it’s not reactive is that the html function embeds the two elements (<button> and <i>) in a parent <span>.

The viewof mechanism requires the HTML element a) to have a .value property and b) to emit an input event on every value change (more details here: GitHub - observablehq/stdlib: The Observable standard library., eg if the HTML element is a button, the expected event is click. As we now have a span, we have to dispatch an input event).

I don’t know if there is a simple and clever way to make it reactive. What I would do:

viewof makeColours = {
  const button = html`<button>Generate some colours</button>`;
  const control = html`${button}<i>this can take a few seconds to respond</i>`;
  button.onclick = e => {
    e && e.preventDefault(); // to avoid dispatching the 'click' event outside
    control.dispatchEvent(new CustomEvent('input'));
  };
  control.value = button.value;
  return control;
}
3 Likes

It might be useful to reference the detailed explanation of the `viewof` keyword by @mootari: What has viewof ever done for us?! / Fabian Iwand / Observable

2 Likes

@severo gave a good example, and I wanted to see how it worked. So I made a notebook:

I changed it so there are two buttons that change the color. You could also have one button that toggles through color options.

The dispatchEvent does make it reactive, but since the value didn’t change, I didn’t understand what was firing.

Furthermore, button.value didn’t have any value set, so nothing was really registered.

4 Likes

For click events you can dispatch an input event with the help of Object.assign:

viewof makeColours = Object.assign(html`<button>Generate some colours</button>
 <i>this can take a few seconds to respond</i>`, {
  value: '',
  onclick(e) { this.dispatchEvent(new Event('input')) }
})
3 Likes

Or use our new hypertext literal (which will eventually be available in our standard library as the default):

Or more practically, use the new Button input:

4 Likes