1

i am trying to filter the various categories i have. Currently my url formats the following : www.site.com/news?cat=industry-news&cat=architecture-design

I understand that i have to change this to a comma separated list but how do i do that and how do i make the filtering works so it does not only shows the first selected filter only? here is my code:

`
{% set queryCategory = craft.request.getParam('cat') ?? '' %}
{% set categoryId = craft.categories.group('news').slug(queryCategory).first().id %}

{% paginate craft.entries.section('news').relatedTo(categoryId).limit(10) as entriesOnPage %}

<form class="js-category">
{% set urlCategories = craft.request.getQueryString() %}
   {% for category in craft.categories.group('news') %}
     <label class="filter__item" >{{ category.title }}
   <input {{ category.slug in urlCategories ? 'checked="checked"' : '' }}   name="cat" type="checkbox" value="{{category.slug}}">
    <span class="filter__checkbox"></span>
    </label>
    {% endfor %}
</form>`
  • How is the query string being built in the first place? That's where you'd need to format your values into a comma-separated format. Your second question is slightly confusing; "how do i make the filtering works so it does not only shows the first selected filter only?". Do you want to show only the first selected category from the query string? Perhaps show an example of the output you're trying to achieve in HTML. – user3788089 Aug 23 at 11:09
1

Here is how I did it at my end for the templating side of things:

  1. Set the queries you need and merge them into arrays like below. Please be aware this was done for craft commerce 2 products but the same can be applied to entries too as it is the same logic.
        {% set types = craft.categories.group('flowerTypes') %}
        {% set sizes = craft.categories.group('size') %}
        {% set prices = craft.categories.group('priceRanges') %}

        {% set flowersQuery = craft.app.request.getParam('flowers') | join(',') %}
        {% set sizeQuery = craft.app.request.getParam('size') | join(',') %}
        {% set priceQuery = craft.app.request.getParam('price') | join(',') %}

        {% set relationParam = [] %}
        {% set sizeParam = [] %}
        {% set flowersValues = [] %}
        {% set collectionValues = [] %}
        {% set sizeValues = [] %}
        {% set priceValues = [] %}

        {% set relationParam = ['and',{targetElement: category}] %}

        {% if flowersQuery != '' %}


            {% for queryFlower in flowersQuery|split(',') %}
              {% set flowerCategory = craft.categories.slug(queryFlower).one() %}
              {% set relationParam = relationParam|merge([{targetElement: flowerCategory}]) %}
              {% set flowersValues = flowersValues|merge([flowerCategory.title]) %}
            {% endfor %}
        {% endif %}

        {% if sizeQuery != '' %}


            {% for querySize in sizeQuery|split(',') %}
              {% set sizeCategory = craft.categories.slug(querySize).one() %}
              {% set sizeParam = sizeParam|merge([{targetElement: sizeCategory}]) %}
              {% set sizeValues = sizeValues|merge([sizeCategory.title]) %}
            {% endfor %}
        {% endif %}

        {% if priceQuery != '' %}


            {% for queryPrice in priceQuery|split(',') %}
              {% set priceCategory = craft.categories.slug(queryPrice).one() %}
              {% set relationParam = relationParam|merge([{targetElement: priceCategory}]) %}
            {% endfor %}
        {% endif %}

## this is where we set the query to grab the relational values

        {% set products = craft.products({
            relatedTo: relationParam,
            hasVariant: {
                relatedTo: sizeParam
            }
        }).status('live') %}
  1. Set the markup for the filters, replace with whatever you use to do the front-end stuff
<section id="filter-box" class="z-3 absolute site-bg w-100 dn">
    <section class="z-3 relative w-100 left-0 site-bg pv4-ns">
        <div class="container center">
            <div class="flex flex-wrap flex-row justify-between items-start">
                <div class="w-50-ns w-100 tl">
                    <h1 class="large-header-text">All {{category.title}} Flowers</h1>
                </div>
                <div class="w-50-ns w-100 tr">

                </div>
            </div>
            <div class="flex-ns dn flex-wrap flex-row justify-between items-start pt4 h4-ns h-auto">
                <div class="w-50-ns w-100 tl order-1-ns order-2 mb0-ns mb3">
                    <div class="filtered is-closed"><p class="ma0 dib v-mid"></p><span class="filtered-amount ma0 dib v-mid"></span></div>
                </div>
                <div class="w-50-ns w-100 db-ns dn tr order-2-ns order-1 mb0-ns mb3 flex flex-row flex-wrap justify-end">
                    {% include "_partials/buttons/filter" %}
                    {% include "_partials/buttons/clear_filters" %}
                </div>
            </div>

                <form id="formTest" class="productFilter w-100 flex-ns flex-wrap flex-row justify-between items-start">
                    <div class="mb3 mt2 Grid-cell text-gray pr3-ns flex-small h-auto-ns h2 overflow-visible-ns overflow-hidden">
                        <div class="w-100 mb4 bb black-40 relative">
                            <p class="ma0 title-medium-small db-ns dib mobileFilter"><span class="dn-ns dib">Filter by</span> Price</p>
                            <span class="dn-ns dib v-mid absolute right-0 top-0 bottom-0" style="line-height: 1.4em;">
                                <svg width="14" height="14" class="svg-blue v-mid db h-100">
                                    <use class="no-barba" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/icons.svg#arrow_down"></use>
                                </svg>
                            </span>
                        </div>
                        <div id="selectedFilters" class="dib v-mid">
                            <fieldset class="pa0 ma0 bw0">
                                <p class="mb1 dn input-style"><label class="pa0 ma0 lh-copy pointer bw1 br0"><input value="all-prices" class="input-reset checked-filters" type="checkbox" name="collections[]">All prices</label></p>
                                {% for p in prices.all() %}

                                    <div class="ma0 input-style w-100-ns relative checkbox-style">
                                        <input value="{{ p.slug }}" data-value="{{p.title}}" class="absolute left-0 top-0 h-100 w-100 input-reset checked-filters ttc z-2" type="checkbox" name="price[]" {% if p.slug in priceQuery %}checked{% endif %}><label class="z-1">{{p.title}}</label>
                                    </div>

                                {% endfor %}
                            </fieldset>
                        </div>
                    </div>
                    <div class="mb3 mt2 Grid-cell text-gray pr3-ns flex-medium h-auto-ns h2 overflow-visible-ns overflow-hidden">
                        <div class="w-100 mb4 bb black-40 relative">
                            <p class="ma0 title-medium-small db-ns dib mobileFilter"><span class="dn-ns dib">Filter by</span> Size</p>
                            <span class="dn-ns dib v-mid absolute right-0 top-0 bottom-0" style="line-height: 1.4em;">
                                <svg width="14" height="14" class="svg-blue v-mid db h-100">
                                    <use class="no-barba" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/icons.svg#arrow_down"></use>
                                </svg>
                            </span>
                        </div>
                        <div id="selectedFilters" class="dib v-mid">
                            <fieldset class="pa0 ma0 bw0">
                                <p class="mb1 dn input-style"><label class="pa0 ma0 lh-copy pointer bw1 br0"><input value="all-sizes" class="input-reset checked-filters" type="checkbox" name="collections[]">All sizes</label></p>
                                {% for s in sizes.all() %}

                                    <div class="ma0 input-style w-100-ns relative checkbox-style">
                                        <input value="{{ s.slug }}" data-value="{{s.title}}" class="absolute left-0 top-0 h-100 w-100 input-reset checked-filters ttc z-2" type="checkbox" name="size[]" {% if s.slug in sizeQuery %}checked{% endif %}><label class="z-1">{{s.title}}</label>
                                    </div>

                                {% endfor %}
                            </fieldset>
                        </div>
                    </div>
                    <div class="mb3 mt2 Grid-cell text-gray pr3-ns flex-large h-auto-ns h2 overflow-visible-ns overflow-hidden">
                        <div class="w-100 mb4 bb black-40 relative">
                            <p class="ma0 title-medium-small db-ns dib mobileFilter"><span class="dn-ns dib">Filter by</span> Flower type</p>
                            <span class="dn-ns dib v-mid absolute right-0 top-0 bottom-0" style="line-height: 1.4em;">
                                <svg width="14" height="14" class="svg-blue v-mid db h-100">
                                    <use class="no-barba" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/icons.svg#arrow_down"></use>
                                </svg>
                            </span>
                        </div>
                        <div id="selectedFilters" class="dib v-mid w-100">
                            <div class="pa0 ma0 bw0 flex flex-row flex-wrap w-100">
                                <div class="ma0 input-style w-third-ns w-100 relative checkbox-style dn">
                                    <input value="all-types" class="absolute left-0 top-0 h-100 w-100 input-reset checked-filters ttc z-2" type="checkbox" name="flowers[]">
                                    <label class="z-1">All flowers</label>
                                </div>
                                {% for t in types.all() %}

                                    <div class="ma0 input-style w-third-ns w-100 relative checkbox-style">
                                        <input value="{{ t.slug }}" data-value="{{t.title}}" class="absolute left-0 top-0 h-100 w-100 input-reset checked-filters ttc z-2" type="checkbox" name="flowers[]" {% if t.slug in flowersQuery %}checked{% endif %}><label class="z-1">{{t.title}}</label>
                                    </div>

                                {% endfor %}
                            </div>
                        </div>
                    </div>

                </form>

        </div>
    </section>
</section>

Then with this it builds the url query for you to grab the products/entries you need from the url.

I then use a bit of js to manipulate the url to append the checked values to the url and do Ajax to get the entries:


site.getProducts = function(url) {

            const requestUrl = url;

                const productWrapper = document.getElementById('productWrapper')
                addClass(productWrapper, 'loading')


                $.ajax({
                    type: 'get',
                    url: requestUrl
                })
                  .fail(function( jqXHR, response ) {

                    const errorMsg = jqXHR.responseText

                  })
                  .done(function( response ) {

                    var parsedResponse = $.parseHTML(response);
                    var result = $(parsedResponse).find("#productWrapper .product-container"),
                        amount = $(parsedResponse).find("#filtered-area").data('product-count');

                    removeClass(productWrapper, 'loading')

                    loadedProductCount = amount;

                    if(!loadedProductCount > 0){
                        $('.product-count').parent().html('No products match your filters.');
                        $('#filtered-area').addClass('vh-100');
                    } else {
                        $('#open-filter').html('See <span class="product-count">'+loadedProductCount+'</span> results');
                        $('#open-filter').css({
                            'borderColor': '',
                            'borderWidth': '',
                            'borderStyle': '',
                            'color': ''
                        });
                        $('#filtered-area').removeClass('vh-100');
                    }


                    $('#productWrapper').html(result);


                  });

};



site.addUrlQuery = function(event) {

        var collections = '',
            flowers = '',
            size = '',
            price = '',
            tempArray = [],
            mergedArray = [];

        var checkedFlowers, checkedSize, checkedCollections, checkedPrice;
        const node = document;
        checkedCollections = node.querySelectorAll('input[name="collections[]"]:checked');
        checkedFlowers = node.querySelectorAll('input[name="flowers[]"]:checked');
        checkedSize = node.querySelectorAll('input[name="size[]"]:checked');
        checkedPrice = node.querySelectorAll('input[name="price[]"]:checked');

        const arrCollections = Array.prototype.map.call(checkedCollections, function (e) {return e.value;}),
              arrFlowers = Array.prototype.map.call(checkedFlowers, function (e) {return e.value;}),
              arrPrice = Array.prototype.map.call(checkedPrice, function (e) {return e.value;}),
              arrSize = Array.prototype.map.call(checkedSize, function (e) {return e.value;});

        mergedArray.push(arrCollections+arrFlowers);

        tempArray.push(arrCollections);

        if(tempArray.length !== 0){
         collections+='collections='+tempArray.toString();
         tempArray = [];
        }

        tempArray.push(arrFlowers);

        if(tempArray.length !== 0){
         flowers+='flowers='+tempArray.toString();
         tempArray = [];
        }

        tempArray.push(arrPrice);

        if(tempArray.length !== 0){
         price+='price='+tempArray.toString();
         tempArray = [];
        }

        tempArray.push(arrSize);

        if(tempArray.length !== 0){
         size+='size='+tempArray.toString();
         tempArray = [];
        }

        // const url = '?'+collections+'&'+flowers,
        //    requestUrl = url;

        const url = '?'+collections+'&'+flowers+'&'+price+'&'+size,
                requestUrl = url;

        window.history.pushState('', '', url);

        if(url.indexOf('?') != -1){

            site.getProducts(url);

        }

};

var cboxArray = [];
let checkItems = '';

  function itemExistsChecker(cboxValue) {

    var len = cboxArray.length;

    if (len > 0) {
      for (var i = 0; i < len; i++) {
        if (cboxArray[i] == cboxValue) {
          return true;
        }
      }
    }

    cboxArray.push(cboxValue);

  } 

  $(".checked-filters").each(function() {

    var cboxValue = $(this).data('value');
    if(window.location.href.indexOf('?') != -1){
        var cboxChecked = null;
    } else {
        var cboxChecked = localStorage.getItem(cboxValue) == 'true' ? true : false;
    }

   //On page load check if any of the checkboxes has previously been selected and mark it as "checked"
    if (cboxChecked) {

      $(this).prop('checked', true);
      itemExistsChecker(cboxValue);

    }

    // On checkbox change add/remove the value from the array based on the choice
    $(this).change(function() {

        site.addUrlQuery();     

        localStorage.setItem(cboxValue, $(this).is(':checked'));

        if ($(this).is(':checked')) {

            itemExistsChecker(cboxValue);

        } else {

            // Delete the vehicle value from the array if its checkbox is unchecked
            var cboxValueIndex = cboxArray.indexOf(cboxValue);

            if (cboxValueIndex >= 0) {
              cboxArray.splice( cboxValueIndex, 1 );
            }

        }

        if(cboxArray.length > 1){ 
            checkItems = cboxArray.length +' filters selected' 
        } else { 
            checkItems = cboxArray.length +' filters selected' 
        }

        $('.filtered p').html(cboxArray.join(", "));
        $('.filtered-amount').html( checkItems );

    });

  });


    if(cboxArray.length > 1){ 
        checkItems = cboxArray.length +' filters selected' 
    } else { 
        checkItems = cboxArray.length +' filters selected' 
    }

    $('.filtered p').html(cboxArray.join(", "));
    $('.filtered-amount').html( checkItems );


    $('.product-count').html(loadedProductCount);

I really hope this gets you on your way here.

Any q's please mention me and ask.

  • Thanks, I managed to learn how to filter through the categories and display the content. Now I am trying to figure how to remove the param out of the url when the user "un checks " the category. here is my code so far `` – Shahyn Kamali Aug 23 at 14:41
  • Great glad you got it working. mark answer as correct if it helped you – M dunbavan Aug 29 at 10:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.