Rendering info from controller in template file

If You want to use some kind of variable in the layout, it should be set by a plug, not a controller.

This is a very common use case… to have the current user available in the layout, so You can set a login or a logout button.

You might have a look at how it is done in phx_gen_auth.

2 Likes

Ahh ok I understand now, there’s no need for a LayoutController since it appears on every page by default. I had a small syntax error that was messing it up, but everything is working now. :sob: Thank you for all the help!!! I can’t thank you enough!

1 Like

Hello! Unfortunately, after pulling from my teammate’s who made some changes to PageController, some of my former code broke and I’m having trouble fixing it.

This is my page_controller.ex now looks like:

defmodule GlobalImpactWeb.PageController do
  use GlobalImpactWeb, :controller

  def index(%{assigns: %{current_user: %{role: "admin"} = user}} = conn, _params) do
    user_full_name = get_user_name(conn)
    render(conn, "index.admin.html", user: user, user_full_name: user_full_name)
  end

  def index(%{assigns: %{current_user: user}} = conn, _params) do
    user_full_name = get_user_name(conn)
    render(conn, "index.html", user: user, user_full_name: user_full_name)
  end

  def index(conn, _params) do
    user_full_name = get_user_name(conn)
    render(conn, "index.html", user_full_name: user_full_name)
  end

  def get_user_name(conn) do
    user = Pow.Plug.current_user(conn)
    "#{user.first_name} #{user.last_name}"
  end
end

This is my root.html.leex now looks like:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <%= csrf_meta_tag() %>

    <%= live_title_tag assigns[:page_title] || gettext("Global Impact") %>

    <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>" phx-track-static>
  </head>

  <body>
    <div class="container">
      <div class="rev-TopBar--left">
        <nav role="navigation">
          <div class="rev-TopBar-item">
            <%= link gettext("iRR®"), to: Routes.page_path(@conn, :index) %>
              <%= menu :ul, verticalRight: true do %>
                <%= if Pow.Plug.current_user(@conn) != nil do %>
                  <%= menu_item :li, [] do %>
                <%= link to: "#", class: "IconLink IconLink--iconLeft" do %>
                  <i data-feather="user"></i>
                  <%= @user_full_name %>
                <% end %>
              <% end %>
                <%= menu_item :li, [] do %>
                  <%= link gettext("Account"), to: Routes.pow_registration_path(@conn, :edit) %>
                <% end %>
                <%= menu_item :li, [] do %>
                  <%= link gettext("Log Out"), to: Routes.pow_session_path(@conn, :delete), method: :delete %>
                <% end %>
              <% end %>
            <% end %>
          </div>
        </nav>
      </div>
      <%= @inner_content %>
    </div> <!-- /container -->
    <script>
      var _rollbarConfig = {
        accessToken: "<%= Application.get_env(:rollbax, :client_token) %>",
        captureUncaught: true,
        captureUnhandledRejections: true,
        checkIgnore: function () {
          return typeof isLteIe9 !== 'undefined' && isLteIe9 === true
        },
        payload: {
          environment: "<%= Application.get_env(:rollbax, :environment) %>"
        }
      }
    </script>
    <%= if Application.get_env(:global_impact, :analytics) do %>
    <script async src="https://www.googletagmanager.com/gtag/js?id=G-HWHKMFXTZG"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
    </script>
    <% end %>
    <script src="<%= Routes.static_path(@conn, "/js/app.js") %>" phx-track-static></script>
  </body>
</html>

Everything works fine until you click the “Account” option, and then I get the error assign @user_full_name not available in eex template. I’m not sure why this is happening since I am assigning @user_full_name in page_controller.ex

Check out Live layouts — Phoenix LiveView v0.15.7

Your assign should be available in root.html.leex (I just tested it myself). I’m thinking the problem might be that if your project was not created with the --live flag, you might be missing

plug :put_root_layout, {MyAppWeb.LayoutView, :root}

from your :browser pipeline in router.ex. Could you check and see if that is the case? Maybe post the full router here as well if you are still having trouble.

So it does look like I have plug :put_root_layout, {MyAppWeb.LayoutView, :root} in the :browser pipeline in router.ex , here’s what the full router looks like:

defmodule MyAppWeb.Router do
  use MyAppWeb, :router
  use Pow.Phoenix.Router
  import Phoenix.LiveDashboard.Router

  use Pow.Extension.Phoenix.Router,
    extensions: [PowResetPassword, PowEmailConfirmation]

  use Plug.ErrorHandler

  alias MyAppWeb.{
    APIAuthentication,
    AppDomainRedirect,
    BrowserAuthentication,
    RequirePermission
  }

  defp handle_errors(conn, error_data) do
    MyAppWeb.ErrorReporter.handle_errors(conn, error_data)
  end

  if Mix.env() == :dev do
    forward "/sent_emails", Bamboo.SentEmailViewerPlug
  end

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug :put_root_layout, {MyAppWeb.LayoutView, :root}
    plug AppDomainRedirect
    plug BrowserAuthentication, otp_app: :my_app
  end

  pipeline :api do
    plug :accepts, ["json"]
    plug APIAuthentication, otp_app: :my_app
  end

  pipeline :require_admin do
    plug RequirePermission, permission: :administrator
  end

  pipeline :protected do
    plug Pow.Plug.RequireAuthenticated,
      error_handler: Pow.Phoenix.PlugErrorHandler
  end

  pipeline :anonymous do
    plug Pow.Plug.RequireNotAuthenticated, error_handler: Pow.Phoenix.PlugErrorHandler
  end

  pipeline :not_authenticated do
    plug Pow.Plug.RequireNotAuthenticated,
      error_handler: GlobalImpactWeb.AuthErrorHandler
  end

  scope "/" do
    pipe_through :browser

    pow_routes()
    pow_extension_routes()
  end

  scope "/", MyAppWeb do
    pipe_through [:browser, :not_authenticated]

    get "/sign_up", RegistrationController, :new, as: :signup
    post "/sign_up", RegistrationController, :create, as: :signup
  end

  scope "/", MyAppWeb do
    pipe_through [:browser, :protected]
    get "/", PageController, :index
    get "/styleguide", StyleguideController, :styleguide
  end

  scope "/", MyAppWeb do
    pipe_through [:browser, :protected, :require_admin]
    get "/projects", ProjectsController, :index
    get "/configurations", ConfigurationsController, :index
  end

  if Mix.env() == :dev do
    scope "/" do
      pipe_through :browser
      live_dashboard "/dashboard", metrics: MyApp.Telemetry
    end
  end

  scope "/admin" do
    pipe_through [:browser, :protected, :require_admin]

    forward("/", Adminable.Plug,
      otp_app: :global_impact,
      repo: GlobalImpact.Repo,
      schemas: [MyApp.User, GlobalImpact.Organization],
      view_module: MyAppWeb.Adminable.AdminView,
      layout: {MyAppWeb.LayoutView, "app.html"}
    )
  end

  scope "/images" do
    pipe_through([:browser, :protected])

    forward("/sign", Transmit,
      signer: Transmit.S3Signer,
      bucket: "global-impact",
      path: "uploads"
    )
  end

  scope "/api", MyAppWeb.API, as: :api do
    pipe_through [:api]

    get "/", MeController, :show
  end
end

Well, since you say it works fine up until you click the link, and the root_layout is being assigned properly. I’d say that the problem is not with your root.html.leex template. Instead let’s focus on

Routes.pow_registration_path(@conn, :edit)

which is where the error is coming up. I’ve never used Pow, and those routes seem to be delegated to another function pow_routes, which is not shown. Find out which controller(s) those routes are using, and try assigning the user_full_name in there just like you did in the page_controller