LiveView: conditionally show an input when a `<select>` changes

I have what seems to be a simple problem to solve in a LiveView form, but for some reason I’m having trouble. It could be one of those old-fashioned brain fevers.

I have a boolean form input which, when toggled to true, should cause a secondary input to display so the user can enter details. It’s not this, but you can imagine that it is.

For the second input, I’m doing something like:

    <.input
      :if={@show_powers_input}
      field={@form[:powers]}
      type="text"
      label={gettext("Please describe your sweet powers")}
    />

So I just need to toggle @show_powers_input appropriately and I’m all set.

My first try was to use a phx-change, but that’s not optimal because it doesn’t make the second input appears as soon as the first one is modified; the user has to tab away from the first input to trigger the change event.

It seems like what I really want is the input event. According to CanIUse:

The input event is fired when the user changes the value of an <input> element, <select> element, or <textarea> element. By contrast, the “change” event usually only fires after the form control has lost focus.

Aside: if I’m understanding correctly, phoenix.js binds to both the ‘change’ and the ‘input’ event, but I don’t see the latter doing anything.

OK, so maybe I can use a hook to bind to input and trigger change, then let LiveView do its normal thing? I tried phx-hook="ChangeOnInput" with

    this.el.addEventListener("input", e => {
      console.log("hey an input", e);
      e.target.dispatchEvent(new Event('change', { 'bubbles': true }));
    })

… and while I do see my console log message, I still don’t get any event in my LiveView until the <select> loses focus.

What am I missing here?

1 Like

As @Dabsy pointed out in Elixir Slack, a checkbox input would change immediately, so that makes more sense for this case. I suppose this question could still be relevant for someone where there are options other than true/false.

1 Like

In your handle_event("validate") check the value of your params and assign that @show_powers_input value to the socket.

Something like:

def handle_event("validate", %{"superhero" => superhero_params}, socket) do
    changeset =
      socket.assigns.job
      |> Superheroes.change_superhero(superhero_params)
      |> Map.put(:action, :validate)

      socket
      |> assign_form(changeset)
      |> assign_show_powers_input(superhero_params)
      
      {:noreply, socket}
end

def assign_show_powers_input(socket, superhero_params) do
  # check some stuff here and assign
  IO.inspect(superhero_params)
  assign(socket, :show_powers_input, true)
end

Edit: oh nvm you’re saying the event isn’t fired until focus is lost