Understanding Forms and Changesets: Difference between revisions

From ElixirBlocks
Jump to: navigation, search
No edit summary
No edit summary
Line 234: Line 234:


https://adopt-liveview.lubien.dev/guides/simple-forms-with-ecto/en
https://adopt-liveview.lubien.dev/guides/simple-forms-with-ecto/en
===A Working Form Without Changesets ===
<source>
defmodule AppWeb.SandLive do
  use AppWeb, :live_view
  alias App.Todos
  alias App.Todos.Todo
  def mount(params,session,socket) do 
   
    {:ok, assign(socket, data: "some data")}
  end
  def render(assigns) do
   
    ~H""" 
        <div>
            <.form  :let={f}  phx-submit="save">
                <.input field={f[:name]} type="text" label="Name" value="" />
                <.button>SAVE </.button>
       
            </.form>
          {@data}
        </div>
      """
  end
  def handle_event("save",params, socket) do
      IO.inspect params
    {:noreply, socket}
  end
end
===The Same Example with a Changeset ===
<source>
defmodule AppWeb.PageLive do
    use AppWeb, :live_view
 
    alias App.Todos
    alias App.Todos.Todo
 
    def mount(_params, _session, socket) do
      changeset = Todos.change_todo(%Todo{})
      {:ok, assign(socket, changeset: changeset)}
    end
 
    def render(assigns) do
      ~H"""
      <h1>Create User</h1>
      <.form :let={f} for={@changeset} phx-submit="save">
      <div>
        <.input field={f[:name]} type="text" label="Name" />
      </div>
      <div>
        <.button>Save</.button>
      </div>
    </.form>
      """
    end
 
    def handle_event("save", %{"todo" => todo_params}, socket) do
      result =  Todos.create_todo(todo_params)
      IO.inspect result
      case Todos.create_todo(todo_params) do
        {:ok, _todo} ->
          {:noreply, socket
          |> put_flash(:info, "User created successfully.")
          |> redirect(to: "/")}
        {:error, %Ecto.Changeset{} = changeset} ->
          {:noreply, assign(socket, changeset: changeset)}
      end
    end
  end
</source>
</source>

Revision as of 22:06, 22 March 2025

This page is in progress

This tutorial assumes you have a basic understanding of Elixir and that you have explored Phoenix. It also assumes that you understand HTML forms.

The methodology of this tutorial includes converting a conventional HTML form into a Phoenix template that uses Elixir to communicate with back end code. You also learn how changesets integrate with controllers.


Getting Started

To begin, create a new empty Phoenix app named app. If you do not know how to do that follow this tutorial:

How to Create an Empty Phoenix Application


Creating an HTML Form and Post Request

In the router create these two routes:

  scope "/", AppWeb do
    pipe_through :browser

    get "/", PageController, :index       # First route
    post "/create", PageController, :new  # Second route


  end


Place the code below in a file named page_controller.ex. Place the file in the controller directory.

app/lib/app_web/controllers/page_controller.ex


defmodule AppWeb.PageController do
  use AppWeb, :controller

  def index(conn, params) do
    IO.inspect params
    csrf_token = Plug.CSRFProtection.get_csrf_token()
    render(conn, :index, data: "Hello World",form: %{},csrf_token: csrf_token)
  end

  def new(conn, params) do
      IO.inspect params
      redirect(conn, to: "/")
  end
end


In the controllers directory create a file named page_html.ex

In this file, copy this code.

defmodule AppWeb.PageHTML do
  use AppWeb, :html

  embed_templates "page_html/*"
end


In the controllers directory create another directory named page_html (technically you can place this directory outside the controllers and it will still work).

app/lib/app_web/controllers/page_html

This directory will contain heex templates for the PageController.

In the page_html directory place a file named index.html.heex.

Inside the file copy the following code.


<%=@data%>



<form method="POST" action="/create">
    <input type="hidden" name="_csrf_token" value={@csrf_token} />
    <input type="text" name="example" />
    <input type="submit">
  </form>


Run the server: mix phx.server

You should see a form field and a submit button with the text "Hello World" above them. Type into the form and submit it, the terminal will display your input.


If you replace the form with the built in form helper, you can remove the csrf code. CSRF protection comes built in with the form helper. Delete or comment out the code in index.html.heex and replace it with the code below.



  <.form :let={f} action={~p"/create"}>
    <.input field={f[:x]} name="submitted-data" />
  </.form>

Delete or comment out the code in page_controller.ex and replace it with the code below.

defmodule AppWeb.PageController do
  use AppWeb, :controller

  def index(conn, params) do
    IO.inspect params
    render(conn, :index, data: "Hello World",form: %{})
  end

  def new(conn, params) do
      IO.inspect params
      redirect(conn, to: "/")
  end
end


When you run the server and visit the landing page, you should see the page render without error.


Form Changesets

Without Changesets we have these two problems:

  • We lose what we typed.
  • The app doesn’t tell us what our errors were.

Changesets solve both of these problems.


Schema and Live View Form

Schema

defmodule App.Todo do
  use Ecto.Schema
  import Ecto.Changeset

  schema "todos" do
    field :description, :string
    field :title, :string

    timestamps(type: :utc_datetime)
  end

  @doc false
  def changeset(todo, attrs) do
    todo
    |> cast(attrs, [:title, :description])
    |> validate_required([:title, :description])
  end
end

Live View Form

defmodule AppWeb.PageLive do
  use AppWeb, :live_view
  alias App.Todo

  def mount(_params, _session, socket) do
    changeset = Todo.changeset(%Todo{}, %{})
    
    {:ok,
     socket
     |> assign(:changeset, changeset)
     |> assign(:todo, %Todo{})
     |> assign(:saved, false)}
  end

  def handle_event("save", %{"todo" => todo_params}, socket) do
   case save_todo(todo_params) do
     {:ok, _todo} ->
       {:noreply,
        socket
        |> assign(:changeset, Todo.changeset(%Todo{}, %{}))
        |> assign(:saved, true)}

     {:error, %Ecto.Changeset{} = changeset} ->
       {:noreply, assign(socket, changeset: changeset, saved: false)}
   end
 end

 defp save_todo(todo_params) do
   %Todo{}
   |> Todo.changeset(todo_params)
   |> App.Repo.insert()
 end

  def render(assigns) do
    ~H"""
    <.form :let={f} for={@changeset} phx-submit="save">
      <div>
        <.input field={f[:title]} type="text" label="Title" />
      </div>

      <div>
        <.input field={f[:description]} type="textarea" label="Description" />
      </div>

      <div>
        <.button>Save</.button>
      </div>
    </.form>

    <%= if @saved do %>
      <div class="alert alert-info">
        Todo saved successfully!
      </div>
    <% end %>
    """
  end

end


https://adopt-liveview.lubien.dev/guides/simple-forms-with-ecto/en



A Working Form Without Changesets

defmodule AppWeb.SandLive do 
  use AppWeb, :live_view

  alias App.Todos
  alias App.Todos.Todo

  def mount(params,session,socket) do  
    
    {:ok, assign(socket, data: "some data")}
  end

  def render(assigns) do
    
    ~H"""  
        <div>
            <.form  :let={f}  phx-submit="save">
                <.input field={f[:name]} type="text" label="Name" value="" />
                <.button>SAVE </.button>
        
            </.form>

          {@data}

        </div>

      """

  end


  def handle_event("save",params, socket) do 
      IO.inspect params

    {:noreply, socket}
  end


end


===The Same Example with a Changeset ===


<source>


defmodule AppWeb.PageLive do
    use AppWeb, :live_view
  
    alias App.Todos
    alias App.Todos.Todo
  
    def mount(_params, _session, socket) do
      changeset = Todos.change_todo(%Todo{})
      {:ok, assign(socket, changeset: changeset)}
    end
  
    def render(assigns) do
      ~H"""
      <h1>Create User</h1>
      <.form :let={f} for={@changeset} phx-submit="save">
      <div>
        <.input field={f[:name]} type="text" label="Name" />
      </div>
      <div>
        <.button>Save</.button>
      </div>
    </.form>
      """
    end
  
    def handle_event("save", %{"todo" => todo_params}, socket) do

      result =  Todos.create_todo(todo_params)
      IO.inspect result

      case Todos.create_todo(todo_params) do
        {:ok, _todo} ->
          {:noreply, socket 
          |> put_flash(:info, "User created successfully.") 
          |> redirect(to: "/")}

        {:error, %Ecto.Changeset{} = changeset} ->
          {:noreply, assign(socket, changeset: changeset)}
      end
    end
  end

</source>