在Jinja 2中的包含文件顶部插入javascript [英] Insert javascript at top of including file in Jinja 2

查看:104
本文介绍了在Jinja 2中的包含文件顶部插入javascript的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Jinja2中,我希望通过运行以下内容来使其看起来像应该的那样工作:

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

本质上,目标是使用{% call js() %} /* some js */ {% endcall %}宏将所有javascript合并到<head>标记中.


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


预期结果

当我通过jinja2运行X.html时,我希望结果是:

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


实际结果

实际结果并不令人鼓舞.我遇到了两种可能的照明错误,例如:

TypeError:宏"js"不带关键字参数调用方"

或者,当我尝试添加另一个基本宏(例如

)时

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

我收到以下异常

jinja2.exceptions.TemplateAssertionError:块'head_js'定义了两次

我感觉好像遇到了关于block标签优先于macro标签的设计问题(即,宏似乎并未按照我期望的方式封装块标签).


我想我的问题很简单:

  1. Jinja2可以执行我正在尝试的操作吗?如果是这样,怎么办?

  2. 如果没有,是否存在另一个支持这种模式的基于Python的模板引擎(例如mako,genshi等),该模式在Google App Engine中不会出现问题

感谢您的阅读-感谢您​​的投入.

布莱恩


我正在尝试编写扩展程序来解决此问题.我到一半了-使用以下代码:

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],
    )

这样可以很容易地将Javascript添加到模板的末尾...例如

<html>
<head></head>
<body>
    {% js %}
    some javascript {{ 3 + 5 }}
    {% endjs %}
    {% js %}
    more {{ 2 }}
    {% endjs %}

<script type="text/javascript">
{% js_content %}
</script>
</body>
</html>

运行env.get_template('x.html').render()将产生一些有启发性的注释,并产生以下预期输出:

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

当然,这与希望的脚本不同,但是至少可以方便地将其合并到一个位置.

但是,解决方案并不完整,因为当您在其中包含{% include "y.html" %}时,其中"y.html"包含一个{% js %}语句,该{% js_content %}会在包含的{% js %}语句之前(即x.htmly.html开始之前已完全解析.)

我还需要但尚未插入具有静态javascript try/catch的常量节点,我表示希望在其中包含.这不是问题.

我很高兴取得进步,感谢您的投入.

我已经打开了一个相关的问题:包含之后的Jinja2编译扩展


编辑

解决方案

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 -->"

完成后,环境将包含具有所有Javascript的变量jbc.我可以通过例如string.Template插入它.


解决方案

从我的评论开始:

如果要使用扩展而不是 包括你可以做到的.但是因为 之间的完全分离 解析并渲染您不会的步骤 能够改变背景 父作用域,直到为时已晚. 此外,Jinja上下文应该 不可改变.

示例:

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

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


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.


解决方案

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

这篇关于在Jinja 2中的包含文件顶部插入javascript的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆