phoenixframework / phoenix_html

Building blocks for working with HTML in Phoenix

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

selected attribute on multiple_select not respected at times

timgent opened this issue · comments

Summary

  • The selected attribute on a multiple_select is not respected if form has a params attribute populated, as params are used in preference
  • This is the case when the changeset for the form has changes on it
  • This is particularly an issue with Liveview, as you expect the changeset with the form to be updated as changes are made

Steps to reproduce

git clone https://github.com/timgent/many_to_many_liveview_issues
cd many_to_many_liveview_issues
mix deps.get
mix phx.server
  • Navigate to http://localhost:4000/
  • Make any update to the form
  • You will get this kind of error: ** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for %{address_id: "2"} of type Map
  • In the example project above lib/many_to_many_liveview_issues_web/live/demo.ex handle_event function has some notes about why the params are being updated in order to get a properly updated changeset

Thoughts on the cause for this

  • As you can see in the below code (and in the repo here), if the param for the given value is found in the form params then it will use that to get the selected attribute for the multiple_select, ignoring the selected option that has been passed in
  • Updating from {sent, opts} to {selected || sent, opts} fixes the issue in the use case I shared above (though causes some other tests to fail)
  • I'm happy to submit a PR for this and look into the failing test, but wanted to first check this is a legitimate bug and if my thinking in terms of the root cause is correct
  defp selected(form, field, opts) do
    {value, opts} = Keyword.pop(opts, :value)
    {selected, opts} = Keyword.pop(opts, :selected)

    if value != nil do
      {value, opts}
    else
      param = field_to_string(field)

      case form do
        %{params: %{^param => sent}} ->
          {sent, opts} # <======================= I believe this line is the issue

        _ ->
          {selected || input_value(form, field), opts}
      end
    end
  end

I think the issue is the updating of the params. multiple_select expects a list of IDs in the params but you are changing it into a map. Ultimately I think using cast_assoc is the wrong approach here (it is usually used together with inputs_for). You probably want to go the put_assoc route shown here: https://hexdocs.pm/ecto/associations.html

Thanks for the quick reply, I can confirm using put_assoc fixes the issue for this example. And I now appreciate changesets keep track of the params used to update them, I'd previously assumed just the data and changes were important so far as forms went.

Also realise it does actually say this in the docs, just that I'd not spotted it. Always learning! Thanks again

The :selected option will mark the given IDs as selected unless the form is being resubmitted. When resubmitted, it uses the form params as values.