Extending twig blocks in a complicated scenario - twig

Let's say I have these twig templates:
base.twig
{# base.twig #}
<html>
<head>
{% include 'head_js.twig' %}
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
head_js.twig
{# head_js.twig #}
{% block headJS %}
<script src='/js/some-script.js'></script>
{% block headJSExtra %}{% endblock %}
{% endblock %}
page.twig (the one loaded by the controller)
{# page.twig #}
{% extends base.twig %}
{% block content %}
<p>Widget 1</p>
{% include 'widget.twig' with { name: 'foo' } %}
<p>Widget 2</p>
{% include 'widget.twig' with { name: 'bar' } %}
{% endblock %}
widget.twig
{# widget.twig #}
{% if wigetAlreadyIncluded is not defined %}
{% block headJSExtra %}
{{ parent() }}
<script src='/js/widget.js'></script>
{% endblock %}
{% set widgetAlreadyIncluded = true %}
{% endif %}
<p>My name is {{ name }}</p>
This code doesn't work (can't use parent() in widget.twig as it's not extending or using any template), but it should illustrate what I'm trying to achieve. The basic idea is:
In order to work, widget.twig requires a js library to be loaded in as a tag in the .
The widget can be rendered several times in one page.
Other widgets should be able to also add their own tags in the in this fashion, but they shouldn't override previous added tags (they should be appended).
I don't want to add more than once any tag required by any widget found in the page.
Any ideas on how can I achieve this would be greatly appreciated. I've read two SO related questions with no luck at all.
https://stackoverflow.com/a/29132604/4949663
https://stackoverflow.com/a/18160977/4949663

I finally got a good answer in the twig github issue tracker.
https://github.com/twigphp/Twig/issues/2275

Related

Twig, is it possible to override an tag atribute of parent template from a child template?

I have two templates, a parent template and a child template.
I want know if its possible to add 'properly' a class to a tag in the parent template from the child template ? and if yes, how ?
By example, if I have this parent.html.twig file :
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
And this child.html.twig file :
{% extends 'parent.html.twig' %}
{% block body %}
{# ... #}
{% endblock %}
From the child.html.twig file, can I add a class to the body tag ? and how ?
Thanks for help :)
You should modify the parent template adding a block, as example:
parent.html.twig
<body {% block bodyclass %}{% endblock %}>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
and use in the child:
child.html.twig
{% extends 'parent.html.twig' %}
{% block bodyclass %}class="child-class"{% endblock %}
{% block body %}
{# ... #}
{% endblock %}
You can try in this twigfiddle

Multiple blocks in Grav cms twig template

I am making a Grav template from a static html. This is a one-pager, so I am using modular templates and content. The problem is that I have a gallery, and my clicking on an image, a modal pops up. The code for the modals are places outside the container I am using for the image gallery.
Is there a way to have a twig modular template creating output code two different places on the base template? I have tried using multiple blocks in the modular template, but in the final output all code are places inside the image gallery container? My templates have multiple level nesting just to make it a little bit easier:
main
Listing of categories
Listing of images for each category
base.html.twig (simplified):
{% block body %}
{% block modal %}{% endblock %}
{% block content %}{% endblock %}
{% endblock %}
modular.html.twig:
{% extends 'partials/base.html.twig' %}
{% block content %}
{{ page.content }}
{% for module in page.collection() %}
<div id="{{ _self.pageLinkName(module.menu) }}">
{{ module.content }}
</div>
{% endfor %}
{% endblock %}
category-list.html.twig (simplified):
{% block content %}
{% for module in page.collection() %}
{{ module.content }}
{% endfor %}
{% endblock %}
content template (simplified):
{% block modal %}
{{ page.titel }}
{% endblock %}
{% block content %}
{{ page.content }}
{% endblock %}
My problem: rendering the block modal from the content template in the block Modal in the base template. Currently the block modal is rendered in block content.
Any ideas?
Thanks

Use grandparent block

I have the following twig templates:
{# layout.twig #}
{% block content %}
THIS IS LAYOUT
{% endblock %}
{# secondary_layout.twig #}
{% extends layout.twig %}
{% block content %}
THIS IS SECONDARY_LAYOUT
{% endblock %}
{# mypage.twig #}
{% extends secondary_layout.twig %}
{% block content %}
{# I WOULD LIKE TO USE layout content block here #}
{% endblock %}
I can call parent() inside the content block in mypage.twig, but how to use a grandparent instead?
There are situations, in which you can achieve this. Unfortunately not in your case. However, it works if only "horizontal re-use" is used (use keyword), but not with inheritance (extends). This for instance applies to form themes.
In my case I defined a form theme which inherits from the bootstrap 3 form theme. The bootstrap theme itself inherits from "form_div_layout". I wanted to override the choice widget and include the grand parent's (form_div_layout) block content, because the bootstrap version of the block didn't fit for me in this single case. So, basically a very similar problem.
This can be solved by inheriting from both, the parent (bootstrap_3_layout) and grand parent layout (form_div_layout), while declaring an alias for the grand parent block to be overridden:
{# my_form_theme.html.twig #}
{% use 'form_div_layout.html.twig' with choice_widget_collapsed as base_choice_widget_collapsed %}
{% use 'bootstrap_3_layout.html.twig' %}
{% block choice_widget_collapsed -%}
{# There is no "grandparent()" function, so instead we can do this: #}
{{- block('base_choice_widget_collapsed') -}}
{%- endblock %}
I'm writing this answer, although it doesn't answer the actual question. But other people will probably also find this question when googling for such a "grandparent"-feature and maybe they'll give up unnecessarily, when they read that it's impossible here.
Ok by writing the problem I got one solution, just modify secondary_layout
{# secondary_layout.twig #}
{% extends layout.twig %}
{% block content %}
{% if use_layout_block %}
{{ parent() }}
{% else %}
THIS IS SECONDARY_LAYOUT
{% endif %}
{% endblock %}
{# mypage.twig #}
{% extends secondary_layout.twig %}
{% block content %}
{% set use_layout_block = true %}
{% endblock %}
It may help someone.
If someone got another solution, feel free to answer.
You can include the block of any template with the function block().
block - Documentation - Twig
block(<block_name>, [template_name])
In your case, the solution could be as follows:
{# mypage.twig #}
{% extends secondary_layout.twig %}
{% block content %}
{{ block('content', 'layout.twig') }}
{% endblock %}

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

How to include a template that set content into blocks using twig

It's difficult to explain but I have a layout.html.twig template like this:
{% block css '' %}
{% block content '' %}
{% block js %}
<script src="jQuery.js"></script>
{% endblock js %}
I have another template index.html.twig that extends the layout one like this
{% extends 'AcmeBundle::layout.html.twig' %}
{% block content %}
{% include 'AcmeBundle:Module:module_1.html.twig' %}
{% include 'AcmeBundle:Module:module_2.html.twig' %}
{% include 'AcmeBundle:Module:module_3.html.twig' %}
{% endblock content %}
{% block js %}
{{ parent() }}
<script ...></script>
{% endblock js %}
Each included module has some javascript inside but they need to have jQuery loaded to work.
But jQuery is set in the layout.html.twig so it's not working.
How can I include my modules being sure that the script is executed after jQuery is loaded?
Is it possible to access the js block somehow? I had a look to embed but I'm not sure it's what I need.
Any ideas?
Cheers,
Maxime

Resources