Phoenix LiveView dynamic select tags in form troubles

2.2k Views Asked by At

In a phoenix LiveView form with 3 select tags, the first influences the other two, i.e. the dependent two should only display the options available for the first one, like so:

<%= f = form_for @changeset, "#", id: "eval-form", phx_target: @myself, phx_change: "validate", phx_submit: "save" %>
  <%= select(f, :item1_id, Enum.map(@item1s, fn {_,v} -> {v.name, v.id} end), prompt: "Choose item1...") %>
  <% item1_id = @changeset |> Ecto.Changeset.get_field(:item1_id) %>
  <%= select(f, :item2_id, item1_id && Enum.map(@item1s[item1_id].item2s, &{&1.name, &1.id}) || []) %>
  <%= select(f, :item3_id, item1_id && Enum.map(@item1s[item1_id].item3s, &{&1.name, &1.id}) || []) %>
</form>

When item1 gets chosen, item2 and item3 select tags do include the correct respective options, but display no chosen item (as they do not include a prompt, the first option should be selected). The changeset does not include changes for item2 and item3 (which matches what is displayed). However, after item2 is chosen, item3 displays the first option as chosen (as intended). After that, the changeset does include item1 and item2 changes, but item3 selected option is not there.

Questions:

  1. How to get the dependent select tags (item2 and item3) to show their respective first option as chosen after a change to the first select (item1)?
  2. How to get the 'default' options from the dependent select tags into the changeset?
2

There are 2 best solutions below

0
On BEST ANSWER

I solved the problem with 'two phase changeset casting'.

When handling phx-change event, firstly the item1 attribute is cast and validated. Next, item2 and item3 attributes are changed if needed (i.e. item values and selected value). Finally, item2 and item3 attributes are cast and validated.

It seems that this also works for any dynamic forms (e.g. in my case when item1 is selected, items for item2 are set, when item2 is selected additional fields appear that are needed for the item2).

I'm open to suggestions if there is a better way of doing this.

1
On

Have you tried using the value option for select tags? Like this:

<%= item2_values = item1_id && Enum.map(@item1s[item1_id].item2s, &{&1.name, &1.id}) || [] %>
<%= select(f, :item2_id, item2_values, value: @changeset |> Ecto.Changeset.get_field(:item2_id, List.first(item2_values))) %>

However, I think you should do this logic in your LiveView module. If you have a phx-change event being fired from your form, you could actually assign item2_values to the socket in a clean function.