Understanding Forms and Changesets: Difference between revisions

From ElixirBlocks
Jump to: navigation, search
No edit summary
No edit summary
 
(11 intermediate revisions by the same user not shown)
Line 5: Line 5:
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.  
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.  


The setup for the tutorial requires the creation of a database table named '''Item''' and its Ecto "Context" data.  We walk through the entire process.


'''Glossary'''
* Changeset:
* Phoenix Template:


=Getting Started=
=Getting Started=
Line 18: Line 13:
[[How to Create an Empty Phoenix Application|How to Create an Empty Phoenix Application]]
[[How to Create an Empty Phoenix Application|How to Create an Empty Phoenix Application]]


When complete, run this command to create database tables and Ecto context code.
<source>




mix phx.gen.context Items Item items name:string
==Creating an HTML Form and Post Request==


</source>
In the [[router]] create these two routes:
 
Run the migrate command


<source>
<source>
mix ecto.migrate
  scope "/", AppWeb do
</source>
    pipe_through :browser


==Seed Data==
    get "/", PageController, :index      # First route
In the file named app/priv/repo/seeds.ex  type the following code to create "dummy data" for this exercise.
    post "/create", PageController, :new  # Second route
 
<source>
App.Items.create_item(%{name: "item-1"})
App.Items.create_item(%{name: "item-2"})
App.Items.create_item(%{name: "item-3"})




  end
</source>
</source>


In the terminal type:
mix run priv/repo/seeds.exs


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


In routes, add the the following routes.
'''app/lib/app_web/controllers/page_controller.ex'''


<source>
<source>
get "/items", ItemController, :index
</source>
==Controller==
In app/lib/app_web/controllers create a file controller named:
item_controller.ex


Copy the following code into it.
defmodule AppWeb.PageController do
 
<source>
defmodule AppWeb.ItemController do
   use AppWeb, :controller
   use AppWeb, :controller


   def index(conn, _params) do
   def index(conn, params) do
     # The home page is often custom made,
     IO.inspect params
     # so skip the default app layout.
     csrf_token = Plug.CSRFProtection.get_csrf_token()
     render(conn, :index, layout: false)
     render(conn, :index, data: "Hello World",form: %{},csrf_token: csrf_token)
   end
   end


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


</source>
</source>




In the controllers directory create a file named '''page_html.ex'''


In the same directory create a file named:
In this file, copy this code.
 
'''item.html.ex'''
 
Open the file and type the following code:


<source>
<source>
defmodule AppWeb.ItemHTML do
defmodule AppWeb.PageHTML do
   use AppWeb, :html
   use AppWeb, :html


   embed_templates "item_html/*"
   embed_templates "page_html/*"
end
end


</source>
</source>


Create a folder named index_html in the same directory like this:
app/lib/app_web/controllers/'''index_html'''
In '''index_html''' create a file named '''index.html.heex''' and copy type the following code into it.
<source>
<div> Items go here</div>
</source>
Start the app and make sure everything works.
'''mix phx.server'''
In your browser go to:
<source>
http://localhost:4000/items
</source>
You will see the phrase "Items go here" in the upper left corner of the screen.
==Capture Parameter Data of URL Link==
When a user clicks a hyper-link you should know how to capture the url parameters via controller. To do so follow these steps:
Open the router file  '''router.ex'''.
Create a new route that looks like this:
'''get "/items/:item", ItemController, :index'''
<source>
  scope "/", AppWeb do
    get "/items/:data", ItemController, :index  #:data is a variable
    get "/items", ItemController, :index
    get "/", PageController, :home
  end
</source>
The :item is a variable and represents a value that is unknown. When you type a URL like
localhost:4000/item/some-data 
The value "some-data" is assigned to the variable  :data as a string and the variable is captured in the controller. Once in the controller, the data can be manipulated.
Open the '''item_controller.ex'''
Place '''IO.inspect(_params)''' in the Item index controller body like this:
<source>
  def index(conn, _params) do
    IO.inspect _params
    render(conn, :index, layout: false)
  end
</source>
Open the Item template in '''app_web/controllers/item_html/index.html.heex'''


Write a hyper link like this:
In the controllers directory create another directory named '''page_html''' (technically you can place this directory outside the controllers and it will still work).


<source>
'''app/lib/app_web/controllers/page_html'''
<a href ="items/some-data-goes-here>Click me</a>
</source>


Start the server, go to localhost:4000/items/some-data-goes-here
This directory will contain heex templates for the PageController.


Open the terminal. While it is open click the link in the website that says '''Click me'''.
In the page_html directory place a file named '''index.html.heex'''.


In the terminal the the following text is presented:
Inside the file copy the following code.


<source>
<source>
%{"data" => "some-data-goes-here"}
</source>


The key is the name of the variable you placed in the route (data) and the string is the data assigned to it in the hyper link (some-data-goes-here)
<%=@data%>




== Submitting Data Through an HTML Form to a Controller==
You are now going to write code that lets you submit data through a form and inspect that data from within a controller. 


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


Elixir Phoenix has it's own syntax for writing forms. This syntax is named Embedded Elixir or Heex for short. It lets you write Elixir code in your HTML template files to work with data from the server, database and user interface.
An example of a form with Heex used looks like this:
<source>
<.form for={@form} phx-change="validate" phx-submit="save">
  <.input type="text" field={@form[:username]} />
  <.input type="email" field={@form[:email]} />
  <button>Save</button>
</.form>


</source>
</source>




Prior to using Heex, you will create a working HTML form ''without'' Heex, and gradually integrate Heex into it.  
Run the server:  mix phx.server
 
To do this you need to create:
 
* A POST route
* A Controller for the POST
* An Html form


===HTML Form in Template ===
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.


Go to your item index template: '''app_web/controllers/item_html/index.html.heex'''


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.


Create basic HTML form.


<source>
<source>
<form method="POST" action="/submit">
  <input type="text" name="example" />
  <input type="submit">
</form>
</source>
===Route===
In your router, create the following route. The endpoint must be "submit".


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


 
Delete or comment out the code in page_controller.ex and replace it with the code below.
===Controller===
 
 
<source>
<source>
defmodule AppWeb.PageController do
  use AppWeb, :controller


   def submit(conn, _params) do
   def index(conn, params) do
    # The home page is often custom made,
     IO.inspect params
    # so skip the default app layout.
     render(conn, :index, data: "Hello World",form: %{})
     IO.inspect _params
     redirect(conn, to: "/items")
   end
   end
 
</source>
Go to localhost:4000/items
You will see a form. Click the submit button.
An error will appear:
<source>
invalid CSRF (Cross Site Request Forgery) token, please make sure that:
  * The session cookie is being sent and session is loaded
  * The request include a valid '_csrf_token' param or 'x-csrf-token' header
</source>
To fix the error add the following line to your form right above the index element.
<source>
<%# <input type="hidden" name="_csrf_token" value={@csrf_token} /> %>
</source>
It will look like this:
<source>
<form method="POST" action="/submit">
  <input type="hidden" name="_csrf_token" value={@csrf_token} />
  <input type="text" name="example" />
  <input type="submit">
</form>
</source>
The controller now needs to be configured to render the CSRF token. Change your Item controller code to reflect the following example:
<source>
  def index(conn, _params) do
    IO.inspect _params


    csrf_token = Plug.CSRFProtection.get_csrf_token()
  def new(conn, params) do
    render(conn, :index, [csrf_token: csrf_token])
      IO.inspect params
      redirect(conn, to: "/")
   end
   end
 
end


</source>
</source>




Start the server and go to localhost:4000/items
When you run the server and visit the landing page, you should see the page render without error.
 
Submit data to the form.
 
In the terminal, the submitted data and CSRF content is viewable.
 
<source>
 
%{
  "_csrf_token" => "CAgbPih1d3QuUwIpW1t1byQvEAIUQz8beoJkRF4BxcNv38LYLHr6ZnLo",
  "example" => "Test"
}
 
</source>
 


The key "example" is the name of the form, and has your form submission data.


===Form Changesets===
Without Changesets we have these two problems:


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


{{In_progress}}
Changesets solve both of these problems.

Latest revision as of 10:30, 29 December 2024

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.