`phx-click` and JS `onclick` together

Here’s one that should be easy, but I haven’t found summarized nicely.

I’ve got fairly standard LiveView application, but some of the elements need a bit of JavaScript for juicy interactive purposes (not standard controls, animations, etc.)

I’d like to have a button, which triggers a simple phx-click event, also fire some onclick javascript. However, these don’t work together (which I should have suspected). Is there a simple way of making these work together in tandem?

I’ve thought about just calling the JS event, then firing off of the phx-click from JS, but I don’t see a clear way of doing that without some server-side interaction, and I’ve just been using document.getElementById(my-phx-button").click() to send Phx event programatically from JavaScript so far.

Can anybody point me in the right direction?

Cheers!
Mis

I think you are going to want to look into JS.push/1: Phoenix.LiveView.JS — Phoenix LiveView v0.17.9

You can pipe JS commands together to have a combination of client-side and server-side interactions.

You may also be interested in phoenix Hooks for more control over client-side interaction if necessary

You can also use dispatch Phoenix.LiveView.JS — Phoenix LiveView v0.17.9 to send events to other DOM elements. For instance, to dispatch a form submit event and hide an element when Enter is pressed on a date input field:

<%= date_input f, :date,
phx_keydown:
  JS.dispatch("submit", to: "#my-form")
  |> JS.hide(to: "#element-i-want-hidden"),
  phx_key: "Enter"
%>

Thanks Johnny! I had to upgrade my LiveView version, which made JS. commands available, and I can use a combination of JS.push and JS.dispatch to achieve my desired effect.

However, this appears to break my document.getElementById("my-phx-button").click() hack. Clicking on the button programmatically no longer sends the event. I need both, since my interface has two ways of triggering the event - a button click and a gesture. Now, clicking the button sends the event and plays the gesture animation, but the gesture no longer triggers the event.

I’ve looked quite thoroughly, but have not been able to find a clear way to send a synthetic event that isn’t tied to a hook or a form.

I see that my JS liveSocket object has a ‘dispatchEvent’ function, but this does not work:

liveSocket.dispatchEvent(new Event("submit_move", {foo: "bar"}));

What’s the secret sauce?

1 Like

Try this, I didnt test it, just basing on this reply Triggering LiveView Form Change Event from Javascript - #2 by chrismccord

document.getElementById("my-phx-button").dispatchEvent(new Event("click", {bubbles: true}))

Thanks, but that doesn’t work as expected. I want to send this event without interaction from an element or a hook.

A weird thing is that that does (sort of) work from the browser’s JS console, but it does not work when called from my application’s JS context.

Maybe you’re looking for push_event? To call client JS from the server as in:

{:noreply, push_event(socket, "scores", %{points: 100, user: "josé"})}

You can then handle that on a LV hook or on a window.addEventListener(...) on your app.js.

The docs are pretty good if you get into them, at least that’s what I found. It took me a little bit to understand, so I wrote this up. Hopefully not adding to any confusion…

Maybe that helps? Have you also seen in the docs: handling server-pushed events and there’s also a section on custom events that you might want?

I’ve personally found that this area of LiveView has allowed me to make magical experiences with almost embarrassingly little effort.

Thanks, this is helpful, but I do already do this for pushing events from the server to the client without problem, the issue here is that I want to do the opposite.

I want to push an event to the server from client-side JavaScript, outside of user interaction with a bound element.

If I understand you, maybe in the hooks, like:

Hooks.HoverCatch = {
    mounted() {
        document.getElementById("read-space").addEventListener("mouseover", e => {
            if (e.target.id != "read-space") {
                this.pushEvent("mouseover", e.target.id)
            }
        })
    }
}

Or:

Hooks.SelectText = {
    mounted() {
        this.el.addEventListener("select", e => {
            start = this.el.selectionStart;
            ending = this.el.selectionEnd;
            this.pushEvent("select_text", {
                start,
                ending
            })
        })
    }
}

Kinda sorta? Pushing to the server from a hook?

Okay! Yes! This led me to a solution that works.

There are a few things that I think cause some confusion, both from the nomenclature and the lack of documentation.

First of all, be mindful of the difference between dispatchEvent(new Event("click", {bubbles: true})) and pushEvent('yourhandler', {foo: "bar"}. dispatchEvent is for in-DOM events, like button clicks, and pushEvent is for events that Phoenix will handle.

The second thing is that you will need to use a combination of both events to make this work.

So, here’s everything you need to submit a Phoenix event from interaction-free JavaScript.

First, define a hook in your app.js like so:

let Hooks = {}
Hooks.EventSender = {
    mounted() {
        this.el.addEventListener("eventSender", e => {
            this.pushEvent("your_phx_event", {foo: "bar"})
        })
    }
}
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, params: {_csrf_token: csrfToken}})
liveSocket.connect()

Include that Hook on your template somewhere with a unique identifier:

<div id="sender_hook" phx-hook="EventSender"></div>

Then, to call it, send the DOM event in your JavaScript, which will be received by the hook, using the ID of the div you bound the hook to:

document.getElementById("sender_hook").dispatchEvent(new Event('eventSender'));

And this will send {foo: 'bar'} back to your your_phx_event handler on your server. Finally, note that if you are using this to have two ways of sending your phx events, one programmatic and one manual, you may need to use attributes or class membership as state to prevent recursion.

Thanks for the help, @malloryerik. I hope this helps anybody who finds it. (I also think the bit about using the Hook-bound object to send the event isn’t included in the documentation and probably should be.)

2 Likes