Local Forms

The following example implements phoenix forms using LocalLiveView.

This example demonstrates uses of onchange and onsubmit form hooks. Live view component stores list of users which can be changed using form component. It validates that usernames are unique and emails are in correct format while typing in the form and on form submission.

Add New User

      
defmodule FormDemoLocal do
  use LocalLiveView
  import Local.CoreComponents

  @impl true
  def render(assigns) do
    ~H"""
    <.form for={@form} id="my-form" pop-change="validate" pop-submit="save" class="bordered">
      <label>USERNAME</label>
      <.input type="text" field={@form[:username]} />
      <label>EMAIL</label>
      <.input type="text" field={@form[:email]} />
      <div class="centered">
        <button class="ghost-button" disabled={@disabled}>SAVE</button>
      </div>
    </.form>
    <%= for error <- @errors do %>
      <p style="color:red;">{error}</p>
    <% end %>
    <div class="bordered">
      <h1>User List:</h1>
      <ul>
        <%= for user <- @users do %>
          <li>Username: {user["username"]}, Email: {user["email"]}</li>
        <% end %>
      </ul>
    </div>
    """
  end

  @impl true
  def mount(_params, _session, socket) do
    users = [
      %{"email" => "[email protected]", "username" => "user1"},
      %{"email" => "[email protected]", "username" => "user2"},
      %{"email" => "[email protected]", "username" => "user3"}
    ]

    user = %{"email" => "", "username" => ""}
    {:ok, assign(socket, users: users, form: to_form(user), errors: [], disabled: true)}
  end

  @impl true
  def handle_event("validate", params, socket) do
    errors = validate(params, socket.assigns.users)
    {:noreply, assign(socket, form: to_form(params), errors: errors, disabled: errors != [])}
  end

  def handle_event("save", user_params, socket) do
    users = socket.assigns.users

    case validate(user_params, users) do
      [] ->
        user = %{"email" => "", "username" => ""}

        {:noreply,
         assign(socket,
           form: to_form(user),
           users: users ++ [user_params],
           errors: [],
           disabled: true
         )}

      errors ->
        {:noreply, assign(socket, errors: errors, disabled: true)}
    end
  end

  defp validate(user, existing_users) do
    (validate_correctness(user) ++ validate_already_existing(user, existing_users))
    |> Enum.filter(fn error -> error != "" end)
  end

  defp validate_already_existing(user, existing_users) do
    user
    |> Enum.filter(fn {key, value} ->
      Enum.any?(existing_users, fn user -> Map.get(user, key) == value end)
    end)
    |> Enum.map(fn {key, _value} ->
      String.capitalize("#{key} already in use")
    end)
  end

  defp validate_correctness(user) do
    Enum.map(user, fn {key, value} -> validate_correctness(key, value) end)
  end

  defp validate_correctness("username", value) do
    cond do
      String.length(value) < 4 -> "Username length must be greater than 3 characters"
      true -> ""
    end
  end

  defp validate_correctness("email", value) do
    with [name, server] <- String.split(value, "@"),
         true <- String.length(name) > 0 and String.contains?(server, ".")
      do
        ""
      else
        _err -> "Email must have an email format"
    end
  end
end