檢視原始碼 表單繫結
表單事件
若要處理表單變更及提交,請使用 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
CoreComponents
模組如果您使用 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
驗證回呼只會根據所有表單輸入值更新變更組,再將變更組轉換成表單並指定給 socket。如果表單變更(例如產生新的錯誤),就會呼叫 render/1
並重新呈現表單。
對 phx-submit
繫結而言也是如此,相同的回呼會被呼叫,並嘗試執行儲存操作。若成功執行,則會傳回 :noreply
元組,並用 Phoenix.LiveView.redirect/2
將 socket 標註為重新導向至新的使用者頁面,否則會使用出錯的變更組更新 socket 指定,以重新呈現給用戶端。
您可能希望單獨的輸入使用自己的變更事件或鎖定不同的元件。這可以使用 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
屬性中指定的網址。這對在針對需要 Plug 會話突變的作業送出至控制器路由之前進行 LiveView 表單送出的最終驗證前非常實用。例如,您可以在 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 plug 或是設定 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"
事件只會關注現在的客戶端表單資料,但伺服器會維護整個嚮導狀態。要在這種情況下復原,您可以在 LiveView 中指定一個復原事件,例如上述的 "recover_wizard"
,它會與下列伺服器呼叫回函碼連結
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-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
注解,這會在事件提交期間將元素的 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
可以用來判定元素,如「客戶端掛鉤」文件所述。
也可以使用「submit」事件來觸發 phx-submit
document.getElementById("my-form").dispatchEvent(
new Event("submit", {bubbles: true, cancelable: true})
)