Include script only once - twig

I am building some twig modules that require specific javascript to work.
A javascript file is an asset that can be included as follows:
<script src="{{ asset('what/ever/hello.js') }}">
All cool so far, but knowing that:
Those modules may or may not be present on the final rendered page.
Those modules may appear more than once on the same page.
I don't want to include the javascript in all pages, only when at least one instance of the module that requires it is present.
If I add the script to the module twig file, and if the module is used multiple times, it will result in my page containing multiple calls to the same script.
How can I approach this case?

You can work with an extension to keep track of the scripts you want included,
<?php
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension {
protected $scripts = [];
public function getFunctions() {
return [
new \Twig\TwigFunction('get_scripts', [ $this, 'getScripts']),
new \Twig\TwigFunction('add_script', [ $this, 'addScript']),
];
}
public function getScripts() {
return $this->scripts;
}
public function addScripts($script) {
if (!in_array($script, $this->getScripts()) $this->scripts[] = $script;
}
}
Just some mock-up modules
{% do add_script('modules/module_a.js') %}
<h1>Module A</h1>
{% do add_script('modules/module_b.js') %}
<h1>Module B</h1>
Just a simple base template
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
{% block content %}
{% endblock %}
{% for script in get_scripts() %}
<script src="{{ asset(script) }}"></script>
{% endfor %}
</body>
</html>
Just a simple child template
{% extends "base.html" %}
{% block content %}
{% incude "modules/module_a.html" %}
{% incude "modules/module_b.html" %}
{% incude "modules/module_a.html" %}
{% incude "modules/module_b.html" %}
{% endblock %}
This would output something like
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<h1>Module A</h1>
<h1>Module B</h1>
<h1>Module A</h1>
<h1>Module B</h1>
<script src="js/modules/module_a.js"></script>
<script src="js/modules/module_b.js"></script>
</body>
</html>

Related

Jinja nested template not found

I've got the following structure in my flask project:
app
-app
-static
-templates
-layouts
footer.html
header.html
main.html
search.html
__init__.py
app.py
MANIFEST.in
setup.py
In app.py:
#app.route('/search')
def show_search_form():
return render_template('search.html')
search.html:
{% extends "layouts/main.html" %}
{% block body %}
Test
{% endblock %}
main.html
{% include 'header.html' %}
{% block content %}
{% endblock %}
{% include 'footer.html' %}
header.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>App</title>
</head>
<body>
footer.html
</body>
</html>
As you can see, I'm extending main.html into search.html and using (in this case) 'Test' as content to be injected in the body block. But it doesn't work, I'm getting the following error:
jinja2.exceptions.TemplateNotFound: header.html
What's wrong with the code?
Since your footer.html and header.html files are under the layout directory, you need to reference them as such in the main templates:
main.html
{% include 'layouts/header.html' %}
{% block content %}
{% endblock %}
{% include 'layouts/footer.html' %}

Django use Admin Template

I created the following simple template:
<!DOCTYPE html>
<html lang="en">
<head> .... </head>
<body>
<h1>Harvester: {{ harvester_name }} </h1>
<textarea rows="10" cols="50">
{{ log_text }}
</textarea>
</body>
</html>
Created in the path myProject/myApp/templates/myApp
Now I would like to use the admin template for my template.
So that I have the header similar to the one used in the change_form.
As seen in the image.
How can I do that?
Thanks in advance
Add this in your template, at top. Documentation
{% extends "admin/change_form.html" %}
{% block content %}
Your stuff here
{% endblock %}

Adding Bootstrap and a logo png to Apostrophe CMS

I am simply trying to add a bootstrap.min.css file with its corresponding bootstrap.min.js file to an Apostrophe CMS project. I have no idea how to simply add a static resource. I have the following in my app.js
'apostrophe-assets': {
stylesheets: [
{
name: 'bootstrap.min',
minify: false
},
{
name: 'site'
}
],
scripts: [
{
name: 'bootstrap.min',
minify: false
}
]
}
but alas, it does nothing. I have removed the .min versions as well and tried those, still nothing.
On that note, I need to be able to link to a .png that will be used in my navbar, but I have no idea where to store any static resources for the website.
Is there a place I can just drop static files that I don't want pushed as apostrophe-assets so they are rendered and consumed properly?
I am the lead developer of Apostrophe at P'unk Avenue.
For this to work, Apostrophe needs the files to be located at:
lib/modules/apostrophe-assets/public/css/bootstrap.min.css
And:
lib/modules/apostrophe-assets/public/js/bootstrap.min.js
Within your project (don't copy them into node_modules). You create your own lib/modules/apostrophe-assets folder within your own project, to parallel the one in the apostrophe npm module.
This is as documented here in the tutorials on pushing assets.
(If you have trouble pushing the CSS file, try renaming it with a .less extension and let me know you had to do that. It ought to be unnecessary though.)
Of course there is also nothing keeping you from overriding any of the blocks in outerLayoutBase.html in your own templates to insert script and link tags, but that shouldn't be necessary. If you follow the practice I'm recommending your files will minify with everything else in production.
I've used the apostrophe-assets module to push css and js files the same approach as provided by #boutell. For pushing js files it's ok, but for css I've got a parse error related to bootstrap css. So it's because the apostrophe tries to compile it into css whereas it is already css.
To solve this problem I've override the outerLayot.html Nunjucks template. So basically in the file
node_modules/lib/modules/apostrohpe-templates/views/outerLayout.html, we have:
{% extends "outerLayoutBase.html" %}
Which itself extends the outerLayoutBase.html
node_modules/lib/modules/apostrohpe-templates/views/outerLayoutBase.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %}{% endblock %}</title>
{{ apos.assets.stylesheets(data.when) }}
{% block standardHead %}
<meta name="viewport" content="width=device-width, initial-scale=1">
{% endblock %}
{% block extraHead %}
{% endblock %}
</head>
<body class="{% block bodyClass %}{% endblock %}">
{% block apostropheMenu %}
{{ apos.adminBar.output() }}
{% endblock %}
{% if data.user %}
<div class="apos-ui">
<div class="apos-context-menu-container">
{{ apos.pages.publishMenu({ publishMenu: data.publishMenu, page: data.page, piece: data.piece, bottom: true }) }}
{{ apos.pages.menu({ contextMenu: data.contextMenu, page: data.page, bottom: true })}}
</div>
</div>
{% endif %}
<div class="apos-refreshable" data-apos-refreshable>
{% block beforeMain %}{% endblock %}
<a name="main"></a>
{% block main %}{% endblock %}
{% block afterMain %}{% endblock %}
</div>
{{ apos.assets.templates(data.when) }}
{{ apos.assets.scripts(data.when) }}
<script type="text/javascript">
{{ data.js.globalCalls }}
{{ data.js.reqCalls }}
</script>
{% block extraBody %}
{% endblock %}
</body>
</html>
Here I have used {% block extraHead %}{% endblock %} block and overrided it into a new file which is:
/lib/modules/apostrohpe-templates/views/outerLayout.html
{% extends "outerLayoutBase.html" %}
{% block extraHead %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
{% endblock %}
In the above file is included CDN of bootstrap css and works as expected. Note the path of this new file. The lib folder is under the root folder not the node_modules.

Extend Twig template without blocks

I have layout template
<html>
<body>
{% block content %}{% endblock %}
</body>
</html>
And many child templates like this
{% extends 'layout/default.twig' %}
{% block content %}
<p>content</p>
{% endblock %}
And it's very annoying that every single child template in Twig must include {% block content %}...{% endblock %} to be extended by parent block, otherwise there will be error: A template that extends another one cannot have a body.
Is there any solution to bind all child template output(that is not located in any block) in some variable, and then use it to paste in parent template? Like this:
Layout
<html>
<body>
{{ _context.childOutput }
</body>
</html>
Child
{% extends 'layout/default.twig' %}
<p>content</p>
It will make child templates code more compact and there will be no dependency from parent templates blocks name.
UPD Submitted new issue on Twig's GitHub https://github.com/twigphp/Twig/issues/2027
The 2 lines you have in each template allows you aswell to redefine many blocks in one template. I can't see how the solution you want can do that.
<html>
<head>
{% block meta %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
you can see include and embed but if you really have only one block in your templates twig is maybe not the solution you need
While seeing in this GitHub issue that you define variables in the controller, I had the following idea. I'll assume that the child template only contain static code since you didn't describe this point.
You can modify the function in your controller in order to fetch the content of the child template then pass it to the parent template directly:
function acmeAction()
{
// …
return $this->render(
'AcmeBundle:layout:default.html.twig',
array(
'title' => $title,
'description' => $description,
'content' => file_get_contents(
$this->container->get('kernel')->locateResource(
'#AcmeBundle/Resources/views/layout/child.html.twig'
)
)
)
);
}
And the parent template:
<head>
<title>{% block title %}{{ title }}{% endblock %}</title>
<meta name="description" content="{% block description %}{{ description }}{% endblock %}" />
</head>
<body>
{% block body %}{{ content }}{% endblock %}
</body>
This way you won't need to define the parent in the child template.
You can define some variables in the child and display them in the parent:
Layout
<html>
<body>
{{ myValue }
</body>
</html>
Child
{% set myValue %}
<p>content</p>
{% endset %}
{% include 'layout/default.twig' %}
This works because:
Included templates have access to the variables of the active context.
Source: http://twig.sensiolabs.org/doc/tags/include.html
And it's very annoying that every single child template in Twig must include {% block content %}...{% endblock %} to be extended by parent block
While it may sound annoying when you only have one variable, you will see the benefits of this approach when you will have to define also the title of the page, the JavaScript code, etc. In this case the use of multiple {% block … %} is really useful.
See this example:
Layout
<html>
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{% block content %}{% endblock %}
{% block javascript %}{% endblock %}
</body>
</html>
Child
{% extends 'layout/default.twig' %}
{% block title %}
My title
{% endblock %}
{% block content %}
<p>content</p>
{% endblock %}
{% block javascript %}
<script>…</script>
{% endblock %}

Jinja2 extends not working?

This is my base.html:
<html>
<head>
{% block head %}
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
This is my meta.html:
{% extends 'templates/base.html' %}
{% block head %}
{% if page == 'index' %}
<meta name="mysite.com" content="{{page}}"></meta>
{% else %}
<meta name="mysite.com" content="other page"></meta>
{% endblock %}
This is my index.html:
{% extends 'templates/base.html' %}
{% block body %}
......
{% endblock %}
This is my view:
#view_config(route_name='index', renderer='templates/index.html', permission='view')
def index(request):
return dict(page="index")
Extending the body works, but the meta tags are not showing up at all. What am I doing wrong?
As per Jinja2 documentation on extends, "The extends tag can be used to extend a template from another one. You can have multiple of them in a file but only one of them may be executed at the time."
When you load index.html from your view, the templating engine assesses index.html. It sees {% extends 'templates/base.html' %} in index.html and uses the blocks within index.html to replace same-name blocks of the parent/extended template (base.html).
So index.html says to use base.html as the skeleton template, but to replace {% block body %} in the parent (base.html) with its own {% block body %}.
Nowhere in this instruction (neither in base.html, nor index.html, nor the view code) does meta.html get mentioned. The templating engine isn't analyzing all the templates in your directory structure to see how it can plug things in. It is only using your explicit directives.
To be explicit about using meta.html, you can use the include directive (documentation found here) in base.html. Your base.html would then look like this:
<html>
<head>
{% include 'templates/meta.html' %}
{% block head %}
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
This would pull in meta.html and replace the same-name blocks of the skeleton (base.html) with the ones that were retrieved from the included template (meta.html).
In addition, the extends statement within meta.html needs to be removed, so it doesn't create a circular template inclusion.

Resources