I’m working on a small project on my spare time.
In the project I have a view generated with phx.gen.live and in the form component I’m trying to add a map and fetching coordinates from Mapbox.
When the user types things in the input for the address I search for corresponding addresses and present suggestions. When the user clicks on one address I want to set the address and the coordinates.
But when the user clicks the address the description is cleared.
The fields in my changeset are:
Address
Description
Lat
Long
This is my FormComponent. I guess I am doing something wrong when I handle the phx-click event but I can’t see what.
defmodule ExampleWeb.TableLive.FormComponent do
use ExampleWeb, :live_component
alias Example.TestEvent
@impl true
def render(assigns) do
~H"""
<div>
<.header>
<%= @title %>
<:subtitle>Use this form to manage table records in your database.</:subtitle>
</.header>
<.simple_form
for={@form}
id="table-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.input field={@form[:address]} type="text" label="Address" phx-debounce="1000" />
<ul class="z-10 absolute bg-white/75 w-full">
<%= for %{name: name, id: id} <- @suggestions do %>
<li phx-target={@myself} phx-click="select_address" phx-value-id={id}> <%= name %> </li>
<% end %>
</ul>
<div phx-hook="Map" id='map' class="rounded relative aspect-square w-full"
style="aspect-ratio: 1 / 1; width: 100%; height: 300px" phx-update="ignore"></div>
<.input field={@form[:description]} type="textarea" label="Description" />
<:actions>
<.button phx-disable-with="Saving...">Save table</.button>
</:actions>
</.simple_form>
</div>
"""
end
@impl true
def update(%{table: table} = assigns, socket) do
changeset = TestEvent.change_table(table)
{:ok,
socket
|> assign(assigns)
|> assign_form(changeset)}
end
@impl true
def handle_event("validate", %{"_target" => ["table", "address"], "table" => %{"address" => _address} = table_params}, socket) do
url = "https://1902d58c-e651-4162-a087-6564338cf4fc.mock.pstmn.io/mock_mapbox"
response = HTTPoison.get!(url)
{:ok, body} = Poison.decode(response.body)
suggestions = body["features"]
|> Enum.map(fn %{"place_name" => name, "geometry" => %{"coordinates" => cords}, "id" => id} -> %{name: name, cords: cords, id: id} end)
changeset = socket.assigns.table
|> TestEvent.change_table(table_params)
|> Map.put(:action, :validate)
socket = socket
|> assign_form(changeset)
|> assign(:suggestions, suggestions)
{:noreply, socket}
end
# this function mocks the call to map box that searches addresses
@impl true
def handle_event("select_address", params, socket) do
selected = socket.assigns.suggestions |> Enum.filter(fn s -> s.id == params["id"] end) |> hd
formatted_coords = %{lng: Enum.at(selected.cords, 0), lat: Enum.at(selected.cords, 1)}
table_params = %{"address" => selected.name, "lng" => formatted_coords.lng, "lat" => formatted_coords.lat}
changeset = socket.assigns.table
|> TestEvent.change_table(table_params)
|> Map.put(:action, :validate)
socket = socket
|> assign_form(changeset)
|> assign(coords: formatted_coords, suggestions: [])
{:noreply, assign(socket, coords: formatted_coords, suggestions: []) |> push_event("position", formatted_coords)}
end
@impl true
def handle_event("validate", %{"table" => table_params}, socket) do
changeset =
socket.assigns.table
|> TestEvent.change_table(table_params)
|> Map.put(:action, :validate)
{:noreply, assign_form(socket, changeset)}
end
def handle_event("save", %{"table" => table_params}, socket) do
save_table(socket, socket.assigns.action, table_params)
end
defp save_table(socket, :edit, table_params) do
case TestEvent.update_table(socket.assigns.table, table_params) do
{:ok, table} ->
notify_parent({:saved, table})
{:noreply,
socket
|> put_flash(:info, "Table updated successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign_form(socket, changeset)}
end
end
defp save_table(socket, :new, table_params) do
case TestEvent.create_table(table_params) do
{:ok, table} ->
notify_parent({:saved, table})
{:noreply,
socket
|> put_flash(:info, "Table created successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign_form(socket, changeset)}
end
end
defp assign_form(socket, %Ecto.Changeset{} = changeset) do
socket = socket
|> assign(:form, to_form(changeset))
case Map.has_key?(socket.assigns, :suggestions) do
false -> assign(socket, :suggestions, [])
_ -> socket
end
end
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end