Insert javascript at top of including file in Jinja 2
Question: In Jinja2, I would like the following to work as it looks like it should, by
running:

    
    
    from jinja2 import Environment, FileSystemLoader
    env = Environment(loader=FileSystemLoader('.'))
    template = env.get_template('x.html')
    print template.render()
    

Essentially the objective is to coalesce all the javascript into the `<head>`
tags by using a a `{% call js() %} /* some js */ {% endcall %}` macro.

* * *

## x.html

    
    
    <html>
    <head>
      <script type="text/javascript>
      {% block head_js %}{% endblock %}
      </script>
      </head>
    <body>
      {% include "y.html" %}
    </body>
    </html>
    

* * *

##  y.html

    
    
    {% macro js() -%}
        // extend head_js
        {%- block head_js -%}
        {{ super() }}
        try { {{ caller() }} } catch (e) {
           my.log.error(e.name + ": " + e.message);
        }
        {%- endblock -%}
    {%- endmacro %}
    
    Some ... <div id="abc">text</div> ...
    
    {% call js() %}
        // jquery parlance:
        $(function () {
            $("#abc").css("color", "red");
        });
    {% endcall %}
    

* * *

## Expected result

When I run X.html through jinja2, I would expect the result to be:

    
    
    <html>
    <head>
      <script type="text/javascript>
      try { {{ $("#abc").css("color", "red"); }} } catch (e) {
           usf.log.error(e.name + ": " + e.message);
        }
      </script>
      </head>
    <body>
          Some ... <div id="abc">text</div> ...
    </body>
    </html>
    

* * *

## Actual result

The actual results are not encouraging. I get a couple types of potentially
illuminating errors, e.g.:

> TypeError: macro 'js' takes no keyword argument 'caller'

or, when I try adding another basis macro such as

    
    
    {% macro js2() -%}
    {%- block head_js -%}
    //     ... something
    {%- endblock -%}
    {%- endmacro %}
    

I get the following exception

> jinja2.exceptions.TemplateAssertionError: block 'head_js' defined twice

I feel as though I am running into a design issue regarding the precedence of
the `block` tags over the `macro` tags (i.e. macros do not seem to encapsulate
block tags in the way I expect).

* * *

I suppose my questions are quite simple:

  1. Can Jinja2 do what I am attempting? If so, how?

  2. If not, is there another Python based templating engine that does support this sort of pattern (e.g. mako, genshi, etc.), which would work without issue in Google App Engine

Thank you for reading - I appreciate your input.

Brian

* * *

#  Edit:

I'm trying to write an extension to resolve this problem. I'm halfway there --
using the following code:

    
    
    from jinja2 import nodes, Environment, FileSystemLoader
    from jinja2.ext import Extension
    
    class JavascriptBuilderExtension(Extension):
        tags = set(['js', 'js_content'])
    
        def __init__(self, environment):
            super(JavascriptBuilderExtension, self).__init__(environment)
            environment.extend(
                javascript_builder_content = [],
            )
    
        def parse(self, parser):
            """Parse tokens """
            tag = parser.stream.next()
            return getattr(self, "_%s" % str(tag))(parser, tag)
    
        def _js_content(self, parser, tag):
            """ Return the output """
            content_list = self.environment.javascript_builder_content
            node = nodes.Output(lineno=tag.lineno)
            node.nodes = []
    
            for o in content_list:
                print "\nAppending node: %s" % str(o)
                node.nodes.extend(o[0].nodes)
            print "Returning node: %s \n" % node
            return node
    
        def _js(self, parser, tag):
            body = parser.parse_statements(['name:endjs'], drop_needle=True)
            print "Adding: %s" % str(body)
            self.environment.javascript_builder_content.append(body)
            return nodes.Const('<!-- Slurped Javascript -->')
    
    env = Environment(
        loader      = FileSystemLoader('.'),
        extensions  = [JavascriptBuilderExtension],
        )
    

This makes it simple to add Javascript to the end of a template ... e.g.

    
    
    <html>
    <head></head>
    <body>
        {% js %}
        some javascript {{ 3 + 5 }}
        {% endjs %}
        {% js %}
        more {{ 2 }}
        {% endjs %}
    
    <script type="text/javascript">
    {% js_content %}
    </script>
    </body>
    </html>
    

Running `env.get_template('x.html').render()` will result in some illuminating
comments and the expected output of:

    
    
    <html>
    <head>
      <script type="text/javascript>
      </script>
      </head>
    <body>
        <!-- Slurped Javascript -->
        <!-- Slurped Javascript -->
    <script type="text/javascript">
        some javascript 8
        more 2
    </script>
    </body>
    </html>
    

Of course, this isn't the same as having the script in the head, as hoped, but
at least it's conveniently coalesced into one place.

However, the solution is not complete because when you have a `{% include
"y.html" %}` in there, where "y.html" includes a `{% js %}` statement, the `{%
js_content %}` gets called before the include's `{% js %}` statement (i.e.
`x.html` is fully parsed before `y.html` starts).

I also need to, but have not yet, inserted constant nodes that would have the
static javascript `try/catch`, which I indicated I wanted to have in there.
This is not an issue.

I'm pleased to be making progress, and I'm grateful for input.

I've opened the related question: [Jinja2 compile extension after
includes](http://stackoverflow.com/questions/4294837/jinja2-compile-extension-
after-includes)

* * *

## Edit

Solution

    
    
    class JavascriptBuilderExtension(Extension):
        tags = set(['js'])
    
        def __init__(self, environment):
            super(JavascriptBuilderExtension, self).__init__(environment)
            environment.extend(jbc = "",)
    
        def parse(self, parser):
            """Parse tokens """
            tag = parser.stream.next()
            body = parser.parse_statements(['name:endjs'], drop_needle=True)
            return nodes.CallBlock(
                self.call_method('_jbc', [], [], None, None),
                [], [], body
            ).set_lineno(tag.lineno)
    
        def _jbc(self, caller=None):
            self.environment.jbc += "\ntry { %s } catch (e) { ; };" % caller()
            return "<!-- Slurped -->"
    

After completed, the environment will contain a variable `jbc` that has all
the Javascript. I can insert this via, for example, `string.Template`.

* * *


Answer: From my comment:

> If you would use extend instead of include you could do it. But because of
> the full separation between the parse and render step you won't be able to
> change the context of the parent scope till after it's too late. Also, the
> Jinja context is supposed to be immutable.

Example:

**base.html**

    
    
    <html>
       <head>
          {% block head %}
    
          <title>{% block title %}This is the main template{% endblock %}</title>
    
          <script type="text/javascript">
          {% block head_js %}
          $(function () {
            $("#abc").css("color", "red");
          });
          {% endblock %}
          </script>
    
          {% endblock head_js %}
       </head>
       <body>
          {% block body %}
          <h1>{% block body_title %}This is the main template{% endblock body_title %}</h1>
    
          {% endblock body %}
       </body>
     </html>
    

**some_page.html**

    
    
    {% block title %}This is some page{% endblock title %}
    
    {% block head_js %}
    {{ super() }}
    try { {{ caller() }} } catch (e) {
       my.log.error(e.name + ": " + e.message);
    }        // jquery parlance:
    {% endblock head_js %}
    

