أدخل javaScript في الجزء العلوي من الملف في Jinja 2
سؤال
في 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 %}
نتيجة متوقعة
عندما أقوم بتشغيل X.HTML عبر 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
العلامات (أي وحدات الماكرو لا يبدو أنها تغلف علامات الكتلة بالطريقة التي أتوقعها).
أفترض أن أسئلتي بسيطة للغاية:
هل يمكن لـ Jinja2 فعل ما أحاول؟ إذا كان الأمر كذلك ، كيف؟
إذا لم يكن الأمر كذلك ، فهل هناك محرك آخر يعتمد على Python يدعم هذا النوع من النمط (على سبيل المثال Mako ، Genshi ، إلخ) ، والذي سيعمل بدون مشكلة في محرك تطبيق Google
شكرا لك على القراءة - أقدر مدخلاتك.
براين
يحرر:
أحاول كتابة تمديد لحل هذه المشكلة. أنا في منتصف الطريق - باستخدام الكود التالي:
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
يبدأ).
أحتاج أيضًا إلى ذلك ، لكن لم يتم إدراج عقد ثابت 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
هذا لديه كل جافا سكريبت. يمكنني إدراج هذا عبر ، على سبيل المثال ، string.Template
.
المحلول
من تعليقي:
إذا كنت تستخدم تمديدًا بدلاً من تضمين ، فيمكنك القيام بذلك. ولكن بسبب الفصل الكامل بين التحليل والخطوة ، فلن تتمكن من تغيير سياق نطاق الوالد حتى بعد فوات الأوان. أيضا ، من المفترض أن يكون سياق جينجا غير قابل للتغيير.
مثال:
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 %}
نصائح أخرى
يمكنك تعميم هذا في امتداد لالتقاط عام يعمل داخل وحدات الماكرو. هذا واحد كتبت:
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 ""
لم ينجح حل لي سيميل لي. أعتقد أن Globals محمية من هذا النوع من التعديل في وقت التشغيل الآن.
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 ''