Feature
Basic LiveView form validation
Inline form validation basically comes out of the box if you generate a new LiveView app. However, if you want to try it out or add it afterwards, this feature can show you how you can very easily implement it.
lib/phoenix_features_web/live/components/form_validation_simple.ex
defmodule PhoenixFeaturesWeb.Components.FormValidationSimple do
use PhoenixFeaturesWeb, :live_component
alias PhoenixFeatures.Products
alias PhoenixFeatures.Products.Product
@impl true
def update(assigns, socket) do
product = %Product{} # Or get it from assigns or the database
changeset = Products.change_product(product)
{:ok,
socket
|> assign(assigns)
|> assign(:changeset, changeset)
|> assign(:product, product)
}
end
@impl true
def handle_event("validate", %{"product" => product_params}, socket) do
changeset =
socket.assigns.product
|> Products.change_product(product_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, :changeset, changeset)}
end
def handle_event("save", %{"product" => product_params}, socket) do
changeset = Products.change_product(%Product{})
{:noreply, assign(socket, :changeset, changeset)}
end
end
lib/phoenix_features_web/live/components/form_validation_simple.html.leex
<%= f = form_for @changeset, "#",
id: @id,
class: "block",
phx_target: @myself,
phx_change: "validate",
phx_submit: "save" %>
<div class="mb-4">
<%= label f, :name, class: "inline-block mb-2" %>
<%= text_input f, :name, class: "block w-full h-auto py-2 px-3 text-base font-normal leading-normal text-gray-700 border border-gray-400 rounded duration-150 transition-colors transition-shadow ease-in-out hover:border-gray-500 focus:border-indigo-200 outline-none" %>
<%= error_tag f, :name %>
</div>
<div class="mb-4">
<%= label f, :price, class: "inline-block mb-2" %>
<%= text_input f, :price, class: "block w-full h-auto py-2 px-3 text-base font-normal leading-normal text-gray-700 border border-gray-400 rounded duration-150 transition-colors transition-shadow ease-in-out hover:border-gray-500 focus:border-indigo-200 outline-none" %>
<%= error_tag f, :price %>
</div>
<div class="mb-4 pl-5">
<%= checkbox f, :published, class: "absolute p-0 mt-1 -ml-5" %>
<%= label f, :published, class: "form-check-label" %>
<%= error_tag f, :published %>
</div>
<div class="mb-4">
<%= label f, :description %>
<%= textarea f, :description, class: "block w-full h-auto py-2 px-3 text-base font-normal leading-normal text-gray-700 border border-gray-400 rounded duration-150 transition-colors transition-shadow ease-in-out hover:border-gray-500 focus:border-indigo-200 outline-none" %>
<%= error_tag f, :description %>
</div>
<div class="mt-4 mb-2">
<%= submit "Save", phx_disable_with: "Saving...", class: "font-normal border border-transparent py-2 px-3 text-base rounded border border-blue-600 bg-blue-600 text-white hover:border-blue-700 hover:bg-blue-700" %>
</div>
</form>
test/phoenix_features_web/live/demo_live_test.exs
defmodule PhoenixFeaturesWeb.Components.FormValidationSimpleTest do
use PhoenixFeaturesWeb.ConnCase
import Phoenix.LiveViewTest
alias PhoenixFeatures.Products
describe "FormValidationSimple" do
test "shows the component", %{conn: conn} do
{:ok, view, _html} = live(conn, Routes.demos_show_path(conn, :show, "form_validation_simple"))
assert view |> element("#form_validation_simple") |> has_element?()
end
test "with invalid input it displays an error message", %{conn: conn} do
{:ok, view, _html} = live(conn, Routes.demos_show_path(conn, :show, "form_validation_simple"))
html = view
|> element("form")
|> render_change(%{"product" => %{"name" => "a"}})
assert html =~ "invalid-feedback"
assert html =~ "should be at least 3 character(s)"
end
test "with valid input it doesnt display error messages", %{conn: conn} do
{:ok, view, _html} = live(conn, Routes.demos_show_path(conn, :show, "form_validation_simple"))
html = view
|> element("form")
|> render_change(%{"product" => %{"name" => "Some name", "price" => 100}})
refute html =~ "invalid-feedback"
end
end
end