I have a LiveView page where I have a small form. This form is supposed ton have a group of radio buttons, and a group of checkboxes.
To achieve this, I am trying to use Form Bindings:
This is what I currently have. I want to give the user the option to receive notifications about some topics.
For the radio button group, the user chooses whether to receive the notification via email or sms. These are mutually exclusive.
For the checkbox group, the user chooses the topics. These are not mutually exclusive.
This is my html.heex
file:
<.form for={@form} phx-submit="save">
<.input id={"email"} name="notifications" type="radio" value={"email"} field={@form[:notifications]} />
<.input id={"sms"} name="notifications" type="radio" value={"sms"} field={@form[:notifications]} />
<.input id={"sports"} name="topics" type="checkbox" value={"sports"} field={@form[:topics]} />
<.input id={"science"} name="topics" type="checkbox" value={"science"} field={@form[:topics]} />
<button>Save</button>
</.form>
And this is the corresponding LivewView:
defmodule MyApp.Settings do
use MyApp, :live_view
@impl true
def mount(_params, _session, socket) do
form = to_form(%{})
updated_socket = assign(socket, form: form)
{:ok, updated_socket}
end
def handle_event(event, params, socket) do
Logger.info("Event: #{inspect(event)} ; #{inspect(params)}")
{:noreply, socket}
end
end
If you are a keen reader, you will see I have no label
, or legend
tags anywhere. This is for simplification purposes.
Even though I get the correct value for the "notifications"
radio group, the problem is that my checkboxes always return true
or false
:
[debug] HANDLE EVENT "save" in MyApp.Settings
Parameters: %{"notifications" => "sms", "topics" => "true"}
This is rather confusing. I was expecting something like:
Parameters: %{"notifications" => "sms", "topics" => ["sports", "science"]}
After reading the relevant parts of the docs, I don't think the type checkbox
is considered a special type, so it is not documented (in the link above mentioned).
I also don't quite understand why I need a schema when to_form
seems to work perfectly fine with %{}
.
to_form
work with a Struct?to_form
?After reading both Articles provided by the Elixir community:
I have arrived to a code that mixes what I learned from both. Definitely recommend the reading. Here are the code pieces for your inspiration:
core_components.ex
@doc """
Generate a checkbox group for multi-select.
## Examples
<.checkgroup
field={@form[:genres]}
label="Genres"
options={[%{name: "Fantasy", id: "fantasy"}, %{name: "Science Fiction", id: "sci-fi"}]}
selected={[%{name: "Fantasy", id: "fantasy"}]}
/>
"""
attr :id, :any
attr :name, :any
attr :label, :string, default: nil
attr :field, Phoenix.HTML.FormField, doc: "a form field struct retrieved from the form, for example: @form[:genres]"
attr :errors, :list
attr :required, :boolean, default: false
attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"
attr :rest, :global, include: ~w(disabled form readonly)
attr :class, :string, default: nil
attr :selected, :any, default: [],
doc: "the currently selected options, to know which boxes are checked"
def checkgroup(assigns) do
new_assigns =
assigns
|> assign(:multiple, true)
|> assign(:type, "checkgroup")
input(new_assigns)
end
def input(%{type: "checkgroup"} = assigns) do
~H"""
<div class="mt-2">
<%= for opt <- @options do %>
<div class="relative flex gap-x-3">
<div class="flex h-6 items-center">
<input id={opt.id} name={@name} type="checkbox" value={opt.id} class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" checked={opt in @selected}>
</div>
<div class="text-sm leading-6">
<label for={opt.id} class="text-base font-semibold text-gray-900"><%= opt.name %></label>
</div>
</div>
<% end %>
</div>
"""
end
usage_live.html.heex
<.simple_form for={@form} phx-change="change" phx-submit="execute">
<div class="mt-4">
<.checkgroup field={@form[:genres]} label="Genres" options={@all_genres} selected={@selected_genres} required/>
</div>
<div class="mt-4">
<.button}>Execute Command</.button>
</div>
</.simple_form>