View Source Components Library

Feel free to grab and customize these components to your needs, and treat them as a starting point when building your form builder.

  import Spike.LiveView.Components

  def errors_component(%{form: _, field: _, errors: _} = assigns) do
    ~H"""
    <.errors let={field_errors} field={@field} form={@form} errors={@errors}>
      <span class="error">
        <%= field_errors |> Enum.map(fn {_k, v} -> v end) |> Enum.join(", ") %>
      </span>
    </.errors>
    """
  end

  def label_component(%{ref: _ref, text: _text, field: _field, required: required} = assigns) do
    if required do
      ~H"""
      <label for={"#{@ref}_#{@field}"}>* <%= @text %></label>
      """
    else
      ~H"""
      <label for={"#{@ref}_#{@field}"}><%= @text %></label>
      """
    end
  end

  def input_component(%{type: "textarea", field: _, form: _, errors: _} = assigns) do
    assigns = assigns |> assign_new(:target, fn -> nil end)

    ~H"""
    <div>
      <%= if @label do %>
        <.label_component text={@label} ref={@form.ref} field={@field} required={is_required?(@form, @field)} />
      <% end %>

      <.form_field field={@field} form={@form} target={@target}>
        <textarea id={"#{@form.ref}_#{@field}"} name="value"><%= @form |> Map.get(@field) %></textarea>
      </.form_field>

      <.errors_component form={@form} field={@field} errors={@errors} />
    </div>
    """
  end

  def input_component(%{type: type, field: _, form: _, errors: _} = assigns)
      when type in ["text", "password", "email"] do
    assigns = assigns |> assign_new(:target, fn -> nil end)

    ~H"""
    <div>
      <%= if @label do %>
        <.label_component text={@label} ref={@form.ref} field={@field} required={is_required?(@form, @field)} />
      <% end %>

      <.form_field field={@field} form={@form} target={@target}>
        <input id={"#{@form.ref}_#{@field}"} name="value" type={type} value={@form |> Map.get(@field)} />
      </.form_field>

      <.errors_component form={@form} field={@field} errors={@errors} />
    </div>
    """
  end

  def input_component(%{type: "checkbox", field: _, form: _, errors: _} = assigns) do
    assigns = assigns
              |> assign_new(:checked_value, fn -> "1" end)
              |> assign_new(:unchecked_value, fn -> "0" end)
              |> assign_new(:target, fn -> nil end)

    ~H"""
    <div>
      <.form_field field={@field} form={@form} target={@target}>
        <span class="float-left">
          <input id={"#{@form.ref}_#{@field}_unchecked"} name="value" type="hidden" value={@unchecked_value} />
          <input id={"#{@form.ref}_#{@field}"} name="value" type="checkbox" value={@checked_value} checked={is_checked?(@form, @field, @checked_value)} />
        </span>

        <span>
          <%= if @label do %>
            <.label_component text={@label} ref={@form.ref} field={@field} required={is_required?(@form, @field)} />
          <% end %>
        </span>

      </.form_field>

      <.errors_component form={@form} field={@field} errors={@errors} />
    </div>
    """
  end

  def input_component(%{type: "select", field: _, form: _, errors: _, options: _} = assigns) do
    assigns = assigns |> assign_new(:target, fn -> nil end)

    ~H"""
    <div>
      <%= if @label do %>
        <.label_component text={@label} ref={@form.ref} field={@field} required={is_required?(@form, @field)} />
      <% end %>

      <.form_field field={@field} form={@form} target={@target}>
        <select id={"#{@form.ref}_#{@field}"} name="value">
          <%= for {value, text} <- @options do %>
            <option value={value || ""} selected={@form |> Map.get(@field) == value}><%= text %></option>
          <% end %>
        </select>
      </.form_field>

      <.errors_component form={@form} field={@field} errors={@errors} />
    </div>
    """
  end

  defp is_checked?(form, field, checked_value) do
    Map.get(form, field) == checked_value || Map.get(form, field) == true
  end

  defp is_required?(form, field) do
    validations = Vex.Extract.settings(form) |> Map.get(field, [])
    {:presence, true} in validations
  end