Spec-Zone.ru › Phoenix

Привязки форм

Исходный код

События формы

Для обработки изменений и отправки формы используйте события phx-change и phx-submit. Как правило, предпочтительнее обрабатывать изменения ввода на уровне формы, когда все поля формы передаются в колбэк LiveView при любом изменении отдельного поля. Например, для проверки формы в реальном времени и сохранения данных в форме используются привязки phx-change и phx-submit. Начнём с примера:

<.form for={@form} id="my-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, которое можно создать на основе changeset или пользовательских параметров с помощью 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

Колбэк validate просто обновляет changeset на основе всех значений полей формы, затем преобразует changeset в форму и присваивает её сокету. Если форма изменится, например в ней появятся новые ошибки, вызывается render/1, и форма отображается повторно.

Аналогично, для привязок phx-submit вызывается тот же колбэк и предпринимается попытка сохранить данные. В случае успеха возвращается кортеж :noreply, а сокету задаётся перенаправление с помощью Phoenix.LiveView.redirect/2 на страницу нового пользователя. В противном случае присваиваемые значения сокета обновляются changeset с ошибками, чтобы повторно отобразить форму клиенту.

Вы можете настроить отдельное поле ввода на обработку собственного события изменения или нацелить его на другой компонент. Для этого добавьте к самому полю ввода атрибут phx-change, например:

<.form for={@form} id="my-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

Примечание

  1. Для поля ввода с атрибутом phx-change в качестве параметров передаётся только само это поле.
  2. Хотя phx-change можно использовать для отдельных полей ввода, они всё равно должны находиться внутри формы.

Отображение ошибок

Для корректного отображения ошибок при обновлении формы LiveView отправляет специальные параметры при событиях формы. Их имена начинаются с _unused_ и указывают, что пользователь ещё не взаимодействовал с полем.

При создании формы из этих параметров с помощью Phoenix.Component.to_form/2 или Phoenix.Component.form/1 для фильтрации сообщений об ошибках можно использовать Phoenix.Component.used_input?/1.

Например, в вашем MyAppWeb.CoreComponents можно использовать эту функцию:

def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
  errors = if Phoenix.Component.used_input?(field), do: field.errors, else: []

  assigns
  |> assign(field: nil, id: assigns.id || field.id)
  |> assign(:errors, Enum.map(errors, &translate_error(&1)))

Теперь отображаются только ошибки полей, с которыми пользователь взаимодействовал.

Чтобы отключить отправку параметров _unused, добавьте атрибут phx-no-unused-field к отдельным полям ввода или ко всей форме.

Числовые поля ввода

Числовые поля ввода — особый случай в формах LiveView. При программном обновлении некоторые браузеры очищают поля с недопустимыми значениями. Поэтому LiveView не отправляет с клиента события изменения, если значение поля недопустимо, позволяя вместо этого использовать встроенный в браузер интерфейс проверки для взаимодействия с пользователем. Когда значение поля становится допустимым, события изменения и отправки формы отправляются как обычно.

<input type="number">

У такого подхода множество известных проблем, включая проблемы с доступностью: большие числа преобразуются в экспоненциальную запись, а прокрутка может случайно увеличить или уменьшить значение.

В качестве альтернативы можно использовать атрибут inputmode, который, возможно, лучше подойдёт для задач вашего приложения и его пользователей. Согласно данным сайта Can I Use?, следующий вариант поддерживается на 94% мирового рынка (по состоянию на ноябрь 2024 года):

<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.

<.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 инициировать стандартную отправку формы на URL, указанный в стандартном атрибуте action формы. Это удобно для предварительной проверки формы 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.

Примечание: чтобы проверить восстановление формы в режиме разработки, обязательно отключите автоматическую перезагрузку в режиме разработки: закомментируйте плагин LiveReload в файле endpoint.ex или задайте code_reloader: false в файле config/dev.exs. Иначе автоматическая перезагрузка может перезагрузить текущую страницу при каждом перезапуске сервера, из-за чего состояние формы будет потеряно.

В большинстве случаев этого достаточно, и форма будет восстанавливаться автоматически. В некоторых ситуациях, когда формы пошагово создаются с сохранением состояния, может потребоваться дополнительная логика восстановления на сервере за пределами существующего кода колбэка 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 id="my-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 никогда не перезапишет текущее значение, даже если оно отличается от обновлений, отображённых сервером. Это хорошо работает при обновлениях, не предполагающих серьёзных побочных эффектов, например при отображении ошибок проверки формы или добавлении элементов интерфейса, связанных со значениями, которые пользователь вводит в форму.

Для таких сценариев поле ввода phx-change не блокирует редактирование, пока событие отправляется на сервер. Когда серверу отправляется событие phx-change, тег поля ввода и родительский тег формы получают CSS-класс phx-change-loading, после чего на сервер отправляются данные с параметром "_target" в корневом объекте данных. Этот параметр содержит пространство ключей имени поля ввода, вызвавшего событие изменения.

Например, если событие изменения вызвало следующее поле ввода:

<input name="user[username]"/>

Колбэк handle_event/3 на сервере получит данные:

%{"_target" => ["user", "username"], "user" => %{"username" => "Name"}}

Событие phx-submit используется для отправки формы, когда обычно возникают серьёзные побочные эффекты, например отображение новых контейнеров, вызов внешней службы или перенаправление на новую страницу.

При отправке формы, связанной с событием phx-submit:

  1. Поля ввода формы переводятся в состояние readonly
  2. Все кнопки отправки формы становятся неактивными
  3. Форма получает класс "phx-submit-loading"

После завершения обработки сервером события phx-submit:

  1. Отправленная форма снова становится активной, а класс "phx-submit-loading" удаляется
  2. Фокус возвращается к последнему полю ввода, находившемуся в фокусе (если фокус не был установлен на другом поле)
  3. Обновления применяются к DOM как обычно

Чтобы обрабатывать длительные события, к тегу <button> формы можно добавить атрибут phx-disable-with. Во время отправки события он заменяет значение innerText элемента на заданное значение. Например, следующий код заменит текст кнопки «Сохранить» на «Сохранение…» и восстановит текст «Сохранить» после подтверждения:

<button type="submit" phx-disable-with="Saving...">Save</button>

Примечание о неактивных кнопках

По умолчанию LiveView делает неактивными только кнопки отправки формы и поля ввода внутри форм, ожидая подтверждения от сервера. Чтобы сделать неактивной кнопку вне формы, не меняя её текст, можно добавить phx-disable-with без значения:

 <button type="button" phx-disable-with>...</button>

Также обратите внимание, что LiveView игнорирует нажатия на элементы, ожидающие подтверждения от сервера. Это значит, что обычная кнопка без phx-disable-with семантически не становится неактивной во время ожидания ответа сервера, но повторные события она не вызывает.

Наконец, phx-disable-with работает со значением innerText элемента, поэтому вложенные элементы DOM, такие как svg или значки, сохраняться не будут. Другие способы решения этой задачи описаны в разделе «Состояния загрузки CSS».

Также можно использовать CSS-классы состояний загрузки LiveView, чтобы менять содержимое формы во время отправки. Например, добавьте следующие правила в файл app.css:

.while-submitting { display: none; }
.inputs { display: block; }

.phx-submit-loading .while-submitting { display: block; }
.phx-submit-loading .inputs { display: none; }

Затем показывайте и скрывайте содержимое с помощью следующей разметки:

<form id="my-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 заменяются, а не перемещаются, что может привести к таким проблемам, как потеря фокуса полями формы.

Вызов событий формы phx- с помощью JavaScript

Часто требуется вызвать событие для элемента DOM без явного взаимодействия пользователя с этим элементом. Например, это может понадобиться для пользовательского элемента формы, такого как выбор даты или пользовательский список, который использует скрытое поле ввода для хранения выбранного значения.

В таких случаях можно использовать функции событий API DOM, например, чтобы вызвать событие phx-change:

document.getElementById("my-select").dispatchEvent(
  new Event("input", {bubbles: true})
)

При использовании клиентского хука для определения элемента можно использовать this.el, как описано в документации «Клиентские хуки».

Также можно вызвать phx-submit с помощью события «submit»:

document.getElementById("my-form").dispatchEvent(
  new Event("submit", {bubbles: true, cancelable: true})
)

Предотвращение отправки формы с помощью JavaScript

В некоторых случаях может потребоваться условно предотвращать отправку формы на основе проверки на стороне клиента или другой бизнес-логики, прежде чем разрешить серверу обработать phx-submit.

Чтобы предотвратить стандартное поведение при отправке формы, можно использовать JavaScript, например, в хуке:

/**
 * @type {import("phoenix_live_view").HooksOptions}
 */
let Hooks = {}
Hooks.CustomFormSubmission = {
  mounted() {
    this.el.addEventListener("submit", (event) => {
      if (!this.shouldSubmit()) {
        // prevent the event from bubbling to the default LiveView handler
        event.stopPropagation()
        // prevent the default browser behavior (submitting the form over HTTP)
        event.preventDefault()
      }
    })
  },
  shouldSubmit() {
    // Check if we should submit the form
    ...
  }
}

Добавьте этот хук к форме следующим образом:

<form id="my-form" phx-hook="CustomFormSubmission">
  <input type="text" name="text" value={@text}>
</form>
← Предыдущая страница Внешняя загрузка
Следующая страница → Взаимодействие с JavaScript

Пакет Hex Предпросмотр Hex (текущий файл) Просмотреть llms.txt Скачать версию ePub

Создано с помощью ExDoc (v0.40.3) для языка программирования Elixir

© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix-live-view.hexdocs.pm/form-bindings.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API