I am trying to create a form on a page whose contents are synced with the query params of the url in a liveview.
I would like it so that any inputs on form inputs are added as query params on the url, and any url with query params automatically also inserts those params into the respective form inputs (sort of like how sharing a google url automatically inserts the text into the form field).
First I create a schema for an item:
defmodule LiveviewPages.Items.Item do
use Ecto.Schema
import Ecto.Changeset
schema "items" do
field :name, :string
field :quantity, :integer
end
@doc false
def changeset(item, attrs) do
item
|> cast(attrs, [:name, :quantity])
|> validate_required(:name)
|> validate_length(:name,
min: 3,
max: 7
)
|> validate_format(:name, ~r/^[a-zA-Z0-9 ]*$/,
message: "must contain only letters, numbers, and spaces"
)
|> validate_required(:quantity)
|> validate_change(:quantity, &validate_quantity/2)
end
defp validate_quantity(:quantity, quantity) do
if quantity < 0 do
[:quantity, "must be greater than 0"]
else
[]
end
end
end
Then I create a corresponding Liveview that has form inputs for name, and quantity and some error_tags.
In the assigns I have a changeset that starts with an empty item, but its action is nil
.
However because I want my form inputs to update when the url updates, I end up having a handle_params that reads the url params and updates the changeset via change_item
.
To ensure that any incoming events from the client also update the changeset I push_patch
from my handle_event("validate")
into my handle_params
after updating the url’s query params with the newly added form input data.
Also in my handle_params
I end up having to |> Map.put(:action, :insert)
to update the changeset action so that its action does not remain nil.
defmodule LiveviewPagesWeb.Changesets do
use LiveviewPagesWeb, :live_view
alias LiveviewPages.Items
alias LiveviewPages.Items.Item
def mount(_params, _session, socket) do
{:ok, assign(socket, changeset: Items.change_item(%Item{}))}
end
def handle_params(params, _uri, socket) do
name = params["name"]
quantity = params["quantity"]
changeset =
%Item{}
|> Items.change_item(%{name: name, quantity: quantity})
|> Map.put(:action, :insert)
{:noreply, assign(socket, changeset: changeset)}
end
def handle_event("validate", %{"item" => params}, socket) do
query_params = URI.encode_query(params)
if query_params != "" do
{:noreply, push_patch(socket, to: "/changesets" <> "?" <> "#{query_params}")}
else
{:noreply, push_patch(socket, to: "/changesets")}
end
end
def render(assigns) do
~H"""
<section class="flex flex-col w-screen h-screen justify-center items-center text-center">
<.form let={f} for={@changeset} phx-change="validate">
<div class="grid grid-cols-8 max-w-xs gap-2">
<%= text_input(f, :name,
class:
"focus:ring-gray-500 focus:border-gray-500 block w-full text-sm border-gray-300 rounded-md col-span-6",
placeholder: "Enter an item name"
) %>
<%= text_input(f, :quantity,
inputmode: "numeric",
class:
"focus:ring-gray-500 focus:border-gray-500 block w-full text-sm border-gray-300 rounded-md col-span-2",
placeholder: "Qty"
) %>
</div>
<div class="my-2 text-left">
<%= error_tag(f, :name) %>
<%= error_tag(f, :quantity) %>
</div>
<div class="grid grid-cols-8 max-w-xs gap-2">
<%= submit("Save",
disabled: !assigns.changeset.valid?,
class:
"col-span-8 bg-black hover:bg-gray-800 text-white font-medium py-2 px-4 rounded" <>
if(!assigns.changeset.valid?, do: " opacity-50", else: "")
) %>
</div>
</.form>
</section>
"""
end
end
The issue I am facing is this.
During the initial HTML response from the liveview all changeset actions are rendered temporarily, since handle_params
updates the changeset action and the phx-no-feedback
class is only added after the websocket connection is established.
To fix this issue, is there any way for me to:
- force
phx-no-feedback
to be applied on the liveview’s initial HTML response OR - distinguish in the
handle_params
whether the clause is being invoked in the initial HTML render or the subsequent websocket connection
UPDATE: I found that the transport_pid in the socket is nil in the initial HTML connection but not in the subsequent websocket connection. This seems hacky but can anyone confirm if it would work with point 2. above?