Feature Request: Ignore "change" events of specific form inputs in LiveView

Hey folks, I have the unique problem that I need to ignore all “change” and “input” events for one specific input element in a LiveView Form because these events are handled by a 3rd party JS library. That library (QuillJS) adds its own eventListener to the input field which creates a race condition with LiveView’s eventListener.

An example:

<.form for={@form} phx-change="validate">
  # Changes to this input should send the "validate" event.
  <.input form={@form[:name]} type="text" />

  # Changes to this input should be ignored and not send the "validate" event.
  <select contenteditable="false">
    <option value="foo">Foo</option>
    <option value="bar">Bar</option>
  </select>
</.form>

I tried these solutions, but none of them worked:

  1. Put phx-update="ignore" on the select input.
    • The phx-update flag only ignores changes coming from the server, not from the client to the server.
  2. Put a custom phx-change="ignore" attribute on the select input.
    • This still sends an “ignore” event to the server which sends back an update which breaks my 3rd party library (quill editor calling highlight.js for syntax highlighting)
  3. Put onchange and oninput attributes with an event.stopPropagation() or event.preventDefaults() call on the select input.
    • These don’t seem to have an effect. I guess because LiveView puts an form.on("change", fn) listener on the form.
  4. Add a custom eventListener to the select-input which calls event.stopPropagation() or event.preventDefaults().
    • Had no effect. The “validate” event was still sent to the server.
  5. Remove the `phx-change=“validate” from the form altogether.
    • That worked, but now I can’t validate the form before it’s submitted.

Ideally, LiveView would respect an phx-update="ignore" attribute and ignore any “change” and “input” events from that input, which means not sending a “validate” event back to the server.

The code in question

Prior discussions about this topic

2 Likes

When using Quill in the past, I’ve had success with something like

<div id="editor_holder" phx-update="ignore">
  <div
    id="editor"
    phx-hook="QuillHook"
    phx-target={@myself}
    data-hidden-input-delta="schema-form_delta_stringify"
    data-hidden-input-html="schema-form_html"
    data-initial-delta={f.source.data.delta_stringify}
  />
  <%= hidden_input(f, :delta_stringify) %>
  <%= hidden_input(f, :html) %>
</div>

where the hook handles the updates

That’s not quite the problem I’m facing. The problem is with the new “language” select of the code syntax highlighter. That one adds a select dropdown and listens for changes. However, these changes also propagate to LiveView which then validates the form and sends a response, all before Quill could update the code data-language attributes and re-highlight the code.

That’s not what phx-update is about though. phx-update defines the behaviour how changes (DOM updates) coming from the server are applied locally. That’s a completely separate thing to event listeners.

Sounds like LV should ignore input events from inputs without names. Their values won’t make it to the server anyways, because inputs without names don’t send data within a form.

2 Likes

That’d be solution indeed! I’m just concerned that this might lead to “bugs” where a field isn’t sent because somebody forgot the name attribute. I mean, it’s an error for sure, but maybe something explicit would be better? For example another attribute like:

<select phx-change-ignore={true}>

That’d be 100% explicit and a small change that only affects this specific use-case.

An input without a name would already not be sent, because there’s no key. You cannot encode what doesn’t exist.

1 Like

You can leverage this fact to improve rendering speed when a page has a lot of inputs.

What do you think would be a good next step to get some feedback on this. Maybe ask @chrismccord or @josevalim? Or should I directly submit a PR?

I’d start with an PR ignoring events of unnamed inputs. This seems pretty uncontroversial given no new data would be sent anyways.

All LiveView events sit on window, so you should able to intercept any event and stopPropagation/stopImmediatePropagation to prevent it bubbling to LiveView. Sounds like your previous attempts at doing this weren’t wired up properly. Keep in mind the "input" event is what is typically triggered for standard inputs, while "change" is for selects, checkboxes, radios. That said some input types dispatch both events depending on the scenario, so you will likely need to handle both types. I don’t think LV needs any special feature here since it should be easy to prevent the bubbling up as we sit all the way at the top on window.