Skip to content

Latest commit

 

History

History
384 lines (278 loc) · 12.6 KB

phoenix_components.livemd

File metadata and controls

384 lines (278 loc) · 12.6 KB

Phoenix Components

Mix.install([
  {:jason, "~> 1.4"},
  {:kino, "~> 0.9", override: true},
  {:youtube, github: "brooklinjazz/youtube"},
  {:hidden_cell, github: "brooklinjazz/hidden_cell"},
  {:ecto, "~> 3.9.5"}
])

Navigation

Review Questions

Upon completing this lesson, a student should be able to answer the following questions.

  • How do we create a form in a Phoenix application?
  • What is a form action and how is it triggered?
  • How do we validate data and display errors in a form using changesets?

Overview

This is a companion reading for the Blog: Visibility Migration exercise. This lesson is an overview of how to work with Phoenix.Component and the core components provided in Phoenix 1.7 applications.

Phoenix.Component

The Phoenix.Component module allows us to define reusable function components.

defmodule MyComponent do
  use Phoenix.Component

  def greeting(assigns) do
    ~H"""
    <p>hello</p>
    """
  end
end

Custom components can be used in any HEEx template.

<.MyComponent.greeting />

Attr

The attr/3 macro defines typed attributes for the component.

Attributes are accessible through the assigns using @ or assigns..

defmodule MyComponent do
  use Phoenix.Component

  attr :name, :string
  def greeting(assigns) do
    ~H"""
    <p><%= @name %></p>
    """
  end
end

Attributes can be configured with default values, requirements, and more. See attr/3 options for more.

Slots

The [slot/3] macro defines function component slots. This makes it easier to provide re-usable parts of a component.

render_slot/2 renders a given slot entry.

defmodule MyComponent do
  use Phoenix.Component

  slot :header
  slot :inner_block, required: true
  slot :footer
  def page(assigns) do
    ~H"""
    <h1>
      <%= render_slot(@header) %>
    </h1>

    <p>
      <%= render_slot(@inner_block) %>
    </p>

    <section>
      <%= render_slot(@footer) %>
    </section>
    """
  end
end

Slots are provided using a colon : in the html element name.

<.page>
  My Inner Block
  <:header>My Header</:header>
  <:footer>My Header</:footer>
</.page>

inner_block is the default slot name for direct children in a component. Any HTML elements not contained in other slots will be contained in the inner_block slot.

Global Attributes

HTML elements take a large number of optional attributes that we might want to be able to pass in without explicitly defining them.

We can use global attributes to collect these attributes. By default, the attributes accepted are those that are common to all standard HTML tags. You can find a comprehensive list of these attributes in the the Global attributes MDN docs.

attr :rest, :global

def hello(assigns) do
  ~H"""
  <p {@rest}>Hello</p>
  """
end

Core Components

Components defined in the core_components.ex file in a Phoenix application are automatically imported, so they can be used without a module name.

<.header>
  Listing Posts
  <:actions>
    <.link href={~p"/posts/new"}>
      <.button>New Post</.button>
    </.link>
  </:actions>
</.header>

Here are the main components you'll often use:

  • modal
  • simple_form
  • button
  • input
  • label
  • error
  • header
  • table
  • list
  • back
  • icon

These components are not documented in the Phoenix docs, because they are defined inside of your application when you generate a Phoenix 1.7 application.

See the core_components.ex file in any Phoenix 1.7 application for documentation and the internal implementation details of these functions.

Simple Form

The simple_form/1 component in core_components.ex defines a form we can use in a HEEx template to let the user send HTTP requests.

attr :for, :any, required: true, doc: "the datastructure for the form"
attr :as, :any, default: nil, doc: "the server side parameter to collect all input under"

attr :rest, :global,
  include: ~w(autocomplete name rel action enctype method novalidate target),
  doc: "the arbitrary HTML attributes to apply to the form tag"

slot :inner_block, required: true
slot :actions, doc: "the slot for form actions, such as a submit button"

def simple_form(assigns) do
  ~H"""
  <.form :let={f} for={@for} as={@as} {@rest}>
    <div class="mt-10 space-y-8 bg-white">
      <%= render_slot(@inner_block, f) %>
      <div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
        <%= render_slot(action, f) %>
      </div>
    </div>
  </.form>
  """
end

Forms take a changeset and an action. The action is the URL to submit the HTTP POST request to.

<.simple_form :let={f} for={@changeset} action={@action}>
  <.error :if={@changeset.action}>
    Oops, something went wrong! Please check the errors below.
  </.error>
  <.input field={f[:title]} type="text" label="Title" />
  <.input field={f[:subtitle]} type="text" label="Subtitle" />
  <.input field={f[:content]} type="text" label="Content" />
  <:actions>
    <.button>Save Post</.button>
  </:actions>
</.simple_form>

Forms can be created without a changeset by providing an empty map. It's also possible to change the type of request to send.

Here's an example of a form without a changeset that sends a GET request to the /posts route.

<.simple_form :let={f} for={%{}} method={"get"} action={~p"/posts"}>
  <.input field={f[:title]} type="text" label="Search Posts" />
  <:actions>
    <.button>Search</.button>
  </:actions>
</.simple_form>

Internal Components

Phoenix.Component also defines several internal components.

For example, the simple_form/1 component in core_components.ex relies on the form/1 component.

Here are some useful components to be aware of:

See components for more.

Phoenix.LiveView.JS

Phoenix.LiveView.JS provides JavaScript commands that allow you to perform common client-side tasks, such as adding or removing CSS classes, modifying tag attributes, and showing or hiding content. Unlike client-side hooks, JS commands are aware of changes to the web page made by the server, ensuring that updates are retained even after patches from the server.

For example, we can hide and show an element when clicking a button.

<button phx-click={JS.toggle(to: "#toggleable")}>TEST</button>
<div id="toggleable">Hide and Show Me</div>

These JS commands can be bound to phoenix events such as phx-click, phx-mounted, and more. See Bindings for more information on these events.

While this module belongs to LiveView, much of its functionality can also be used in traditional controller view.

Phoenix *HTML Modules

Phoenix 1.7 replaced Views with unified function components.

The *HTML modules in a Phoenix application use Phoenix.Component under the hood.

defmodule BlogWeb.PostHTML do
  use BlogWeb, :html

  embed_templates "post_html/*"

  @doc """
  Renders a post form.
  """
  attr :changeset, Ecto.Changeset, required: true
  attr :action, :string, required: true

  def post_form(assigns)
end

Therefore, function components can be defined inside of *HTML modules. These function components can return HEEx using sigil ~H in the body of the function.

Alternatively, functions can omit the body to automatically delegate to a corresponding *.html.heex file, as seen above in post_form/1 which would delegate to a post_form.html.heex file.

Phoenix.HTML

The Phoenix.HTML project also defines html elements which use embedded Elixir syntax for elements. It's useful to be aware of this as many Phoenix projects still use this syntax, especially with Phoenix 1.6 or older.

<%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %>
  Name: <%= text_input f, :name %>
<% end %>

Further Reading

Consider the following resource(s) to deepen your understanding of the topic.

Commit Your Progress

DockYard Academy now recommends you use the latest Release rather than forking or cloning our repository.

Run git status to ensure there are no undesirable changes. Then run the following in your command line from the curriculum folder to commit your progress.

$ git add .
$ git commit -m "finish Phoenix Components reading"
$ git push

We're proud to offer our open-source curriculum free of charge for anyone to learn from at their own pace.

We also offer a paid course where you can learn from an instructor alongside a cohort of your peers. We will accept applications for the June-August 2023 cohort soon.

Navigation