alexslade

alexslade

I’ve got a custom button in a form (choosing an image from a library). I’m struggling to write an event handler for that button. I started with update_params/3 as this seems to indicate that it should be used for such a situation.

This can be useful for things like customized inputs or buttons, that have special handlers in your live view. For example, if you have an appointment that expresses a list of available times in the UI, but the action just takes a single time argument, you can make each available time a button, like so:

<.button phx-click="time-selected" phx-value-time="<%= time %>" />

That function works if the form has been interacted with in some way (e.g. change handler that has populated “params” on the form).

But if this button click is the first interaction, the form’s params have not been established yet, which causes issues when update_params does validate etc which seems to expect a full list of params for the form. For example, if there is some other form field that is required (in my case: “Name”), after running update_params that field will now be blank and will fail validation, and try to clear that field.

I’ve tried the only_touched option and some other functions, like using “validate” with partial params, but I can’t find a solution.

I suspect I’m misunderstanding how the form should be used. Any tips?

Showing Posts 1 to 6

zachdaniel

zachdaniel

Creator of Ash

Hmm…yeah, this may be a bug. As a test, you could try something like this before calling update_params

params = AshPhoenix.Form.params(form)
form = AshPhoenix.Form.validate(form, params)
# update params
alexslade

alexslade OP

Would nested forms / embedded resources be causing this? (I’ve tried including fields that are not nested, and they also don’t appear in AshPhoenix.Form.params(socket.assigns.form))

(Again, this is only if socket.assigns.form is the form assigned on mount. If I’ve interacted with the form via a validate function or something (passing params from the phx-change action) then params are present)

A specific question: Would you expect a form to have params if it’s straight out of AshPhoenix.Form.for_update(socket.assigns.current_user, :update_settings) ?

Here is my simplified code if it helps. I’ve also pasted a gist (look in handle_event("choose_profile_photo" for the URL) to give some dbg content.

  @impl true
  def mount(_params, session, socket) do
    form = AshPhoenix.Form.for_update(socket.assigns.current_user, :update_settings)

    socket
    |> assign(:form, form)
    |> ok()
  end
  def render(assigns) do
    ~H"""
    <div>
      <.form
        :let={f}
        for={@form}
        id="account-form"
        phx-submit="save"
        phx-change="validate"
        class="max-w-2xl space-y-4"
      >
        <.error :if={Enum.any?(@form.source.errors)}>
          Oops, something went wrong! Please check the errors below. %>
        </.error>

        <.inputs_for :let={settings} field={f[:settings]}>
          <div>
            <.label>First Name</.label>
            <.input field={settings[:first_name]} />
          </div>
          <div>
            <.label>Profile Photo ID</.label>
            <.input field={settings[:profile_photo_id]} />
          </div>
          <div>
            <.label>Profile Photo</.label>
            <div class="p-4 border border-gray-200 rounded shadow-md">
              <.live_component
                module={WinkWeb.EditorComponents.FilePicker}
                id="profile-photo-picker"
                files_context={@files_context}
                selected_file={@form.source.data.settings.profile_photo_id}
                file_chosen_callback="choose_profile_photo"
                file_position="profile"
                allowed_types={["asset"]}
                current_view="Assets"
              />
            </div>
          </div>
        </.inputs_for>
        <.button type="submit">Save Changes</.button>
      </.form>
    </div>
    """
  end
  @impl true
  def handle_event("choose_profile_photo", %{"file-id" => file_id}, socket) do
    params = AshPhoenix.Form.params(socket.assigns.form)
    dbg(params)
    # params #=> %{"_form_type" => "update", "id" => "47f9713d-499a-4079-b35b-adba01397a0a"}

    dbg(socket.assigns.form)
    # gist - https://gist.github.com/alexslade/2ea996d74394b3a344cf0f72e8a0c892

    socket
    |> noreply()
  end

actions do 
  ...
    update :update_settings do
      accept [:settings, :email]
    end
  ...
end
zachdaniel

zachdaniel

Creator of Ash

:thinking: no, the params wouldn’t be there at first because only_touched? defaults to true when generating params. Try doing params = AshPhoenix.Form.params(form, only_touched?: false) on that first button click. It’s possible that what you may need to do is, on mount, if you want this to work, validate the form with those full params.

alexslade

alexslade OP

Adding only_touched?: false gives me a new “settings” key but without any fields. Compare to if the form has previously gone through a validate call (phx-change="validate") which sends all the form payload to get the params set up .

   
  def handle_event("choose_profile_photo", %{"file-id" => file_id}, socket) do
    # TODO: This is a temporary hack implementation, I've got an open question on the forum about how to handle this properly.
    # https://forum.elixirforum.com/t/using-validate-with-partial-params/71334
    params = AshPhoenix.Form.params(socket.assigns.form, only_touched?: false)
    dbg(params)

    socket
    |> assign(:chosen_image_id, file_id)
    |> noreply()
  end

Without a validate step:

    params #=> %{
      "_form_type" => "update",
      "id" => "47f9713d-499a-4079-b35b-adba01397a0a",
      "settings" => %{"_form_type" => "update"}
    }

With a change to “email”

params #=> %{
  "_form_type" => "update",
  "_touched" => "email,settings",
  "email" => "alex@upmailsolutions.coms",
  "id" => "47f9713d-499a-4079-b35b-adba01397a0a",
  "settings" => %{
    "_form_type" => "update",
    "_persistent_id" => "0",
    "_touched" => "_form_type,_persistent_id,_touched,_unused_date_format,_unused_first_name,_unused_profile_photo_id,_unused_time_format,_unused_timezone,date_format,first_name,profile_photo_id,time_format,timezone",
    "_unused_date_format" => "",
    "_unused_first_name" => "",
    "_unused_profile_photo_id" => "",
    "_unused_time_format" => "",
    "_unused_timezone" => "",
    "date_format" => "%d/%m/%Y",
    "first_name" => "Alex",
    "profile_photo_id" => "5",
    "time_format" => "%H:%M",
    "timezone" => "UTC"
  }
}

zachdaniel

zachdaniel

Creator of Ash

Yeah, so the problem effectively is that there is a sort of bidirectional communication here. The underlying form data structure doesn’t know what fields you are or are not going to include in the UI, those fields get their defaults from the form, and then on type the client sends it back to us. Until then, we don’t have a full picture of what params looks like. It isn’t ideal.

You could likely do this with a bit of js on the button.

phx-click={JS.dispatch("validate", to: "#your-form") |> JS.push("time-selected", value: %{time: time})}

EDIT: "validate" probably wouldn’t be what you want, you’d want to trigger some input as if it was typed into potentially, or find some way to trigger the phx-change hook etc. I’m sure it can be done but I can’t recall how :laughing:

alexslade

alexslade OP

Thanks for confirming. I’ll find another way around. Likely modifying the form inputs for now, as we don’t need validation when this image is changed. I also think our form should be factored to split this stuff up more, instead of having complex state across many entities with different validation rules, so we’ll do that if we need to make this more sophisticated.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews