Skip to main content

Multiple choices filters

Demo

Installation

Import the function into the JavaScript file of your project

import { MultipleFilters } from "@overdog/fn"

Create and initialize an instance

new MultipleFilters("#filter-wrapper-multiple", {
   divIds: ["listing"],
   clearAllButtonId: "clear-all"
}).init()

Twig template Code

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

{% set relatedTo = [] %}

{# --------------------- FETCH CATEGORIES GROUP AND RADIO BUTTON FIELD TO POPULATE FILTERS #}

{# categories group - example #}
{% set colorsCatGroup = craft.categories().group('groupFoodColors').all() %}
{% set typesCatGroup = craft.categories().group('groupFoodTypes').all() %}
{# radio field - example #}
{% set radioField = craft.app.fields.getFieldByHandle('structureFoodAvailability') %}


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

{% set colorsUrlQueryName = 'colors'|t %} {# variable also used in the form name #}
{% 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 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 %}

{# --------------------- RADIO BUTTON FIELD 3 - AVAILABILITY
   no need to split param - radio button always give one value at the time (same thing with a select input)
#}
{% set availabilityUrlQueryName = 'availability'|t %} {# variable also used in the form name #}
{% set availabilityUrlQueryValue = craft.app.request.getQueryParam(availabilityUrlQueryName) %}

{# --------------------- SEARCH INPUT 4 #}

{% set searchUrlQueryName = 'keyword'|t %}
{% set searchUrlQueryValue = craft.app.request.getQueryParam(searchUrlQueryName) %}


{# --------------------- MERGE THE CATEGORIES FOR THE MAIN QUERY RELATEDTO #}

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

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

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


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

{# ID is used by Javascript - a wrapper around your form is necessary > see docs #}
<div id="filter-wrapper-multiple" class="grid grid-cols-1 md:grid-cols-3 gap-half mb-half">
  {# COLORS FORM -> checkbox - category #}
  {% 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 
                  class="inset-0 opacity-0 absolute -z-1 peer" 
                  id="{{ cat.slug }}" 
                  type="checkbox" 
                  name="{{ cat.slug }}" 
                  value="{{ cat.slug }}" {{(cat.slug in colorsUrlQueryValue) ? 'checked' }}
               />
               <label 
                  class="cursor-pointer flex items-center w-full
                  peer-checked:before:bg-black
                  before:bg-gray-200 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>
    </form>
  {% endif %}

  {# TYPES FORM -> checkbox - category #}
  {% 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 
                     class="inset-0 opacity-0 absolute -z-1 peer" 
                     id="{{ cat.slug }}" 
                     type="checkbox" 
                     name="{{ cat.slug }}" 
                     value="{{ cat.slug }}" {{(cat.slug in typesUrlQueryValue) ? 'checked' }}
                  />
                  <label
                     class="cursor-pointer flex items-center w-full
                     peer-checked:before:bg-black
                     before:bg-gray-200 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>
    </form>
  {% endif %}

  {# AVAILABILITY FORM -> radio button - field #}
  <form data-fn-filter-group="{{ availabilityUrlQueryName }}">
    <fieldset>
      <legend class="font-bold mb-quarter">Radio Button Field</legend>
      <div class="flex items-center relative py-1">
         {# HINT : name on all input must be the SAME for radio button #}
         <input 
            id="all"
            class="inset-0 opacity-0 absolute -z-1 peer" 
            type="radio" 
            name="availability" 
            value=""
         />
         <label
            class="cursor-pointer flex items-center w-full
            peer-checked:before:bg-black
            before:bg-gray-200 before:rounded-full before:cursor-pointer before:inline-block before:h-4 before:w-4 before:mr-4 before:shrink-0" 
            for="all"
         >
            {{ 'All availabilities'|t }}
         </label>
      </div>
      {% for option in radioField.options %}
         <div class="flex items-center relative py-1">
            {# HINT : name on all input must be the SAME for radio button #}
            <input 
               id="{{ option.value }}"
               class="inset-0 opacity-0 absolute -z-1 peer" 
               type="radio" 
               name="availability" 
               value="{{ option.value }}" {{(option.value in availabilityUrlQueryValue) ? 'checked' }}
            />
            <label
               class="cursor-pointer flex items-center w-full
               peer-checked:before:bg-black
               before:bg-gray-200 before:rounded-full before:cursor-pointer before:inline-block before:h-4 before:w-4 before:mr-4 before:shrink-0" 
               for="{{ option.value }}"
            >
               {{ option.label }}
            </label>
         </div>
      {% endfor %}
    </fieldset>
  </form>

  {# SEARCH FORM > The role search on the form is needed by JS #}
  <form role="search" class="flex md:col-span-2 max-w-[600px]" data-fn-filter-group="{{ searchUrlQueryName }}">
      <div class="h-[44px] relative w-full">
         <input 
            class="border-b border-gray-300 h-full px-4 w-full" 
            type="text"
            name="{{ searchUrlQueryName }}" 
            autocomplete="off" 
            placeholder="Search.."
         >
         {# searchbox reset #}
         <input class="text-[#999] cursor-pointer text-[13px] h-full absolute right-0 px-4" type="reset" value="delete">
      </div>
      <button 
         class="rounded-full shrink-0 text-0.9em h-[44px] ml-4 w-[44px] bg-slate-700 text-white" 
         type="submit" 
         value="submit"
      >
         Go
      </button>
  </form>

   <button id="clear-all" type="reset" value="clear">clear all</button>
</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>

Options