So, I followwed this tutorial for creating dynamic forms: http://blog.plataformatec.com.br/2016/09/dynamic-forms-with-phoenix/
Okay, this is my actual input_helpers.ex
:
defmodule Conts.InputHelpers do
@moduledoc """
Define <%= input f, :pass, type: :password_input %> syntax
to create dynamic form inputs
"""
use Phoenix.HTML
alias Phoenix.HTML.Form, as: PhxForm
import ContsWeb.ErrorHelpers, only: [error_tag: 2]
@label_opts "basic-form-label"
@input_opts "basic-form-input"
def input(form, field, opts \\ []) do
label_text = opts[:label] || humanize(field)
type = opts[:type] || PhxForm.input_type(form, field)
additional_classes = opts[:class] || ""
label_opts = [class: @label_opts]
input_opts = [
class: "#{@input_opts} #{state_class(form, field)} #{additional_classes}",
id: opts[:id]
]
label_opts = if opts[:id], do: [for: opts[:for]] ++ label_opts, else: label_opts
content_tag :fieldset do
label = label(form, field, label_text, label_opts)
input = input(type, form, field, input_opts)
error = error_tag(form, field)
error = if Enum.empty?(error), do: "", else: error
[label, input, error]
end
end
defp state_class(form, field) do
cond do
# The form was not yet submitted
!form.action -> ""
form.errors[field] -> "border-red-500"
# we don't need any additional style at all
# only with focus:within
true -> ""
end
end
# Implement clauses below for custom inputs.
# defp input(:datepicker, form, field, input_opts) do
# raise "not yet implemented"
# end
defp input(:password_confirmation, form, field, input_opts) do
apply(PhxForm, :password, [form, field, input_opts])
end
defp input(type, form, field, input_opts) do
apply(PhxForm, type, [form, field, input_opts])
end
end
I this project we have a cliente
and a user
schema, with a 1-1 realtionship!
In Live View template, I did as:
<%= input f, :nome, label: "Nome completo", phx_debounce: "blur" %>
<%= input f, :telefone, label: "Número de telefone", phx_debounce: "blur", class: "mobile-masked" %>
<%= inputs_for f, :user, fn u -> %>
<%= input u, :email, phx_debounce: "blur" %>
<%= input u, :password, label: "Senha", phx_debounce: "blur" %>
<%= input u, :password_confirmation, label: "Confirme a Senha", phx_debounce: "blur" %>
<% end %>
The first two fields are from cliente
and other from user
changeset. When the validation starts, aka users start to type input, cliente
's fields correctly applies the border-red-500
class, however user
's fields do not!
I debug it and found that when it tries to validate any field of the user changeset, in the state_class
function of input_helpers.ex
, it falls to the !form.acton
clause.
My question is: how can I handle this nested form action? Why it falls into the aforementioned cond
clause?
The nested fields are missing the :action
field. On the parent form, it is usually :validate
.
There is cheeky "hacky" way of achieving what you want. Just change your HTML from this:
<%= inputs_for f, :user, fn u -> %>
to this:
<%= for u <- inputs_for(f, :user) |> Enum.map(&Map.put(&1, :action, :validate)) do %>
:P