Search code examples
templatesflaskjinja2bulletedlist

How to output dynamically nested bulleted list?


I'm outputting a list of events which occur in North America and I'd like to break it down by country, then by region. Given the following data:

CA Event 1, Edmonton, AB, Canada
CA Event 2, Edmonton, AB, Canada
CA Event 3, Burnaby, BC, Canada
BE Event 1, Brussels, NULL, Belgium
US Event 1, Juneau, AK, USA
US Event 2, Birmingham, AL, USA
US Event 3, Silverhill, AL, USA

I'd like output which looks like this

Canada
    Alberta
        CA Event 1, Edmonton, AB, Canada
        CA Event 2, Edmonton, AB, Canada
    British Columbia
        CA Event 3, Burnaby, BC, Canada
Belgium
    BE Event 1, Brussels, Belgium
USA
    Alaska
        US Event 1, Juneau, AK, USA
    Alabama
        US Event 2, Birmingham, AL, USA
        US Event 3, Silverhill, AL, USA

I've currently got this working by outputting nested bulleted lists, but I'm struggling to figure out how to avoid duplicate UL tags. Again, given the input data above, the desired HTML output should look like this:

<h2>Canada</h2>
<h3>Alberta</h3>
<ul>
    <li>CA Event 1, Edmonton, AB, Canada</li>
    <li>CA Event 2, Edmonton, AB, Canada</li>
</ul>
<h3>British Columbia</h3>
<ul>
    <li>CA Event 4, Burnaby, BC, Canada</li>
</ul>

But right now it looks like this:

<h2>Canada</h2>
<h3>Alberta</h3>
<ul>
    <li>CA Event 1, Edmonton, AB, Canada</li>
</ul>
<ul>
    <li>CA Event 2, Edmonton, AB, Canada</li>
</ul>
<h3>British Columbia</h3>
<ul>
    <li>CA Event 4, Burnaby, BC, Canada</li>
</ul>

Here's my template code, it's using the Jinja templating language as part of Flask, but any response doesn't specifically need that. I just can't figure out how to ONLY output the UL tags when needed.

{% set ns = namespace() %}
{% set ns.country_header = '' %}
{% set ns.region_header = '' %}

{% for convention in context.conventions %}

    {% if ns.country_header != convention.country %}
        {% set ns.country_header = convention.country %}
        <h3>{{ns.country_header}}</h3>
    {% endif %}

    {% if ns.region_header != convention.region %}
        {% set ns.region_header = convention.region %}
        <h4>{{ns.region_header}}</h4>
    {% endif %}

    <ul>
        <li>
            {{convention.name}} – 
            {{convention.city}}, {{convention.region}} {% endif %}
        </li>
    </ul>
{% endfor %}

Solution

  • Caveat: I don't speak this template language specifically, but I do speak logic.

    Ah, the joy of flat record lists. :/

    emit the <ul> when the region header changes -- basically as part of the <h4> tag.

    You will have to emit the </ul> at the top of the loop before you think about the <h3> or <h4> items, by checking to see if something changed.

    As a special case, the first time you enter the loop you must avoid emitting the </ul>.

    You can emit the </ul> after the endfor.

    {% for convention in context.conventions %}
        {% if <<not first record>> %}
          </ul> 
        {% endif %}
    
        {% if ns.country_header != convention.country %}
            {% set ns.country_header = convention.country %}
            <h3>{{ns.country_header}}</h3>
        {% endif %}
    
        {% if ns.region_header != convention.region %}
            {% set ns.region_header = convention.region %}
            {% if convention.region != "NULL" %}
            <h4>{{ns.region_header}}</h4>
            {% endif %}
          <ul>
        {% endif %}
    
            <li>
                {{convention.name}} – 
                {{convention.city}}, {{convention.region}} {% endif %}
            </li>
    {% endfor %}
    {% if << at least one record >> %}
        </ul>
    {% endif %}
    

    Note: I think I see an extra {% endif %} in the <li> block.

    Note 2: This loop would fail to emit the <h4> record in the off chance the state/province were the same as the last record from the <h3> above. Think "Russia / Georgia"** and "USA / Georgia". You may want to clear the ns.region_header in your <h3> emit block just to be safe.

    ** someone will probably harass me over calling Georgia part of Russia. But it's the only semi-obvious example I could think of quickly.