How to link two widgets in InteractNext, WebIO?

Suppose I have two sliders and I want the relationship x = 10-y to hold. I can do the following:

using WebIO, InteractNext, Blink

s = slider(0:10, label="x")
t = slider(0:10, label="y")

x = obs(s)
y = obs(t)

on(x) do val
    y.val = 10 - val

on(y) do val
    x.val = 10 - val

ui = dom"div"(s,t)

w = Window()
body!(w, ui)

And the observable values are linked, but this doesn’t visually update the sliders to the observable values. The listeners that visually update the sliders are added during the body!(w,ui) call, but those listeners will also call the other listeners of the observable. I can add a listener on x or y to call the visual update listener of the other, but doing it for both leads to infinite recursion.

Is there any way to either

  • Link two widgets such that they update each other, or
  • Update the visual widget without calling the listeners of the observable?

I’ve previously tried a timer based approach so that only one widget at a time can affect the other, but a less fragile solution would be very helpful.


Edit: This approach is possible but also a little inconvenient:

lx = on(x) do val
    if ly in y.listeners, ly)
    y[] = y[]
    on(ly, y)

ly = on(y) do val
    if lx in x.listeners, lx)
    x[] = x[]
    on(lx, x)

That’s certainly a clever way to do that. :+1: I think we should add it as a library function to Observables. something like connect!(x, y, f_xtoy, f_ytox). There’s also a secret Observables.setexcludinghandlers which essentially does the filtering of handlers like you do but without the problem of race conditions.

Thank you for the answer.

I’m not quite sure how to use setexcludinghandlers in this context since a call to the backedge listener always seems to call all other listeners of the observable (multiple times in the case of a slider if the value is changed a lot). For example:

lx = on(x) do val
    Observables.setexcludinghandlers(y, y.val, z->isa(z, WebIO.Backedge))

ly = on(y) do val
    Observables.setexcludinghandlers(x, x.val, z->isa(z, WebIO.Backedge))

has the same issue and doesn’t update just the graphic; all listeners are called in the process. Any tips?

It’s something like this:

function set_y(x)
    setexcludinghandlers(observe(t), 10-x, f->f !== set_x)
function set_x(y)                      
    setexcludinghandlers(observe(s), 10-y, f->f !== set_y)