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 ?
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.
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.
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:
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>
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.