Implement suggested radio button group solution

I followed the tips suggested by @chrismccord in this PR to display some radio buttons as follows:

def render(assigns) do
    ~H"""
    <p>Hello from radio buttons</p>

    <.form for={@form}>
      <div class="flex gap-x-2">
        <%= for color <- ~w(gray red yellow green blue indigo pink purple) do %>
          <.radio_group field={@form[:color]}>
            <:radio value={color}><%= color %></:radio>
          </.radio_group>
        <% end %>
      </div>
      <input type="submit" value="Save" />
    </.form>
    """
  end

what raises some errors in the browser console saying:
Multiple IDs detected: color-0. Ensure unique element ids.

I added the radio_group component function to the core_componenets.ex module as follows:

attr :field, Phoenix.HTML.FormField, required: true

  attr :rb_class, :string,
    default: "rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6"

  slot :radio, required: true do
    attr :value, :string, required: true
  end

  slot :inner_block

  def radio_group(assigns) do
    ~H"""
    <div>
      <%= render_slot(@inner_block) %>
      <div :for={{%{value: value} = rad, idx} <- Enum.with_index(@radio)}>
        <label for={"#{@field.id}-#{idx}"}><%= render_slot(rad) %></label>
        <input
          type="radio"
          name={@field.name}
          id={"#{@field.id}-#{idx}"}
          value={value}
          checked={to_string(@field.value) == to_string(value)}
          class={@rb_class}
        />
      </div>
    </div>
    """
  end

When inspecting the generated HTML, I can see that it does contain multiple IDs with the same value and looks like this:

<div>
  
        
  <div>
    <label for="color-0">red</label>
    <input type="radio" name="color" id="color-0" value="red" class="rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6">
  </div>
</div>
...
other radio inputs with the same `id="color-0"` value.

By the way, the above implementation does make it possible to provide any global attributes that might be updated.
Any ideas about that?

I think you need to move your for to be only around the :radio slot, Now you are rendering the whole group for every color with only a single color, hence the index is always zero. BTW I would add an id to the .radio_group and prefix that to the generated field id. If not the in the off-chance that you would need to add a second .radio_group on the same page of the same field (in different forms) you still get duplicate id’s.

Nope, it will fit if I move the for inside of radio_group:

<.form for={@form}>
      <div class="flex gap-x-2">
        <.radio_group field={@form[:color]}>
          <%= for color <- ~w(gray red yellow green blue indigo pink purple) do %>
            <:radio value={color}><%= color %></:radio>
          <% end %>
        </.radio_group>
      </div>
      <input type="submit" value="Save" />

The error:

== Compilation error in file lib/live_draft_web/live/radio_buttons_live.ex ==
** (Phoenix.LiveView.Tokenizer.ParseError) lib/live_draft_web/live/radio_buttons_live.ex:25:13: invalid slot entry <:radio>. A slot entry must be a direct child of a component
    (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/tag_engine.ex:1376: Phoenix.LiveView.TagEngine.raise_syntax_error!/3
...

With slots you need to use :for as slots need to be direct children of their component. They cannot be wrapped in eex tags.

2 Likes

Yay, that was it, thank you!
Here is the full working version:

<.form for={@form}>
      <div class="flex gap-x-2">
        <.radio_group field={@form[:color]}>
          <:radio :for={color <- ~w(gray red yellow green blue indigo pink purple)} value={color}>
            <%= color %>
          </:radio>
        </.radio_group>
      </div>
      <input type="submit" value="Save" />
    </.form>

Just a drive-by curiosity: why is radio_group with the dot syntax and radio with the colon one? :thinking:

One notation is for function components and the other for their nested slots.

I have not kept up with the client-side Phoenix for a while, can you link me material that clarifies what do these two mean?

https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#module-named-slots

2 Likes

Ohhh I see, very nice actually. Thanks!

The only trick would be to pass in a custom class to every radio button, compared to the outdated version:

<div class="w-3/5 mb-6">
    <%= label f, :color, class: "block mb-2 text-sm" %>
    <div class="flex gap-x-2">
      <%= for color <- ~w(gray red yellow green blue indigo pink purple) do %>
        <label class="relative cursor-pointer" phx-target={@myself} phx-click="set_color" phx-value-color={color}>
          <div class={"inline-block w-8 h-8 #{color}-bg rounded-full"}></div>
          <%= radio_button f, :color, color, class: "hidden" %>
          <%= if @current_color == color do %>
            <span class="absolute z-10 inline-block w-4 h-4 text-white top-1 left-1/2 -translate-x-1/2">
              <i class="fas fa-check"></i>
            </span>
          <% end %>
        </label>
      <% end %>
    </div>
    <%= error_tag f, :color %>

Maybe, it will be easier with Petal, - Forms (v2)