Filtering products in categories

Product filtering is a Category's most powerful feature allowing users to find products within a large product catalogue. Hence it's probably the most complicated aspect of a Category template. 

Product filtering is optional per Category. A website administrator may decide to enable filtering in one category and not another. Within your template you should determine whether to show filter options with the category.filterable property.


<?ev if (category.filterable) { ?>
    ...
<?ev } ?>

You should then determine if any filters are available within the category.filters() method which returns a CategoryFilterCollection.


<?ev if (category.filterable && category.filters().length) { ?>
    <div class="category-filters">
        ...
    </div>
<?ev } ?>

Iterating through filter groups

There are various types of filter groups represented as a CategoryFilter. These allow you to filter by:

  • Product Specification CategoryFilter.isSpecification
  • Price CategoryFilter.isPrice
  • Manufacturer CategoryFilter.isManufacturer

Accessing each group type is done in the same way:


<?ev if (category.filterable && category.filters().length) { ?>
    <div class="category-filters">
        <?ev for (filter of category.filters()) { ?>
            <!-- Filter Group -->
            <div class="filter-group">
                <!-- Filter Heading -->
                <div class="filter-heading">
                    <strong>{{ filter.title }}</strong>
                    <br>
                    {{ filter.description }}
                </div>
            </div>
        <?ev } ?>
    </div>
<?ev } ?>


Identifying selected filter groups

An active/selected filter group implies that a user has interacted with one or more of its options and the products listed on the page are filtered accordingly. You can identify whether a filter group has one or more selected filters using the CategoryFilter.isActive property. This allows you to expand filter groups if your theme collapses filters by default, for example.


<?ev if (category.filterable && category.filters().length) { ?>
    <div class="category-filters">
        <?ev for (filter of category.filters()) { ?>
            <!-- Filter Group -->
            <div class="filter-group {{ filter.isActive ? 'active' : 'inactive' }}">
                <!-- Filter Heading -->
                <div class="filter-heading">
                    <strong>{{ filter.title }}</strong>
                    <br>
                    {{ filter.description }}
                </div>
            </div>
        <?ev } ?>
    </div>
<?ev } ?>


Filter display preferences & options

There are three display preferences for category filters:

  • Single-select CategoryFilter.isSingleSelect
    Users may select one option from the available options.
    Note, you do not have to use radio input fields (as illustrated below)
  • Multi-select CategoryFilter.isMultiSelect
    Users may select multiple options from the available options.
    Note, you do not have to use checkbox input fields (as illustrated below)
  • Ranges CategoryFilter.isRangeSelect
    Users must be allowed to set a minimum and maximum value. This is usually achieved with either input fields, a range slider or both. A range display preference typically requires some JavaScript.

<?ev if (category.filterable && category.filters().length) { ?>
    <div class="category-filters">
        <?ev for (filter of category.filters()) { ?>
            <!-- Filter Group -->
            <div class="filter-group {{ filter.isActive ? 'active' : 'inactive' }}">
                <!-- Filter Heading -->
                <div class="filter-heading">...</div>
            
                <?ev 
                    if (filter.isRangeSelect) { 
                        var option = filter.options[0];
                        var name = filter.type;
                        if (filter.isSpecification) {
                            name += '[' + filter.id + ']';
                        }
                ?>
                <!-- Filter Range -->
                <div class="filter-range">
                    <!-- Range minimum -->
                    <ev:textfield 
                        type="number" 
                        name="{{ name }}[min]" 
                        value="{{ option.minValue.decimal(0) }}"
                        prefix="{{ option.min.prefix() }}" />
                    
                    <!-- Range maximum -->
                    <ev:textfield 
                        type="number" 
                        name="{{ name }}[max]" 
                        value="{{ option.maxValue.decimal(0) }}"
                        prefix="{{ option.max.prefix() }}" />
                </div>
                <?ev } else { ?>
                <!-- Filter Options -->
                <div class="filter-options">
                    <ul>
                        <?ev for (var option of filter.options) { ?>
                            <li class="filter-option">
                                <a href="{{ option.url }}" rel="nofollow">
                                    {{ option.value }}
                                </a>
                            </li>
                        <?ev } ?>
                    </ul>
                </div>
                <?ev } ?>
            
            </div>
        <?ev } ?>
    </div>
<?ev } ?>

A note about SEO crawl quota

You should add a rel="nofollow" attribute to filter option links to avoid category filters consuming valuable search engine crawl quota. We have illustrated this in the example code above.

Adding image & colour swatches

Some category filter options may have a colour or an image if the option represents a swatch based Product Specification. You can recognise swatch preferences with CategoryFilter.isSwatch.


<?ev if (category.filterable && category.filters().length) { ?>
    <div class="category-filters">
        <?ev for (filter of category.filters()) { ?>
            <!-- Filter Group -->
            <div class="filter-group {{ filter.isActive ? 'active' : 'inactive' }}">
                
                ... 
                
                <!-- Filter Options -->
                <div class="filter-options">
                    <ul>
                        <?ev for (var option of filter.options) { ?>
                            <li class="filter-option">
                                <a href="{{ option.url }}" rel="nofollow">
                                
                                    <?ev if (filter.isSpecification && filter.isSwatch) { ?>
                                        <!-- Option Swatch -->
                                        <div 
                                            class="filter-option-swatch" 
                                            style='background-color: {{ option.value.color }};' > 
                                            <ev:img src="{{ option.value.image }}" width="50" height="50" />
                                        </div>
                                    <ev } ?>
                                    
                                    <!-- Option Text -->
                                    {{ option.value }}
                                </a>
                            </li>
                        <?ev } ?>
                    </ul>
                </div>
                
                ...
            
            </div>
        <?ev } ?>
    </div>
<?ev } ?>

Available on Github

Please note, the code above is incomplete for easy reading.
A more complete code sample category/filter.partial.evml  is available on Github.

Go to Github


Further reading

To find out more about the full capabilities of filters please take a look at the following built-in EVML objects.