How to get a cell to update in response to event?

I am still relatively new to working with Observable and its reactive model, so apologies if the answer to this question is obvious.

I understand how I can use an input element to trigger a cell to update. For example:

viewof updateButton = Inputs.button("Update")
rand = {
  updateButton; // Force recalculation when updateButton emits an input event
  return Math.random();
}

However, it is not clear to me how I can do something similar when not using an input element. For example how might I get a click on a Canvas to trigger an update?

myCanvas = {
  const ctx = DOM.context2d(200, 200);
  d3.select(ctx.canvas).on("click", (evt) =>
    console.log("How do I force rand to update?")
  );
  return ctx.canvas;
}
rand = {
  return Math.random();
}

This is beginner friendly:

mutable rand = undefined
htl.html`<button onclick=${() => (mutable rand = Math.random())}>a rand?`

You can merge these things into a single thing with a viewof but it take a while to really grok this construction IMHO. It does highlight its HTML emitting “input” events is the fundamental signaling mechanism in Dataflow (mostly)

viewof randView = {
  let ui = htl.html`<div><button onclick=${() => {
    ui.value = Math.random();
    ui.dispatchEvent(new Event('input'));
  }}>a rand?`;
  return ui;
}

My internet is slow at the moment but there are some excellent canonical texts written by Observbalehq team on these concepts

“mutable” “hyper text literal” “views”

Thanks @tomlarkworthy , but that’s not quite what I want (apologies if my question wasn’t clear enough). I don’t want to create any buttons (or other HTML input elements). Rather I want to be able dispatch the event generated by clicking on a canvas to another cell in order to force it to update itself. The example I provided used Button to do something similar, but I would like to use the click event on the Canvas instead.

You’re not limited to inputs. Any DOM element can be used, down to a simple EventTarget instance (or anything that implements the same interface).

However, if you want to fully embrace Observable’s reactivity, you have to:

  1. use viewof,
  2. dispatch an input event,
  3. optionally assign a value property with a value that is not undefined

In your example, an implementation could look like this:

viewof myCanvas = {
  const ctx = DOM.context2d(200, 200);
  ctx.canvas.value = true; // put anything here except undefined
  ctx.canvas.onclick = event => {
    // Relay a click as an input event, to notify Observable's Runtime
    // that dependant cells should be updated.
    ctx.canvas.dispatchEvent(new Event("input"));
  };
  return ctx.canvas;  
}
rand = myCanvas, Math.random()

You may find this introduction to viewof helpful:

2 Likes

Perfect thanks. Helpful in providing not only a working solution and reference article, but an indication of idiomatic event propagation in Observable.