Legen Sie Javascript an der Spitze der darunter Datei in Jinja 2
Frage
In Jinja2, möchte ich folgendes zu arbeiten, wie es aussieht wie es sollte, indem Sie:
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('x.html')
print template.render()
Im Wesentlichen das Ziel ist es, alle Javascript in die <head>
Tags zu vereinigen, indem ein ein {% call js() %} /* some js */ {% endcall %}
Makro.
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 %}
Erwartetes Ergebnis
Wenn ich X.html durch jinja2 laufen, würde ich das Ergebnis erwarten sein:
<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>
Die tatsächliche Ergebnis
Die tatsächlichen Ergebnisse sind nicht ermutigend. Ich bekomme ein paar Arten von potenziell Beleuchtungsfehler, z.
Typeerror: Makro 'js' nimmt kein Schlüsselwort-Argument 'Anrufer'
oder, wenn ich versuche, das Hinzufügen eines weiteren Basis-Makro wie
{% macro js2() -%}
{%- block head_js -%}
// ... something
{%- endblock -%}
{%- endmacro %}
Ich erhalte die folgende Ausnahme
jinja2.exceptions.TemplateAssertionError: block 'head_js' definiert zweimal
Ich fühle mich, als ob ich in ein Design-Problem bin mit Bezug auf die Priorität der block
Tags über die macro
Tags (das heißt Makros scheinen nicht zu verkapseln Block-Tags in der Art, wie ich erwarten).
Ich nehme an, meine Fragen sind recht einfach:
-
Can Jinja2 tun, was ich bin versucht? Wenn ja, wie?
-
Wenn nicht, gibt es eine andere Python basierten Template-Engine, die diese Art von Muster jedoch zwingend nötig (z mako, genshi, etc.), die ohne Problem in Google App Engine
funktionieren würde
Danke für das Lesen - ich schätze Ihre Eingabe
.Brian
Edit:
Ich versuche, eine Erweiterung zu schreiben, um dieses Problem zu lösen. Ich bin auf halbem Weg dort - mit dem folgenden 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],
)
Dies macht es einfach Javascript Ende einer Vorlage hinzufügen ... z.
<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()
Lauf in einigen Beleuchtungs Kommentaren führen und die erwarteten Ausgabe von:
<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>
Natürlich, das ist nicht das gleiche wie das Skript im Kopf, wie erhofft, aber zumindest ist es bequem an einen Ort verschmolzen.
Allerdings ist die Lösung nicht vollständig, weil, wenn Sie einen {% include "y.html" %}
dort haben, wo „y.html“ enthält eine {% js %}
Aussage, die {% js_content %}
aufgerufen wird, bevor die die {% js %}
Erklärung enthalten (dh x.html
vollständig vor y.html
beginnt analysiert wird).
Ich brauche auch, aber noch nicht, konstant Knoten eingefügt, der die statische Javascript try/catch
haben würde, die ich angedeutet ich dort haben wollte. Das ist kein Problem.
Ich bin froh, Fortschritte zu machen, und ich bin dankbar für die Eingabe.
Ich habe die damit verbundene Frage eröffnet: Jinja2 Kompilierung Erweiterung umfasst nach
Bearbeiten
Lösung
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 -->"
Nach der Fertigstellung wird die Umgebung eine Variable jbc
enthalten, die alle Javascript. Ich kann diese über einfügen, beispielsweise string.Template
.
Lösung
Von meinem Kommentar:
Wenn Sie statt verwenden würden verlängern schließen Sie es tun könnte. Aber weil der vollständigen Trennung zwischen dem analysieren und machen Schritt, den Sie nicht sein Lage, den Kontext der Änderungen geordneten Bereich bis nach dem es zu spät ist. Auch wird der Jinja Kontext soll sein unveränderlich.
Beispiel:
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 %}
Andere Tipps
Sie können diese in eine generische Erfassung Erweiterung verallgemeinern, die innerhalb von Makros funktioniert. Hier ist ein schrieb ich:
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 ""
Die Lösung von Lee Semel nicht für mich arbeiten. Ich denke, Globals von dieser Art der Änderung zur Laufzeit geschützt wird jetzt.
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 ""
Die Antworten oben fast beantwortet meine Frage (Ich wollte unterschiedliche Bits JavaScript, um alle an einem Ort setzen - unten), übernehmen mit der ‚+ =‘ Sorte, die verursacht Aufnahmen zueinander anfügt Probleme beim Aktualisieren. Die Einnahme würde mit mehreren Kopien von allem, was am Ende und verursacht alle möglichen Probleme, je nachdem wie oft Refresh Hit war.
ich gearbeitet, um dieses durch die Zeilennummer des Tags in einem Wörterbuch mit Aufnahmen zu gewährleisten, werden nur einmal getan. Der eine geringfügige Nachteil dieses Ansatzes ist, die globalen Bedürfnisse jedes Mal ein Capture-tage angetroffen wird wieder aufgebaut werden.
funktioniert gut für mich aber.
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 ''