Two-way binding in PlutoUI.jl?

312 views Asked by At

I am trying to do a simple (I think) thing. I want to have a TextField and some variable bound to it and be able to modify this variable and by that the text displayed by the TextField.

I tried:

@bind x TextField()

print(x)

It works fine and whatever I type in the tex field is printed.

Now, what if I want programmatically change x and display it in the TextField?

if I write x="trelemorele", I am getting:

  • error of "Multiple definitions for x", if it is in a separate cell
  • no error but my TextField disappear, if it is in the same cell within begin .. end

Any ideas?

2

There are 2 answers

2
Nils Gudat On BEST ANSWER

What you're trying to do is fundamentally at odds with Pluto's reactivity.

At the heart of the idea of Pluto is that cells are connected by a dependency graph which Pluto automatically figures out for you from the code you've written, and which it uses to determine which cells need to be updated when another cell changes, because they depend on the changed cell.

So in simple terms if you have:

# Cell 1
x = 1

# Cell 2
y = x^2

Pluto will determine that cell 2 depends on cell 1, as x is defined there and used as an input in cell 2. But what if you now add

# Cell 3
x = 2

? Well you've now broken reactivity - Pluto can't work out the result of cell 2 anymore because there's now two competing definitions of x. Now you might say "well but I've put in the definition on cell 3 after the one in cell 1, so clearly it should be using x = 2", but that's exactly what Pluto wants to avoid. This is possible in Jupyter notebooks - the value of x would depend on whether cell 1 or cell 3 was executed last, but this means there's "hidden state" in the notebook; it is not possible from just looking at cell 1 or cell 3 whether their definitions haven't been overwritten elsewhere.

When you do @bind x TextField() that's roughly the same as doing x = TextField() (the actual generate code is a bit more complicated, but in the end it assigns something to the variable x), so doing that in one cell and x = "trelemorele" is the same thing.

Now as to your second point, Pluto does not complain because you now have an unambiguous ouptut for what should be bound to x after executing your cell - it's the last assignment to x, which is the same in a normal Julia session:

julia> begin
           x = 1
           x = 2
       end;

julia> x
2
0
lungben On

In addition to the answer of Nils, there are ways to work around reactivity in Pluto using mutable objects.

# ╔═╡ 652416e0-e8a3-11ec-09d4-37c76de750be
begin
    using PlutoUI
    # make sure the refs are executed first by putting them together with the using
    default_txt = Ref("Hello World!")
    in_txt = Ref("")
end

# ╔═╡ 9c26218a-6c4a-434e-aad5-a6e0fe078949
@bind txt TextField(; default=default_txt[])

# ╔═╡ 32b3fa56-d922-4d37-baec-e4917a7ad26b
@bind upd Button("update")

# ╔═╡ 5204277d-5411-4c60-89d0-c4ffeb5b0994
in_txt[] = txt # use 2nd mutable object to prevent auto-updates of next cell if txt changes

# ╔═╡ b6b39250-6a94-4e2d-9fde-acefb9dae662
begin
    upd
    default_txt[] = in_txt[] # only update default_txt if button is pressed
end

Be aware that this is an advanced pattern that goes against the standard Pluto workflow and should only be used when really needed.