Insert javascript at top of including file in Jinja 2

狂风中的少年 提交于 2019-11-30 05:09:58

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

You can generalize this into a generic capture extension that works within macros. Here's one I wrote:

from jinja2 import nodes
from jinja2.ext import Extension

class CaptureExtension(Extension):
    """
    Generic HTML capture, inspired by Rails' capture helper

    In any template, you can capture an area of content and store it in a global
    variable:

    {% contentfor 'name_of_variable' %}
        blah blah blah 
    {% endcontentfor %}

    To display the result
    {{ name_of_variable }}

    Multiple contentfor blocks will append additional content to any previously 
    captured content.  

    The context is global, and works within macros as well, so it's useful for letting macros define
    javascript or <head> tag content that needs to go at a particular position
    on the base template.

    Inspired by http://stackoverflow.com/questions/4292630/insert-javascript-at-top-of-including-file-in-jinja-2
    and http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html
    """
    tags = set(['contentfor'])

    def __init__(self, environment):
        super(CaptureExtension, self).__init__(environment)

    def parse(self, parser):
        """Parse tokens """
        tag = parser.stream.next()
        args = [parser.parse_expression()]
        body = parser.parse_statements(['name:endcontentfor'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_capture', args),[], [], body).set_lineno(tag.lineno)

    def _capture(self, name, caller):
        if name not in self.environment.globals:
            self.environment.globals[name] = ''
        self.environment.globals[name] += caller()
        return ""

The solution by Lee Semel did not work for me. I think globals are protected from this kind of modification at runtime now.

from jinja2 import nodes
import jinja2
from jinja2.ext import Extension

class CaptureExtension(Extension):
    """
    Generic HTML capture, inspired by Rails' capture helper

    In any template, you can capture an area of content and store it in a global
    variable:

    {% capture 'name_of_variable' %}
        blah blah blah 
    {% endcapture %}
    {% capture 'a'  %}panorama{% endcapture %}

    To display the result
    {{ captured['name_of_variable'] }}
    {{ captured['a'] }}

    The context is global, and works within macros as well, so it's useful for letting macros define
    javascript or <head> tag content that needs to go at a particular position
    on the base template.

    Inspired by http://stackoverflow.com/questions/4292630/insert-javascript-at-top-of-including-file-in-jinja-2
    and http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html
    """
    tags = set(['capture'])

    def __init__(self, environment):
        super(CaptureExtension, self).__init__(environment)
        assert isinstance(environment, jinja2.Environment)
        self._myScope = {}
        environment.globals['captured'] = self._myScope

    def parse(self, parser):
        """Parse tokens """
        assert isinstance(parser, jinja2.parser.Parser)
        tag = parser.stream.next()
        args = [parser.parse_expression()]
        body = parser.parse_statements(['name:endcapture'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_capture', args),[], [], body).set_lineno(tag.lineno)

    def _capture(self, name, caller):
        self._myScope[name] = caller()
        return ""

The answers above nearly answered my query (I wanted to put disparate bits of JavaScript all in one place - the bottom), accept using the '+=' variety which appends captures to each other caused problems on refresh. The capture would end up with multiple copies of everything and caused all sorts of issues depending on how many times refresh was hit.

I worked around this by using the line number of the tag in a dictionary to ensure captures are only done once. The one minor disadvantage of this approach is the global needs to be rebuilt every time a capture tage is encountered.

Works well for me though.

from jinja2 import Markup, nodes
from jinja2.ext import Extension

class CaptureExtension(Extension):
    tags = set(['capture'])

    def __init__(self, environment):
        super(CaptureExtension, self).__init__(environment)
        environment.globals['captured'] = {}
        self._captured = {}

    def parse(self, parser):
        lineno = next(parser.stream).lineno
        args = [parser.parse_expression(), nodes.Const(lineno)]
        body = parser.parse_statements(['name:endcapture'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_capture', args), [], [], body).set_lineno(lineno)

    def _capture(self, name, lineno, caller):
        if name not in self._captured:
            self._captured[name] = {}
        self._captured[name][lineno] = caller()
        markup = Markup(''.join(s for s in self._captured[name].values()))
        self.environment.globals['captured'][name] = markup
        return ''
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!