Phoenix_live_view: phx-change event not triggered on input change.

Created on 3 Apr 2020  路  9Comments  路  Source: phoenixframework/phoenix_live_view

I have a liveview form and a text input:
<%= search_input :filter, :search, id: "filter_search_field", class: "input", placeholder: "Search", value: @search_field, "phx-debounce": "300"%>

Now there is a moment when I want to clear this field programatically, I do this by changing the assign:

{:noreply, assign(socket, :search_field, "")}

The problem with this is that the phx-change event assigned to the form is not triggered.

I also tried to create a JS hook and change the value of the field from the hook, however that does not emmit the event either.

Is there a workaround for this situation?

Most helpful comment

I was able to achieve what you want with a phx-hook added to the Reset button

<!-- index.html.leex -->
<a phx-click="reset" phx-hook="TestHook">Reset</a>
// app.js
Hooks.TestHook = {
  updated() {
    let text_input = document.getElementById("filter_text_input")
    text_input.dispatchEvent(createEvent("change"));
  },
}

let createEvent = (name) => {
  const e = document.createEvent("Event");
  e.initEvent(name, true, true);
  return e;
}

This will trigger the form's phx-change event when you click the Reset button.

All 9 comments

Hi @D4no0, please fill in the issues template with as much information as you can. In particular, if you can provide a small application that reproduces the issue, it will make it much easier for us to understand what is happening. Thanks!

Hi @D4no0. form changes and submissions, using the phx-change and phx-submit events, are handled at the form level. See https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#module-form-events

I understand what you imply. What I want could be achieved using a changeset and using submit as a reset.
What I want to know, can this be achieved without using a changeset? The changeset does not seem relevant in this case.

I was able to achieve what you want with a phx-hook added to the Reset button

<!-- index.html.leex -->
<a phx-click="reset" phx-hook="TestHook">Reset</a>
// app.js
Hooks.TestHook = {
  updated() {
    let text_input = document.getElementById("filter_text_input")
    text_input.dispatchEvent(createEvent("change"));
  },
}

let createEvent = (name) => {
  const e = document.createEvent("Event");
  e.initEvent(name, true, true);
  return e;
}

This will trigger the form's phx-change event when you click the Reset button.

Thanks a lot, it seems this will do for now, at least until form will be extended to receive simple values, not only changesets.

You're welcome.

The change event is fired from the Javascript side; for example, here's how&when a textinput will fire a change event:

When the element loses focus after its value was changed, but not commited (e.g., after editing the value of input type="text").

Source: HTMLElement: change event

So, when you change an input's value from LiveView, you "edit" the value, but you can't actually "lose focus" since that's inherently client-side, so you need to manually fire the event.

I also managed to hook the form and push the event when the value of the form is changed, the downside of this is that when the user changes the form the event is emitted twice.

Solution given here by @sfusato was helpful to me while handling similar problem. However, I ended up with the following solution because of different requirements for my live_component:

  • the change event has to be triggered on demand, not on every update (update() is triggered multiple times which may lead to unwanted duplicate actions)
  • if multiple identical components exist in the page, the change event has to be triggered for the changed component only. Since the hook is the same, update() is triggered for every live_component.

In my case I have a custom dropdown (not using select tag) that triggers phx-change when an option is selected (options are button tags). So I used an hidden input, and it does not trigger a change event when value changes dynamically.

My solution:
I add the input tag with a hook. The id follows this pattern: <prefix>_<id of component> to be unique.

<!-- leex -->
<input phx-hook="FormChange" type="hidden" name="<%= @name %>" id="selection_<%= @id %>" value="<%= @selected_item.id %>">

``` js
Hooks.FormChange = {
mounted(){
let id = this.el.getAttribute("id")
console.log("id: " + id)
this.handleEvent(id, () =>
this.el.dispatchEvent(new Event('change', { 'bubbles': true }))
)
}
}

The drop down is made of buttons like this:
``` html
<button
  phx-click="select"
  phx-target="<%= @myself %>"
  phx-value-id="<%= item.id %>">
    <%= item.text %>
</button>

When an item is clicked, the "select" event is sent to the component and is handled by this code:

def handle_event("select", %{"id" => id}, socket) do
  {:noreply, push_event(socket, "selection_" <> socket.assigns.id, %{})}
end

With this solution phx-changeis called when changing the live_component. Moreover, even if there are several dropdown live_components in the page, the event is only fired once for the changed component since the hook will only emit the event when the id matches.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

josevalim picture josevalim  路  3Comments

LightningK0ala picture LightningK0ala  路  5Comments

glennr picture glennr  路  4Comments

jamilabreu picture jamilabreu  路  5Comments

gregjopa picture gregjopa  路  5Comments