Вставьте JavaScript в верхней части, включая файл в Jinja 2

StackOverflow https://stackoverflow.com/questions/4292630

Вопрос

В Jinja2 я хотел бы работать, чтобы работать так, как будто она должна работать:

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

По сути, цель состоит в том, чтобы объединить весь JavaScript в <head> Теги с помощью AA {% call js() %} /* some js */ {% endcall %} макрос


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

Ожидаемый результат

Когда я запускаю xhtml через jinja2, я бы ожидал, что результат будет:

<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: Macro 'JS' не принимает ключевых слов аргумента «звонящий»

или, когда я пытаюсь добавить другой основной макрос, такой как

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

Я получаю следующее исключение

jinja2.exceptions.templateassertionError: блок 'head_js' определен дважды

Я чувствую, как будто я бежим в проблему дизайна относительно приоритета block Теги над macro Метки (т.е. макросы, кажется, не капсулируют теги блока в том, как я ожидаю).


Я полагаю, мои вопросы довольно просты:

  1. Может ли jinja2 делать то, что я пытаюсь? Если так, как?

  2. Если нет, есть ли другой шаблон на основе Python на основе Python, который поддерживает этот вид шаблона (например, Mako, Genshi и т. Д.), Что бы работать без выпуска в приложении Google App

Спасибо за чтение - я ценю ваш вклад.

Брайан


Редактировать:

Я пытаюсь написать расширение, чтобы решить эту проблему. Я на полпути, используя следующий код:

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.html полностью проанализирован раньше y.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 -->"

После завершения среда будет содержать переменную jbc У этого есть все JavaScript. Я могу вставить это, например, 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>

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

Другие советы

Вы можете обобщить это в универсальное расширение захвата, которое работает в макросах. Вот один я писал:

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

Решение Ли Серия не работала для меня. Я думаю, что глобалы защищены от такого типа модификации во время выполнения сейчас.

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

Ответы выше почти Ответил мой запрос (я хотел поставить различия битов JavaScript все в одном месте - внизу), принимайте с использованием сорта «+ =», которые присоединяются к захвату друг к другу, вызванные проблемы на обновлении. Захват окажется с несколькими копиями всего и вызвал все виды проблем в зависимости от того, сколько раз обновляются.

Я работал об этом, используя номер линии тега в словаре, чтобы гарантировать, что захваты только один раз делается один раз. Один незначительный недостаток этого подхода заключается в том, что глобальные должны быть восстановлены каждый раз, когда встречается натадка захвата.

Хорошо работает для меня.

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 ''
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top