The menu_links macro in menu.html.twig wraps its {% for item in items %}
in a {% if items %}
to prevent the opening and closing ul from outputing. That's a standard PHP pattern. But it is NOT a standard Twig pattern. Twig has a special loop variable that is built specifically for cases like this, the loop
variable. http://twig.sensiolabs.org/doc/tags/for.html#the-loop-variable
Currently we do this:
{% if items %}
{% if menu_level == 0 %}
<ul{{ attributes.addClass('menu') }}>
{% else %}
<ul class="menu">
{% endif %}
{% for item in items %}
<li>…
{% endfor %}
</ul>
{% endif %}
But we should use Twig's loop variable like this instead:
{% for item in items %}
{% if loop.first %}
{% if menu_level == 0 %}
<ul{{ attributes.addClass('menu') }}>
{% else %}
<ul class="menu">
{% endif %}
{% endif %}
<li>…
{% if loop.last %}
</ul>
{% endif %}
{% endfor %}
Additionally, our if-ul-else-ul-endif construct means we have 2 opening ul tags and 1 closing ul tags. This causes a Twig analysis "error" in IDEs like PhpStorm:
Element ul is not closed
So this is even better:
{% for item in items %}
{% if loop.first %}
<ul
{% if menu_level == 0 %}
{{ attributes.addClass('menu') }}
{% else %}
class="menu"
{% endif %}
>
{% endif %}
<li>…
{% if loop.last %}
</ul>
{% endif %}
{% endfor %}
Comment | File | Size | Author |
---|---|---|---|
#25 | menu-macro-if-2763911-25.patch | 3.38 KB | Sutharsan |
#19 | menu-macro-if-2763911-19.patch | 8.25 KB | Sutharsan |
#19 | interdiff-2763911-6-19.txt | 2.55 KB | Sutharsan |
#15 | drupal_attribute_coming-2763911-15.patch | 1.49 KB | Anonymous (not verified) |
#6 | menu-macro-if-2763911-6.patch | 8.46 KB | Sutharsan |
Comments
Comment #2
JohnAlbinComment #3
star-szrCool :)
Comment #4
JohnAlbinWhoops. Fixing final code snippet in the issue summary. I copied and pasted the wrong thing.
Comment #6
Sutharsan CreditAttribution: Sutharsan commentedPatch looks and works good. But personally I do not like the white space it introduces in the
<ul>
. This is an HTML snippet:The attached patch adds
{% spaceless %}
.Comment #7
Sutharsan CreditAttribution: Sutharsan commentedResult after the patch:
Comment #8
Sumit kumar CreditAttribution: Sumit kumar at gai Technologies Pvt Ltd commented@Sutharsan patch is working it showing ul and li as you define in your comment #7
Comment #9
Anonymous (not verified) CreditAttribution: Anonymous commentedThanks for this issue, but it does not look like a cool. IMHO, here it looks cool:
And the page of TWIG documentation does not describe 'for + loop variable' as a pattern for this case. But I found another page, where in the second sample uses.. if for this case :)
I hope my comment will understand correctly.
Comment #10
Sutharsan CreditAttribution: Sutharsan commentedI agree that
# set attributes by conditions
would be cool. But I have not found a way to create an attributes object in a twig template, i.e. an attribute with class="menu".Comment #11
Anonymous (not verified) CreditAttribution: Anonymous commentedYeah, you're right! Although I have a few ways to create it, but their implementation is not real in this world)
1. The Ugly: just clone, and unset all property
2. The Bad: add new method to the core/lib/Drupal/Core/Template/Attribute.php
3. The Good: architectural solution in Drupal, giving their own attributes for all sub-menu (not only for root menu).
But the goal of my post #9 was just to draw attention to the fact that
is worse than
Thanks!
Comment #12
Jeff Burnz CreditAttribution: Jeff Burnz commentedWhitespace can easily be removed, e.g. this is from one of my menu templates:
.. something along those lines etc.
Comment #13
Sutharsan CreditAttribution: Sutharsan commented@JeffBurnz, I did consider this. This this would be the code:
And the HTML result:
It is not possible to do
{%- else -%}
, as this would lead to<ulclass="menu">
.Weighing pros and cons, I still choose my patch. But perhaps you can find a better way to achieve the same result.
Comment #14
Sutharsan CreditAttribution: Sutharsan commentedRegarding @vaplas' alternatives:
'The Good' will make the preprocess significantly more complex (bartik_preprocess_menu() is now a one liner) as it must iterate down the menu tree and set attributes. The preprocess also has to be backwards compatible.
As an alterernative to 'The Bad' and 'The Ugly', I would suggest a new Twig function that creates an Attribute object:
Comment #15
Anonymous (not verified) CreditAttribution: Anonymous commentednew Twig function - a great idea! It seems that it will be useful not only in these menu.twig templates. Unfortunately the pure name 'attribute' is occupied of Twig, but we can use drupal way pefix 'drupal_attribute'.
Examples:
And nit refactoring
also we can use two macro for rendering: wrapper (ul), content (li). Maybe it's better for cleaning twig template (at least, it's better than the loop ;)
Comment #16
Anonymous (not verified) CreditAttribution: Anonymous commentedRegarding 'The Good'. Yeah, it will make some things done qualitatively. But it also adds flexibly manage by sub-menu (programmatically and via the UI). I sometimes need this opportunity for creative menu. But now it ossified piece for dull menu :)
Comment #17
Jeff Burnz CreditAttribution: Jeff Burnz commentedGuys, the issue is to use loop and also prevent warnings in IDE like PhpStorm etc, this discussion seems to be about lots of other things beside this simple problem.
Comment #18
Sutharsan CreditAttribution: Sutharsan commented@Jeff Burnz, Yea, thanks for the focus.
Comment #19
Sutharsan CreditAttribution: Sutharsan commentedMoving the
</ul>
outside of the "for" loop.The patch is based on #6; #15 is only for a twig function to create an Attribute object for which we don't seem to have consensus. I let others decide on changing the whitespace solution.
Comment #20
Jeff Burnz CreditAttribution: Jeff Burnz commentedI quite like the spaceless tags and moving the
</ul>
out of the for loop as per #19.Comment #21
Anonymous (not verified) CreditAttribution: Anonymous commentedWe can not moving the /ul outside of the "for" loop, because if empty items the result:
</ul>
The idea of #1 - use only check by
{% for item in items %}
without{% if items %}
. Hence all code must be inside loop. But we are very costly to pay for it. And not only because of the extra performance conditions within the loop. The pattern itself is not successful.It's not look like:
It's look like:
It's antipattern. We inject parent code into children. If we want clear template, maybe better use two macro, like this:
But I am satisfied with the fact that we have now. We just need to solve the problem of double-ul and assigning attributes.
#17: I think this issue primarily focus about improving the template of menu. Loop - only one variant (and not the best). My english is very poor, and I'm tired of pointing this out. Hopefully, John Albin and all others correctly sees my attempt to help. Thanks and good luck!
Comment #22
Jeff Burnz CreditAttribution: Jeff Burnz commented@vaplas yes I know what you mean, however is this performance issue so great?
If we just want to remove double UL issue this seems trivial, don't use the loop twig variable,
So not as pure as #15 but also not that bad either - adding new twig functions is scope creep (a big scope creep), since we'er not trying to fix every problem in one issue - better to open follow up issues :)
Comment #23
joelpittetThis would actually introduce an extra white space before if there are no attributes so
<ul >
How about:
<ul{{ menu_level == 0 ? attributes }}>
Still confused what this issue is trying to accomplish, maybe @Sutharsan you can explain to me...
Comment #24
Sutharsan CreditAttribution: Sutharsan commentedI discussed the issue and the options with @joelpittet here at DrupalCon. The issue at hand is that we want to get rid of the waring in IDE's like PhpStorm. And we like to fix only this problem, for the other suggestions we can create a follow up issue. Further we want to minimize the footprint of the patch to maximize the change it gets in.
The ideal patch would do:
However due to (what we believe is) a bug in Twig,
class="menu"
gets escaped. The alternative we came up with is:This can be reworked once the ternary operator bug gets fixed upstream. To see the twig bug in action, try the following in for example twigfiddle.com:
Comment #25
Sutharsan CreditAttribution: Sutharsan commentedCreated a new patch accordingly.
Comment #26
joelpittetI'd say RTBC to #25 for now but this is @JohnAlbin's original issue and I disagree with the issue summary making things more clear and adding conditions in the loop when they are to apply things twice. Also I don't see the bug other than PHPStorm, so if #25 is the wrong direction we can move that to a new issue.
Assigning to the author for review.
Comment #27
Sumit kumar CreditAttribution: Sumit kumar at gai Technologies Pvt Ltd commentedComment #28
lauriiiI don't see much benefit for the change made on #25 and would like to say that it shouldn't be committed as is.
I think what we are trying to do here, is to make the menu.html.twig easier to read? If we want to do that, I think we need some fresh ideas how to do it.
Comment #29
JohnAlbinThank you, Lauri, for putting a better title on this issue. Indeed my original description was a technical solution in search of a problem.
I agree the most recent patch doesn't make things easier to understand.
While _I_ find checking the loop variable easier to understand, the
{% if menu_level == 0 %}
code is making the whole thing overly complicated. I have some ideas on how to remove that.Comment #30
joelpittet@Sutharsan maybe move the patch to a follow-up issue to deal with the PHPStorm/IDE reporting missing closing tags issue?
Comment #31
Sutharsan CreditAttribution: Sutharsan commented@joelpittet, no problem but I'd rather wait a little to see where this issue is going to.
Comment #32
Anonymous (not verified) CreditAttribution: Anonymous commentedWhat's up?)
After #2616756: Allow instantiating Attribute objects within Twig, we can solve problem with double-ul like this:
But if we need some fresh ideas, how to make the menu.html.twig easier to read.. Gentlemen, what do you think, if we apply the pattern Mediator (or maybe it's called a Facade, Bridge, .. your variant here). This can well help separate the logic from the layout:
Expanded variant:
And magic core/modules/system/templates/menu_render.twig:
Edit: correct mistake
Hence now macro link have new param attributes. Because it
item.attributes = ...
not work, sorry!Comment #33
Anonymous (not verified) CreditAttribution: Anonymous commentedAlso, it may need to supplement the documentation of the available variables:
Comment #41
kthullI recently got bit by this: it seems like
{% if menu_level == 0 %}
would be better written as{% if menu_level is same as(0) %}
which is the equivalent to php's '==='