Featured image of post Phoenix v1.7.14 Views (components) and how it is different from other MVC frameworks

Phoenix v1.7.14 Views (components) and how it is different from other MVC frameworks

Introduction

There isn’t many resources on how to do view in the Phoenix framework (1.7.14) I read the official document and searched the web and it was lacking.

Especially on passing data onto the view template from component.

Phoenix framework (1.7.14) is MVC but have changed the way V(view) works. So this is just a documentation for myself.

File Structure

./gira/*

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
assets/
config/
lib/
priv/
test/
.formatter.exs
.gitignore
README.md
mix.exs
mix.lock

./gira/lib/gira_web/

1
2
3
4
5
6
components/
controllers/
endpoint.ex
gettext.ex
router.ex
telemetry.ex

./gira/lib/gira_web/controllers/

Notice the page_controller.ex, page_html.ex, and page_html/.

The page_html.ex and page_html/ is where the “view” logics are located. This is the convention in the Phoenix framework (1.7.14) and I would suggest sticking to the default.

Again controllers and views lives in the controllers/ folder. View in the Phoenix framework (1.7.14) is postscript with _html and are further organized in two places.

1
2
3
4
5
page_html/
error_html.ex
error_json.ex
page_controller.ex
page_html.ex

./gira/lib/gira_web/components/

1
2
3
layouts/
core_components.ex
layouts.ex

Codes - Simple Component for layout

This is a simple component navbar that doesn’t uses any data (at least not yet). I decided to put the navbar view logic in the layout.

./gira/lib/gira_web/components/layouts.ex

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
defmodule GiraWeb.Layouts do
  @moduledoc """
  This module holds different layouts used by your application.

  See the `layouts` directory for all templates available.
  The "root" layout is a skeleton rendered as part of the
  application router. The "app" layout is set as the default
  layout on both `use GiraWeb, :controller` and
  `use GiraWeb, :live_view`.
  """
  use GiraWeb, :html

  embed_templates "layouts/*"

  def navbar(assigns) do
    ~H"""
      <nav class="bg-white dark:bg-gray-900 fixed w-full z-20 top-0 start-0 border-b border-gray-200 dark:border-gray-600">
        <div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
          <a href="#" class="flex items-center space-x-3 rtl:space-x-reverse">
              <img src="https://flowbite.com/docs/images/logo.svg" class="h-8" alt="Flowbite Logo" />
              <span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">GiraUpdates</span>
          </a>
          <button data-collapse-toggle="navbar-dropdown" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-dropdown" aria-expanded="false">
              <span class="sr-only">Open main menu</span>
              <svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
                  <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"/>
              </svg>
          </button>
          <div class="hidden w-full md:block md:w-auto" id="navbar-dropdown">
            <ul class="flex flex-col font-medium p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:space-x-8 rtl:space-x-reverse md:flex-row md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
              <li>
                <a href="#" class="block py-2 px-3 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 md:dark:text-blue-500 dark:bg-blue-600 md:dark:bg-transparent" aria-current="page">Home</a>
              </li>
              <li>
                  <button id="dropdownNavbarLink" data-dropdown-toggle="dropdownNavbar" class="flex items-center justify-between w-full py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 md:w-auto dark:text-white md:dark:hover:text-blue-500 dark:focus:text-white dark:border-gray-700 dark:hover:bg-gray-700 md:dark:hover:bg-transparent">Reading<svg class="w-2.5 h-2.5 ms-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
          <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
        </svg></button>
                  <!-- Dropdown menu -->
                  <div id="dropdownNavbar" class="z-10 hidden font-normal bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700 dark:divide-gray-600">
                      <ul class="py-2 text-sm text-gray-700 dark:text-gray-400" aria-labelledby="dropdownLargeButton">
                        <li>
                          <a href="#" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Dashboard</a>
                        </li>
                        <li>
                          <a href="#" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Settings</a>
                        </li>
                        <li>
                          <a href="#" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Earnings</a>
                        </li>
                      </ul>
                      <div class="py-1">
                        <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Sign out</a>
                      </div>
                  </div>
              </li>
              <li>
                <a href="#" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Series</a>
              </li>
              <li>
                <a href="#" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Tools</a>
              </li>
              <li>
                <a href="#" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Login</a>
              </li>
            </ul>
          </div>
        </div>
      </nav>
    """
  end
end

./gira/lib/gira_web/components/layouts/app.html.heex

1
<.navbar />

Codes - Complex Component with Controller (connecting with model)

This one is to have a table component, the rows are generated base on the data given by the model.

./gira/lib/gira_web/controllers/page_controller.ex

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
defmodule GiraWeb.PageController do
  use GiraWeb, :controller

  alias Gira.NovelUpdate
  #alias Gira.NovelUpdate.Update

  def home(conn, _params) do
    # The home page is often custom made,
    # so skip the default app layout.
    updates = NovelUpdate.list_updates()
    render(conn, :home, updates: updates)
  end
end

./gira/lib/gira_web/controllers/page_html.ex

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
defmodule GiraWeb.PageHTML do
  @moduledoc """
  This module contains pages rendered by PageController.

  See the `page_html` directory for all templates available.
  """
  use GiraWeb, :html

  embed_templates "page_html/*"

  def table_update(assigns) do
    ~H"""
    <div class="relative overflow-x-auto shadow-md sm:rounded-lg">
    <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
    <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
    <tr>
    <th scope="col" class="px-6 py-3">
    Date
    </th>
    <th scope="col" class="px-6 py-3">
    <div class="flex items-center">
    Title
    </div>
    </th>
    <th scope="col" class="px-6 py-3">
    <div class="flex items-center">
    Release
    </div>
    </th>
    <th scope="col" class="px-6 py-3">
    <div class="flex items-center">
    Translation Group
    </div>
    </th>
    </tr>
    </thead>
    <tbody>
    <tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700" :for={update <- @updates}>
    <td class="px-6 py-4">
    <%= update.update_date %>
    </td>
    <td scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
    <%= update.title %>
    </td>
    <td class="px-6 py-4">
    <%= update.release %>
    </td>
    <td class="px-6 py-4">
    <%= update.translation_group %>
    </td>
    </tr>
    </tbody>
    </table>
    </div>
    """
  end
end

./gira/lib/gira_web/controllers/page_html/home.html.heex

As of the writing this, I couldn’t find an explicit example of how to connect model data to the template and component. I’ve tried countless combination until I figured it out especially the code below.

1
<.table_update updates={@updates} />

Credit

  1. Ai Generated Phoenix Fantasy royalty-free stock illustration. Free for use & download.
comments powered by Disqus

Desiderata by Max Ehrmann

Go placidly amid the noise and the haste, and remember what peace there may be in silence. As far as possible without surrender be on good terms with all persons. Speak your truth quietly and clearly; and listen to others, even to the dull and the ignorant, they too have their story. Avoid loud and aggressive persons, they are vexations to the spirit.

If you compare yourself with others, you may become vain or bitter; for always there will be greater and lesser persons than yourself. Enjoy your achievements as well as your plans. Keep interested in your own career, however humble; it is a real possession in the changing fortunes of time.

Exercise caution in your business affairs, for the world is full of trickery. But let not this blind you to what virtue there is; many persons strive for high ideals, and everywhere life is full of heroism. Be yourself. Especially do not feign affection. Neither be cynical about love; for in the face of all aridity and disenchantment it is as perennial as the grass. Take kindly the counsel of the years, gracefully surrendering the things of youth.

Nurture strength of spirit to shield you in sudden misfortune. But do not distress yourself with dark imaginings. Many fears are born of fatigue and loneliness. Beyond a wholesome discipline, be gentle with yourself. You are a child of the universe, no less than the trees and the stars; you have a right to be here. And whether or not it is clear to you, no doubt the universe is unfolding as it should.

Therefore, be at peace with God, whatever you conceive Him to be. And whatever your labors and aspirations in the noisy confusion of life, keep peace in your soul. With all its sham, drudgery and broken dreams; it is still a beautiful world. Be cheerful.

Strive to be happy.

Built with Hugo
Theme Stack designed by Jimmy