I have a LiveView application which searches for airport codes. When a user enters ham
it should replace the content of that form field with HAM
(String.upcase/1) but it doesn't. But according to my understanding of my code it should. What do I have to change to replace all inputs in that field with an upcased version?
BTW: It works if I add a button to it and use phx-submit
instead of phx-change
. But I'd like to have it working for phx-change
.
$ mix phx.new travelagent --live --no-ecto
$ cd travelagent
lib/travelagent_web/router.ex
[...]
scope "/", TravelagentWeb do
pipe_through :browser
live "/", PageLive, :index
live "/search", SearchLive
end
[...]
lib/travelagent/airports.ex
defmodule Travelagent.Airports do
def search_by_code(""), do: []
def search_by_code(code) do
list_airports()
|> Enum.filter(&String.starts_with?(&1.code, code))
end
def list_airports do
[
%{name: "Berlin Brandenburg", code: "BER"},
%{name: "Berlin Schönefeld", code: "SXF"},
%{name: "Berlin Tegel", code: "TXL"},
%{name: "Bremen", code: "BRE"},
%{name: "Köln/Bonn", code: "CGN"},
%{name: "Dortmund", code: "DTM"},
%{name: "Dresden", code: "DRS"},
%{name: "Düsseldorf", code: "DUS"},
%{name: "Frankfurt", code: "FRA"},
%{name: "Frankfurt-Hahn", code: "HHN"},
%{name: "Hamburg", code: "HAM"},
%{name: "Hannover", code: "HAJ"},
%{name: "Leipzig Halle", code: "LEJ"},
%{name: "München", code: "MUC"},
%{name: "Münster Osnabrück", code: "FMO"},
%{name: "Nürnberg", code: "NUE"},
%{name: "Paderborn Lippstadt", code: "PAD"},
%{name: "Stuttgart", code: "STR"}
]
end
end
lib/travelagent_web/live/search_live.ex
defmodule TravelagentWeb.SearchLive do
use TravelagentWeb, :live_view
alias Travelagent.Airports
def mount(_params, _session, socket) do
socket =
socket
|> assign(:airport_code, "")
|> assign(:airports, [])
{:ok, socket}
end
def handle_event(
"airport_code_search",
%{"airport_code" => airport_code},
socket
) do
airport_code = String.upcase(airport_code)
socket =
socket
|> assign(:airport_code, airport_code)
|> assign(:airports, Airports.search_by_code(airport_code))
{:noreply, socket}
end
end
lib/travelagent_web/live/search_live.html.leex
<form phx-change="airport_code_search">
<fieldset>
<label for="nameField">Airport Code</label>
<input type="text" name="airport_code" value="<%= @airport_code %>"
placeholder="e.g. FRA"
autofocus autocomplete="off" />
</fieldset>
</form>
<%= unless @airports == [] do %>
<h2>Search Results</h2>
<table>
<thead>
<tr>
<th>Airport Code</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<%= for airport <- @airports do %>
<tr>
<td><%= airport.code %></td>
<td><%= airport.name %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
If I use this form:
<form phx-submit="airport_code_search">
<fieldset>
<label for="nameField">Airport Code</label>
<input type="text" name="airport_code" value="<%= @airport_code %>"
placeholder="e.g. FRA"
autofocus autocomplete="off" />
<input class="button-primary" type="submit" value="Search Airport">
</fieldset>
</form>
I get this working result (after entering fra
and clicking the button):
PS: There are a million of possible solutions for this problem with JavaScript or CSS. But I'd like to know how to properly solve this with LiveView.
According to the documentation,
The JavaScript client is always the source of truth for current input values. For any given input with focus, LiveView will never overwrite the input's current value, even if it deviates from the server's rendered updates.
A solution in this case is to use JS hooks.