Text entered stuck on the first input using Kino.Input.text and Kino.Input.read in LiveBook

Hello. I’m seeking help to a problem in a function using LiveBook. I’m trying to have two buttons created using Kino.Control.button to get a text using Kino.Input.read from the same text box created using Kino.Input.text . Each button should use the same input from the text box to do different operation.

Problem is, when changing the text in the text box, and click any button which will read the text box again. The text returned is always the first text entered unless I reevaluate the cell.

I wrote this sample code to simplify the problem:

First cell:
frame = Kino.Frame.new()

Second cell:
button1 = Kino.Control.button(“Click1”)
button2 = Kino.Control.button(“Click2”)
text_box = Kino.Input.text(“enter something:”)
layout = Kino.Layout.grid([button1, button2, text_box], columns: 1)
Kino.Frame.render(frame, layout)

Third cell:
Kino.listen(button1, fn _event →
from_btn1 = Kino.Input.read(text_box)
IO.puts(“Click1: #{inspect(from_btn1)}”)
end)

Kino.listen(button2, fn _event →
from_btn2 = Kino.Input.read(text_box)
IO.puts(“Click2: #{inspect(from_btn2)}”)
end)

Also, I have tried to use Kino.Control.form function which updates the value upon changing. But it seems that it doesn’t support two submit buttons. For example:

First cell:
frame = Kino.Frame.new()

Second cell:
form = Kino.Control.form(
[
text_box: Kino.Input.text(“enter something:”)
],
submit: “Click1”
)

Kino.Frame.render(frame, form)
Kino.listen(form, fn event →
IO.puts(“click1 : #{inspect(event.data.text_box)}”)
end)

How to achieve the target of having two buttons reading from the same text box and capable of using the new entered text when a button is clicked?

Hey @LifeDart : ) Reading the value synchronously with Kino.Input.read/1 always returns the value that the input had when the cell started evaluating (for reproducibility purposes). To achieve what you want, you can subscribe to input changes and keep track of the current value, and also subscribe to button clicks in the same stream, so that you can use the latest value:

Kino.Control.tagged_stream(
  button1: button1,
  button2: button2,
  text_box: text_box
)
|> Kino.listen(%{text: ""}, fn
  {:text_box, event}, state ->
    {:cont, %{state | text: event.value}}

  {:button1, _event}, state ->
    IO.puts("Click1: #{inspect(state.text)}")
    {:cont, state}

  {:button2, _event}, state ->
    IO.puts("Click2: #{inspect(state.text)}")
    {:cont, state}
end)
2 Likes

Thank you @jonatanklosko for your reply. Your answer has solved my problem. :tada:

2 Likes