LiveView phx-change attribute does not emit event on input text

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

2 Likes

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.

1 Like

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.

1 Like

Hi :wave:

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 :slight_smile:

2 Likes

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.

2 Likes

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.

1 Like

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:

  1. If the input event applies, queue a task to fire a simple event that bubbles named input at the input element.
  2. If the change event applies, queue a task to fire a simple event that bubbles named change at the input 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.

5 Likes

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

2 Likes

And also the relevant section here: Form bindings — Phoenix LiveView v0.17.10