View Source LiveViewNative.LiveForm.Component (live_view_native_live_form v0.4.0-rc.0)

Base components for LiveForm

Summary

Functions

Please see the documentation for Phoenix.Component.async_result/1

Renders a form.

Renders nested form inputs for associations or embeds.

Checks if the input field was used by the client.

Functions

async_result(assigns)

Please see the documentation for Phoenix.Component.async_result/1

Attributes

Slots

  • loading - rendered while the assign is loading for the first time.
  • failed - rendered when an error or exit is caught or assign_async returns {:error, reason} for the first time. Receives the error as a :let.
  • inner_block - rendered when the assign is loaded successfully via AsyncResult.ok/2. Receives the result as a :let.

form(assigns)

Renders a form.

This form component behaves identital to Phoenix.Component.form/1. Please refer to that docmentation for usage details. on arguments. This component is platform agnostic. The <LiveForm> element will be implemented in all supported clients. Third-party clients can also implement their own <LiveForm> element and take advantage of this component.

Attributes

  • for (:any) (required) - An existing form or the form source data.

  • action (:string) - The action to submit the form on. This attribute must be given if you intend to submit the form to a URL without LiveView.

  • as (:atom) - The prefix to be used in names and IDs generated by the form. For example, setting as: :user_params means the parameters will be nested "user_params" in your handle_event or conn.params["user_params"] for regular HTTP requests. If you set this option, you must capture the form with :let.

  • csrf_token (:any) - A token to authenticate the validity of requests. One is automatically generated when an action is given and the method is not get. When set to false, no token is generated.

  • errors (:list) - Use this to manually pass a keyword list of errors to the form. This option is useful when a regular map is given as the form source and it will make the errors available under f.errors. If you set this option, you must capture the form with :let.

  • method (:string) - The HTTP method. It is only used if an :action is given. If the method is not get nor post, an input tag with name _method is generated alongside the form tag. If an :action is given with no method, the method will default to post.

  • multipart (:boolean) - Sets enctype to multipart/form-data. Required when uploading files.

    Defaults to false.

  • Global attributes are accepted. Additional HTML attributes to add to the form tag. Supports all globals plus: ["autocomplete", "name", "rel", "enctype", "novalidate", "target"].

Slots

  • inner_block (required) - The content rendered inside of the form tag.

inputs_for(assigns)

Renders nested form inputs for associations or embeds.

Attributes

  • field (Phoenix.HTML.FormField) (required) - A %Phoenix.HTML.Form{}/field name tuple, for example: {@form[:email]}.

  • id (:string) - The id to be used in the form, defaults to the concatenation of the given field to the parent form id.

  • as (:atom) - The name to be used in the form, defaults to the concatenation of the given field to the parent form name.

  • default (:any) - The value to use if none is available.

  • prepend (:list) - The values to prepend when rendering. This only applies if the field value is a list and no parameters were sent through the form.

  • append (:list) - The values to append when rendering. This only applies if the field value is a list and no parameters were sent through the form.

  • skip_hidden (:boolean) - Skip the automatic rendering of hidden fields to allow for more tight control over the generated markup.

    Defaults to false.

  • options (:list) - Any additional options for the Phoenix.HTML.FormData protocol implementation.

    Defaults to [].

Slots

  • inner_block (required) - The content rendered for each nested form.

Examples

<.form
  :let={f}
  phx-change="change_name"
>
  <.inputs_for :let={f_nested} field={f[:nested]}>
    <.input type="text" field={f_nested[:name]} />
  </.inputs_for>
</.form>

Dynamically adding and removing inputs

Dynamically adding and removing inputs is supported by rendering named buttons for inserts and removals. Like inputs, buttons with name/value pairs are serialized with form data on change and submit events. Libraries such as Ecto, or custom param filtering can then inspect the parameters and handle the added or removed fields. This can be combined with Ecto.Changeset.cast/3's :sort_param and :drop_param options. For example, imagine a parent with an :emails has_many or embeds_many association. To cast the user input from a nested form, one simply needs to configure the options:

schema "mailing_lists" do
  field :title, :string

  embeds_many :emails, EmailNotification, on_replace: :delete do
    field :email, :string
    field :name, :string
  end
end

def changeset(list, attrs) do
  list
  |> cast(attrs, [:title])
  |> cast_embed(:emails,
    with: &email_changeset/2,
    sort_param: :emails_sort,
    drop_param: :emails_drop
  )
end

Here we see the :sort_param and :drop_param options in action.

Note: on_replace: :delete on the has_many and embeds_many is required when using these options.

When Ecto sees the specified sort or drop parameter from the form, it will sort the children based on the order they appear in the form, add new children it hasn't seen, or drop children if the parameter instructs it to do so.

The markup for such a schema and association would look like this in SwiftUI:

<.inputs_for :let={ef} field={@form[:emails]}>
  <LiveHiddenField name="mailing_list[emails_sort][]" value={ef.index} />
  <.input type="text" field={ef[:email]} placeholder="email" />
  <.input type="text" field={ef[:name]} placeholder="name" />
  <LiveButton
    type="submit"
    name="mailing_list[emails_drop][]"
    value={ef.index}
    phx-click={Native.dispatch("change")}
  >
    <.icon name="hero-x-mark" class="w-6 h-6 relative top-2" />
  </LiveButton>
</.inputs_for>

<LiveHiddenField name="mailing_list[emails_drop][]" />

<Button name="mailing_list[emails_sort][]" value="new" phx-click={Native.dispatch("change")}>
  add more
</Button>

We used inputs_for to render inputs for the :emails association, which contains an email address and name input for each child. Within the nested inputs, we render a hidden mailing_list[emails_sort][] input, which is set to the index of the given child. This tells Ecto's cast operation how to sort existing children, or where to insert new children. Next, we render the email and name inputs as usual. Then we render a button containing the "delete" text with the name mailing_list[emails_drop][], containing the index of the child as its value.

Like before, this tells Ecto to delete the child at this index when the button is clicked. We use phx-click={Native.dispatch("change")} on the button to tell LiveView to treat this button click as a change event, rather than a submit event on the form, which invokes our form's phx-change binding.

Outside the inputs_for, we render an empty mailing_list[emails_drop][] input, to ensure that all children are deleted when saving a form where the user dropped all entries. This hidden input is required whenever dropping associations.

Finally, we also render another button with the sort param name mailing_list[emails_sort][] and value="new" name with accompanied "add more" text. Please note that this button must have type="button" to prevent it from submitting the form. Ecto will treat unknown sort params as new children and build a new child. This button is optional and only necessary if you want to dyamically add entries. You can optionally add a similar button before the <.inputs_for>, in the case you want to prepend entries.

to_form(data_or_params, options \\ [])

See Phoenix.Component.to_form/2.

used_input?(arg)

Checks if the input field was used by the client.

This function conforms exactly the same as Phoenix.Component.used_input?/1