Triggering LiveView Form Change Event from Javascript

I am using LiveView to render a form and a phx_change event to send updates back to the controller. I’m also using an extra library (Select2: https://select2.org/) as a more robust select field control for a select input on the form. I have a JavaScript event listener tied to the Select2’s change event so that I can perform certain actions, and I’d like in my event listener to then be able to push the phx_change event back to the LiveView.

I’ve inspected the change event payload and noted it’s like any other event and just contains an encoded chunk of the full form data, so I can replicate that behavior in my own code if need be, but ideally I’d like a LiveView JS-interop way to do this programmatically. Something either like this.pushFormChange() or finding the form instance and making a trigger call on the event that the JS interop has bound to, but so far I’ve been unable to find anything either in the documentation, in the source code, or in some trial-and-error that works the way I want.

Is this something that’s possible or that anybody has done? Alternatively, is my approach simply wrong here?

1 Like

via a hook on the input tag:

To trigger a phx-change:

this.el.dispatchEvent(new Event("input", {bubbles: true}))

To trigger a phx-submit:

this.el.dispatchEvent(new Event("submit", {bubbles: true}))

19 Likes

This is exactly what I had been searching for and dancing around without quite finding it. Thank you.

I’m trying to do the same but I’m running into a very pesky error where the hook dispatches the event just fine but LiveView appears to be crashing for some other reason. I am having a hard time debugging since this seems to be in the guts of LV.

Any help would be appreciated – I realize this is not much to go off of but maybe someone has experienced the same. Thank you!

Uncaught TypeError: Cannot read property 'querySelectorAll' of null
    at Object.all (phoenix_live_view.js:1716)
    at Object.showError (phoenix_live_view.js:1868)
    at eval (phoenix_live_view.js:2845)
    at Object.eval [as callback] (phoenix_live_view.js:2702)
    at eval (phoenix.js:232)
    at Array.forEach (<anonymous>)
    at e.value (phoenix.js:231)
    at Object.eval [as callback] (phoenix.js:250)
    at e.value (phoenix.js:405)
    at Object.eval [as callback] (phoenix.js:292)

You might need to post some code. Are you sure the element exists or the ID is correct? Cannot read property 'querySelectorAll' of null implies something is trying to call element_but_its_null.querySelectorAll.

1 Like

@chrismccord Have been searching for this answer for some time too. Seeing the answer got 12 likes, I suppose I was not the only one. Maybe it’s a good idea to add it to the JavaScript interoperability — Phoenix LiveView v0.15.5? That is were I tried to find the answer.

I had to use this function one for input with phx-debounce="blur"

function fireInputChangeEvent(input) {
    input.dispatchEvent(new Event("input", { bubbles: true }));
    if (input.phxPrivate && input.phxPrivate["debounce-blur"]) {
        setTimeout(() => input.dispatchEvent(new Event("blur", { bubbles: true })));
    }
}

To be sure, basically phx-change on form element has behaviour that live_view is listening to all of these events: onchange, oninput, onblur inside given form element.

Is there some way to recognize which event triggered the phx-change (input,change or blur) ? I mean in live_view handle_event("phx-change-event")

thanks

This works well in Webkit and Chromium-based browsers, but I’m striking an issue with Firefox: Firefox insists on submitting the form over HTTP when I dispatch a submit event to the form. This is the code (using AlpineJS, I’m attempting to submit the popover form when the user clicks away):

<%= f = form_for @changeset, "#",
    id: "my_form",
    phx_submit: "submit",
    phx_target: @phx_target,
    phx_trigger_action: false,
    "x-data": "{show_form: true}",
    "x-show": "show_form",
    # Submit the form when we click off, and hide the popover
    "x-on:mousedown.outside": "show_form = false; $el.dispatchEvent(new Event('submit', {bubbles: true}))" %>

Anyone know how to prevent Firefox from actually submitting over HTTP?

$el.dispatchEvent(new Event(‘submit’, {bubbles: true , cancelable: true}))

How would I trigger a form phx_submit from a global hotkey?

IE ctrl + s anywhere on the page would trigger it via window.onkeydown.

Also reading the javascript interop docs, it looks like you add client side JS in app.js, which is loaded for every page? Why can’t you inline the JS into your specific view?