Checkbox with many-to-many relationship

Hey @loics2 ,
Thanks for posting your solution as well.
I found a way to do it without a virtual field and additional decoration of the ecto data.

The error you described pops up as you described when you click a select. This is because in that event the “value” assign is suddenly a list of Ecto.Changeset. This changeset contains the updated tags in a data field, so you can use these to figure out the selection.

And in some update cycles the field value is not even a changeset, but only a list of strings (the selected ids).

So I wrote a helper function to pick the right values:

  defp pick_selected( assigns ) do
     assigns.value
      |> Enum.map(fn x->
           case x do
             %Ecto.Changeset{ action: :update, data: data } ->
               data.id
             %Ecto.Changeset{} -> nil
             %{id: id} ->
               id
             x when is_binary(x) -> if x == "", do: nil, else: x
             _ -> nil
           end
         end)
      |> Enum.filter( &!is_nil(&1))
  end

And this is how you use it, first line in described checkgroup:

   def input(%{type: "checkgroup"} = assigns) do
    assigns = assign( assigns, :selected, pick_selected(assigns) )
    
    ~H"""
    <div phx-feedback-for={@name} class="text-sm">
      <.label for={@id}><%= @label %></.label>
      <div class="mt-1 w-full bg-white border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
        <div class="grid grid-cols-1 gap-1 text-sm items-baseline">
              <input
                type="hidden"
                name={@name}
                value=""
              />
          <div class="flex items-center" :for={{label, value} <- @options}>
            <label
              for={"#{@name}-#{value}"} class="font-medium text-gray-700">
              <input
                type="checkbox"
                id={"#{@name}-#{value}"}
                name={@name}
                value={value}
                checked={value in @selected}
                class="mr-2 h-4 w-4  border-gray-300 text-indigo-600 focus:ring-indigo-500 transition duration-150 ease-in-out"
                {@rest}
              />
              <%= label %>
            </label>
          </div>
        </div>
      </div>
      <.error :for={msg <- @errors}><%= msg %></.error>
    </div>
    """
  end

3 Likes