AshPhoenix.Form (ash_phoenix v0.4.23-rc.0) View Source
A module to allow you to fluidly use resources with phoenix forms.
The general workflow is, with either liveview or phoenix forms:
- Create a form with
AshPhoenix.Form
- Render that form with Phoenix's
form_for
(or, if using surface, <Form>) - To validate the form (e.g with
on-change
for liveview), pass the input toAshPhoenix.Form.validate(form, params)
- On form submission, pass the input to
AshPhoenix.Form.validate(form, params)
and then useAshPhoenix.Form.submid(form, ApiModule)
If your resource action accepts related data, (for example a managed relationship argument, or an embedded resource attribute), you can
use Phoenix's inputs_for
for that field, but you must explicitly configure the behavior of it using the forms
option.
See Form.for_create/3
for more.
For example:
form =
user
|> AshPhoenix.Form.for_update(:update, forms: [
profile: [
resource: MyApp.Profile,
data: user.profile,
create_action: :create,
update_action: :update
forms: [
emails: [
data: user.profile.emails,
resource: MyApp.UserEmail,
create_action: :create,
update_action: :update
]
]
]
])
LiveView
AshPhoenix.Form
(unlike ecto changeset based forms) expects to be reused throughout the lifecycle of the liveview.
You can use phoenix events to add and remove form entries and submit/2
to submit the form, like so:
alias MyApp.MyApi.{Comment, Post}
def render(assigns) do
~L"""
<%= f = form_for @form, "#", [phx_change: :validate, phx_submit: :save] %>
<%= label f, :text %>
<%= text_input f, :text %>
<%= error_tag f, :text %>
<%= for comment_form <- inputs_for(f, :comments) do %>
<%= hidden_inputs_for(comment_form) %>
<%= text_input comment_form, :text %>
<%= for sub_comment_form <- inputs_for(comment_form, :sub_comments) do %>
<%= hidden_inputs_for(sub_comment_form) %>
<%= text_input sub_comment_form, :text %>
<button phx-click="remove_form" phx-value-path="<%= sub_comment_form.name %>">Add Comment</button>
<% end %>
<button phx-click="remove_form" phx-value-path="<%= comment_form.name %>">Add Comment</button>
<button phx-click="add_form" phx-value-path="<%= comment_form.name %>">Add Comment</button>
<% end %>
<button phx-click="add_form" phx-value-path="<%= comment_form.name %>">Add Comment</button>
<%= submit "Save" %>
</form>
"""
end
def mount(%{"post_id" => post_id}, _session, socket) do
post =
Post
|> MyApp.MyApi.get!(post_id)
|> MyApi.load!(comments: [:sub_comments])
form = AshPhoenix.Form.for_update(post, forms: [
comments: [
resource: Comment,
data: post.comments,
create_action: :create,
update_action: :update
forms: [
sub_comments: [
resource: Comment,
data: &(&1.sub_comments),
create_action: :create,
update_action: :update
]
]
]
])
{:ok, assign(socket, form: form)}
end
def handle_event("save", _params, socket) do
case AshPhoenix.Form.submit(socket.assigns.form) do
{:ok, result} ->
# Do something with the result, like redirect
{:error, form} ->
assign(socket, :form, form)
end
end
def handle_event("add_form", %{"path" => path}, socket) do
form = AshPhoenix.Form.add_form(socket.assigns.form, path)
{:noreply, assign(socket, :form, form)}
end
def handle_event("remove_form", %{"path" => path}) do
form = AshPhoenix.Form.remove_form(socket.assigns.form, path)
{:noreply, assign(socket, :form, form)}
end
Link to this section Summary
Functions
Adds a new form at the provided path.
Creates a form corresponding to a create action on a resource.
Creates a form corresponding to a destroy action on a record.
Creates a form corresponding to an update action on a record.
Returns the parameters from the form that would be submitted to the action.
A utility for parsing paths of nested forms in query encoded format.
Removes a form at the provided path.
Submits the form by calling the appropriate function on the provided api.
Same as submit/3
, but raises an error if the submission fails.
Validates the parameters against the form.
Link to this section Types
Specs
t() :: %AshPhoenix.Form{ action: atom(), data: nil | Ash.Resource.record(), errors: boolean(), form_keys: Keyword.t(), forms: map(), id: term(), method: String.t(), name: term(), opts: Keyword.t(), params: map(), resource: Ash.Resource.t(), source: Ash.Changeset.t(), submit_errors: Keyword.t() | nil, transform_errors: nil | (Ash.Changeset.t(), error :: Ash.Error.t() -> [ {field :: atom(), message :: String.t(), substituations :: Keyword.t()} ]), type: :create | :update | :destroy, valid?: boolean() }
Link to this section Functions
Specs
Adds a new form at the provided path.
Doing this requires that the form has a create_action
and a resource
configured.
path
can be one of two things:
- A list of atoms and integers that lead to a form in the
forms
option provided.[:posts, 0, :comments]
to add a comment to the first post. - The html name of the form, e.g
form[posts][0][comments]
to mimic the above
:prepend
- If specified, the form is placed at the beginning of the list instead of the end of the list The default value isfalse
.:params
- The initial parameters to add the form with. The default value is%{}
.
Specs
for_create(Ash.Resource.t(), action :: atom(), opts :: Keyword.t()) :: t()
Creates a form corresponding to a create action on a resource.
Options:
:forms
- Nested form configurations. See for_create/3 docs for more.:as
- The name of the form in the submitted params. You will need to pull the form params out using this key. The default value is"form"
.:id
- The html id of the form. The default value is"form"
.:transform_errors
- Allows for manual manipulation and transformation of errors.
If possible, try to implementAshPhoenix.FormData.Error
for the error (if it as a custom one, for example). If that isn't possible, you can provide this function which will get the changeset and the error, and should return a list of ash phoenix formatted errors, e.g[{field :: atom, message :: String.t(), substituations :: Keyword.t()}]
:method
- The http method to associate with the form. Defaults topost
for creates, andput
for everything else.
Any additional options will be passed to the underlying call to Ash.Changeset.for_create/4
. This means
you can set things like the tenant/actor. These will be retained, and provided again when Form.submit/3
is called.
Nested Form Options
:type
- The cardinality of the nested form. The default value is:single
.:forms
- Forms nested inside the current nesting level:for
- When creating parameters for the action, the key that the forms should be gathered into. Defaults to the key used to configure the nested form.:resource
- The resource of the nested forms. Unnecessary if you are providing thedata
key, and not adding additional forms to this path.:create_action
- The create action to use when building new forms. Only necessary if you want to useadd_form/3
with this path.:update_action
- The update action to use when building forms for data. Only necessary if you supply thedata
key.:data
- The current value or values that should have update forms built by default.
You can also provide a single argument function that will return the data based on the data of the parent form. This is important for multiple nesting levels of:list
type forms, because the data depends on which parent is being rendered.
Specs
for_destroy(Ash.Resource.record(), action :: atom(), opts :: Keyword.t()) :: t()
Creates a form corresponding to a destroy action on a record.
Options:
:forms
- Nested form configurations. See for_create/3 docs for more.:as
- The name of the form in the submitted params. You will need to pull the form params out using this key. The default value is"form"
.:id
- The html id of the form. The default value is"form"
.:transform_errors
- Allows for manual manipulation and transformation of errors.
If possible, try to implementAshPhoenix.FormData.Error
for the error (if it as a custom one, for example). If that isn't possible, you can provide this function which will get the changeset and the error, and should return a list of ash phoenix formatted errors, e.g[{field :: atom, message :: String.t(), substituations :: Keyword.t()}]
:method
- The http method to associate with the form. Defaults topost
for creates, andput
for everything else.
Any additional options will be passed to the underlying call to Ash.Changeset.for_destroy/4
. This means
you can set things like the tenant/actor. These will be retained, and provided again when Form.submit/3
is called.
Specs
for_update(Ash.Resource.record(), action :: atom(), opts :: Keyword.t()) :: t()
Creates a form corresponding to an update action on a record.
Options:
:forms
- Nested form configurations. See for_create/3 docs for more.:as
- The name of the form in the submitted params. You will need to pull the form params out using this key. The default value is"form"
.:id
- The html id of the form. The default value is"form"
.:transform_errors
- Allows for manual manipulation and transformation of errors.
If possible, try to implementAshPhoenix.FormData.Error
for the error (if it as a custom one, for example). If that isn't possible, you can provide this function which will get the changeset and the error, and should return a list of ash phoenix formatted errors, e.g[{field :: atom, message :: String.t(), substituations :: Keyword.t()}]
:method
- The http method to associate with the form. Defaults topost
for creates, andput
for everything else.
Any additional options will be passed to the underlying call to Ash.Changeset.for_update/4
. This means
you can set things like the tenant/actor. These will be retained, and provided again when Form.submit/3
is called.
Specs
Returns the parameters from the form that would be submitted to the action.
This can be useful if you want to get the parameters and manipulate them/build a custom changeset afterwards.
A utility for parsing paths of nested forms in query encoded format.
For example:
parse_path!(form, "post[comments][0][sub_comments][0])
[:comments, 0, :sub_comments, 0]
Removes a form at the provided path.
See add_form/3
for more information on the path
argument.
If you are not using liveview, and you want to support removing forms that were created based on the data
option from the browser, you'll need to include in the form submission a custom list of strings to remove, and
then manually iterate over them in your controller, for example:
Enum.reduce(removed_form_paths, form, &AshPhoenix.Form.remove_form(&2, &1))
Specs
submit(t(), Ash.Api.t(), Keyword.t()) :: {:ok, Ash.Resource.record()} | :ok | {:error, t()}
Submits the form by calling the appropriate function on the provided api.
For example, a form created with for_update/3
will call api.update(changeset)
, where
changeset is the result of passing the Form.params/3
into Ash.Changeset.for_update/4
.
If the submission returns an error, the resulting form can simply be rerendered. Any nested errors will be passed down to the corresponding form for that input.
Same as submit/3
, but raises an error if the submission fails.
Specs
Validates the parameters against the form.
Options:
:errors
- Set to false to hide errors after validation The default value istrue
.