Como renderizar uma árvore no Twig

89

Eu gostaria de representar uma árvore com uma profundidade indeterminada (filhos de filhos de crianças, etc.). Eu preciso percorrer a matriz recursivamente; como posso fazer isso no Twig?

T-RonX
fonte

Respostas:

117

Eu brinquei com a ideia da domi27 e cheguei a essa conclusão . Fiz uma matriz aninhada como minha árvore, ['link'] ['sublinks'] é nula ou outra matriz de mais do mesmo.

Modelos

O arquivo de submodelo para recursar com:

<!--includes/menu-links.html-->
{% for link in links %}
    <li>
        <a href="{{ link.href }}">{{ link.name }}</a>
        {% if link.sublinks %}
            <ul>
                {% include "includes/menu-links.html" with {'links': link.sublinks} %}
            </ul>
        {% endif %}
    </li>
{% endfor %}

Então, no modelo principal, chame isso (tipo de coisa redundante 'com' lá):

<ul class="main-menu">
    {% include "includes/menu-links.html" with {'links':links} only %}
</ul>

Macros

Um efeito semelhante pode ser alcançado com macros:

<!--macros/menu-macros.html-->
{% macro menu_links(links) %}
    {% for link in links %}
        <li>
            <a href="{{ link.href }}">{{ link.name }}</a>
            {% if link.sublinks %}
                <ul>
                    {{ _self.menu_links(link.sublinks) }}
                </ul>
            {% endif %}
        </li>
    {% endfor %}
{% endmacro %}

No modelo principal, faça o seguinte:

{% import "macros/menu-macros.html" as macros %}
<ul class="main-menu">
    {{ macros.menu_links(links) }}
</ul>
random-coder-1920
fonte
9
Muito bem obrigado! Se você quiser usar a macro no mesmo modelo, poderá usar {{ _self.menu_links(links) }}.
gripe de
obrigado, pensar nisso fez meu cérebro doer, mas sua resposta faz todo o sentido.
azzy81 de
Tive um problema com meu projeto com comentários. subcomentários (sublinks) também foram incluídos na coleção principal (links). então, antes de incluir, tive que verificar se o comentário tinha uma entrada 'pai'.
Jevgeni Smirnov
4
Usar {{_self.menu_links}}é uma prática ruim ! Leia uma observação aqui: macro Quando você define uma macro no modelo em que vai usá-la, pode ficar tentado a chamar a macro diretamente via _self.input () em vez de importá-la; mesmo que pareça funcionar, este é apenas um efeito colateral da implementação atual e não funcionará mais no Twig 2.x. Você deve importar macroses localmente mais uma vez no sitemenu_links
dr.scre
35

Twig 2.0 - 2.11

Se você quiser usar uma macro no mesmo modelo , deve usar algo assim para permanecer compatível com o Twig 2.x :

{% macro menu_links(links) %}
    {% import _self as macros %}
    {% for link in links %}
        <li>
            <a href="{{ link.href }}">{{ link.name }}</a>
            {% if link.sublinks %}
                <ul>
                    {{ macros.menu_links(link.sublinks) }}
                </ul>
            {% endif %}
        </li>
    {% endfor %}
{% endmacro %}

{% import _self as macros %}

<ul class="main-menu">
    {{ macros.menu_links(links) }}
</ul>

Isso estende random-codera resposta de e incorpora dr.screa dica de à documentação do Twig sobre macros a serem usadas agora _self, mas importadas localmente.

Twig> = 2,11

A partir do Twig 2.11 , você pode omitir o {% import _self as macros %}, já que as macros embutidas são importadas automaticamente no _selfnamespace (consulte o anúncio do Twig: Importação automática de macro ):

{# {% import _self as macros %} - Can be removed #}

<ul class="main-menu">
    {{ _self.menu_links(links) }} {# Use _self for inlined macros #}
</ul>
gripe
fonte
2

Se você estiver executando o PHP 5.4 ou superior, há uma nova solução maravilhosa (em maio de 2016) para este problema por Alain Tiemblo: https://github.com/ninsuo/jordan-tree .

É uma tag "árvore" que serve exatamente a esse propósito. A marcação ficaria assim:

{% tree link in links %}
    {% if treeloop.first %}<ul>{% endif %}

    <li>
        <a href="{{ link.href }}">{{ link.name }}</a>
        {% subtree link.sublinks %}
    </li>

    {% if treeloop.last %}</ul>{% endif %}
{% endtree %}
Jordan Lev
fonte
1
Você não pode passar variáveis ​​adicionais para subtree. No meu caso, o código precisa saber se haverá mais filhos e passa o número de níveis para a macro para que possa fazer um <div class="{{ classes[current_level].wrapper }} {% if levels > current_level %}accordion-wrapper{% endif %}">. Calcular isso exigiria a iteração do nível atual uma segunda vez apenas para capturar se há algum filho.
chx
1

Primeiro pensei que isso poderia ser resolvido de uma maneira simples, mas não é tão fácil.

Você precisa criar lógica, talvez com um método de classe PHP, quando incluir um subtemplate Twig e quando não.

<!-- tpl.html.twig -->
<ul>
    {% for key, item in menu %}
        {# Pseudo Twig code #}
        {% if item|hassubitem %}
            {% include "subitem.html.tpl" %}
        {% else %}
            <li>{{ item }}</li>
        {% endif %}
    {% endfor %}
</ul>

Portanto, você poderia usar a variável de loop Twig especial , que está disponível dentro de um loop for Twig . Mas não tenho certeza sobre o escopo dessa variável de loop .

Esta e outras informações estão disponíveis no Twigs "for" Docu !

domi27
fonte
0

Pegou a resposta da gripe e modificou um pouco:

{# Macro #}

{% macro tree(items) %}
    {% import _self as m %}
        {% if items %}
        <ul>
            {% for i in items %}
                <li>
                    <a href="{{ i.url }}">{{ i.title }}</a>
                    {{ m.tree(i.items) }}
                </li>
            {% endfor %}
        </ul>
    {% endif %}
{% endmacro %}

{# Usage #}

{% import 'macros.twig' as m %}

{{ m.tree(items) }}
Sergey Atroshchenko
fonte
-1

As respostas aqui me levam à minha solução.

Eu tenho uma entidade de categoria com uma associação muitos-para-um com autorreferência (pai para filhos).

/**
 * @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
 */
private $parent;

/**
 * @ORM\OneToMany(targetEntity="Category", mappedBy="parent")
 */
private $children;

No meu modelo Twig, estou renderizando a visualização em árvore assim:

<ul>
{% for category in categories %}
    {% if category.parent == null %}
        <li>
            <a href="{{ category.id }}">{{ category.name }}</a>
            {% if category.children|length > 0 %}
            <ul>
            {% for category in category.children %}
                <li>
                    <a href="{{ category.id }}">{{ category.name }}</a>
                </li>
            {% endfor %}
            </ul>
            {% endif %}
        </li>
    {% endif %}
{% endfor %}
</ul>
Patric Robert Gutersohn
fonte
E se você tiver mais de um nível de hierarquia de categoria?
pmoubed