Is there a way for an HTML module to render some content that can be hoisted up into one of the parent layouts?
Our goal is to mimic the behavior of “Portals” that are present in some of the JS frameworks. This feature can be helpful for things like dynamically adding script
tags at the bottom of the layout on a per-route basis or for adding DOM elements that might be displayed completely outside of the hierarchy for where they would otherwise be rendered. (Modals, for example.)
* root.html.heex
* app.html.heex
* records_controller.ex => Demo.UsersController
* records_web.ex => Demo.UsersHTML
In app.html.heex
<div>
<%= @inner_content %>
</div>
<!-- Render some content here that an HTML module might have created. -->
In Demo.UsersHTML
defmodule Demo.UsersHTML do
use DemoWeb, :html
embed_templates "templates_html/*"
def index(assigns) do
// Not sure if we could set/render something here,
// update assigns but still render index.html.heex
end
end
In index.html.heex:
<div>My Index Page</div>
<!-- Alternatively, render something right in the template that is captured? -->
<_some_tag_or_slot_>This should go inside app layout.</_some_tag_or_slot_>
Not really. Slots allow a parent to place parts of something larger into distinct parts within the actual markup, but it‘s always going to be top-down.
You can send a Phoenix Component in the controller assigns and use it in the root layout.
In your controller:
defmodule NewPhoenixWeb.PageController do
use NewPhoenixWeb, :controller
def home(conn, _params) do
render(conn, :home, my_root_component_content: apply(NewPhoenixWeb.Layouts, :custom_content_in_layout, [[]]))
end
def test(conn, _params) do
render(conn, :home, my_root_component_content: apply(NewPhoenixWeb.Layouts, :another_custom_content_in_layout, [[]]))
end
end
In your layouts.ex
:
defmodule NewPhoenixWeb.Layouts do
use NewPhoenixWeb, :html
embed_templates "layouts/*"
def custom_content_in_layout(assigns) do
~H"""
<p>content one</p>
"""
end
def another_custom_content_in_layout(assigns) do
~H"""
<p>content two</p>
"""
end
end
Finally, in your root.html.heex
(or any other layout file you want to use):
<div class="custom-content">
<%= @my_root_component_content %>
</div>
Also, you can use slots:
defmodule NewPhoenixWeb.Layouts do
slot :inner_block
def my_root_component(assigns) do
~H"""
<div>
<%= render_slot(@inner_block) %>
</div>
"""
end
end
In root.html.heex
:
<.my_root_component>
<%= @my_root_component_content %>
</.my_root_component>
There’s a similar question here: Phoenix 1.7 and inversion of control to render a sidebar
Interesting, thanks for sharing that. We did know that we could set an assigns
from within the Controller, but not sure if we could set it from inside the View module.
The benefit of the View module is that we have one for HTML
and one for JSON
content, so any injection can be isolated to just the HTML
Module. As well, the actual content we want to “send” to the root layout is resolved inside the HTML View’s template, not the Controller.
We’re basically wondering if we can recreate something like Portals
, as provided by frameworks like React and SolidJS:
References:
1 Like