TermUI.Elm behaviour (TermUI v0.2.0)
View SourceThe Elm Architecture implementation for TermUI components.
This module provides the core callbacks for implementing components
using The Elm Architecture pattern: update/2 for state changes and
view/1 for rendering.
The Pattern
- Events arrive from terminal input
- event_to_msg/2 converts events to component-specific messages
- update/2 transforms state based on messages, returns new state + commands
- view/1 renders current state to a render tree
- Commands execute asynchronously, sending result messages back
Usage
defmodule Counter do
use TermUI.Elm
def init(_opts), do: %{count: 0}
def event_to_msg(%Event.Key{key: :up}, _state), do: {:msg, :increment}
def event_to_msg(%Event.Key{key: :down}, _state), do: {:msg, :decrement}
def event_to_msg(_, _), do: :ignore
def update(:increment, state), do: {%{state | count: state.count + 1}, []}
def update(:decrement, state), do: {%{state | count: state.count - 1}, []}
def view(state) do
text("Count: #{state.count}")
end
end
Summary
Callbacks
Converts an event to a component-specific message.
Initializes component state from options.
Updates component state based on a message.
Renders the current state to a render tree.
Functions
Normalizes update result to standard form.
Validates that an update function is pure (best effort).
Types
@type command() :: term()
@type event_to_msg_result() :: {:msg, msg()} | :ignore | :propagate
@type msg() :: TermUI.Message.t()
@type render_tree() :: term()
@type state() :: term()
Callbacks
@callback event_to_msg(TermUI.Event.t(), state()) :: event_to_msg_result()
Converts an event to a component-specific message.
This callback transforms raw terminal events into domain-specific messages that have semantic meaning for the component.
Parameters
event- The terminal event (Key, Mouse, Resize, etc.)state- Current component state
Returns
{:msg, message}- Event converted to a message for update:ignore- Event not handled by this component:propagate- Pass event to parent component
Initializes component state from options.
Called once when the component is created.
Parameters
opts- Options passed to the component
Returns
Initial state for the component.
@callback update(msg(), state()) :: update_result()
Updates component state based on a message.
This is the core logic of the component. It receives the current state and a message, and returns the new state plus any commands to execute.
Update functions must be pure—no side effects, no external calls. Side effects are performed through commands returned in the result.
Parameters
msg- The message to handlestate- Current component state
Returns
{new_state, commands}- New state and commands to execute{new_state}- Shorthand for{new_state, []}:noreply- Keep state unchanged, no commands
Examples
def update(:increment, state) do
{%{state | count: state.count + 1}, []}
end
def update({:fetch_data, url}, state) do
cmd = Command.http_get(url, {:data_loaded, :response})
{%{state | loading: true}, [cmd]}
end
def update(:noop, _state), do: :noreply
@callback view(state()) :: render_tree()
Renders the current state to a render tree.
View functions must be pure—given the same state, they always produce the same output. View functions should be fast since they run every frame.
Parameters
state- Current component state
Returns
A render tree structure that will be processed into terminal output.
Examples
def view(state) do
box(border: true) do
text("Count: #{state.count}")
end
end
Functions
@spec normalize_update_result(update_result(), state()) :: {state(), [command()]}
Normalizes update result to standard form.
Converts shorthand forms to the full {state, commands} tuple.
Validates that an update function is pure (best effort).
Returns warnings if the update function appears to have side effects. This is a heuristic check, not a guarantee.