Replace only one nested block in twig - php

I have the following twig structure:
base.twig
<html>
<head>
</head>
<body class="fade-in {{body_class}}">
<main>
{% block menu %}
{% include 'menu.twig' %}
{% endblock %}
</main>
</body>
</html>
menu.twig
<header>
<div>
{% block menu_main %}
{% include 'menu-main.twig' %}
{% endblock %}
{% block menu_country %}
{% include 'menu-country.twig' with { menu_country: dropdownland } %}
{% endblock %}
</div>
</header>
child.twig
{% extends "base.twig" %}
{% block menu %}
{% block menu_country %}
{% include 'menu-country.twig' with { menu_country: menu_ap_countries } %}
{% endblock %}
{% endblock %}
What i want to achieve is, just replace the block menu_country inside child.twig. If i use the approach above, the whole block menu gets replaced by only menu_country which means that the block menu_main is missing.
I tried also
{% extends "base.twig" %}
{% block menu %}
{{ parent() }}
{% block menu_country %}
{% include 'menu-country.twig' with { menu_country: menu_ap_countries } %}
{% endblock %}
{% endblock %}
but this renders the block menu_country twice. Is there any way to achieve this?
Thanks a lot.

After further investigation due to #DarkBees answer, i came accross the embed function which does exactly what i need in this case. So the extending template becomes this:
{% block menu %}
{% embed 'menu.twig'%}
{% block menu_country %}
{% include 'menu-country.twig' with { menu_country: menu_ap_countries } %}
{% endblock %}
{% endembed %}
{% endblock %}
By embedding, I am able to overwrite the block inside menu.twig

Including templates does not mean u are importing said blocks inside the template.
This mean only the block menu will exists inside child.twig.
In your first example you are actually just overwriting the block menu and creating a new block menu_country inside of it.
In your second example, you are telling twig to output the default content of the block menu and append a new block menu_country to it.
A possible solution would be to change set-up to this e.g.
menu.twig
<header>
<div>
{% block menu_main %}
{% include 'menu-main.twig' %}
{% endblock %}
{% block menu_country %}
{% include 'menu-country.twig' %}
{% endblock %}
</div>
</header>
menu-country.twig
<ul class="country">
{% for country in menu_country|default(dropdownland) %}
<li>{{ country }}</li>
{% endfor %}
</ul>
child.twig
{% extends "base.twig" %}
{% block menu %}
{% include 'menu.twig' with { menu_country: menu_ap_countries, } %}
{% endblock %}
demo

Related

twig internal block won't work

I am have one problem, my internal blocks on twig won't work.
This is my layout.html.twig file:
<body>
{% block body %}{% endblock %}
//some code here
</body>
This is my index.html.twig
{% extends ':layout:dashboard.html.twig' %}
{% block body %}
{% block header %}{% endblock header %}
//some code here
{% endblock body %}
and this is header.html.twig.
{% extends '#Dashboard/Dashboard/index.html.twig' %}
{% block header %}
//some code here
{% endblock header %}

symfony2 / twig : how to use include in a block used in a form theme?

To process my form collections I have a custom form theme for the block collection widget. This block collection widget is rendered as a table and so depends on a block_collection_header and a block_collection_body.
The block collection widget always stays the same, but sometimes I customize the two other blocks, block collection header and block collection body
My working code :
{# From file myview.html.twig #}
{% form_theme form ':Model:prototype_table_collection.html.twig' %}
{% form(form) %}
Ant this form theme is the following :
{# From file ':Model:prototype_table_collection.html.twig' #}
{% block collection_widget %}
{% spaceless %}
<div class="collection">
{% if prototype is defined %}
{% set body = prototype %}
{% set attr = attr|merge({'data-prototype': block('collection_body') }) %}
{% set header = prototype %}
{% set attr = attr|merge({'data-header': block('collection_header') }) %}
{% endif %}
{% if form.vars.allow_delete is defined and form.vars.allow_delete %}
{% set allow_delete = true %}
{% else %}
{% set allow_delete = false %}
{% endif %}
<div {{ block('widget_container_attributes') }} class="protoype">
{{ form_errors(form) }}
<table class="subtable table">
<thead>
<tr class="headers" style="display: none;">
{% if form.children|length > 0 %}
{% if form.children[0]|length > 0 %}
{% set header = form.children[0] %}
{{ block('collection_header') }}
{% endif %}
{% endif %}
</tr>
</thead>
<tbody class="container_rows">
{% for rows in form %}
{% spaceless %}
{% if rows.children|length > 0 %}
{% set body = rows %}
{{ block('collection_body') }}
{% endif %}
{% endspaceless %}
{% endfor %}
</tbody>
</table>
{% if prototype is defined %}
{% if form.vars.attr['data-add_label'] is defined %}
{% set add_label = form.vars.attr['data-add_label'] ~ ' ' %}
{% else %}
{% set add_label = 'Ajouter ' %}
{% endif %}
{{ add_label }}<i class="fa fa-plus"></i>
{% endif %}
<br>
</div>
</div>
{% endspaceless %}
{% endblock collection_widget %}
{% block collection_header %}
{% for field in header %}
<th>
{% if 'checkbox' not in field.vars.block_prefixes %}
{{ form_label(field)|raw }}
{% else %}
{% if field.vars.attr['data-label'] is defined %}
{{ field.vars.attr['data-label'] }}
{% else %}
Options
{% endif %}
{% endif %}
</th>
{% endfor %}
{% if allow_delete %}
<th class="align_center">Supprimer</th>
{% endif %}
{% endblock %}
{% block collection_body %}
{% spaceless %}
{% set fieldNum = 1 %}
<tr class="row_to_delete child_collection">
{{ form_errors(body) }}
{% for field in body %}
<td class="field{{ fieldNum }} data-label">
{{ form_widget(field) }}
{{ form_errors(field) }}
</td>
{% set fieldNum = fieldNum + 1 %}
{% endfor %}
{% if allow_delete %}
<td class="align_center align_middle"><i class="fa fa-times"></i></td>
{% endif %}
</tr>
{% endspaceless %}
{% endblock %}
The code I'd like to use and which is not working:
The view stays the same
{# From file myview.html.twig #}
{% form_theme form ':Model:prototype_table_collection.html.twig' %}
{% form(form) %}
Here I am trying to externalize the code from within the first block
{# From file ':Model:prototype_table_collection.html.twig' #}
{% block collection_widget %}
{{include(':Model:collection_widget.html.twig')}}
{%end block%}
{% block collection_header %}
{#stays the same as the previous code for this block. It is called by the block collection_widget #}
{%end block%}
{% block collection_body %}
{#stays the same as the previous code for this block. It is called by the block collection_widget #}
{%end block%}
The new externalized file :
{#From file ':Model:collection_widget.html.twig' #}
{# Here I put the same exact code as I had before inside the block collection_widget, I'm not changing the code, I'm just trying to externalize this part #}
The include does not work, my collection does not load.
I have tried with extending a layout, it doesn't work either.
Example :
{# From file ':Model:prototype_table_collection.html.twig' #}
{% extends :Model:parent.html.twig' %}
{% block content %}
{% block collection_header %}
{#stays the same as the previous code for this block. It is called by the block collection_widget #}
{%end block%}
{% block collection_body %}
{#stays the same as the previous code for this block. It is called by the block collection_widget #}
{%end block%}
{%end block%}
and the parent :
{# From file ':Model:parent.html.twig' #}
{% block collection_widget %}
{# same code as brefore #}
{%end block%}
{% block content %}
{% endblock %}
How can I avoid repeating this {% block collection_widget %} code in every form template where I use it ?
I believe you are looking for the horizontal reuse functionality:
Horizontal reuse is an advanced Twig feature that is hardly ever needed in regular templates. It is mainly used by projects that need to make template blocks reusable without using inheritance.
Just include the use tag in the main template:
{# :Model:prototype_table_collection.html.twig #}
{% use ':Model:collection_widget.html.twig' %}
{% block collection_header %}
{# code #}
{%end block%}
{% block collection_body %}
{# code #}
{%end block%}
Then define the collection_widget block as if it was inside of the prototype_table_collection.html.twig file in the first place:
{# :Model:collection_widget.html.twig #}
{% block collection_widget %}
{# code #}
{% endblock %}

Twig - render block from included or parent template

is there any way to render block from included or parent template?
I have 3 templates
main.html.twig:
{% include 'navbar.html.twig' %}
<div id="main_content">
{% block application_content %}
{% endblock application_content %}
</div>
{% block application_footer %}
footer
{% endblock application_footer %}
navbar.html.twig:
<p>bla bla</p>
{% block navbar_profile_photo %}
<img src="{{ image }}">
{% endblock navbar_profile_photo %}
content.html.twig:
{% extends "main.html.twig" %}
{% block application_content %}
lorem ipsum
{% block foo %}
dolor sit amet
{% endblock foo %}
{% endblock application_content %}
I want to be able call something like this
$twig->loadTemplate('content.html.twig')->renderBlock('navbar_profile_photo', array('image' => 'bar.jpg'))
But I can get only blocks from 'content.html.twig'
$twig->loadTemplate('content.html.twig')->getBlockNames();
returns
['application_content', 'foo']
I could add to content.html.twig
{% use 'navbar.html.twig' %}
but in my case I had to do it in many templates with many different use statements
Is there some way to get the final template with all includes and extends and blocks?
In content.html.twig file you must add:
{% extends "EverlutionApplicationBundle::main.html.twig" %}
{% block navbar_profile_photo %}
{{ parent() }}
{% endblock navbar_profile_photo %}
.....

Symfony2: Twig blocks doesn't show up

I'm learning symfony2 and I came across this problem.
I have index.html.twig in my "ShopBundle", which look like this:
{% extends '::base.html.twig' %}
{% block body %}
<div class="container">
<h2>Index template</h2>
<div class="col-md-6">
{% block login %}{% endblock %}
</div>
<div class="col-md-6">
{% block profile%}{% endblock %}
</div>
</div>
{% endblock %}
as you can see I want to display login form and some user info in my index.
Here is my login form twig
{% extends "FOSUserBundle::layout.html.twig" %}
{% trans_default_domain 'FOSUserBundle' %}
{% block login %}
{% block fos_user_content %}
.....
{% endblock fos_user_content %}
{% endblock login %}
and here is my profile info twig
{% trans_default_domain 'FOSUserBundle' %}
{% block profile %}
{% block fos_user_content %}
...
{% endblock fos_user_content %}
{% endblock profile %}
And here is my file structure
So why it's not working? What am I doing wrong? Is it even possible to do it like this?
Thank you very much for all advices.

How to check if a block is overridden in Twig?

I would like to check whether a block is overriden in a child template.
template.html.twig:
<html>
...
<nav class="menu">
{% block menu %}
{% endblock %}
</nav>
...
<div class="contents">
{% block contents %}
{% endblock %}
</div>
...
</html>
page1.html.twig -- contains a menu:
{% extends '::template.html.twig' %}
{% block menu %}
<ul>
<li>...</li>
</ul>
{% endblock %}
{% block contents %}
Hello World!
{% endblock %}
page2.html.twig -- does not contain a menu:
{% extends '::template.html.twig' %}
{% block contents %}
Hello World!
{% endblock %}
I would like to show the nav element in the template, only if it is overriden in the child template (the goal is to avoid an empty <nav /> element).
The Twig syntax has a is empty test, but it only applies to variables, not blocks.
What am I doing wrong?
You can set a variable in your child template check for that:
{# template.html.twig #}
{% if show_menu is not empty %}
<nav class="menu">
{% block menu %}
{% endblock %}
</nav>
{% endif %}
{# page1.html.twig #}
{% set show_menu = true %}
Maybe you should also consider putting this markup in the block definition for a more straight-forward approach.
You could add a wrapper to your nav element and then override it in page2 to make it blank when you don't want a menu. This doesn't require an extra variable.
template.html.twig
... snip ...
{% block nav %}
<nav class="menu">
{% block menu %}
{% endblock %}
</nav>
{% endblock %}
... snip ...
page2.html.twig
{% extends '::template.html.twig' %}
{% block nav '' %} {# here's the kicker ... #}
{% block contents %}
Hello World!
{% endblock %}
In any other page that has a menu you would continue to override the 'menu' block normally.

Categories