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
end

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

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.

Thanks

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


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

ly = on(y) do val
    if lx in x.listeners
        Observables.off(x, lx)
    end
    x[] = x[]
    on(lx, x)
end
3 Likes

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))
end

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

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)
end                                    
                                       
function set_x(y)                      
    setexcludinghandlers(observe(s), 10-y, f->f !== set_y)
end