This is likely a feature request unless we’re overlooking something, but it would be a nice improvement to the developer experience if there were LiveView Bindings for calling a Javascript function when a DOM event fires.
The existing Bindings offer phx-click
for sending messages to the server, but not for calling local Javascript. phx-mounted
is the closest binding, but then you have to define a bunch of Hooks just to register for regular DOM events.
The small set of JS Commands is helpful, but doesn’t allow us to call arbitrary functions.
Something like:
<div phx-click={JS.call(myJavascriptFunction(evt)}></div>
Most times, we just want an onClick
, onInput
or onChange
handler to call some local JavaScript without any round trips to the server. So extending the Bindings to include phx-on...
would be equally helpful.
Or perhaps:
<div phx-onInput={JS.call(...)}></div>
<div phx-onEvent={'input', JS.call(...)}></div>
The Client Hooks via phx-hook
section explains exactly how to do what we want, but we’re curious if there’s an appetite for trying to improve this so that we don’t have to write all the Hook
functions.
2 Likes
I feel the docs are a bit confusing. When given a client-side JS command, phx-click
will not send any request to the server. Eg, phx-click={JS.show()}
. However if you read the Bindings sections shared by @kccarter, you might think this is the case. Even when you know about JS commands, it’s still confusing wording. I can bring it up in the issue tracker unless someone thinks I’m really off-base here.
1 Like
Hmm, not sure this is quite what we’re looking for because you still need to write the addEventListener
code to listen for your custom event.
We want to be able to call a specific Javascript function, like you would in a lot of the front-end frameworks.
Example:
app.js
function myEventHandler(event) {
// ... do something interestion
}
index.html.heex
<input phx-onInput={JS.call(myEventHandler)} />
The distinction here is that Phoenix is automatically registering for the input
event and dispatching that to the function we specify.
The LiveView Bindings page has a decent number of bindings, but it doesn’t cover every DOM event, and as you note requires the “trampoline” of having to use dispatch.
Something like phx-on-[arbitrary_event_name]
would be helpful.
1 Like
At least with this example, I think it was clear enough (to us) that JS.show
, for example, was not making a server call.
The Client Utility Commands made reasonable sense to us. Perhaps we’re just looking for a few more commands. Almost like something along the lines of JS.eval()
, though I imagine that might cause raised eyebrows.
1 Like
I to would like this in certain projects but I think the previous time this came up it was mentioned that it’s a security vulnerability if you allow anything that’s not already set up.
A content security policy would protect against most of it but it would be far too easy for someone to have an insufficient CSP or be sending things ultimately from the user through that sort of functionality leading to vulnerable browsers and sites.
If you need it though, you could add it to your own app though. You could write an intermediate event handler whose only purpose was to call some arbitrary function from a data attribute.
Just know that unless you have CSP locked down and are very careful, you’ll make yourself and users vulnerable
1 Like
# // app.js
# window.addEventListener("js:call", e => window[e.detail.name](...e.detail.args))
#
# heex
# <button phx-click={js_call("alert", ["clicked!"])}>Click me</button>
#
def js_call(name, args \\ []) do
JS.dispatch("js:call", detail: %{name: name, args: args})
end
7 Likes
Hah! Thanks so much Chris. I was curious if something like this would be proposed by someone. We were on the fence as to the level of “hackiness” this would bring…
One issue being that the JS function being called need to have been previously defined on the window
. But it’s not unreasonable that we could probably define a few top-level “utility” functions that extend the existing support and cover most of our (admittedly experimental) use-cases.
Cheers and thanks for the fun framework!
1 Like
Your example had implicit window, but you could pull the functions from whatever scope you wanted. Wherever you wire up your event listener could point to imported/in scope object literal where your functions live, so they don’t need to be global. Just replace window[...]
with myFunctions[...]
and off to the races.
1 Like