the params will contain a "_target"
which is the name of the form field which triggered the change, so you can use that to handle the city => state => etc flow
Thanks, my purpose solved.
If anyone comes to this thread for same situation ⦠select country, then state then city,
here is how I solved.
I did not need to create a liveview-component.
In my formcomponent.ex
def handle_event("validate", params, socket) do
form_title = "landmark"
landmark_params = params[form_title]
[form_title, target] = params["_target"]
target_value = landmark_params[target]
result = case target do
"city_id" -> add_regions(socket, target_value)
_ -> validate_form(socket, landmark_params)
end
{:noreply, result}
end
defp add_regions(socket, city_code) do .... assign(socket, :cities, calculated) end
defp validate(.... , ...) do validation_logic end
.
.
.
Any better solution, welcome.
Personally I find using hooks a perfectly acceptable solution for this situation, in conjunction with using pushEvent to trigger Liveview handlers. Maybe I donāt have the same amount of distaste for writing JavaScript as others, but when it comes to dom event handlers JS seems to work fine, especially for edge cases like gigantic data heavy forms. Itās the rendering step where LV is indispensable. The phx-* handlers seem more like conveniences than critical features of a Liveview template.
Hi
Reviving this post a little bit.
I am currently building a form that contains one auto-complete input (but without using the HTML5 <datalist />
as I want to style the list).
I managed to build it and generate the list of autocompletions using phx-change
binding on my form.
Now, I would like to extract this autocomplete input so I can re-use it in other forms.
So far, I have created a livecomponent for it, and pass the form builder struct to this new component. I have also writen a hooks to push an event to the live component on input
to refresh the suggestion list.
My issue is that while this work well, the change
of the parent form also triggers. And there is a concurrent update of the DOM which leads to super janky experience (the form blinks).
Is there a way to prevent the form to run phx-change
, from the autocomplete component ? I tried to use e.preventDefault()
and e.stopPropagation()
in my hook but it didnāt work.
I know there is a way to pattern match on the target in the handle_even("validate..")
for my form component, but this feels wrong to me. The parent should have to be aware of the input to āignoreā from a child component. This seems private to the autocomplte component.
Thanks in advance.
EDIT
I might have something quite elegant, using Live components block.
- The Form component (where the form
f
lives) now calls:
<%= live_component @socket, AutocompleteComponent, id: :some_id %>
<%= hidden_input f, :item_id, value: @selected_item.id %>
<% end %>
- The AutocompleteComponent basically does:
<%= if @selected_item do %>
<%= @inner_content.(selected_item: @selected_item) %>
<% end %>
<% text_input :unused_form_id, :query, phx_keyup: "search_item" %>
<%= unless @selected_item do %>
<%= for item <- @results do %>
<a phx-click="select_item" phx-value-id="<%= item.id %>" phx-target="<%= @myself %>">
<%= item.name %>
</a>
<% end %>
<% end %>
The logic of searching and selecting item is now self-contained into the AutocompleteComponent
, exactly what I wanted.
Thanks for you time if you read this post
phx_change
being only allowed on the form element causes me a lot of frustrations when working with live view.
Case:
I have a nested form (inputs_for
) listing a list of entities; you can add/edit/remove an entity.
I must attach phx-change
to the form to support editing all text fields; otherwise, if for example I add or delete an entity from that nested form, the changeset will not include the entered text. Therefore, for every text entered, I update the changeset.
Problem:
I was listening a checkbox in the same form through phx_click
, that when clicked some updates are done in the changeset. Changes seemed alright when logging but the view was not correct⦠after some research I noticed it conflicted somehow with that global phx_change
listener. The changes done after the checkbox click in phx_click
are immediately reset in the phx_change
event. So there was no other choice then to move all the code handling that checkbox in phx_change
(and imagine having an increasing amount of actions controlled by form inputs for which you have to change the changeset⦠the listener on form changes will get huge). Iām still busy on that now but I find the code became horribly complex.
Problem with putting such code into phx_change
is that itās not trivial to know whether the checkbox changed its state. Have to take the changeset and compare with the form params, before creating the new changeset from the params; and this for every other such inputs.
You can add phx-change behavior to individual inputs by using the form attribute
Example:
<form phx-change="individual_event" id="individual_form">
</form>
<form phx-change="save" id="product_form">
<input type="text" name="product[name]">
...
<input type="text" id="individual_input" form="individual_form">
</form>
We added a separate form (outside the main form) for the individual input. When you now change the value of the #individual_input, the āindividual_eventā will be emitted.
Thatās some form of hack, which shows that something is wrong when you need to resort to such things.
But when you submit the product_form
you wonāt get the data from individual_input
with it, because technically they belong to the individual_form
Itās unfortunate that phx-change
is not supported on the input level.
I need to add an autocomplete field to the existing form in eex template. I expected it to be as easy as inserting render_live
to render a LV component inside a form. Now I know that it wonāt work without attaching phx-change
to the form, which cannot be done without rewriting the whole action from controller to LV. So Iām kind of stuck right now, exploring other options.
I played with phx-change
on a form and I cannot say I understand it. On a complex form with many fields (data, filters, checkboxes, etc) it generates a lot of noise, and you have to parse params to understand whatās happening. Wrapping each element in its own form makes you wonder why then do you need a form element at all, if it becomes just a stub for a hook. Maybe I just need some enlightenment
Thereās phx-trigger-action
which allows your form to trigger a standard form submit to a controller and still use LV.
By parse do you mean pattern match? You can be as granular as you want and have a handle_event
callback for each formās field if thatās what you want. Or ignore some fields. Check the _target
key in the eventās payload.
My point being you can already achieve a phx-change
like effect at the input level.
Chiming in here with several use cases we have run into while developing our app.
We use Surface heavily which pushed us a lot into using live_component
. Which worked great⦠Until we realized that we cannot handle any input changes inside our live_component
without adding a form. And as soon as we try to put live_component
inside another form it all breaks as forms cannot be nested based on HTML5 specification.
We have many autocomplete and select fields and need to resort to writing lots of custom hooks with push events through alpine just to work around it.
I do feel that it shows that the current implementation of live_component
needs more work. Specifically, the change
event based on HTML5 specification is raised on the element, not on the form. It only bubbles to the form:
When the user agent changes the elementās value on behalf of the user (e.g. as part of a form prefilling feature), the user agent must follow these steps:
- If the
input
event applies, queue a task to fire a simple event that bubbles namedinput
at theinput
element.- If the
change
event applies, queue a task to fire a simple event that bubbles namedchange
at theinput
element.
A simple use case that is trivial in React or even plain javascript seems quite problematic to make work with live_component
.
We have a live_component
with pairs of select input and text input. When a certain select value is selected the input field should be hidden. Right now we hack it with this:
<Select
class="w=1/3 border bg-white rounded py-2 outline-none"
options={space_type_options(index)}
opts={
"x-model": "selectedType",
id: "subpremiseSelector2",
"phx-hook": "Inputs.SubpremiseInputs#subpremiseInputs",
"x-on:change": "subpremiseInputs.pushEventTo($el, 'type_changed', $el.value)"
}
/>
<div data-show={@show} x-show="selectedType!=$el.dataset.show">
<Field name={:value}>
<TextInput class="border bg-white rounded py-2 outline-none" opts={hook: "subpremiseInputs"} />
<ErrorTag />
</Field>
</div>
# the hook
let subpremiseInputs = {
mounted() {
window.subpremiseInputs = this;
},
};
window.subpremiseInputs = subpremiseInputs;
export { subpremiseInputs };
Being able to just have phx-change
on select and handle_event inside the live_component
would remove the need for custom hook hacking and adding alpine magic where its not necessary needed.
Is there any news about this topic?
Since liveview 0.17.8 you can have phx-change
on individual input elements, see changelog: phoenix_live_view/CHANGELOG.md at master Ā· phoenixframework/phoenix_live_view Ā· GitHub
And also the relevant section here: Form bindings ā Phoenix LiveView v0.20.17