1

I've built a product filtering system using tags and currently have 2 available tag groups under different fields. Size and Type

Currently if I select 1 tag from both size and type, it shows all products with either selected, however I want it to only show if the product contains both tags.

{% set query = craft.request.getQuery() %}
{% set selectedTagsIds = query.tags ?? [] %}

{% for tag in craft.tags.group('size').all() %}
    {% set checkedAttr = tag.id in selectedTagsIds ? 'checked' : '' %}
    <input type="checkbox" name="tags[]" value="{{ tag.id }}" {{ checkedAttr }}>{{ tag.title }}</input>
{% endfor %}

{% for tag in craft.tags.group('type').all() %}
    {% set checkedAttr = tag.id in selectedTagsIds ? 'checked' : '' %}
    <input type="checkbox" name="tags[]" value="{{ tag.id }}" {{ checkedAttr }}>{{ tag.title }}</input>
{% endfor %}

{% set relatedToParam = ['or'] %}

{% for id in selectedTagsIds %}
    {% set relatedToParam = relatedToParam|merge([{
        targetElement: id
    }]) %}
{% endfor %}

{% set relatedToParam = relatedToParam|length > 1 ? relatedToParam : null %}

{% set entries = craft.products.type('accommodation').relatedTo(relatedToParam) %}

{% for product in entries %}
    // Product content here
{% endfor %}

Is there a way I can modify what I have above to make it so that if user selects an option from each tag group, that it only shows products with both selected tags? And if more than 1 of each group are selected to show those too?

I've based my current code based off this answer.

1 Answer 1

2

Not sure i understand well, but to be sure: you want to be able to filter for (let's say you sell shirts) a Red AND Large shirt, so it filters only the shirts which contain the color red and that are available in large?

On that note, have you tried setting relatedToParam to and instead of or?

The answer you linked seems to work for multiple selections that are not related. For example: i want the website to show me all the red shirts and all the large shirts, but not necessarily just the red large shirts.

Following that logic, changing relatedToParam to and should do the trick?


EDIT (2019-02-07):

So, i tried quite a few solutions and that seems to be working, please read the comments.

EDIT 2:

There was an issue! For example, if no size was selected, the loop simply won't do nothing. You could switch the loop to start with types, but then again, if no type is selected, same problem. Now it's working but still really NOT versatile. Feel free to edit or comment with a better solution!

{% set sizes = craft.tags.group('size').all() %}
{% set types = craft.tags.group('type').all() %}

{# You'll need to separate tags inside the query itself #}
{# ?size=......&type=...... #}

{# The split filter doesn't work directly on getParam #}

{# Error when getParam is empty #}
{# Because it is set to [] and we can't split on an array #}
{# You will need to find a little condition to make it bulletproof #}

{% set selectedSizesIds = craft.app.request.getParam('size') ?? [] %}
{% set selectedSizesIds = selectedSizesIds|split(',') %}

{% set selectedTypesIds = craft.app.request.getParam('type') ?? [] %}
{% set selectedTypesIds = selectedTypesIds|split(',') %}

{# You can actually use a if inside a print statement #}
{# Take a look at the "checked" print condition #}

{% for size in sizes %}
    <input type="checkbox" name="tags[]" value="{{ size.id }}" {{ (size.id in selectedSizesIds) ? 'checked' : '' }}>{{ size.title }}</input>
{% endfor %}

{% for type in types %}
    <input type="checkbox" name="tags[]" value="{{ type.id }}" {{ (type.id in selectedTypesIds) ? 'checked' : '' }}>{{ type.title }}</input>
{% endfor %}

{# Where it gets tricky #}
{% set relatedToParam = ['and'] %}

{# Loop through the second set of tags while looping through the first set of tags #}
{# Still not quite sure that's the best way to go but it works well #}

{% if selectedSizesIds|length > 0 %}
    {% for size in selectedSizesIds %}
        {% if selectedTypesIds|length > 0 %}
            {% for type in selectedTypesIds %}
                {% set relatedToParam = relatedToParam|merge([
                    { targetElement : [size,type] }
                ]) %}
            {% endfor %}
        {% else %}
            {% set relatedToParam = relatedToParam|merge([
                { targetElement : size }
            ]) %}
        {% endif %}
    {% endfor %}
{% elseif selectedTypesIds|length > 0 %}
    {% for type in selectedTypesIds %}
        {% set relatedToParam = relatedToParam|merge([
            { targetElement : type }
        ]) %}
    {% endfor %}
{% endif %}

{% set relatedToParam = relatedToParam|length > 1 ? relatedToParam : null %}

{% set entries = craft.products.type('accommodation').relatedTo(
    relatedToParam
).all() %}

{% for product in entries %}
   {{ product.title }}
{% endfor %}
6
  • I think what I need is a combination of both 'and' and 'or'. As an example, If the user were to select 'L' and 'XL' from sizes, and 'Tops' from type, I'd like it to show all L and XL tops. Putting 'and' will make that search show nothing. Not sure how I'd go about it, but I think I'd need to merge each filter on its own first with the 'or' param, and then combine those together with an 'and'.
    – Amy
    Feb 6, 2019 at 9:12
  • Can the user search for XL + L + TOPS + BOTTOMS? Anyway, i guess you could loop through the first set of tags (size) and inside that loop, you could loop through the second set of tags (type). When doing this, you build an array for each possible filter, giving you something like that: (XL AND TOPS) OR (XL AND BOTTOMS) OR (L AND TOPS) OR (L AND BOTTOMS) Does that make sense? Sorry i can't give you a real code snippet right now! (tell me if you don't get it and i'll try to make one later) Feb 6, 2019 at 23:28
  • I understand the process, i'm not not quite sure how to go about it. If you could update your answer with a code snippet later it would be much appreciated!
    – Amy
    Feb 7, 2019 at 11:07
  • @Amy, done! Feel free to use it and ask questions :) Feb 7, 2019 at 21:15
  • I'm currently getting a 'explode() expects parameter 2 to be string, array given' error when I submit a search form no matter how many filters are selected. I've made the condition to do nothing if the array is empty, but as soon as the array is populated I get the error. Not sure if it would have something to do with how the URL is outputted, currently being '?size%5B%5D=475&type%5B%5D=478' or if it's something else. Have you come across this?
    – Amy
    Feb 8, 2019 at 11:15

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.