Skip to main content

Multiple filters with tags

This extension allows you to easily add tags for the selected options of your filters. You can choose where to place the tags. They can be placed under each filter or in the same div. The data-tags-row attribute, which you add to each input, specifies the ID of the div where you want to display the tag.

The template for your tag is taken from a <template> tag. This allows you to easily modify the HTML structure of your tag without touching the JavaScript function.

Demo

Installation

Import the function into the JavaScript file of your project

import { MultipleFiltersWithTags } from "@overdog/fn"

Create and initialize an instance

new MultipleFiltersWithTags("#filter-wrapper-tags", {
   divIds: ["listing"]
}).init()
This element uses the same options and arguments as the Multiple choices filters.

Twig template Code

{# --------------------- SET RELATEDTO ARRAY #}

{% set relatedTo = [] %}

{# --------------------- FETCH CATEGORIES GROUP TO POPULATE FILTERS #}

{# categories group - example #}
{% set colorsCatGroup = craft.categories().group('groupFoodColors').all() %}
{% set typesCatGroup = craft.categories().group('groupFoodTypes').all() %}

{# --------------------- CATEGORY GROUP 1 - COLORS #}

{% set colorsUrlQueryName = 'colors'|t %} {# variable also used in the form name #}
{% set relatedColorsCats = [] %}
{% set colorsUrlQueryValue = craft.app.request.getQueryParam(colorsUrlQueryName)|split(',')|filter %}
{% if colorsUrlQueryValue %}
   {% set relatedColorsCats = colorsCatGroup|filter(cat => cat.slug in colorsUrlQueryValue) %}
   {% set relatedTo = relatedTo|merge([{ targetElement: relatedColorsCats }]) %}
{% endif %}

{# --------------------- CATEGORY GROUP 2 - TYPES #}

{% set typesUrlQueryName = 'types'|t %} {# variable also used in the form name #}
{% set relatedTypesCats = [] %}
{% set typesUrlQueryValue = craft.app.request.getQueryParam(typesUrlQueryName)|split(',')|filter %}
{% if typesUrlQueryValue %}
   {% set relatedTypesCats = typesCatGroup|filter(cat => cat.slug in typesUrlQueryValue) %}
   {% set relatedTo = relatedTo|merge([{ targetElement: relatedTypesCats }]) %}
{% endif %}

{% if relatedTo|length > 1 %}
  {% set relatedTo = ['and']|merge(relatedTo) %}
{% endif %}

{# --------------------- MAIN QUERY #}

{% set foodQuery = craft.entries()
  .section('structureFood')
  .orderBy('title')
  .relatedTo(relatedTo ? relatedTo : null)
  .with(['structureFoodColors', 'structureFoodTypes'])
  .all()
%}

{# --------------------- FILTER LAYOUT #}

{# ID is used by Javascript - a wrapper around your form is necessary as elem for your instance #}
<div id="filter-wrapper-tags" class="grid grid-cols-1 md:grid-cols-3 gap-half mb-half">

   {#  MACRO TAGS SECTION #}
   {% macro tag(id = "", content = "Category Name") %}
      <div 
      class="inline-flex items-center bg-[#c4cad2] rounded-xl cursor-pointer text-0.6em px-3 py-1" 
      data-fn-tag-id="{{ id }}"
      >
         {# you can replace the SVG with one from your SVG Sprite #}
         <svg class="h-2 w-2 mr-4px shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
          </svg>
         <div data-fn-tag-content>{{ content }}</div>
      </div>
   {% endmacro %}
   
   {#  IMPORTANT : HTML TEMPLATE FOR THE TAGS SECTION #}
   {# template for tags - used by JS - not rendered by the browser #}
   <template data-fn-tag-template>
      {{ _self.tag() }}
   </template>  

  {# COLORS FILTER #}
  {% if colorsCatGroup|length %}
    <form data-fn-filter-group="{{ colorsUrlQueryName }}">
      <fieldset>
        <legend class="font-bold mb-quarter">{{ 'Colors'|t }}</legend>
        {% for cat in colorsCatGroup %}
         <div class="flex items-center relative py-1">
            <input 
               id="{{ cat.slug }}"
               class="inset-0 opacity-0 absolute -z-1 peer"
               data-fn-tag-row="tag-row-colors"
               type="checkbox" 
               name="{{ cat.slug }}" 
               value="{{ cat.slug }}"
               {{(cat in relatedColorsCats) ? 'checked' }}
            />
            <label
               data-fn-tag-title="{{ cat.title }}" {# optionnal, value for the tag. If not present, label title will be used #}
               class="cursor-pointer flex items-center w-full
               peer-checked:before:bg-black
               before:bg-[#c4cad2] before:cursor-pointer before:inline-block before:h-4 before:w-4 before:mr-4 before:shrink-0" 
               for="{{ cat.slug }}"
            >{{ cat.title }}
         </label>
         </div>
        {% endfor %}
      </fieldset>

      {# TAGS ROW with initial content to show tags on page load #}
      <div id="tag-row-colors" class="flex flex-wrap mt-3 gap-2">
         {% if colorsUrlQueryValue|length %}
            {% for cat in relatedColorsCats %}
               {{ _self.tag(cat.slug, cat.title) }}
            {% endfor %}
         {% endif %}
      </div>

    </form>
  {% endif %}

  {# TYPES FILTER #}
  {% if typesCatGroup|length %}
    <form data-fn-filter-group="{{ typesUrlQueryName }}">
      <fieldset>
        <legend class="font-bold mb-quarter">{{ 'Types'|t }}</legend>
        {% for cat in typesCatGroup %}
         <div class="flex items-center relative py-1">
               <input 
                  id="{{ cat.slug }}"
                  class="inset-0 opacity-0 absolute -z-1 peer"
                  data-fn-tag-row="tag-row-types"
                  type="checkbox" 
                  name="{{ cat.slug }}" 
                  value="{{ cat.slug }}" 
                  {{(cat in relatedTypesCats) ? 'checked' }}
               />
               <label
                  class="cursor-pointer flex items-center w-full
                  peer-checked:before:bg-black
                  before:bg-[#c4cad2] before:cursor-pointer before:inline-block before:h-4 before:w-4 before:mr-4 before:shrink-0" 
                  for="{{ cat.slug }}"
               >{{ cat.title }}</label>
         </div>
        {% endfor %}
      </fieldset>

      {# TAGS ROW with initial content to show tags on page load #}
      <div id="tag-row-types" class="flex flex-wrap mt-3 gap-2">
         {% if typesUrlQueryValue|length %}
            {% for cat in relatedTypesCats %}
               {{ _self.tag(cat.slug, cat.title) }}
            {% endfor %}
         {% endif %}
      </div>
    </form>
  {% endif %}

</div>

{# --------------------- LAYOUT WIH LOOP FOR CARDS #}

{# ID is used by Javascript - The div must be always visible (do not wrap an If condition around) #}
<div id="listing" class="grid grid-col-1 md:grid-cols-3 gap-third opacity-1 is-loading:opacity-40 transition-opacity">
  {% if foodQuery|length %}
      {{ include('_lib-fn/_card', { queryName: foodQuery }, ignore_missing = true )}}
  {% else %}
    <div>{{ ('There is no content matching your criteria.')|t }}</div>
  {% endif %}
</div>