How to make a macro in Twig? - symfony

I use a big piece of code for displaying friends and I use it in several templates, so I wanted to make it a macro, but the syntax is a way unusal and I don't know if there is a way this to be done.
The part of the code, which I want to seperate is:
{% if(fr.email != null) %}
<p>
<span class ="label">Email Address: </span>
<a class="email" href="{{ path('friend_email', {'id': fr.id}) }}">
{{ fr.email }}
</a>
</p>
{% endif %}
{% if(fr.phone != null) %}
<p>
<span class="label">Phone: </span>
{{ fr.phone }}
</p>
{% endif %}
and so on for about 10 variables. In another template I use this, but instead of fr.email, fr.phone and so on, I need friend.email, friend.phone...
I tried this but without success:
{% macro display_friend(fr) %}
{% if({{ fr }}.email != null) %}
<p>
<span class ="label">Email Address: </span>
<a class="email" href="{{ path('friend_email', {'id': {{ fr }}.id}) }}">
{{ {{ fr }}.email }}
</a>
</p>
{% endif %}
{% if({{ fr }}.phone != null) %}
<p>
<span class="label">Phone: </span>
{{ {{ fr }}.phone }}
</p>
{% endif %}
{% endmacro %}
If necessary, I can use fr.email, fr.phone, fr.* ... in each template, so maybe inheritance will work?
So my question is: is there a way to make this part of code macro and if yes will it be better or inheritance will be better?

In your situation, rather than a using a macro I would just include a twig file, using the with option. For example, you can do:
{% include 'AcmeDemoBundle:Demo:showFriend.html.twig' with {'fr': friend} %}
and showFriend.html.twig will be:
{% if(fr.email != null) %}
<p>
<span class ="label">Email Address: </span>
<a class="email" href="{{ path('friend_email', {'id': fr.id}) }}">
{{ fr.email }}
</a>
</p>
{% endif %}
{% if(fr.phone != null) %}
<p>
<span class="label">Phone: </span>
{{ fr.phone }}
</p>
{% endif %}

Within the macro you can just use fr.var so
{% if({{ fr }}.email != null) %}
will become
{% if fr.email %}
As you can see, specifying != null is not required (I even doubt if it'll work, it probably should be fr.email not is null )

Related

How to css customize symfony form errors with bootstrap and twig?

For symfony form and twig I use
{{ form_errors(form) }}
with bootstrap_4_horizontal_layout.html.twig, it works fine and output is like:
<span class="alert alert-danger d-block"><span class="d-block">
<span class="form-error-icon badge badge-danger text-uppercase">Error</span> <span class="form-error-message">Error</span>
</span></span>
But I need to customize it to translate that bootstrap ERROR label next to the message. Where I can customize it?
you can and probably should write your own form theme and override the form_errors block to adapt it to your needs. as reference: https://github.com/symfony/symfony/blob/537d373e0d8cb11fa70ddbe9559f2c4a741117a9/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig#L294
so to implement your own form theme you just have to have a file, let's say my_form_style.html.twig:
{% extends "bootstrap_4_horizontal_layout.html.twig" %}
{# override the blocks you want ... or add some, this is the original #}
{%- block form_errors -%}
{%- if errors|length > 0 -%}
<span class="{% if form is not rootform %}invalid-feedback{% else %}alert alert-danger{% endif %} d-block">
{%- for error in errors -%}
<span class="d-block">
<span class="form-error-icon badge badge-danger text-uppercase">{{ 'Error'|trans({}, 'validators') }}</span> <span class="form-error-message">{{ error.message }}</span>
</span>
{%- endfor -%}
</span>
{%- endif %}
{%- endblock form_errors %}
and afterwards you just set the form theme to yours:
{% form_theme form "my_form_style.html.twig" %}
enjoy

3 Level Deep Timber menu (Wordpress)

I'm not familiar with timber at all, but am helping a friend finish up a project that was built with it. So any help would go a long way please!
I have only the first two tiers showing up. Is there a way to call on the child of a child?
I'm using the code here, and added to it another tier under child https://timber.github.io/docs/guides/menus/
{% if menu %}
<div class="header-menu-items">
<div class="header-menu-item mod-title">
<a href="{{ site.url }}" class="" rel="home">
<div class="header-item-logo">
<div class="sitelogo">{{ site.name }}</div>
</div>
</a>
</div>
{% for item in menu.get_items() %}
<div class="header-menu-item {{ item.current ? 'is-active' : '' }}">
<div class="header-menu-item-link">
<a target="{{ item.target }}" href="{{ item.link }}">{{ item.title }}</a>
</div>
<div class="header-menu-item-triangle"></div>
<div class="header-menu-item-mega {{ item.section_colour ? " mod-#{item.section_colour}" : '' }}">
{% if item.master_object.thumbnail %}
<div class="mega-image mod-image" style="background-image: url( {{item.master_object.thumbnail.src('thumbnail') }} )">
{% else %}
<div class="mega-image">
{% endif %}
{{ item.title }}
</div>
<div class="mega-items">
{% for child in item.children %}
<div class="mega-item">
<a target="{{ child.target }}" href="{{ child.link }}">
<span class="mega-item-title">{{ child.title }}<br /></span>
<span class="mega-item-excerpt">Mega menu description lorem ipsum dolores</span>
</a>
</div>
{% for child in child.children %}
Just testing to see if it'll even show up first before applying style<br />
{{ child.title }}<br />
{% endfor %}
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
You can access menus several layers deep by nesting for loops inside of one another. Here is a code snippet that I have tested and works.
{% for item in menu__main.items %} {# This is the top level #}
<p>{{ item }}</p>
{% if item.children %}
{% for child in item.children %} {# 2nd Level #}
<p><em>{{ child }}</em></p>
{% if child.children %}
{% for third in child.children %} {# 3rd Level #}
<p><strong>{{ third }}</strong></p>
{% endfor %} {# for third in child.children #}
{% endif %} {# if child.children #}
{% endfor %} {# for child in item.children #}
{% endif %} {# if item.children #}
{% endfor %} {# for item in menu__main.items #}
I have added comments to the end of the lines to hopefully make this more clear. So at the top, you are looping through item in menu__main.items
Then to get the children inside of these, you loop through item.children since item is the variable that represents each nav item at the top/main level. You loop through item.children to get to the next level or the children inside of the main/top level.
Then to get inside of the third level, you loop through child.children since child is the variable that represents the 2nd level. We want to loop through the children of this 2nd level. so we do third in child.children. third is the variable that represents the items 3 levels down.
I hope you can see the pattern here and you can continue this even further if you have items at even deeper levels, although at some point it will probably get ridiculous. Like if you have items nested 5 or 6 levels deep.
Let me know if that makes sense and if not I will be more than happy to try and explain it another way if you still have questions.
Cheers

Twig, how to print a nl2br output in one line

I used the nl2br filter like this:
{{ knp_pagination_render(requests)|raw|nl2br}}
It prints the html object separated by a few line breaks, and I need to print all the objects in one single line. How can I reach that?
Any help would be great.
{{ string | replace({"\\n":""}) }}
See fiddle.
I suggest you to override the default pagination template, so you can customize how to generate the underling HTML code.
As described in the doc You can do simply:
{{ knp_pagination_render(request, 'MyBundle:Pagination:pagination.html.twig') }}
You can copy the current pagination implementation from here and customize it as you need.
This is the default implementation:
{# default Sliding pagination control implementation #}
{% if pageCount > 1 %}
<div class="pagination">
{% if first is defined and current != first %}
<span class="first">
<<
</span>
{% endif %}
{% if previous is defined %}
<span class="previous">
<
</span>
{% endif %}
{% for page in pagesInRange %}
{% if page != current %}
<span class="page">
{{ page }}
</span>
{% else %}
<span class="current">{{ page }}</span>
{% endif %}
{% endfor %}
{% if next is defined %}
<span class="next">
>
</span>
{% endif %}
{% if last is defined and current != last %}
<span class="last">
>>
</span>
{% endif %}
</div>
{% endif %}

Recursive parsing of hierarchical Twig layout with parents, children, grandchildren

In my SF2 project I have an entity (Category) which I am representing in a hierarchical format with a parent at the top, followed by children, grandchildren etc.
The Category entity has a getChildren method, which works and returns Category entity objects.
I'm trying to work out a way to make this layout more dynamic, rather than having to explicitly set children and grandchildren variables within the template.
Is there a better way to do this?
<ul class="sortable">
{% for cat in cats %}
{% set children = cat.getChildren %}
<li id="menuItem_{{ cat.id }}">
<div data-id="{{ cat.id }}">
<span>{{ cat.name }}</span>
</div>
{% for child in children %}
{% set grandchildren = child.getChildren %}
<ul>
<li id="menuItem_{{ child.id }}">
<div data-id="{{ child.id }}">
{{ child.name }}
</div>
{% for grandchild in grandchildren %}
<ul>
<li id="menuItem_{{ grandchild.id }}">
<div data-id="{{ grandchild.id }}">
{{ grandchild.name }}
</div>
</li>
</ul>
{% endfor %}
</li>
</ul>
{% endfor %}
</li>
{% endfor %}
</ul>
so what you are trying to achieve is recursive parsing of a tree in twig right?
If so, have a look at macros
.
{% import _self as macros %}
{% macro showChild(object) %}
{% import _self as macros %}
<ul>
{% for child in object.children %}
{{ macros.showChild(child) }}
{% endfor %}
<li id="menuItem_{{ object.id }}">
<div data-id="{{ object.id }}">
{{ object.name }}
</div>
</li>
</ul>
{% endmacro %}
<ul class="sortable">
{% for cat in cats %}
{{ macros.showChild(cat) }}
{% endfor %}
</ul>
that's all :)
let me know if you need help
EDIT 1:
If you want to use the macro in another file, remove the "import _self" line and just import it with an alias in another file:
index.html.twig:
{% import 'macro_file_name.html.twig' as macros %}
then you can use the same notation to call it

Twig loop and skip in loop

I pretty new to Twig first of all!
I'm trying to loop through an array of products, like so:
{% for product in products %}
What I further try to do is to check if one of the product.title equals the name lookbook.
if so then this product cannot be showed in my template/page. The things I tried just display an empty div instead of not showing anything.
Is there any way to accomplish this?
What I have so far:
{% for product in products %}
{% if product.title == 'lookbook' %}
.... dont show?? ....
{% else %}
<div class="product>
<h3>{{ product.fulltitle }}</h3>
<a href="{{ product.url | url }}" title="{{ product.fulltitle }}">
<img src="{{ product.image | url_image('220x220x2', product.fulltitle) }}" />
</a>
</div>
{% endif %}
{% endfor %}
I also tried:
{% for product in products and product.title != 'lookbook' %}
Thanks in advance!
Official doc :
{% for user in users if user.active %}
<li>{{ user.username|e }}</li>
{% endfor %}
In your case :
{% for product in products if product.title != 'lookbook' %}
Update 08/2021
Tip
As of Twig 2.10, use the filter filter instead, or an if condition
inside the for body (if your condition depends on a variable updated
inside the loop and you are not using the loop variable).
Try:
{% for product in products %}
{% if product.title != 'lookbook' %}
<div class="product>
<h3>{{ product.fulltitle }}</h3>
<a href="{{ product.url | url }}" title="{{ product.fulltitle }}">
<img src="{{ product.image | url_image('220x220x2', product.fulltitle) }}" />
</a>
</div>
{% endif %}
{% endfor %}

Resources