Uikit.Components.Live.Select (elixir_uikit v0.9.0)

Copy Markdown

A searchable single/multi select ("power select") live component. Render it through Uikit.Components.uk_power_select/1. Needs the PowerSelect hook.

The component only owns transient UI state (the current search string). All domain state — the available options and the selected values — is owned by the caller and passed in as assigns.

Assigns

  • :id (required) — unique DOM id, also included in every message so the caller can distinguish between multiple selects on one page.
  • :options — list of values to show in the dropdown. The dropdown is open while this list is non-empty or the search input has text, which offers an Add "..." row for the typed text. Defaults to [].
  • :multiple — true (the default) for a multi select, false for a single select.
  • :selected — the selected values, rendered as removable chips: a list for a multi select, a single value or nil for a single select. Defaults to [].
  • :attr — when :options/:selected contain maps or structs, the key to read from each entry. It is used as the display label, to check whether an option is selected, and as the value sent in the component's messages. When nil (the default), entries are treated as plain values. Example: with attr={:name} and options like %{id: 1, name: "foobar"}, the component renders "foobar" and messages carry "foobar".
  • :gettext_backend — {backend, domain} of the consuming app, e.g. gettext_backend={{MyAppWeb.Gettext, "uikit"}}. When nil (the default), the English texts are used as-is.

Gettext

The component's texts are translated at runtime with Gettext.dgettext/4 through the backend and domain injected with :gettext_backend, in the backend's current locale. mix gettext.extract does not pick them up — copy priv/gettext/uikit.pot of this library into the app's priv/gettext and translate it from there.

Messages

Domain events are not handled by the component itself. Instead they are sent to the parent LiveView process, which reacts in handle_info/2 and feeds updated :options/:selected assigns back down:

  • {:power_select, id, {:search, query}} — the user typed in the search input. The caller is expected to look up matching options and re-assign :options.
  • {:power_select, id, {:select, value}} — the user picked a value from the dropdown, or the typed text from the Add "..." row. The caller should add it to :selected (replace it for a single select) and usually clear :options.
  • {:power_select, id, {:remove, value}} — the user removed a chip. The caller should drop the value from :selected (set it to nil for a single select).
  • {:power_select, id, :reset} — the user clicked or tabbed out of the select, or pressed Escape. The component clears its search string; the caller should clear :options.

The search input has the id "<id>-input", point a <label for> at it.

Example

Options and selections are usually maps (e.g. Ecto/Ash records). Pass :attr so the component knows which key to display and to send in its messages — the caller then resolves that value back to the full record:

<.uk_power_select
  id="fruit-select"
  options={@fruit_options}
  selected={@fruits}
  attr={:name}
/>

def handle_info({:power_select, "fruit-select", {:search, query}}, socket) do
  # e.g. a DB query returning [%{id: 1, name: "apple"}, ...]
  {:noreply, assign(socket, :fruit_options, search_fruits(query))}
end

def handle_info({:power_select, "fruit-select", {:select, name}}, socket) do
  case Enum.find(socket.assigns.fruit_options, &(&1.name == name)) do
    nil ->
      {:noreply, assign(socket, :fruit_options, [])}

    fruit ->
      {:noreply,
       socket
       |> update(:fruits, &Enum.uniq(&1 ++ [fruit]))
       |> assign(:fruit_options, [])}
  end
end

def handle_info({:power_select, "fruit-select", {:remove, name}}, socket) do
  {:noreply, update(socket, :fruits, &Enum.reject(&1, fn f -> f.name == name end))}
end

def handle_info({:power_select, "fruit-select", :reset}, socket) do
  {:noreply, assign(socket, :fruit_options, [])}
end