Readme

Spike.LiveView provides a wrapper around Phoenix.LiveView and Phoenix.LiveComponent, which simplifies working with memory-backed forms, including nested forms that require contextual validation.

Installation

Available in Hex, the package can be installed by adding spike_liveview to your list of dependencies in mix.exs:

def deps do
  [
    {:spike_liveview, "~> 0.2"}
  ]
end

Documentation can be found at https://hexdocs.pm/spike_liveview.

Quick start

Once installed in a Phoenix project, open up your *_web.ex file and add the following functions:

  def form_live_view do
    quote do
      use Phoenix.LiveView,
        layout: {MyAppWeb.LayoutView, "live.html"}

      unquote(view_helpers())

      use Spike.LiveView
    end
  end

  def form_live_component do
    quote do
      use Phoenix.LiveComponent

      unquote(view_helpers())

      use Spike.LiveView
    end
  end

This allows you to build LiveViews and LiveComponents that ship with form and form erors handling capabilities out of the box.

You will need a Spike form. For usage how to build these, refer to Spike docs.

For example, your simplest possible registration form may look like this:

defmodule MyApp.RegistrationForm do
  use Spike.Form do
    field(:username, :string)
    field(:password, :string)

    validates(:username, presence: true, by: &__MODULE__.validate_not_taken/2)
    validates(:password, presence: true)
  end

  def validate_not_taken(value, _context) do
    if MyApp.Repo.get_by(MyApp.User, username: value) do
      {:error, "username already taken"}
    else
      :ok
    end
  end
end

And a LiveView to handle registration process would be:

defmodule MyAppWeb.RegistrationLive do
  use MyAppWeb, :form_live_view
  import Spike.LiveView.Components

  def mount(_params, _, socket) do
    form = MyApp.RegistrationForm.new(%{})

    {:ok,
     socket
     |> assign(%{form: form, errors: Spike.errors(form)})}
  end

  def render(assigns) do
    ~H"""
    <h1>Register</h1>

    <div>
      <label for="username">Username:</label>

      <.form_field field={:username} form={@form}>
        <input id="username" name="value" type="text" value={@form.username} />
      </.form_field>

      <.errors let={field_errors} field={:username} form={@form} errors={@errors}>
        <span class="error">
          <%= field_errors |> Enum.map(fn {_k, v} -> v end) |> Enum.join(", ") %>
        </span>
      </.errors>
    </div>

    <div>
      <label for="password">Password:</label>

      <.form_field field={:password} form={@form}>
        <input id="password" name="value" type="text" value={@form.password} />
      </.form_field>

      <.errors let={field_errors} field={:password} form={@form} errors={@errors}>
        <span class="error">
          <%= field_errors |> Enum.map(fn {_k, v} -> v end) |> Enum.join(", ") %>
        </span>
      </.errors>
    </div>

    <a href="#" phx-click="register">Register!</a>
    """
  end

  def handle_event("register", _, socket) do
    if socket.assigns.errors == %{} do
      # perform registration logic here
      IO.puts("Registering user with #{socket.assigns.form.username} and #{socket.assigns.form.password}....")
      {:noreply, socket}   
    else
      {:noreply, socket |> assign(:form, Spike.make_dirty(socket.assigns.form))}
    end
  end
end

Remember to mount it at router and visit http://localhost:4000/register:

  scope "/", MyAppWeb do
    pipe_through :browser

    live "/register", RegistrationLive
  end

Usage of form components provided by this library is, however, pretty low-level, and we recommend you build your own form components instead.

See Spike Example app for an examples how to build components library.