Custom Module with Category filter

Hey Treepl gurus

Haven’t played around with this in a while but how do I display a List of News articles on a page via a nav of categories?

I have many different blocks of code but this one was my closest I think.

    {% for category in this.ItemCategories %}
    {{category.name}}
    {% endfor %}

    <ul class="mb-8 nav nav-pills justify-content-center">

      {% for category in this.ItemCategories %}
      <li class="nav-item">
        <a class="nav-link text-secondary link-dark py-2 px-4 fs-12" href="/news?category={{ category | url_encode }}">{{category.name | remove: "-" }}</a>
      </li>
      {% endfor %}
    </ul>

    <div class="row row-cols-1 row-cols-md-3 gy-4">

      {% if request.request_url.params.category %}
      {% assign filterValue = request.request_url.params.category %}
      {% assign filterBy = 'ItemCategories' %}
      {% else %}
      {% assign filterValue = this.id %}
      {% assign filterBy = 'parentid' %}
      {% endif %}

      {% component source: "News", layout: "List", sortBy: "ReleaseDate", filterBy: "{{filterBy}}", filterValue: "{{filterValue}}", type: "module" %}

example

Also when I linked the category to the custom module it was called “News and Social” now I renamed it to “News” it still renders it as “News and Social” on the page.
I have relinked it in the module but it won’t clear.

ok I’ve worked it out I think. I now have to work out how to make my nav links active.

    
{% assign isSelected = "" %}
{% if request.request_url.params.category != "News/Events" %}
{% assign isSelected = "active" %}
{% endif -%}

{% if request.request_url.params.category %}
{% assign filterValue = request.request_url.params.category %}
{% assign filterBy = 'ItemCategories' %}
{% else %}
{% assign filterValue = this.id %}
{% assign filterBy = 'parentid' %}
{% endif %}

<section class="py-4 py-lg-6 py-xl-8 bg-white">
      <div class="container">
        <div class="row">


          {% capture categoriesLayout %}{% component type: "categories", parentCategory: "21651", levelsDeep: "-1", collectionVariable: "catCollection" %}{% endcapture %}

          <ul class="mb-8 nav nav-pills justify-content-center">

            {% for category in catCollection.items %}
            <li class="nav-item">
              <a class="nav-link {{isSelected}} py-2 px-4 link-dark" href="/news?category={{ category.fullname | url_encode }}">{{ category.name | remove: "-" }}</a>
            </li>
            {% endfor %}

        </div>

        <div class="row row-cols-1 row-cols-md-3 gy-4">



          {% component source: "News", layout: "List", sortBy: "ReleaseDate", filterBy: "{{filterBy}}", filterValue: "{{filterValue}}", type: "module" %}



        </div>

      </div>
    </section>

Nice work solving this one @luke

Regarding the isSelected state, try moving your if statement for this into your nav menu for loop and compare the URL param with each looped nav item.

{% for category in catCollection.items %}

    {% assign isSelected = "" %}
    {% if request.request_url.params.category == category.fullname %}
    {% assign isSelected = "active" %}
    {% endif -%}

    <li class="nav-item">
       <a class="nav-link {{isSelected}} py-2 px-4 link-dark" href="/news?category={{ category.fullname | url_encode }}">{{ category.name | remove: "-" }}</a>
    </li>
{% endfor %}

You may have to do some encoding/decoding in that if statement to get them to match, but this is the general idea.

Also, if your category name still hasn’t updated, that might need to be a support ticket for the devs to take a look at.

1 Like

Adam once again thank you! you save me so much time and stress. It works perfectly.
One more question is can I make the URL a little neater?
to this:
/news/category

To fix the category not updating I just created a new one and relinked it all.

Great, glad that worked for you.

The URL parameters really need to be present in the URL for this method to work, so you can’t really clean this up.
Categories don’t create their own directories or pages, so you can’t construct the URL in that way.

To get a ‘proper’ URL structure like that you would need to build a nested module set up, where categories are the parent module items and the news articles are child items. eg:
/news/category-name/article-name

1 Like

Gotcha! Thanks mate.

Also, I wanted to add an all to the nav which runs no filter so I’ve taken the parentid off that module if statement but how would I add that to the nav and change that if statement to show selected?
At the moment I have a separate list outside of the for loop with href set as the page.


      <section class="py-4 py-lg-6 py-xl-8">
         <div class="container">
            <div class="row">
               <div class="col">

                  {% capture categoriesLayout %}{% component type: "categories", parentCategory: "22728", levelsDeep: "-1", collectionVariable: "catCollection" %}{% endcapture %}

                  <ul class="nav nav-pills justify-content-center my-4">

                     <li class="nav-item">
                        <a class="nav-link active bg-light rounded-pill link-dark" href="/the-feed">All</a>
                     </li>

                     {% for category in catCollection.items %}

                     {% assign isSelected = "" %}
                     {% if request.request_url.params.category == category.fullname %}
                     {% assign isSelected = "active bg-light rounded-pill" %}
                     {% endif -%}



                     <li class="nav-item">
                        <a class="nav-link {{isSelected}} link-dark" href="/the-feed?category={{ category.fullname }}">{{ category.name | remove: "-" }}</a>
                     </li>
                     {% endfor %}

                  </ul>

               </div>
            </div>
            <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 gy-4 my-4">

               {% if request.request_url.params.category %}
               {% assign filterValue = request.request_url.params.category %}
               {% assign filterBy = 'ItemCategories' %}
               {% else %}
               {% assign filterValue = this.id %}
               {% assign filterBy = '' %}
               {% endif %}

               {% component source: "The Feed", layout: "List", filterBy: "{{filterBy}}", filterValue: "{{filterValue}}", sortBy: "ReleaseDate", type: "module" %}

            </div>

         </div>
      </section>

I would keep it in the loop so you can still run a similar check for the active state.
But check for the non existence of the URL param instead. And use the forloop.first to only render the “All” item once at the start of the loop:

<ul class="nav nav-pills justify-content-center my-4">

    {% for category in catCollection.items %}

    {% assign isSelected = "" %}
    {% assign isSelectedAll = "" %}
    {% if request.request_url.params.category == null %}
    {% assign isSelectedAll = "active bg-light rounded-pill" %}
    {% elsif request.request_url.params.category == category.fullname %}
    {% assign isSelected = "active bg-light rounded-pill" %}
    {% endif -%}
    
    {% if forloop.first %}
    <li class="nav-item">
        <a class="nav-link {{isSelectedAll}} link-dark" href="/the-feed">All</a>
    </li>
    {% endif %}
    
    <li class="nav-item">
        <a class="nav-link {{isSelected}} link-dark" href="/the-feed?category={{ category.fullname }}">{{ category.name | remove: "-" }}</a>
    </li>
    {% endfor %}

</ul>
1 Like

Your awesome Adam, thanks!

@Adam.Wilson

Last question I promise!

With my list, I wanted to add a TAG of FEATURED that has a different layout.

I know it’s probably something easy but my head is stuck in a forloop!

Code for custom module

         <div class="container">
            <div class="row g-4">

               {% if request.request_url.params.category %}
               {% assign filterValue = request.request_url.params.category %}
               {% assign filterBy = 'ItemCategories' %}
               {% else %}
               {% assign filterValue = this.id %}
               {% assign filterBy = '' %}
               {% endif %}

               {% component source: "The Feed", layout: "List", filterBy: "{{filterBy}}", filterValue: "{{filterValue}}", sortBy: "ReleaseDate", type: "module" %}

            </div>

Code for the list

<div class="col-3">
   <a href="#">
      <div class="position-relative mw-100">
         <img class="w-100 rounded-4" src="{{ this.MainImage }}" alt="" style="height: 480px; object-fit: cover; object-position: top;">
         <div class="position-absolute start-0 top-0 m-4">
            <span class="badge text-white bg-primary rounded-3">{% if this.ItemCategories == null or this.ItemCategories | size > 1 %} {{ filtervalue | remove: "The Feed/" }} {% else %} {{ this.ItemCategories | remove: "The Feed/" }} {% endif %}</span>
         </div>
         <div class="position-absolute bottom-0 start-0 w-100 rounded-4">
            <div class="p-3">
               <div class="bg-dark bg-opacity-75 p-4 w-100 rounded-4">
                  <h3 class="fs-5 text-white">{{ this.name }}</h3>
                  <p class="text-secondary small mb-0">{{ this.description | strip_html | truncatewords: 6 }}</p>
               </div>
            </div>
         </div>
      </div>
   </a>
</div>

Code for featured

<div class="col-12 col-lg-9 align-items-stretch">
   <a href="#">
      <div class="position-relative mw-100">
         <img class="w-100 rounded-4" src="{{ this.MainImage }}" alt="" style="height: 480px; object-fit: cover; object-position: bottom;">
         <div class="position-absolute start-0 top-0 m-4">
            <span class="badge text-white bg-primary bg-opacity-75 rounded-3">{% if this.ItemCategories == null or this.ItemCategories | size > 1 %} {{ filtervalue | remove: "The Feed/" }} {% else %} {{ this.ItemCategories | remove: "The Feed/" }} {% endif %}</span>
         </div>
         <div class="position-absolute bottom-0 end-0 w-50 rounded-4">
            <div class="p-3">
               <div class="bg-dark bg-opacity-75 p-4 rounded-4">
                  <h3 class="fs-2 text-white">{{ this.name }}</h3>
                  <p class="text-secondary mb-0">{{ this.description | strip_html | truncatewords: 16 }}</p>
               </div>
            </div>
         </div>
      </div>
   </a>
</div>

Assuming you are using actual Tags (and not a custom property to assign the ‘Featured’ state), in your list layout something like this should work:

{% for tag in this.ItemTags %}
{% if tag == 'Featured' %}
{% assign isFeatured = true %}
{% break %}
{% else %}
{% assign isFeatured = false %}
{% endif %}
{% endfor %}

{% if isFeatured == true %}
    // Featured list code
{% else %}
    // Normal list code
{% endif %}

Thanks Adam but for some reason, it also changes everything after the selected tag to the featured layout?

{% for tag in this.ItemTags %}
{% if tag == 'Featured' %}
{% assign isFeatured = true %}
{% break %}
{% else %}
{% assign isFeatured = false %}
{% endif %}
{% endfor %}

{% if isFeatured == true %}

<div class="col-12 col-lg-9 align-items-stretch">
   <a href="#">
      <div class="position-relative mw-100">
         <img class="w-100 rounded-4" src="{{ this.MainImage }}" alt="" style="height: 480px; object-fit: cover; object-position: bottom;">
         <div class="position-absolute start-0 top-0 m-4">
            <span class="badge text-white bg-primary bg-opacity-75 rounded-3">{% if this.ItemCategories == null or this.ItemCategories | size > 1 %} {{ filtervalue | remove: "The Feed/" }} {% else %} {{ this.ItemCategories | remove: "The Feed/" }} {% endif %}</span>
         </div>
         <div class="position-absolute bottom-0 end-0 w-50 rounded-4">
            <div class="p-3">
               <div class="bg-dark bg-opacity-75 p-4 rounded-4">
                  <h3 class="fs-2 text-white">{{ this.name }}</h3>
                  <p class="text-secondary mb-0">{{ this.description | strip_html | truncatewords: 16 }}</p>
               </div>
            </div>
         </div>
      </div>
   </a>
</div>

{% else %}

<div class="col-3">
   <a href="#">
      <div class="position-relative mw-100">
         <img class="w-100 rounded-4" src="{{ this.MainImage }}" alt="" style="height: 480px; object-fit: cover; object-position: top;">
         <div class="position-absolute start-0 top-0 m-4">
            <span class="badge text-white bg-primary rounded-3">{% if this.ItemCategories == null or this.ItemCategories | size > 1 %} {{ filtervalue | remove: "The Feed/" }} {% else %} {{ this.ItemCategories | remove: "The Feed/" }} {% endif %}</span>
         </div>
         <div class="position-absolute bottom-0 start-0 w-100 rounded-4">
            <div class="p-3">
               <div class="bg-dark bg-opacity-75 p-4 w-100 rounded-4">
                  <h3 class="fs-5 text-white">{{ this.name }}</h3>
                  <p class="text-secondary small mb-0">{{ this.description | strip_html | truncatewords: 6 }}</p>
               </div>
            </div>
         </div>
      </div>
   </a>
</div>

{% endif %}

Actually, you gave me the idea of just doing a custom property but I have saved your code in my snippets folder for another use so it didn’t go to waste.

Cheers mate

{% if this.featured == true %}

<div class="col-12 col-lg-9 align-items-stretch order-first">
   <a href="#">
      <div class="position-relative mw-100">
         <img class="w-100 rounded-4" src="{{ this.MainImage }}" alt="" style="height: 480px; object-fit: cover; object-position: bottom;">
         <div class="position-absolute start-0 top-0 m-4">
            <span class="badge text-white bg-primary bg-opacity-75 rounded-3">{% if this.ItemCategories == null or this.ItemCategories | size > 1 %} {{ filtervalue | remove: "The Feed/" }} {% else %} {{ this.ItemCategories | remove: "The Feed/" }} {% endif %}</span>
         </div>
         <div class="position-absolute bottom-0 end-0 w-50 rounded-4">
            <div class="p-3">
               <div class="bg-dark bg-opacity-75 p-4 rounded-4">
                  <h3 class="fs-2 text-white">{{ this.name }}</h3>
                  <p class="text-secondary mb-0">{{ this.description | strip_html | truncatewords: 16 }}</p>
               </div>
            </div>
         </div>
      </div>
   </a>
</div>

{% else %}

<div class="col-3">
   <a href="#">
      <div class="position-relative mw-100">
         <img class="w-100 rounded-4" src="{{ this.MainImage }}" alt="" style="height: 480px; object-fit: cover; object-position: top;">
         <div class="position-absolute start-0 top-0 m-4">
            <span class="badge text-white bg-primary rounded-3">{% if this.ItemCategories == null or this.ItemCategories | size > 1 %} {{ filtervalue | remove: "The Feed/" }} {% else %} {{ this.ItemCategories | remove: "The Feed/" }} {% endif %}</span>
         </div>
         <div class="position-absolute bottom-0 start-0 w-100 rounded-4">
            <div class="p-3">
               <div class="bg-dark bg-opacity-75 p-4 w-100 rounded-4">
                  <h3 class="fs-5 text-white">{{ this.name }}</h3>
                  <p class="text-secondary small mb-0">{{ this.description | strip_html | truncatewords: 6 }}</p>
               </div>
            </div>
         </div>
      </div>
   </a>
</div>

{% endif %}
1 Like

Excellent, glad you worked out a different way.

With the tagging version, I probably should be resetting the isFeatured variable for each item, so also adding:

{% assign isFeatured = false %}

above that tag forloop should fix it.

But your way is cleaner, so all good :slight_smile:

1 Like