← All series

Series contents

Log in to track your progress

  1. 1. Simple Phoenix LiveView App: Setup Episode
  2. 2. Simple Phoenix LiveView App: Planning DB Schemas Episode
  3. 3. Simple Phoenix LiveView App: Upgrade to LiveView 0.5.1 Episode
  4. 4. Simple Phoenix LiveView App: Socket Session info, CSRF & .leex Episode
  5. 5. Simple Phoenix LiveView App: Scaffolding Users & Content Episode
  6. 6. Simple Phoenix LiveView App: PubSub & Index Episode
  7. 7. Simple Phoenix LIveView App: Show & Edit Pages Episode
  8. 8. Simple Phoenix LiveView App: Upgrade LiveView to v0.8.1 Episode
  9. 9. Simple Phoenix LiveView App: Fonts, logos and sass Episode
  10. 10. Simple Phoenix LiveView App: Markdown show notes Episode
  11. 11. Simple Phoenix LiveView App: The Podcast Listing Page Episode
  12. 12. Simple Phoenix LiveView App: Upgrade LiveView to v0.12.1 Episode
  13. 13. How I add auth to Phoenix LiveView apps Episode
  14. 14. Simple Phoenix LiveView App: planning comments Episode
  15. 15. Showing comment counts on the index page (Simple Phoenix LiveView App) Episode
  16. 16. Simple Phoenix LiveView app: Embedded comments form Episode
  17. 17. Passing redirect URLs + fragments Episode
  18. 18. Simple Phoenix LiveView App: Series Complete! Episode

Simple Phoenix LIveView App: Show & Edit Pages

This is episode #8 of a Phoenix LiveView series. Last episode we converted the new users page and form into live views. We also had a chance to see what LiveView form validations look like.

In this episode, we'll build the live views and templates for the show and edit pages, fix a few bugs and finish converting users from traditional CRUD to LiveView. After this episode, we won't need the user controller at all.

Note: This episode is using Phoenix LiveView, version 0.5.1. If you've been following along with the series you'll be fine but if you've already upgraded to a newer version you'll hit some issues. We'll be upgrading LiveView next episode.

The templates

The first thing we'll do is update the link tags in our show.html.eex and edit.html.eex templates to use LiveView. That means changing link to live_link and changing path to live_path and updating the extension.

In edit.html.leex

<%= live_link "Back", to: Routes.live_path(@socket, UserLive.Index) %>

In show.html.leex

<%= live_link "Edit", to: Routes.live_path(@socket, UserLive.Edit, @user) %>
<%= live_link "Back", to: Routes.live_path(@socket, UserLive.Index) %>

Create the LiveViews

First, create user_live/show.ex:

defmodule ReactorWeb.UserLive.Show do
  use Phoenix.LiveView
  alias Reactor.Accounts
  alias ReactorWeb.{UserLive, UserView}
  alias ReactorWeb.Router.Helpers, as: Routes
  alias Phoenix.LiveView.Socket

  def render(assigns), do: UserView.render("show.html", assigns)
  def mount(_session, socket), do: {:ok, socket}

  def handle_params(%{"id" => id}, _url, socket) do
    if connected?(socket), do: Accounts.subscribe(id)
    {:noreply, socket |> assign(id: id) |> fetch()}
  end

  def fetch(%Socket{assigns: %{id: id}} = socket) do
    assign(socket, user: Accounts.get_user!(id))
  end

  def handle_info({Accounts, [:user, :updated], _}, socket) do
    {:noreply, fetch(socket)}
  end

  def handle_info({Accounts, [:user, :deleted], _}, socket) do
    {:stop,
     socket
     # |> put_flash(:error, "This user has been deleted.")
     |> redirect(to: Routes.live_path(socket, UserLive.Index))}
  end
end

This is very similar to the LiveView we created last episode. We've got a render function that just passes the template file and the assigns into UserView.render, and we've got a mount function that returns our initial socket. As before, we're using the generated functions in our Accounts context to do our basic CRUD as well as the subscribe function we added to it.

The major new piece for this file is the handle_info function, which matches the assigns we got from the router, and gets the ID of the user being shown.

Our LiveView for edit.ex is a bit more complex:

defmodule ReactorWeb.UserLive.Edit do
  use Phoenix.LiveView
  alias Phoenix.LiveView.Socket
  alias Reactor.Accounts
  alias Accounts.User
  alias ReactorWeb.{UserLive, UserView}
  alias ReactorWeb.Router.Helpers, as: Routes
  alias Phoenix.LiveView.Socket

  def render(assigns), do: UserView.render("edit.html", assigns)
  def mount(_session, socket), do: {:ok, socket}

  def handle_params(%{"id" => id}, _url, socket) do
    if connected?(socket), do: Accounts.subscribe(id)
    {:noreply, socket |> assign(id: id) |> fetch()}
  end

  defp fetch(%Socket{assigns: %{id: id}} = socket) do
    user = Accounts.get_user!(id)
    assign(socket, user: user, changeset: Accounts.change_user(user))
  end

  def handle_event("validate", %{"user" => params}, socket) do
    cset =
      socket.assigns.user
      |> Accounts.change_user(params)
      |> Map.put(:action, :update)

    {:noreply, assign(socket, changeset: cset)}
  end

  def handle_event("save", %{"user" => params}, socket) do
    case Accounts.update_user(socket.assigns.user, params) do
      {:ok, user} ->
        {:stop,
         socket
         #  |> put_flash(:info, "user updated")
         |> redirect(to: Routes.live_path(socket, UserLive.Show, user))}

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

  def handle_info({Accounts, [:user, :updated], _}, socket) do
    {:noreply, fetch(socket)}
  end

  def handle_info({Accounts, [:user, :deleted], _}, socket) do
    {:stop,
     socket
     # |> put_flash(:error, "This user has been deleted.")
     |> redirect(to: Routes.live_path(socket, UserLive.Index))}
  end
end

All the pieces are familiar though. Like new.ex, we need to pass a changeset to the template so form actions can make changes to our DB. We'll also need the same event handlers (but have them call update rather than create). Like show.ex, we need to add handle_info pages to reflect any external changes to the user being shown on the page.

Notifying Subscribers (Account Context)

Note that the Accounts.notify_subscribers/2 function broadcasts the module, the event which has just occurred and the "result" (AKA data needed by the LiveView) to all clients currently subscribed, and then it passes the exact same {:ok, result} or {:error, reason} tuple passed in as its first argument.

This is why we can drop the function into our CRUD pipelines without changing the data being passed through.

  defp notify_subscribers({:ok, result}, event) do
    Phoenix.PubSub.broadcast(Reactor.PubSub, @topic, {__MODULE__, event, result})

    Phoenix.PubSub.broadcast(
      Reactor.PubSub,
      @topic <> "#{result.id}",
      {__MODULE__, event, result}
    )

    {:ok, result}
  end

  defp notify_subscribers({:error, reason}, _), do: {:error, reason

(Source code available for premium members)

Next time, we'll update LiveView to version 0.8.1 and also get the /foo page updated.

Open standalone page ↗

No Comments