Phoenix LiveView callbacks - to put text input focus on the selected input field?

You can also focus an input by using a LiveView push_event if you need the server, or via the LiveView.JS push api without the server by adding JS.dispatch to it. So these are just slightly different flows with slightly different use-cases from the hooks example that @ibarch posted.

From the Server

Push a JS event from a handle_event or handle_input function. You catch your pushed event in either app.js with a window.addEventListener or in a LiveView hook, and then send your changes to the DOM from there.

So that’s something like:

  1. Start with an instigating event, for example a click.
<span phx-click="do_stuff_plus_focus_my_input" ...>Just click it</span> 
  1. Handle it and send a push_event. I’ve called mine focusElementById and sent a little map with the id of the input I want to focus. That map becomes the value for the JavaScript event object’s detail key, as you’ll see in the third step.
 def handle_event("do_stuff_plus_focus_my_input", %{"foo" => bar}, socket) do
    socket = assign(socket, foo: bar)
    {:noreply, push_event(socket, "focusElementById", %{id: "my-input"})}
 end
  1. Now catch that pushed event either from a hook or, as here, from app.js. The id we included (“my-input”) is in the event object as event.detail.id. Then just use standard JS’ .focus() and you’re done.
# app.js 
window.addEventListener(`phx:focusElementById`, (event) => {
  document.getElementById(event.detail.id).focus() 
})

Anyway the push_event docs go over all of this and are probably better written than what I have here.

Client-only Version

I’ve also finally noticed that LiveView.JS has a function called push that lets you compose events that you send from templates. We can use that with JS.dispatch to cut out the server.

  1. JS.push and JS.dispatch in action.
# plz excuse the formatting...
<span phx-click={ JS.push("choose_part_of_speech") 
                  |> JS.dispatch("phx:focusMe", to: "#my-input") 
                }>Click it good</span>

  1. We can grab that event with a window.addEventListener. This is almost the same as with the server, but we’re sending the id name as a string and not in a map, so our JavaScript event object in our listener now has our element ID under target and not detail in a map. So we access the id with event.target.id. Just small differences that keep me in the docs and that I hope will be automated away… You’ll see below.
# app.js
window.addEventListener(`phx:focusMe`, (event) => {
  document.getElementById(event.target.id).focus()
})

And that’s it. It’s all in the docs, but writing things out makes it easier for me to remember.

6 Likes