1

Context: I'm trying to build a search form that will filter users based on Name, Skills and School. Users who have any of the search criteria should appear in my results.

Name Is a fuzzy keyword search of user.fullName

Skills Is a multiple select of tags, where any should match (if multiple are selected)

Schools Is a single select of a tag.

Problem 1: When I search just by tags, leaving the Name field blanks, only the last tag is used to return results. So if two users were tagged with multiple tags, only the one relating to the last search term tag will be returned.

Problem 2:
It seems I can't combine the query so that I am searching not only by 'who has these tags', but also 'who's name partially matches this search' as well.

Thanks for any suggestions!

Code: Form

<form action="{{ url('search') }}">
    <input type="search" name="qName" placeholder="Name" value="{{craft.app.request.getParam('qName')}}">

    <label for="qSkills">Skills</label>
    <select multiple id="qSkills" name="qSkills[]">
        {% for item in craft.tags.group('profileSkills').all() %}
        <option value="{{ item.title }}">{{item.title}}</option>
        {% endfor %}
    </select>

    <label for="qSchool">School</label>
    <select id="qSchool" name="qSchool">
        <option value="">All</option>
        {% for item in craft.tags.group('schoolPrograms').all() %}
        <option value="{{ item.id }}">{{item.title}}</option>
        {% endfor %}
    </select>

    <input type="submit" value="Search">
</form>

Code: Results (on same page)

<div class="results">
    {% set searchName = craft.app.request.getParam('qName') %}
    {% set searchSkills = craft.app.request.getParam('qSkills') %}
    {% set searchSchool = craft.app.request.getParam('qSchool') %}
    {% set queryParams = {} %}

    {% if searchName is defined and searchName is not empty %}
        {% set queryParams = queryParams|merge({
            search: {
                query:'fullName:*' ~ searchName ~'*',
            },
        }) %}
    {% endif %}

    {% if searchSkills is defined and searchSkills is not empty %}
        {% set skillList = "" %}
        {% for skill in searchSkills %}
            {% set skillList = skillList ~ 'profileSkills:' ~  skill ~ " OR " %}
        {% endfor %}
        {% set queryParams = queryParams|merge({
            search: {
                query:  skillList,
            },
        }) %}

    {% endif %}

    {% if searchSchool is defined and searchSchool is not empty %}
        {% set queryParams = queryParams|merge({
            search: {
                query: 'profileSchoolProgram:' ~ searchSchool,
            },
        }) %}
    {% endif %}


    {% set entries = craft.users(queryParams).group('members')
        .orderBy('score')
        .all() 
    %}

    {% if entries|length %}
        <p>{{ entries|length }} results:</p>
        <ul>
            {% for entry in entries %}
                <li><a href="{{ entry.url }}">{{ entry.fullName }}</a></li>
            {% endfor %}
        </ul>
    {% else %}
        <p>Your search didn’t return any results.</p>
    {% endif %}
</div>

1

In order to get at the tag relationships, I think you'll need to use the relatedTo parameter instead of search. You'll still use search for the full-name search. This should solve both your problems.

The tricky part is fetching the appropriate elements to pipe into relatedTo() - you can't just send through the raw title string from the post data - you'll need to fetch the actual element ids.

Here's a very rough example. You'll need to replace 'facetFieldHandle_n` with the field handle names of your tag selector fields held against the users.

{% set facet1 = craft.tags.title(searchSkills).ids() %}
{% set facet2 = craft.tags.title(searchSchool).ids() %}

{% set params = ['and',
    { targetElement: facet1, field: 'facetFieldHandle_1' },
    { targetElement: facet2, field: 'facetFieldHandle_2' }
] %}

{% set users = craft.users.relatedTo(params).search(searchName).orderBy('score').all() %}

<ul>
    {% for user in users %}
        <li>{{ user.fullName }}</li>
    {% endfor %}
</ul>
| improve this answer | |
  • Thanks James — I guess I'm trying to filter by 3 parameters in order: First by School, then from those who are tagged with School, then by Skill(s), and then by keywords. – Adrian May 13 at 12:23
1

@James Smith Your note and some searching around yielded some results. Here's my working solution. I realized that all I needed to do was combine all tags with an "AND" operator, and that my search was actually not an "AND" + "OR" search at all.

Form:

{% set query = craft.request.getQuery() %}
{% set selectedSkills = query.skills ?? [] %}
{% set selectedSchool = query.school ?? [] %}
{% set nameSearch = query.nameSearch ?? "" %}

<form action="{{ url('search') }}">
    <input type="search" name="nameSearch" placeholder="Name" value="{{nameSearch}}">

    <label for="skills">Skills</label>
    <select multiple id="skills" name="skills[]">
        {% for tag in craft.tags.group('profileSkills').all() %}
            {% set selectedAttr = tag.id in selectedSkills ? 'selected' : '' %}
            <option value="{{ tag.id }}" {{selectedAttr}} >{{tag.title}}</option>
        {% endfor %}
    </select>

    <label for="school">School</label>
    <select id="school" name="school">
        <option value="">All</option>
        {% for tag in craft.tags.group('schoolPrograms').all() %}
            {% set selectedAttr = tag.id in selectedSchool ? 'selected' : '' %}
            <option value="{{ tag.id }}" {{selectedAttr}} >{{tag.title}}</option>
        {% endfor %}
    </select>

    <input type="submit" value="Search">
    <a href="/search"><button type="button">Reset</button></a>
</form>

Results:

<div class="results">

    {# Set initial search operator to AND #}
    {% set relationParam = ['and'] %}
    {% set searchParams = "" %}

    {% if query.nameSearch is defined and query.nameSearch is not empty %}
        {% set searchParams = 'fullName:*' ~ query.nameSearch ~'*' %}
    {% endif %}

    {# This is a multi-tag select #}
    {% if query.skills is defined and query.skills is not empty %}
        {% for skill in query.skills %}
            {% set relationParam = relationParam|merge([{targetElement:skill }]) %}
        {% endfor %}
    {% endif %}

    {# This is a single tag select #}
    {% if query.school is defined and query.school is not empty %}
            {% set relationParam = relationParam|merge([{targetElement:query.school }]) %}
    {% endif %}

    {# Get users of group "member" with any keywords provided #}
    {% set users = craft.users.group('members').search(searchParams) %}

    {# Check to see if we have any tags selected#}
    {% if relationParam|length > 1 %}
        {# Filter users based on all selected tags #}
        {% set users = users.relatedTo(relationParam) %}
    {% endif %}

    {%- if users|length %}
        <p>{{ users|length }} results:</p>
        <ul>
            {% for entry in users %}
                <li><a href="{{ entry.url }}">{{ entry.fullName ? entry.fullName : entry.name }}</a></li>
            {% endfor %}
        </ul>
    {% else %}
        <p>Your search didn’t return any results.</p>
    {% endif -%}        

</div>
| improve this answer | |

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.