Trigger phx-change from JS.dispatch

in a form, using js.dispatch I can manually change input fields or checkbox with

js:set_input_value

and

js:set_checked

now if I want to save the form, what’s been changed by JS.dispatch
did not round trip to the server and so did not trigger a validate and so is not in the changeset, and so is lost.

I need to manually change some input in the form to trigger the normal chain and keep what’s been programmatically changed by JS.dispatch

any way to trigger the phx-change through JS.dispatch ?

Js.dispatch(“input”, to: “#idform”)

or

Js.dispatch (“phx-change”, to “#idform”)

does not work

You may need to use execJS here. Check out the example in the documentation here (last example): JavaScript interoperability — Phoenix LiveView v0.19.3

You can trigger forms events using a couple more lines of javascript: Form bindings — Phoenix LiveView v0.19.3
I think that the first example is what you are looking for.

ok thanks for your answer
but can you do this with JS.dispatch, so as to not write any javascript directly?

Just change the id you’re sending to match the modified input, not the form id.

JS.dispatch("input", to: "#input_id")

Can you elaborate more on why you’re manipulating forms with JS? I feel like a lot of this would be a lot easier if you were driving this state from the server side where liveview state is supposed to generally live.

2 Likes

Js.dispatch(“input”, to: “#input_id”)
Js.dispatch(“input”, to: “#input_id”, bubbles: true)

does not work

this is a long form, there are buttons to shortcut the answer of entire blocks, with predefined answer.

the button triggers a Js.dispatch sequence on all concerned input fields or checkbox, putting the predefined answer in each. this works ok but then no phx-validate event is triggered so upon saving all is lost (except if you additionally manually change any field before saving)

the idea was to avoid round trip for each intermediate element of a block that is being filled by javascript,
and round trip only at the end of all dispatches

in form
<a href="#" phx-click={zero_fill()}>zero fill</a>

in live view

  def zero_fill(js \\ %JS{}) do
    input_elements = ["#id1", "#id2", "#id3"]
   checks = ["#id4", "id5"]
    js
    |> zero_input(input_elements)
    |> docheck(checks)
    |> JS.dispatch("input", to: "#id1")

end
 def zero_input(js, elements) do
        elements
        |> Enum.reduce(js, fn x, acc ->
          JS.dispatch(acc, "js:set_input_value", to: x, detail: "0")
        end)
      end
   def docheck(js, elements) do
        elements
        |> Enum.reduce(js, fn x, acc ->
          JS.dispatch(acc, "js:set_checked", to: x, detail: "true")
        end)
      end

Hmm, just to clarify, handling autofill on the server side should usually be one round trip/re-render each time a user clicks on a button to autofill a block, not once for every input field in an autofilled block.

yes that’s the idea,
I used Js.dispatch instead of a regular callback in the live view that would actualise the changeset once for a bunch of selected items to modify
because with a regular call back I need to manipulate the param of the entire form with all elements

I thought it was simpler to just set the value I need in the inputs I need as if someone had entered them
but then no phx-change after a JS dispatch

The only real advantage of autofilling from the client side would be an arguably negligible quicker autofilling of the form inputs from a UX standpoint. Since you want to run through server side validations anyways, there will be at least one round trip to the server so it’d be much cleaner to autofill by manually pushing the default "validate" event handler to create a new changeset with the autofill params and then updating the socket’s form assign from the server and triggering a re-render all at once.

# manually push a `"validate"` event with the autofill params
<div phx-click={JS.push("validate", value: %{order: @address_autofill_params})} phx-target={@myself}>
<div phx-click={JS.push("validate", value: %{order: @payment_autofill_params})} phx-target={@myself}>

  # default autogenerated `"validate"` LiveComponent event handler for form components
  def handle_event("validate", %{"order" => order_params}, socket) do
    changeset =
      socket.assigns.order
      |> Showcase.change_order(order_params)
      |> Map.put(:action, :validate)

    {:noreply, assign_form(socket, changeset)}
  end

Probably unnecessary, but you could try combining client side autofilling and server side autofilling by chaining them together:

<a href="#" phx-click={zero_fill()}>zero fill</a>

  def zero_fill(js \\ %JS{}) do
    input_elements = ["#id1", "#id2", "#id3"]
   checks = ["#id4", "id5"]
    js
    |> zero_input(input_elements)
    |> docheck(checks)
    |> JS.push("validate", value: %{order: @address_autofill_params}, target: @myself)
end

that would work for the specific automatic change on #id1, #id2, #id3 but that would discard any manually other changed field by the user before clicking on the button.

that’s why I wanted to trigger the regular form phx-validate which takes everything into account

the only way working so far is manually change the form after clicking on the automatic button

also tried with hooks but no luck

Hooks.validate = {
 mounted() {
    this.el.addEventListener("click", e => {

  # target the next input available, since any input change triggers form validate
 # the input event is fired on the input element but no form validate follows  
  let input = e.target.parentElement.parentElement.querySelector("input");
    input.dispatchEvent(new Event("input", {bubbles: true}));
    })
  }

}
;

in form
<a href="#" phx-click={zero_fill()} phx-hoox="validate">zero fill</a>

Ahh, the other changed fields could be retrieved from the Phoenix.HTML.Form struct stored in the socket under the form assign.

ok that works

but still, I would have preferred to do it without manipulating the form params

Hmm, you could implement the “checkbox disguised as a button” design pattern used to dynamically add/remove nested forms inputs in the latest LiveView generators.

<label class="block cursor-pointer">
  <input type="checkbox" name="order[autofill_address][]" class="hidden" />
  Autofill Address
</label>

<label class="block cursor-pointer">
  <input type="checkbox" name="order[autofill_payment][]" class="hidden" />
  Autofill Payment
</label>

This approach means you wouldn’t need to manually configure a phx-click since clicking the autofill “checkbox disguised as a button” would simply check a checkbox input which will directly trigger the phx-change form validation. Then on the server, you’d just merge in the autofill params based on the state of the autofill checkboxes in the form params.

On another note, you can try dispatching a "change" event instead based off of the @sfusato reply here: phx-change event not triggered on input change. · Issue #754 · phoenixframework/phoenix_live_view · GitHub