LoginSignup
12
0

ElixirDesktop 保存したデータの一覧とフローティングアクションボタンの実装

Last updated at Posted at 2023-12-12

はじめに

この記事は Elixirアドベントカレンダーのシリーズ4の12日目の記事です

11日目でGoogleBookAPIのデータ使用して保存したデータの表示とフローティングアクションボタンの実装を行います

ネストルーティング

保存できるようになったので、見れるようにしていきましょう
本棚に紐づいた本を一覧したいので、ネストさせたURLを作成します

lib/bookshelf_web/router.ex
  scope "/", BookshelfWeb do
    pipe_through :browser

    live "/shelves", ShelfLive.Index, :index
    live "/shelves/new", ShelfLive.Index, :new
    live "/shelves/:id/edit", ShelfLive.Index, :edit
    live "/shelves/:id/delete", ShelfLive.Index, :delete

-    live "/shelves/:id", ShelfLive.Show, :show
-    live "/shelves/:id/show/edit", ShelfLive.Show, :edit
+    live "/shelves/:shelf_id/books", BookLive.Index, :index
+    live "/shelves/:shelf_id/books/:id", BookLive.Show, :show
+    live "/shelves/:shelf_id/books/:id/edit", BookLive.Show, :edit
     live "/search", SearchLive.Index, :index
  end

本棚一覧のリンクを変更します

lib/bookshelf_web/live/shlef_live/index.html.heex
<.gheader title="Shelves">
  <:actions>
    <.link patch={~p"/shelves/new"}>
       <.icon name="hero-plus-solid" class="h-6 w-6 mr-4" /> 
    </.link>
  </:actions>
</.gheader>

<.table
  id="shelves"
  rows={@streams.shelves}
- row_click={fn {_id, shelf} -> JS.navigate(~p"/shelves/#{shelf}") end}
+ row_click={fn {_id, shelf} -> JS.navigate(~p"/shelves/#{shelf}/books") end}
>
  <:col :let={{_id, shelf}} label="Name"><%= shelf.name %></:col>
  <:action :let={{_id, shelf}}>
    <div class="sr-only">
-     <.link navigate={~p"/shelves/#{shelf}"}>Show</.link>
+     <.link navigate={~p"/shelves/#{shelf}/books"}>Show</.link>
    </div>
    <.link patch={~p"/shelves/#{shelf}/edit"}>Edit</.link>
  </:action>
  <:action :let={{_id, shelf}}>
    <.link
      patch={~p"/shelves/#{shelf}/delete"}
    >
      Delete
    </.link>
  </:action>
</.table>

<.bottom_tab title="Shelf" />
...

書籍一覧

index.html.heexの中身をを消して、検索のを使いまわします

lib/bookshelf_web/live/book_live/index.html.heex
<.gheader title={@shelf.name}>
  <:back>
    <.link navigate={~p"/shelves"}>
      <.icon name="hero-chevron-left-solid" class="h-6 w-6" />
    </.link>
  </:back>
</.gheader>

<div
  id="books"
  class="flex flex-wrap gap-4 mt-4"
  phx-update={match?(%Phoenix.LiveView.LiveStream{}, @streams.books) && "stream"}
>
  <%= for {id, book} <- @streams.books do %>
    <.link
      id={id}
      class="card bg-base-100 shadow-xl w-44 select-none"
      navigate={~p"/shelves/#{book.shelf_id}/books/#{book}"}
    >
      <figure><img class="mt-1" src={book.thumb} /></figure>
      <div class="p-2">
        <p class="text-xs"><%= book.title %></p>
        <p class="text-xs mt-2"><%= book.author %></p>
      </div>
    </.link>
  <% end %>
</div>
<.bottom_tab title="Shelf" />

マウント時にパラメータに本棚IDを必須にし、本棚を取得しassignします
この画面からCreateUpdateDeleteはしないので削除します

lib/bookshelf_web/live/book_live/index.ex
defmodule BookshelfWeb.BookLive.Index do
  use BookshelfWeb, :live_view

- alias Bookshelf.Books
- alias Bookshelf.Books.Book
+ alias Bookshelf.Shelf

  @impl true
- def mount(_params, _session, socket) do
-   {:ok, stream(socket, :books, Books.list_books())}
- end
+ def mount(%{"shelf_id" => shelf_id}, _session, socket) do
+   shelf = Shelves.get_shelf!(shelf_id)
+
+   socket
+   |> assign(:shelf, shelf)
+   |> stream(:books, shelf.books)
+   |> then(&{:ok, &1})
+ end
  

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

-  defp apply_action(socket, :edit, %{"id" => id}) do
-    socket
-    |> assign(:page_title, "Edit Book")
-    |> assign(:book, Books.get_book!(id))
-  end

-  defp apply_action(socket, :new, _params) do
-    socket
-    |> assign(:page_title, "New Book")
-    |> assign(:book, %Book{})
-  end

  defp apply_action(socket, :index, _params) do
    socket
    |> assign(:page_title, "Listing Books")
    |> assign(:book, nil)
  end

-  @impl true
-  def handle_info({BookshelfWeb.BookLive.FormComponent, {:saved, book}}, socket) do
-    {:noreply, stream_insert(socket, :books, book)}
-  end

- @impl true
- def handle_event("delete", %{"id" => id}, socket) do
-   book = Books.get_book!(id)
-   {:ok, _} = Books.delete_book(book)
-
-   {:noreply, stream_delete(socket, :books, book)}
- end
end

スクリーンショット 2023-09-13 2.10.19.png

書籍詳細画面

詳細画面では、リンクを差し替えて、サムネイルをimgタグで上の方に置きます

lib/ebookwormer_web/live/book_live/show.html.heex
- <.header>
-  Book <%= @book.id %>
-  <:subtitle>This is a book record from your database.</:subtitle>
-  <:actions>
-    <.link patch={~p"/books/#{@book}/show/edit"} phx-click={JS.push_focus()}>
-      <.button>Edit book</.button>
-    </.link>
-  </:actions>
- </.header>
+ <.gheader title={@book.title}>
+   <:back>
+     <.link navigate={~p"/shelves/#{@book.shelf_id}/books"}>
+       <.icon name="hero-chevron-left-solid" class="h-6 w-6" />
+     </.link>
+   </:back>
+   <:actions>
+     <.link patch={~p"/shelves/#{@book.shelf_id}/books/#{@book}/edit"} phx-click={JS.push_focus()}>
+       <.icon name="hero-pencil-square-solid" class="h-6 w-6 mr-4" />
+     </.link>
+   </:actions>
+ </.gheader>
+ <figure><img class="mt-1" src={@book.thumb} /></figure>

<.list>
  <:item title="Title"><%= @book.title %></:item>
  <:item title="Author"><%= @book.author %></:item>
- <:item title="Thumb"><%= @book.thumb %></:item>
  <:item title="Isbn"><%= @book.isbn %></:item>
  <:item title="Published date"><%= @book.published_date %></:item>
</.list>

- <.back navigate={~p"/books"}>Back to books</.back>
+ <.bottom_tab title="Shelf" />

- <.modal :if={@live_action == :edit} id="book-modal" show on_cancel={JS.patch(~p"/books/#{@book}")}>
+ <.modal :if={@live_action == :edit} id="book-modal" show on_cancel={JS.patch(~p"/shelves/#{@book.shelf_id}/books/#{@book}")}>

  <.live_component
    module={BookshelfWeb.BookLive.FormComponent}
    id={@book.id}
    title={@page_title}
    action={@live_action}
    book={@book}
-   patch={~p"/books/#{@book}"}
+   patch={~p"/shelves/#{@book.shelf_id}/books/#{@book}"} 
  />
</.modal>

詳細画面
スクリーンショット 2023-09-13 2.10.29.png
編集画面
スクリーンショット 2023-12-13 0.05.23.png

フローティングアクションボタン(FAB)

次にFABを実装します
FABは重要な機能に素早くアクセスさせるために使用されることが多いです

開閉制御を JS.hideJS.showで行いますので JSモジュールをaliasでJSで使えるようにします
phx-clickでボタンクリックイベント、phx-click-awayで領域外クリックイベントを発火します

lib/bookshelf_web/components/navigation.ex
defmodule BookshelfWeb.Navigation do
  use Phoenix.Component

  import BookshelfWeb.CoreComponents
+ alias Phoenix.LiveView.JS
  ...
  # 以下追加
  def fab(assigns) do
    ~H"""
    <div class="fixed z-90 bottom-20 right-8">
      <div class="flex flex-col">
        <div id="fab-menu" class="hidden">
          <.menu />
        </div>
        <label class="btn btn-circle self-end">
          <div phx-click-away={
            JS.show(to: "#open-fab") |> JS.hide(to: "#close-fab") |> JS.hide(to: "#fab-menu")
          }>
            <div
              id="close-fab"
              class="hidden"
              phx-click={
                JS.show(to: "#open-fab") |> JS.hide(to: "#close-fab") |> JS.hide(to: "#fab-menu")
              }
            >
              <.icon name="hero-x-mark-solid" class="current-fill w-8 h-8" />
            </div>
            <div
              id="open-fab"
              phx-click={
                JS.show(to: "#close-fab") |> JS.hide(to: "#open-fab") |> JS.show(to: "#fab-menu")
              }
            >
              <.icon name="hero-plus-solid" class="current-fill w-8 h-8" />
            </div>
          </div>
        </label>
      </div>
    </div>
    """
  end

  def menu(assigns) do
    ~H"""
    <ul class="menu bg-base-200 w-42 p-2 rounded-box mb-4">
      <li>
        <.link navigate="/search">
          <.icon name="hero-magnifying-glass-solid" class="h-8 w-8" /> Search Books
        </.link>
      </li>
      <li>
        <.link navigate="/shelves/new">
          <.icon name="hero-book-open-solid" class="h-8 w-8" /> New Shelf
        </.link>
      </li>
    </ul>
    """
  end
end

タブの下辺りに追加します

lib/ebookworm_web/live/shlef_live/index.html.heex
...
<.bottom_tab title="Shelf" />

<.fab/>
...

スクリーンショット 2023-09-13 2.32.13.png
スクリーンショット 2023-09-13 2.33.15.png

最後に

前回に続き保存した書籍を表示・編集する箇所に加え、フローティングアクションボタンを実装しました
本棚アプリの作成は本記事で終了になります
スタンドアローンで動作するアプリをWebアプリケーションスタックで簡単に実装できそうと思っていただければ幸いです

次からはスマホアプリとして一般的な、Phoenixサーバーからデータを取得するサーバークライアント方式での実装について解説します

本記事は以上になりますありがとうございました

12
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
0