Nested layouts in Phoenix LiveView - a scope with independent navigation?

Hey,

Suppose I have the default root & app layouts, and now I want to have a new layout, called cms, and I want it to be nested under app.
The idea is simple, I have a scope called /cms and I want it to have its own navigation while keeping functionality from app layout.
Further down the line I’ll probably have a bit more layouts as well.

I went over the web, the docs, the forum, the different LLMs, but couldn’t find a way of achieving that.

Would love to get some ideas.
Thanks.

1 Like

There is no arbitrary nesting of layouts. The split of root/app layout exists for purely technical reasons and didn‘t exist before LiveView was added to phoenix.

Instead of the nesting at the layout level I‘d suggest composing layouts from shared parts e.g. being function components. Those components can be arbitrarily nested.

2 Likes

So in that case, I’ll have a lot of repetition, won’t I?
Suppose I have 10 pages under /cms, each will have to have it.
I can make a function component to host it, but it feels meh, seems like having a nested layout is way more efficient.

What repetition are you talking about? You can configure the used layout on the router level with live_session. There might be a bit of repetition between app.html.heex and cms.html.heex, but with a handful of function components that’s mostly boilerplate and no details.

I feel like I’m missing something in your answers.
I’ve attached a screen shot of what I’m trying to achieve.
I want to have a dedicated layout for the CMS.

app.html.heex

<.app_layout>
  <%= @inner_content %>
</.app_layout>

cms.html.heex

<.app_layout>
  <.cms_layout>
    <%= @inner_content %>
  <.cms_layout>
</.app_layout>

Yes, you have tiny amounts of duplication, but really you’ll hardly touch those template files. The meat of what those layouts are about is in the function components.

1 Like

Oh, now I see what you meant.
Awesome, thanks.
It’s weird though that composing layouts is not a thing.

Seems like something’s off with the way assigns get passed around.

In my router, I got -

  defp assign_cms_env(conn, _opts) do
    conn =
      assign(conn, :cms_env, :dev)

    IO.inspect(conn.assigns, label: "Conn")

    conn
  end

  pipeline :cms do
    plug :assign_cms_env
  end

Then, for my ‘/cms’ scope, I pipe it like that -

    pipe_through [:browser, :cms]

The inspect with the “Conn” label prints out properly.
But then, no matter what I’m doing in my cms.html.heex layout, I never get access to the cms_env value.
I do have other assigns that were made in the page’s mount function, but not the ones from the pipe.

Do you have an idea?
That’s my current cms.html.heex file, many attempts were made to pass stuff around -

<.app_layout flash={@flash} assigns={assigns}>
  <%= inspect(assigns, pretty: true) %>
  <.cms_layout assigns={assigns}>
    <%= @inner_content %>
  </.cms_layout>
</.app_layout>

Thank you for your assistance.

I’m guessing you are using LiveView, then you have to put it in the socket, not in the conn.

For example using on_mount/1 Phoenix.LiveView — Phoenix LiveView v1.0.0

2 Likes

How are you setting the CMS layout? Is that the LiveView layout? If so it only accesses assigns from the LiveView’s mount :slight_smile:

1 Like

I’m probably missing out something.
If I look at UserAuth.fetch_current_user, that’s being used in the browser pipeline, it alters the conn and then I have it on my socket.
I was under the impression that data on conn gets passed down to the socket.
What am I getting wrong here?

Thanks for the link you gave, it did the trick. Finally :slight_smile: