查看源代码 表单绑定
表单事件
要处理表单更改和提交,请使用 phx-change
和 phx-submit
事件。通常情况下,建议在表单级别处理输入更改,在该级别,所有表单字段都将传递到 LiveView 的回调,以响应任何单个输入更改。例如,要处理实时表单验证和保存,您的表单将同时使用 phx-change
和 phx-submit
绑定。让我们从一个示例开始
<.form for={@form} phx-change="validate" phx-submit="save">
<.input type="text" field={@form[:username]} />
<.input type="email" field={@form[:email]} />
<button>Save</button>
</.form>
.form
是在 Phoenix.Component.form/1
中定义的功能组件,我们建议阅读其文档以详细了解其工作原理和所有支持的选项。 .form
预计有一个 @form
赋值,它可以通过 Phoenix.Component.to_form/1
从变更集或用户参数创建。
input/1
是一个用于渲染输入的功能组件,通常在您的应用程序中定义,通常封装标签、错误处理等。以下是一个简单的入门版本
attr :field, Phoenix.HTML.FormField
attr :rest, :global, include: ~w(type)
def input(assigns) do
~H"""
<input id={@field.id} name={@field.name} value={@field.value} {@rest} />
"""
end
The
CoreComponents
module如果您的应用程序是用 Phoenix v1.7 生成的,那么
mix phx.new
会自动导入许多现成的功能组件,例如具有内置功能和样式的.input
组件。
渲染表单后,您的 LiveView 会在 handle_event
回调中拾取事件,以相应地验证并尝试保存参数
def render(assigns) ...
def mount(_params, _session, socket) do
{:ok, assign(socket, form: to_form(Accounts.change_user(%User{})))}
end
def handle_event("validate", %{"user" => params}, socket) do
form =
%User{}
|> Accounts.change_user(params)
|> to_form(action: :validate)
{:noreply, assign(socket, form: form)}
end
def handle_event("save", %{"user" => user_params}, socket) do
case Accounts.create_user(user_params) do
{:ok, user} ->
{:noreply,
socket
|> put_flash(:info, "user created")
|> redirect(to: ~p"/users/#{user}")}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
验证回调只是根据所有表单输入值更新变更集,然后将变更集转换为表单并将其分配给套接字。如果表单发生更改,例如生成新的错误,则会调用 render/1
,并且表单将重新渲染。
同样对于 phx-submit
绑定,会调用相同的回调,并尝试进行持久化。成功后,将返回一个 :noreply
元组,并使用 Phoenix.LiveView.redirect/2
为套接字添加注释以重定向到新的用户页面,否则套接字赋值将使用错误的变更集进行更新,以便为客户端重新渲染。
您可能希望单个输入使用自己的更改事件或定位不同的组件。这可以通过在输入本身添加 phx-change
来实现,例如
<.form for={@form} phx-change="validate" phx-submit="save">
...
<.input field={@form[:email]} phx-change="email_changed" phx-target={@myself} />
</.form>
然后您的 LiveView 或 LiveComponent 将处理该事件
def handle_event("email_changed", %{"user" => %{"email" => email}}, socket) do
...
end
注意:对于使用 phx-change
标记的输入,仅发送单个输入作为参数。
错误反馈
为了在表单更新时获得适当的错误反馈,错误标签必须指定它们属于哪个输入。这是通过 phx-feedback-for
实现的。
phx-feedback-for
注释指定了它所属的输入的名称(或 ID,用于向后兼容)。如果没有添加 phx-feedback-for
属性,会导致显示用户尚未更改的表单字段的错误消息(例如,页面下方必需的字段)。
例如,您的 MyAppWeb.CoreComponents
可能会使用此函数
def input(assigns) do
~H"""
<div phx-feedback-for={@name}>
<input
type={@type}
name={@name}
id={@id || @name}
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
class={[
"phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400",
"border-zinc-300 focus:border-zinc-400 focus:ring-zinc-800/5",
]}
{@rest}
/>
<.error :for={msg <- @errors}><%= msg %></.error>
</div>
"""
end
def error(assigns) do
~H"""
<p class="phx-no-feedback:hidden">
<Heroicons.exclamation_circle mini class="mt-0.5 h-5 w-5 flex-none fill-rose-500" />
<%= render_slot(@inner_block) %>
</p>
"""
end
现在,任何具有 phx-feedback-for
属性的 DOM 容器,在表单字段尚未收到用户输入/焦点的情况下,将接收一个 phx-no-feedback
类。使用新的 CSS 规则或 tailwindcss 变体,允许您在反馈发生变化时显示、隐藏和设置错误的样式。
数字输入
数字输入是 LiveView 表单中的一个特殊情况。在程序化更新中,某些浏览器将清除无效的输入。因此,当输入无效时,LiveView 不会从客户端发送更改事件,而是允许浏览器的本机验证 UI 驱动用户交互。一旦输入变得有效,更改和提交事件将正常发送。
<input type="number">
这已知存在许多问题,包括可访问性、将大数字转换为指数表示法,以及滚动可能会意外地增加或减少数字。
一个替代方法是 inputmode
属性,它可能更适合您的应用程序需求和用户。根据 Can I Use?,以下方法在全球 86% 的市场(截至 2021 年 9 月)中得到支持
<input type="text" inputmode="numeric" pattern="[0-9]*">
密码输入
密码输入在 Phoenix.HTML
中也是特殊情况。出于安全原因,渲染密码输入标签时不会重复使用密码字段值。这要求您在标记中显式设置 :value
,例如
<.input field={f[:password]} value={input_value(f[:password].value)} />
<.input field={f[:password_confirmation]} value={input_value(f[:password_confirmation].value)} />
嵌套输入
嵌套输入是使用 .inputs_for
功能组件处理的。默认情况下,它将添加必要的隐藏输入字段,以跟踪 Ecto 关联的 ID。
<.inputs_for :let={fp} field={f[:friends]}>
<.input field={fp[:name]} type="text" />
</.inputs_for>
文件输入
LiveView 表单支持 反应式文件输入,包括通过 phx-drop-target
属性进行拖放支持
<div class="container" phx-drop-target={@uploads.avatar.ref}>
...
<.live_file_input upload={@uploads.avatar} />
</div>
有关更多信息,请参见 Phoenix.Component.live_file_input/1
。
通过 HTTP 提交表单操作
可以在表单中添加 phx-trigger-action
属性,以在 DOM 修补到表单标准 action
属性中指定的 URL 时触发标准表单提交。这对于在将 LiveView 表单提交发布到控制器路由以进行需要 Plug 会话变异的操作之前,执行最终验证非常有用。例如,在您的 LiveView 模板中,您可以使用布尔赋值为 phx-trigger-action
添加注释
<.form :let={f} for={@changeset}
action={~p"/users/reset_password"}
phx-submit="save"
phx-trigger-action={@trigger_submit}>
然后,在您的 LiveView 中,您可以切换赋值以在下次渲染时触发表单,并使用当前字段。
def handle_event("save", params, socket) do
case validate_change_password(socket.assigns.user, params) do
{:ok, changeset} ->
{:noreply, assign(socket, changeset: changeset, trigger_submit: true)}
{:error, changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
一旦 phx-trigger-action
为 true,LiveView 就会断开连接,然后提交表单。
崩溃或断开连接后的恢复
默认情况下,所有标记有 phx-change
并具有 id
属性的表单,在用户重新连接或 LiveView 在崩溃后重新挂载后,都会自动恢复输入值。这是通过客户端在挂载完成后立即向服务器触发相同的 phx-change
来实现的。
注意:如果您想查看开发中的表单恢复工作,请确保通过在您的 endpoint.ex
文件中注释掉 LiveReload 插件或在您的 config/dev.exs
中设置 code_reloader: false
来禁用开发中的实时重新加载。否则,实时重新加载可能会在您重新启动服务器时导致当前页面重新加载,这将丢弃所有表单状态。
对于大多数用例,这就是您所需要的,表单恢复将在没有考虑的情况下发生。在某些情况下,如果表单以有状态的方式逐步构建,则可能需要在服务器上对现有 phx-change
回调代码之外进行额外的恢复处理。要启用专门的恢复,请在表单上提供一个 phx-auto-recover
绑定,以指定用于恢复的不同事件,该事件将照常接收表单参数。例如,假设一个 LiveView 向导表单,其中表单是有状态的,并且根据用户所在的步骤和之前的选择进行构建
<form id="wizard" phx-change="validate_wizard_step" phx-auto-recover="recover_wizard">
在服务器端,"validate_wizard_step"
事件只关心当前客户端表单数据,但服务器维护着向导的整个状态。为了在此场景中恢复,您可以指定一个恢复事件,例如上面的 "recover_wizard"
,它将连接到您的 LiveView 中的以下服务器回调
def handle_event("validate_wizard_step", params, socket) do
# regular validations for current step
{:noreply, socket}
end
def handle_event("recover_wizard", params, socket) do
# rebuild state based on client input data up to the current step
{:noreply, socket}
end
要放弃自动表单恢复,请设置 phx-auto-recover="ignore"
。
重置表单
要重置 LiveView 表单,可以使用表单按钮或输入上的标准 type="reset"
。单击后,表单输入将重置为其原始值。表单重置后,会发出一个 phx-change
事件,其中 _target
参数包含重置的 name
。例如,以下元素
<form phx-change="changed">
...
<button type="reset" name="reset">Reset</button>
</form>
可以在服务器上以与常规更改函数不同的方式进行处理
def handle_event("changed", %{"_target" => ["reset"]} = params, socket) do
# handle form reset
end
def handle_event("changed", params, socket) do
# handle regular form change
end
JavaScript 客户端详细信息
JavaScript 客户端始终是当前输入值的真相来源。对于任何具有焦点的输入,LiveView 永远不会覆盖输入的当前值,即使它偏离了服务器渲染的更新。这对于预计不会产生重大副作用的更新非常有效,例如表单验证错误,或者在用户填写表单时围绕用户输入值的增量 UX。
对于这些用例,phx-change
输入并不关心在事件发送到服务器时禁用输入编辑。当 phx-change
事件发送到服务器时,输入标签和父表单标签会接收 phx-change-loading
CSS 类,然后将有效负载推送到服务器,并在根有效负载中包含一个 "_target"
参数,其中包含触发更改事件的输入名称的键空间。
例如,如果以下输入触发了一个更改事件
<input name="user[username]"/>
服务器的 handle_event/3
将接收一个有效负载
%{"_target" => ["user", "username"], "user" => %{"username" => "Name"}}
phx-submit
事件用于表单提交,在表单提交中通常会发生重大副作用,例如渲染新的容器、调用外部服务或重定向到新页面。
在提交绑定了 phx-submit
事件的表单时
- 表单的输入将设置为
readonly
- 表单上的任何提交按钮都将被禁用
- 表单将接收
"phx-submit-loading"
类
在完成服务器对 phx-submit
事件的处理后
- 提交的表单将被重新激活,并失去
"phx-submit-loading"
类 - 恢复最后一个具有焦点的输入(除非另一个输入获得了焦点)
- 更新将照常修补到 DOM
为了处理延迟事件,表单的 <button>
标签可以添加 phx-disable-with
注释,该注释在事件提交期间用提供的 value 交换元素的 innerText
。例如,以下代码将把“保存”按钮更改为“正在保存...”,并在确认后将其恢复为“保存”
<button type="submit" phx-disable-with="Saving...">Save</button>
您还可以利用 LiveView 的 CSS 加载状态类在表单提交时交换表单内容。例如,使用 app.css
中的以下规则
.while-submitting { display: none; }
.inputs { display: block; }
.phx-submit-loading .while-submitting { display: block; }
.phx-submit-loading .inputs { display: none; }
您可以使用以下标记显示和隐藏内容
<form phx-change="update">
<div class="while-submitting">Please wait while we save our content...</div>
<div class="inputs">
<input type="text" name="text" value={@text}>
</div>
</form>
此外,我们强烈建议在表单上包含唯一的 HTML “id” 属性。当 DOM 同级元素发生变化时,没有 ID 的元素将被替换而不是移动,这会导致诸如表单字段失去焦点等问题。
使用 JavaScript 触发 phx-
表单事件
通常情况下,希望在没有显式用户交互的情况下触发 DOM 元素上的事件。例如,自定义表单元素,如日期选择器或自定义选择输入,它使用隐藏的输入元素来存储选择状态。
在这些情况下,可以使用 DOM API 上的事件函数,例如触发 phx-change
事件
document.getElementById("my-select").dispatchEvent(
new Event("input", {bubbles: true})
)
当使用客户端钩子时,可以使用 this.el
来确定元素,如“客户端钩子”文档中所述。
也可以使用“提交”事件触发 phx-submit
事件
document.getElementById("my-form").dispatchEvent(
new Event("submit", {bubbles: true, cancelable: true})
)