Nest EEX Templates

I’m trying to nest EEX templates. I’d like this approach to be able to pass HTML as a parameter to a render function. To make this happen I tried to use the ~E sigil from Phoenix.HTML.

Given the following code I get an error:

<%= render(AcmeAdmin.PartialView, "_card.html", content: ~E"""
        <th><%= "Name" |> gettext %></th>
""") %>

** (TokenMissingError) lib/acme_admin/templates/partial/foo.html.eex:4: missing terminator: """ (for heredoc starting at line 1)
    (eex) lib/eex/compiler.ex:45: EEx.Compiler.generate_buffer/4
    (phoenix) lib/phoenix/template.ex:349: Phoenix.Template.compile/3
    (phoenix) lib/phoenix/template.ex:160: anonymous fn/4 in Phoenix.Template."MACRO-__before_compile__"/2
    (elixir) lib/enum.ex:1925: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix) expanding macro: Phoenix.Template.__before_compile__/1
    lib/acme_admin/views/partial_view.ex:1: AcmeAdmin.PartialView (module)
    (elixir) lib/kernel/parallel_compiler.ex:206: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/6

I tried to escape the inner eex block with double %%.

How can this be implemented?

1 Like

In MyAppWeb.view/0 add:

      defmacro render_block(module, template, assigns, do: block) do
        quote do
          block_content = unquote(block)

            unquote(assigns) |> Map.put_new(:block_content, block_content)

and use it like so:

<%= render_block AcmeAdmin.PartialView, "_card.html", assigns do %>
  some HTML
<% end %>

You can’t really use ~E or anything like it in an EEX template because when you write a %> inside that string then it will escape back out to the outer EEX file instead, so you’d have to escape those, which just becomes a pain overall. You should always opt to go out to the main eex template instead. The render_block that @LostKobrakai gave is not a bad idea at all, although there are inline methods too (not quite ‘as’ succinct as render_block though).

Doesn’t need to be a macro:

def render_in(module, template, assigns \\ %{}, do: yield),
  do: Phoenix.View.render(module, template, put_in(assigns[:yield], yield))

Why did I not think of that, this is much a simpler solution than what I imagined. Thanks all!