I am looking for a way to push an event from Javascript. In JavaScript interoperability — Phoenix LiveView v0.20.17 I can find pushEvent
in specific lifecycle callbacks.
But how could I push arbitrary data to the server?
Thank you
I am looking for a way to push an event from Javascript. In JavaScript interoperability — Phoenix LiveView v0.20.17 I can find pushEvent
in specific lifecycle callbacks.
But how could I push arbitrary data to the server?
Thank you
Maybe this conversation and the library attached will be helpful?
I’m not entirely sure what you mean by “pushing arbitrary data,” but I’m guessing you mean something unrelated to a typical user interaction event like a click and so on; is that right?
When I needed this I did create a hook to relay events to LV. A little awkward, but I remeber asking here if there is a better solution and nothing came up. Would be nice, if you could just push an event from JS to LV.
Hooks.RelayHook = {
mounted() {
relay = this;
document.addEventListener("relay-event", (e) =>
relay.pushEvent(e.detail.event, e.detail.payload)
);
},
};
...
window.dispatchToLV = function (event, payload) {
let relay_event = new CustomEvent("relay-event", {
detail: { event: event, payload: payload },
});
document.dispatchEvent(relay_event);
};
You can use the main container of the LV for the hook-element.
<!-- live.hmtl.heex -->
<main class="container" phx-hook="RelayHook">
...
<%= @inner_content %>
...
</main>
As mentioned above, live_json wraps this ability into a function:
import { sendData } from 'live_json';
sendData('your_handler', your_data);
Although it feels restrictive sometimes, organizing your javascript into a bunch of hooks, each corresponding to some element in the DOM, is a good thing. What do you mean by “push arbitrary data”? It has to be triggered by some event on some element, right? So you install event listeners at the mounted()
callback, pointing to some methods within the same hook then you can pushEvent
in your event listener.
When I did this I did not see a way to go the way you propose.
I needed to send updates to the server from a complex component written in JS.
You can dispatch a customEvent
, embedded with whatever data you like, from anywhere in the js. In your hook, you can install an event listener in the mounted()
call back to another method of the same hook. The customEvent
will land in your hook method and you can pushEvent
as usual.
I don’t get it. Isn’t that what I’m doing?
I was replying to this. If you already know how to do it then you can ignore my message. My point is: having to go through a hook is a good thing, for better code organization and also the tie in to the life cycle management of the liveview and the element of interest.
So I install event listener at the mounted()
callback:
import startDragging from "./mouse_dragging"
[…]
Hooks.MouseDrag = {
mounted()
{
this.el.addEventListener("mousedown", startDragging);
}
}
which works nice, I do all the element dragging around and at some point the element gets dropped ("mouseup"
). This is the moment where I’d like to push the final position back to the server. Now – if I understand correctly – what you suggest is to install additional listener(s) for a custom event, but at the “pointing to some methods within the same hook”
I am no longer sure I understand this. Could you post a quick example?
In the event listener in the hook, startDragging
, you call pushEvent
or pushEventTo
to send an event to the server.
https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook
You can only call pushEvent
from a method of the hook because you need the this
from it. So I was proposing a 3 step process:
mounted
of your hook, call another method of the hookthis.pushEvent()
from the methodThe following code use standard events, A customEvent should work similarly.
import {toByteArray} from "../../vendor/base64-js/index.js"
import * as Sanitizer from '../sanitizer.js';
export default {
xDown: null,
yDown: null,
attachments: [],
blobURLs: [],
controller: null,
textContainer: null,
htmlContainer: null,
mounted() {
this.controller = new AbortController();
this.el.addEventListener("touchstart", (e) => this.browseTouchStart(e.touches[0]));
this.el.addEventListener("touchmove", (e) => this.browseTouchMove(e.touches[0]));
document.addEventListener('keydown',
(e) => this.handleKeyDown(e.key),
{signal: this.controller.signal});
This file has been truncated. show original
In the event listener in the hook,
startDragging
, you callpushEvent
orpushEventTo
to send an event to the server.
This doesn’t seem to work, as once I am in my handlers I no longer have pushEvent
available. As @derek-zhou mentioned, the original this
is needed for “this” and is no longer there.
call another method of the hook
@derek-zhou what exactly do you mean by “another method of the hook”? Anonymous function attached to an extra (customEvent) listener on the original element?
Because if I do:
import startDragging from "./mouse_dragging"
[…]
Hooks.MouseDrag = {
mounted()
{
this.el.addEventListener("mousedown", startDragging);
this.el.addEventListener("mousedragfinished", function(e) {
console.log(e);
});
}
}
and in the handler:
draggedElement.dispatchEvent(new CustomEvent("mousedragfinished", {x: posX, y: posY}));
then I (for some obvious reason?) don’t seem to be catching this.
then I (for some obvious reason?) don’t seem to be catching this.
You need to dispatch to the same element as the addEventHandler
from your hook. Any element will do as long as they match. You can do document.dispatchElement()
and document.addEventHandler()
Well, if I attach my listener to a global element (like window
or document
) then it surely works. But the problem (?) is that in such case all hooked elements’ handlers are triggered instead of only the one I am interested in (the one that was dragged), resulting in the code being executed x times (where “x” is the number of draggable elements in the window). Probably I need to double-check whether I am really dispatching it correctly when attached to the element in question and not something higher in the hierarchy.
UPDATE:
Yup, I was attaching listener to the drag handle inside of the element, while dispatching event to the draggable element (upper in the hierarchy) so this part is solved - thank you for pointing me in the right direction.
This sort of stuff works.
const SomeHook = {
mounted() {
const pushit = (event) => this.pushEvent(event)
const throttledPushEvent = throttle(pushit, ms)
this.el.addEventListener("mouseenter", () => {
throttledPushEvent(event)
});
}
}
const SomeHook = {
pushSomething () { this.pushEvent("something", {}) }
mounted() {
const el = ...;
el.addEventListener("um", () => { this.pushSomething() }
}
The OP was about how to pushEvent from any javascript to liveview. If your problem is more specific, can you start a new post with details and your current implementation, so we can suggest possible improvements?
I understand. My problem was almost exactly the same though. And I wanted to solve it by following your suggestions (so it felt natural to ask here). This initially seemed not to work but once you doubled-down on the fact that it in fact should, and stressed out that the elements need to match (which I thought I was doing correctly) then I quickly found the reason. So thanks once more - my problem is solved.
This sort of stuff works
Roger that, tnx. I’ve got it working too now.