Bash-Shell.Net

I'm a developer, not a writer


When I work on a Django project I normally have a simple block at the bottom of my base template which is empty, something like so:

<html>
  <body>
    Common page elements and text here
    {% block content %}{% endblock content %}
    Some more common page elements such as a site footer, etc. here.
    <!-- Load jquery, etc. whatever common js to every page here -->
    <!-- now this block is overridden in child templates to load page specific external js or in page js -->
    {% block page_js %}{% endblock page_js %}
  </body>
</html> 

And then child templates for pages would be something like

{% 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 %}

It was not super clear how to do that with Phoenix, though, because it does not use inheritance.  Instead it uses composition, injecting your "child" templates into the main app.html.eex template.  So I hit up google and came across over complicated solutions involving using webpack or making changes to allow brunch to load page specific modules, requiring changes to several files and had limitations such as only including a single .js file for a page rather than allowing multiple <script> tags for loading page specific external js files or very small in page js.

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> 

So I came up with my own solution. In web/views/layout_view.ex I add a simple function, page_scripts/3:

defmodule MyApp.LayoutView do

  use MyApp.Web, :view
  # Add this function here
  @doc """
  Takes a view module, template currently being rendered, and the assigns.
  Uses the view module to load a page specific template for javascript where
  the view template name has _scripts.html appended to it.
  """
  def page_scripts(view_module, view_template, assigns) do
    scripts_template = "#{view_template}_scripts.html"
    try do
      raw render_to_string(view_module, scripts_template, assigns: assigns)
    rescue
      Phoenix.Template.UndefinedError -> ""
    end
  end
end 

Then in my templates/app.html.eex I just add this one line:
<%= page_scripts @view_module, @view_template, assigns %>

Giving us the following

<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 -->
    <!-- page specific javascript stuff will now load here -->
    <%= page_scripts @view_module, @view_template, assigns %>
  </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/edit.html.eex, I just add myapp/templates/edit.html_scripts.html.eex. I imagine that could have the .html after _scripts removed for edit.html_scripts.eex or even have the function look for edit_scripts.html.eex, etc.

Member of The Internet Defense League