hayleigh
Best way to handle Custom Element events in LiveView?
Hey folks! I’m the author of Lustre which is a frontend framework written in Gleam. Lustre has the ability to run components as native Custom Elements with support for all the good stuff like attributes, properties, custom events, form association, …
I’m currently working on a headless ui library and I’d like to document and encourage its use in Phoenix and LiveView apps, but I’m not LV expert and it’s a little unclear to me how one is supposed to handle events emitted by a custom element. I found a thread from last year that asks a similar question but it looks like the conversation fizzled out.
As an example, the following is a hypothetical menu that supports both keyboard and pointer interaction. When a menu item is selected (by any means) the menu will emit an event - "lustre-ui:select" - with a payload containing the value of the item selected.
<lustre-ui-menu>
<lustre-ui-menu-item value="ignore">
Ignore
</lustre-ui-menu-item>
<lustre-ui-menu-item value="block" class="danger">
Block
</lustre-ui-menu-item>
<lustre-ui-menu-item value="report" class="danger">
Report user profile
</lustre-ui-menu-item>
</lustre-ui-menu>
In a typical client app, you would attach an event listener to the <lustre-ui-menu> element and perform whatever associated action when an item was selected, but of course what we’d like to do is send a message to our LiveView process some how.
As I understand it, LiveView only supports a fixed and predetermined set of native events for things like phx-click. What would be the recommended way to work with custom elements that emit non-standard events in LV?
Thanks y’all! ![]()
Most Liked
hayleigh
Just like click / input / change are enough for native link / button / input / select…
…doesn’t venture out to invent new ways to interact with those elements.
Just so we’re clear, native HTML <select> supports all of the things I just listed. We are in agreement that the standards set by native ui widgets should be the baseline, but I think that baseline is much harder to meet than most folks initially think.
Accessibility is about both functionality and semantics: this is why aria-* attributes exist so that we can tell the browser to replace the native semantics of an element with something else. This is important because when we use existing HTML elements, non-sighted users do not get visual cues that the thing they’re interacting with does something different compared to normal.
In the case of our menu made up of buttons, it is a common expectation of the “menu” pattern that it close once an item is selected. We were satisfied that our styled <div> with some buttons in looked like a menu but for a non-sighted user they saw a collection of controls. When they activate one, focus will be taken away from the button and (if you were diligent) moved elsewhere (and if you werent diligent they now have no focus at all).
Fortunately there are ways to communicate these semantics, (in this case role menu on the wrapper, role menuitem on the buttons). Custom elements can report these semantics natively, without need for developers to provide aria-* attributes.
On the question of why this might be of particular relevance for LV users. As i see it the current state of play is mostly one of these options:
-
Be satisfied with native HTML elements and the interfaces you can craft with them, don’t fix what isn’t broke. In lots of cases this is not just good enough but good.
-
Be unsatisfied with what native HTML elements provide and begin building pieces of interactive ui in LV using the interaction patterns LV makes simple (click, keydown, etc) similar to our hypothetical button-based menu.
-
Look to exist client-side rendering options and how you might integrate them with LV, (livesvelte, livevue, i assume theres some react version, …)
If the first option describes you that is totally valid, but is also not the kind of user to ever find a ui component library useful, so perhaps a bit moot to discuss.
For the second option: one of the best things about LV is that it provides an entry for backend devs and engineers in general that would otherwise be put off doing frontend work to actually start crafting interactive experiences on the web. The flipside of that, perhaps, is we might be more willing to make trade-offs in terms of accessibility and user experience because of different priorities, lack of experience, or lack of interest.
And I mean the third option means fully embracing JS and everything that entails - for some that means getting access to the best of both worlds, but I would guess for many of us it means interacting with an ecosystem we would really rather not be and spending time writing code in a language we would really rather prefer not to.
I think a robust custom element library meets LV exactly where it wants to be: rendering HTML, while ideally abstracting all the hard bits away ![]()
spicychickensauce
In my projects, I’ve started to move away from hooks and started using the following pattern instead:
window.addEventListener("phx-x:register-events", (event) => {
const target = event.target;
const onScrollend = target.getAttribute("phx-x-on-scrollend");
target.addEventListener("scrollend", (e) => {
liveSocket.execJS(target, onScrollend);
})
})
<div
phx-mounted={JS.dispatch("phx-x:register-events")}
phx-x-on-scrollend={JS.push("scrollend")}
/>
This has the advantage over hooks that you do not need to set an ID, which makes it a lot more reusable.
I think this pattern might also work quite well with custom elements.
I really would advise against using hooks for UI elements, as the ID requirement leads to having to pass down id prefixes everywhere.
Btw, thank you for trying to tackle this hard problem! I’ve spent hours searching for good custom elements headless UI libraries, but I couldn’t find any.
LostKobrakai
I’d argue the big “unknown” with webcomponents is the question if the emitted events are actually intended to be sent to the server (vs. meant to used client side). The current solution would be to have a liveview js hook somewhere up the tree, which would attach an event listener explicitly for events and use this.pushEvent in the hook to forward the data to the server.
As for (form-)inputs the best way is imo to implement ElementInternals - Web APIs | MDN to make the webcomponent a proper form enabled component. Those should then work out of the box with the form abstractions of liveview.
Last Post!
spicychickensauce
With liveview 1.1 there is now a new way to do it without using any internals, see:
So here is the new way of doing it:
window.addEventListener("phx-x:register-events", (event) => {
const target = event.target;
const onScrollend = target.getAttribute("phx-x-on-scrollend");
target.addEventListener("scrollend", (e) => {
const js = liveSocket.js();
js.push(target, onScrollend, { value: { someData: 42 } });
})
})
<div
phx-mounted={JS.dispatch("phx-x:register-events")}
phx-x-on-scrollend="scrollend"
/>
Popular in Questions
Other popular topics
Latest Phoenix Threads
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #hex
- #security










