Capture Keyboard Input(barcode scanner) without input form in Liveview

I’m trying to build a Liveview with 2 parts:

  • top:
    • switch button: camera / hardware scanner
      • show camera scanner
      • or empty
  • bottom:
    • scan history table from db with pubsub: statistics, colors, time,…

When the hardware scanner is used and a scan is made, a sequence of characters is send ending with an Enter-key.

defmodule HelloWeb.ScanLive do
  use HelloWeb, :live_view


  def render(assigns) do
    ~H"""
    <div phx-window-keyup="scanner">
    </div>
    """
  end

  def handle_event("scanner", key, socket) do
    IO.inspect(key)
    {:noreply, socket}
  end
end

Is this the proper way to do this in Phoenix Liveview using handle_event with keyup?
Can you capture a sequence+Enter at once or do you have to capture separately and build it up.

2 Likes

If you want to capture each key individually, yes.

This requires a form input. I am curious why specifically you wish to avoid this. I can give you a working example (using an ancient Symbol LS1900 barcode scanner to demonstrate :slight_smile: ):

Here’s the code:

defmodule HelloWeb.ScanLive do
  use HelloWeb, :live_view

  def render(assigns) do
    ~H"""
    <form id="form" phx-submit={JS.push("capture") |> JS.push_focus(to: "#input")}>
      <input
        id="input"
        data-count={@count}
        type="text"
        name="code"
        autofocus
        phx-debounce="blur"
        placeholder="Scan barcode..."
        value=""
        class="w-full"
      />
      <button class="hidden" type="submit">Submit</button>
    </form>

    <ul>
      <li :for={code <- @codes}><%= code %></li>
    </ul>
    """
  end

  def mount(_, _, socket) do
    socket = socket |> assign(:count, 0) |> assign(:codes, [])
    {:ok, socket}
  end

  def handle_event("capture", %{"code" => code}, socket) do
    IO.inspect(code)

    socket =
      socket
      |> update(:count, &(&1 + 1))
      |> update(:codes, &[code | &1])

    {:noreply, socket}
  end
end

Usually there are (at least) two tricky parts. The first is clearing and re-focusing the input after the form submits. You can use a combination of JS.push() and JS.push_focus() to submit the form and re-focus the input. To ensure the input is cleared after submit, you need to force the input to re-render. In this case I am setting a data-count attribute and incrementing the count on each submit. This will force LiveView to re-render the input with the empty value provided on the element.

The other tricky part is that barcode scanners don’t always send an Enter key as a suffix. I left the phx-debounce="blur" on the input because you will definitely want that if you try to add a phx-change event to the form. Since LiveView won’t re-render focused inputs on change, you would probably want to push an event from the server and use it to reset the input value.

3 Likes

that’s so cool!

Thank you. I’ve tried similar with a form & changeset & onfocus: "this.value=''"'to reset the value. Your code is much cleaner though.

I have currently 2 reasons. I’m testing with an android phone with 2d scanner (Blovedream U9000)

  • When the focus is on the input the software keyboard keeps popping up. I though when there is no input, there would be no software keyboard filling half the screen.
    Edit: Adding inputmode="none" to input fixes this.

  • When the user clicks outside the input, the focus is lost. Scans are not captured until you refocus on the input.

Edit:
I’ve also added autocomplete="off" to input