Bash-Shell.Net

I'm a developer, not a writer


Last night I came up with a solution for the specific problem of page or template specific javascript in Phoenix, which I posted here.  When I woke up this morning I realized that the more generic problem was simulating the {% block %} template tag.  So to expand upon the example from my previous post, a top level parent Django template (without lots of elements to keep it clear) might look like this.

<html>
  <body>
    Common page elements and text here
    {% block content %}Page specific content gets displayed here.{% endblock content %}

    Some more common page elements.

    <!-- Now another block with default text which we may want to override per page -->
    {% block some_stuff %}More standard stuff for every page with default content which might get overridden by a page's template goes here{% endblock some_stuff %}

    <!-- now the page specific javascript -->
    {% block page_js %}{% endblock page_js %}
  </body>
</html> 

And then child templates for pages would be something like this to override the content and page_js blocks but use the default from the some_stuff block.

{% extends 'base.html' %}
{% block content %}
Stuff here
{% endblock content %}
{% block page_js %}
<script src="https://example.org/some_external_script.js"></script>
<script>
  alert("Page specific annoying alert!")
</script>
{% endblock page_js %}

Or perhaps to override all three blocks a template might look like this.

{% extends 'base.html' %}
{% block content %}
Stuff here
{% endblock content %}
{% block some_stuff %}I don't like the default some_stuff block for this page.  It needs custom content.{% endblock some_stuff %}
{% block page_js %}
<script src="https://example.org/some_external_script.js"></script>
<script>
  alert("Page specific annoying alert!")
</script>
{% endblock page_js %}

Because of Phoenix' use of composition from the top level down, inserting lower level templates, rather than Django's OO based templating where children inherit from and then override the parent a way to accomplish this was not immediately obvious.  The standard base page template for phoenix looks like this, and when it is rendered you cannot pass data up to it/override it in the lower level templates.

<html>
  <body>
    Common page elements and text here
    <%= render @view_module, @view_template, assigns %>
    Some more common page elements such as a site footer, etc. here.
    <!-- Load jquery, etc. whatever common js to every page here -->
    <!-- but our specific view is being dealt with up in the render above, so it has no way of
        injecting more content further down the template, such as here -->
  </body>
</html> 

Based on the page specific javascript solution I made a more generic function to handle this, called page_block/2 below as well as a default_page_block/2 to provide a default.

defmodule MyApp.LayoutView do
  use MyApp.Web, :view
  # Add this function here
  @doc """
  Render a template as a block anywhere in the page.
  """
  def page_block(block_name, conn) do
    view_template = conn.private[:phoenix_template]
    view_module = conn.private.phoenix_view
    block_template = "#{view_template}_#{block_name}.html"
    try do
      raw render_to_string(view_module, block_template, assigns: conn.assigns)
    rescue
      Phoenix.Template.UndefinedError -> default_page_block(block_name, conn)
    end
  end

  @doc """
  Provide a default template and value for a block which may be overridden
  by the view.
  """
  defp default_page_block(block_name, conn) do
    block_template = "#{block_name}.html"
    try do
      raw render_to_string(__MODULE__, block_template, assigns: conn.assigns)
    rescue
      Phoenix.Template.UndefinedError -> ""
    end
  end
end

Then in my templates/app.html.eex I just these lines wherever I want them to appear and be able to be overridden.

<%= page_block 'page_js', @conn %>
 <%= page_block 'some_stuff', @conn %>

Giving us the following

<html>
  <body>
    Common page elements and text here
    <%= render @view_module, @view_template, assigns %>
    Some more common page elements here.

    <!-- now some stuff I may want to override -->
    <%= page_block 'some_stuff', @conn %>

    <!-- Load jquery, etc. whatever common js to every page here -->
    <!-- page specific javascript stuff will now load here -->
    <%= page_block 'page_js', @conn %>
  </body>
</html>

Now for any template I can just add one with the same name with _scripts.html on it. So for an edit view with a template named myapp/templates/a_model_name/edit.html.eex, I just add myapp/templates/edit.html_scripts.html.eex.  A default template can be added to myapp/templates/layout/page_js.html.eex and myapp/templates/layout/some_stuff.html.eex.

Member of The Internet Defense League