# `Kino.Screen`
[🔗](https://github.com/livebook-dev/kino/blob/v0.19.0/lib/kino/screen.ex#L1)

Provides a LiveView-like experience for building forms in Livebook.

The screen receives its initial state and must implement the `c:render/1`
callback. Event handlers can be attached by calling the `control/2` function.
The first render of the screen is shared across all users and then further
interactions happen within a per-user process.

## Dynamic form/select

Here is an example that allows you to build a dynamic form that renders
values depending on the chosen options. On submit, you then process the
data (with optional validation) and writes the result into a separate frame.
The output frame could also be configured to share results across all users.

    defmodule MyScreen do
      @behaviour Kino.Screen

      # Import Kino.Control for forms, Kino.Input for inputs, and Screen for control/2
      import Kino.{Control, Input, Screen}

      @countries [nil: "", usa: "United States", canada: "Canada"]

      @languages [
        usa: [nil: "", en: "English", es: "Spanish"],
        canada: [nil: "", en: "English", fr: "French"]
      ]

      @defaults %{
        country: nil,
        language: nil
      }

      # This is a function we will use to start the screen.
      #
      # Our screen will be placed in a grid with one additional
      # frame to render results into. And the state of the screen
      # holds the form data and the result frame itself.
      def new do
        result_frame = Kino.Frame.new()
        state = %{data: @defaults, frame: result_frame}

        Kino.Layout.grid([
          Kino.Screen.new(__MODULE__, state),
          result_frame
        ])
      end

      def render(%{data: data}) do
        form(
          [
            country: country_select(data),
            language: language_select(data)
          ],
          report_changes: true,
          submit: "Submit"
        )
        |> control(&handle_event/2)
      end

      defp country_select(data) do
        select("Country", @countries, default: data.country)
      end

      defp language_select(data) do
        if languages = @languages[data.country] do
          default = if languages[data.language], do: data.language
          select("Language", languages, default: default)
        end
      end

      def handle_event(%{data: data, type: :change}, state) do
        %{state | data: data}
      end

      def handle_event(%{data: data, type: :submit, origin: client}, state) do
        # If you want to validate the data, you could do
        # here and render a different message.
        markdown =
          Kino.Markdown.new("""
          Submitted!
          * **Country**: #{data.country}
          * **Language**: #{data.language}
          """)

        # We render the results only for the user who submits it,
        # but you can share it across all by removing to: client.
        Kino.Frame.render(state.frame, markdown, to: client)

        # Reset form values on submission
        %{state | data: @defaults}
      end
    end

    MyScreen.new()

## Wizard example

Here is an example of how to build wizard-like functionality with `Kino.Screen`:

    defmodule MyScreen do
      @behaviour Kino.Screen

      # Import Kino.Control for forms, Kino.Input for inputs, and Screen for control/2
      import Kino.{Control, Input, Screen}

      # Our screen will guide the user to provide its name and address.
      # We also have a field keeping the current page and if there is an error.
      def new do
        state = %{page: 1, name: nil, address: nil, error: nil}
        Kino.Screen.new(__MODULE__, state)
      end

      # The first screen gets the name.
      #
      # The `control/2` function comes from `Kino.Screen` and it specifies
      # which function to be invoked on form submission.
      def render(%{page: 1} = state) do
        form(
          [name: text("Name", default: state.name)],
          submit: "Step one"
        )
        |> control(&step_one/2)
        |> add_layout(state)
      end

      # The next screen gets the address.
      def render(%{page: 2} = state) do
        form(
          [address: text("Address", default: state.address)],
          submit: "Step two"
        )
        |> control(&step_two/2)
        |> add_layout(state)
      end

      # The final screen shows a success message.
      def render(%{page: 3} = state) do
        Kino.Text.new("Well done, #{state.name}. You live in #{state.address}.")
        |> add_layout(state)
      end

      # This is the layout shared across all pages.
      defp add_layout(element, state) do
        prefix = if state.error do
          Kino.Text.new("Error: #{state.error}!")
        end

        suffix = if state.page > 1 do
          button("Go back")
          |> control(&go_back/2)
        end

        [prefix, element, suffix]
        |> Enum.reject(&is_nil/1)
        |> Kino.Layout.grid()
      end

      ## Events handlers

      defp step_one(%{data: %{name: name}}, state) do
        if name == "" do
          %{state | name: name, error: "name can't be blank"}
        else
          %{state | name: name, error: nil, page: 2}
        end
      end

      defp step_two(%{data: %{address: address}}, state) do
        if address == "" do
          %{state | address: address, error: "address can't be blank"}
        else
          %{state | address: address, error: nil, page: 3}
        end
      end

      defp go_back(_, state) do
        %{state | page: state.page - 1}
      end
    end

    MyScreen.new()

# `state`

```elixir
@type state() :: term()
```

The state of the screen

# `render`

```elixir
@callback render(state()) :: term()
```

Callback invoked to render the screen, whenever there
is a control event.

It receives the state and it must return a renderable
output.

The first time this function is called, it is done within
a temporary process until the user first interacts with
an element via `control/2`. Then all events happen in a
user-specific process.

# `control`

```elixir
@spec control(element, (map(), state() -&gt; state())) :: element
when element: Kino.Control.t() | Kino.Input.t()
```

Receives a control or an input and invokes the given 2-arity function
once its actions are triggered.

# `new`

```elixir
@spec new(module(), term()) :: Kino.Frame.t()
```

Creates a new screen with the given module and state.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
