关于凤凰的HTML功能组件的注释
#html #elixir #phoenix #liveview

可以以形式使用的一些简单的html functional components

表单组件

我们构建了带有一个形式的:live_component来使用这些HTML组件。我们使用示意性更改MyForm来验证数据。它基于struct %MyForm{}导出函数changeset/2

我们在需要时在mount/1中实例化分配,即,如果尚未存在。这些是将发送为数据库的字段。

def mount(socket) do
  {:ok,
   assign(socket,
     changeset: MyForm.changeset(%MyForm{}),
     [... other assigns...]
   )}
end

我们声明将读取的分配以捕获render/1中的差异:

def render(assigns) do
  assigns = assign(assigns, ....
     [...declare the accessed fields...]

  ~H"""
  <div>
    <.form for={@changeset} id="my_form"
      phx-change="change" phx-submit="send" phx-target={@myself}
      class="..."
    >
    [...HTML function components here...]
    </.form>
  """

所有formdata字段都将以“ my_form”的ID值(例如name="my_form[distance]")为单位。
“发送”处理程序将收到表格的表单:

%{"my_form" => %{"val1" => "a", "val2" => 2,...}}

我们在“更改”上添加事件处理程序以解析表格的参数,检查约束并使用changesetassigns上的更改更新套接字。

def handle_event("change", %{"my_form" => params}, socket) do
  changeset =
      %MyForm{}
      |> MyForm.changeset(params)
      |> Map.put(:action, :validate)

  socket = 
    socket
    |> assign(:changeset, changeset)
    [... other assigns...]
  {:noreply, socket}
end

我们用compile-time验证(不是运行时)声明组件属性。

attr(:some_attr, :string)

施工过程

  • 用法:使用“属性= {@nistion}”的函数。例如:
<.html_function attrib1={@assign1} attr2={@assign2}>
  <.inner_code />
</.html_function
  • 声明用于编译时间检查的属性:
    attr(:attrb1, :string)

  • 检查mount/1render/1

  • 中的分配
  • 构建功能组件,以使用属性渲染HTML元素。例如:

def html_function(assigns) do
  ~H""" 
    <label for={@attrib1}>
      <input id={@attrib1} value={attrib2} name=.../>
   </label>
    ...
  """
end
  • 如果使用更改的“更改”,请更新带有更改值的套接字分配

传递隐藏输入的示例

我们希望将给定值传递给FormData,但没有明确的输入。您可以使用hidden属性。

“普通html”方式只是:

<input type="hidden" name="my_form[radius]" value={@distance} />

但是,为了说明该过程,我们使用函数html组件。

  • 用法:带有函数的模块SAID pass_assign具有使用我们要通过的分配的属性。在下面的情况下,我们的分配中有distance
<.pass_assign radius={@distance} />
  • 声明属性:
attr(:radius, :float, doc: false)
  • 该组件使用属性,然后在提交时将值传递给FormData:
def pass_assign(assigns) do
  ~H"""
    <input type="hidden" value={@radius} 
      name="my_form[radius]"
    />
  """
end
  • 此值没有更改处理程序,该表格将提交:
%{"my_form" => %{"radius" => 12345}}

可重复使用的日期输入的示例

当我们想重用类型date的输入时,一个示例更有趣。

  • 用法:用功能date导入模块,并将其用作:
@date_class "w-15 rounded-ml"

 <.date date={@start_date} name="start" 
   class_date={@date_class} />
 <.date date={@end_date} name="end" 
   class_date={@date_class} />

您有3个属性和2个分配。

  • 在live_component函数mount/1中,实例化分配:
# def mount(socket)
{:ok, assign(socket,
  ...
  start_date: Date.utc_today(),
  end_date: Date.utc_today() |> Date.add(1)
}
  • 声明组件中使用的属性:
attr(:name, :string)
attr(:date, :string)
attr(:class_date, :string)
  • 构建函数date/1并使用属性:
def date(assigns) do
  ~H"""
   <label for={@name}> <%= @name %>
    <input type="date" id={@name} 
      name={"my_form[#{@name}_date]"}
      value={@date} 
      class={@class_date}
    />
  </label>
  """
end
  • 更新“更改”事件处理程序中的更改分配:
socket = socket
    |> assign(:changeset, changeset)
    |> assign(:start_date, params["start_date"])
    |> assign(:end_date, params["end_date"])

提交的参数(formData)为:

%{"my_form" => %{"start_date" => 01/01/2023, "end_date" => 02/01/2023}}

tode:示例与dynamic number of attributes:koud19,

有条件的课程

如果用户的状态为“ admin”,我们希望文本颜色为蓝色。
由于属性class接受列表,我们可以做:

def date(assigns) do
  ~H"""
    <input type="date" id={@name} 
      name={"myform[#{@name}_date]"}
      value={@date} 
      class={[@user.status == "admin" && "text-blue-700", @class_date]}
    />
  """
end

集成错误

  • 我们想集成验证错误。我们可以设计这样的组件:
<.date_err name="my_form[date]" 
    class="w-30" class_err="mt-1"
    date={@date} errors={@changeset.errors}
/>
  • 我们添加了额外属性:
attr(:errors, :list)
attr(:class_err, :string)
  • 该函数组件具有一个错误类,可以帮助DOM中的位置。
def date_err(assigns) do
  messages =
    assigns.errors
    |> Enum.reduce([], fn {_, {msg, _}}, acc -> [msg | acc] end)

  assigns = assign(assigns, :messages, messages)

  ~H"""
  <label for={@name}>
    <input type="date" id={@name} name={@name}
      value={@date} class={@class}
    />
    <span class={["text-red-700",  @class_err]} 
      :for={error <- @messages}
    >
      <%= error %>
    </span>
  </label>
  """
end

单个选择菜单的示例

一个更好的示例:我们可以轻松地从单个select功能组件中的菜单中捕获所选值。我们使用boolean html属性selected从菜单中捕获所选的“选项”,然后将其传递给formdata“ on Change”。

注意:如果选择了多个选择,即使我们使用列表而不是单个字符串,我们也需要管理此列表中的可能更改。

  • 用法:我们声明一个带有两个输入的函数selectl - 菜单列表和初始选择 - 和四个属性(可能具有更多属性,例如类...)。
<.select options={@menu} choice={@status} 
  name="my_form[status]" class="form-select w-20"
/>
  • 我们在mount/1中实例化任务:
# mount(socket)
{:ok,
   assign(socket,
     changeset: MyForm.changeset(%MyForm{}),
     menu: ["" | ~w(a b c)],
     status: "",
     [...]
}
  • 我们声明属性:
attr(:options, :list)
attr(:choice, :string)
attr(:name, :string)
atttr(:class, :string)
  • 我们构建具有属性的功能HTML组件select/1
def select(assigns) do
  ~H"""
    <select name={@name} class={@class} id={@name}>
      <option 
        :for={option <- @options} 
        selected={option == @choice}
      >
        <%= option %>
      </option>
    </select>
  """
end

和commit(在“更改”验证之后)新值带有事件处理程序“更改”到分配中:

socket =
  socket
    |> assign(:changeset, changeset)
    |> assign(:status, params["status"])

动态数据学家的示例

一个动态填充的koude25的示例。
我们使用关联的input将值传递给formdata。

我们希望数据师在用户开始键入时被填充。

  • 用法:数据库组件可以是:
<.datalist users={@users} user={@user}/>
  • 分配:我们在mount/1中实例化两个分配users: []user: ""
    或在需要/需要的情况下将user: assign.current_user设置在render/1中。

  • 属性:我们声明两个属性

attr(:users, :list)
attr(:user, :string)
  • 功能组件是:
def datalist(assigns) do
  ~H"""
    <input list="datalist" id="datalist-input"
        name="my_form[user]"
        phx-change="search-email"
        value={@user}
        />
    <datalist id="datalist">
        <option :for={user <- @users} value={user}  />
    </datalist>
  """
end

和“更改”处理程序可以像:

def handle_event("search-email", %{"my_form" => %{"user" => string}}, socket) do
  datalist = 
    MyApp.User.search(string) |> Enum.map(& &1.email)
  {:noreply, assign(socket, users: datalist, user: string}
end