Reputation: 4950
I rely on template inheritance system to insert extra_css
and/or extra_js
into my pages:
base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Core CSS -->
{% block extra_css %}
{# Override this in templates to add extra stylesheets #}
{% endblock %}
</head>
<body>
{% block content %}{% endblock content %}
<!-- Core JS -->
{% block extra_js %}
{# Override this in templates to add extra javascript #}
{% endblock extra_js %}
</body>
</html>
page.html:
{% extends "base.html" %}
{% block extra_css %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
{% endblock %}
{% load wagtailcore_tags wagtailimages_tags %}
{% block content %}
<div class="blog-post">
<!-- Custom HTML -->
{% block content %}
{% include_block page.body %}
{% endblock %}
</div><!-- /.blog-post -->
{% endblock %}
{% block extra_js %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<script type="text/javascript">
// Custom JavaScript
</script>
{% endblock extra_js %}
This works great, thus extra css/js is indeed inserted where it belongs.
The problem arise if I need to use streamfield
where one of its block templates need custom css/js. In this case custom resources are inserted along with the block, but not in the specified locations in the base.html.
For instance if extra_js
from the page.html in the example above has been added to the block template instead, then select2
would complain about jquery
not being present and it would be totally right, because it's inserted with the block, but not as intended after the Core JS libraries.
Loading jquery
twice leads to other issues: https://stackoverflow.com/a/25782679/2146346
Other option is to load all block dependencies into the page, but it would fill pages with redundant resources as not every block from the streamfield
might be used on the page.
Are there other options?
Upvotes: 3
Views: 1768
Reputation: 14361
UPDATE: here’s a much better way that uses JavaScript to only load libraries once: https://github.com/FlipperPA/wagtailcodeblock/blob/main/wagtailcodeblock/templates/wagtailcodeblock/code_block.html#L6
I haven't come up with a way I like of doing this yet. The tools for doing this on the Wagtail editor side are nice. However, here's what I do for WagtailCodeBlock:
{% load static wagtailcodeblock_tags %}
{% spaceless %}
{# This is ugly, as it'll inject this code for each block, but browsers are smart enough to not load each time. #}
<script src="{% static 'wagtailcodeblock/js/prism.min.js' %}" type='text/javascript'></script>
<link href="{% static 'wagtailcodeblock/css/prism.min.css' %}" rel="stylesheet">
{% load_prism_theme %}
{% for key, val in self.items %}
{% if key == "language" %}
<script>
language_class_name = 'language-{{ val }}';
</script>
{% endif %}
{% if key == "code" %}
<pre class="line-numbers">
<code id="target-element-current">{{ val }}</code>
</pre>
<script>
var block_num = (typeof block_num === 'undefined') ? 0 : block_num;
block_num++;
document.getElementById('target-element-current').className = language_class_name;
document.getElementById('target-element-current').id = 'target-element-' + block_num;
</script>
{% endif %}
{% endfor %}
{% endspaceless %}
In this example, I load the JS/CSS assets in each, and let the browser settle it out. It also assumed jQuery is loaded at the parent level. However, it is also possible to use the JavaScript context to ensure things are only loaded once, which is my next step. For now, it isn't pretty but it works.
On the Wagtail editor side, there's the @property media
. I wish there was something analogous on the rendered side:
class CodeBlock(StructBlock):
"""
Code Highlighting Block
"""
WCB_LANGUAGES = get_language_choices()
off_languages = ['html', 'mathml', 'svg', 'xml']
language = ChoiceBlock(choices=WCB_LANGUAGES, help_text=_('Coding language'), label=_('Language'))
code = TextBlock(label=_('Code'))
@property
def media(self):
theme = get_theme()
prism_version = get_prism_version()
if theme:
prism_theme = '-{}'.format(theme)
else:
prism_theme = ""
js_list = [
"https://cdnjs.cloudflare.com/ajax/libs/prism/{}/prism.min.js".format(
prism_version,
),
]
for lang_code, lang_name in self.WCB_LANGUAGES:
# Review: https://github.com/PrismJS/prism/blob/gh-pages/prism.js#L602
if lang_code not in self.off_languages:
js_list.append(
"https://cdnjs.cloudflare.com/ajax/libs/prism/{}/components/prism-{}.min.js".format(
prism_version,
lang_code,
)
)
return Media(
js=js_list,
css={
'all': [
"https://cdnjs.cloudflare.com/ajax/libs/prism/{}/themes/prism{}.min.css".format(
prism_version, prism_theme
),
]
}
)
class Meta:
icon = 'code'
template = 'wagtailcodeblock/code_block.html'
form_classname = 'code-block struct-block'
form_template = 'wagtailcodeblock/code_block_form.html'
I hope this gives you some ideas, and I'm all ears if you come up with a better way. Good luck.
Upvotes: 1
Reputation: 98
Could you create another js block that you only add into the streamfield block template? So in base.html you'll have extra_js block as well as streamblock_js. You can have your jquery in extra_js and the extra dependency for the streamblock in streamblock_js. And if you have multiple custom css/js per streamblock on one page, you could add as many extra blocks in the base.html template to load all the dependencies. I'm not sure if this will work, but that's my idea.
Upvotes: 0