Facets, filters, and attributes

📘

This is an article for advanced users of Frontastic.

As a Developer within Frontastic, you'll come across the terms facet, filter, and attribute quite frequently and they can often be used as synonyms. However, they're also used with multiple meanings. We'll try to clear up the confusion here but we can't promise anything...

In our stack, the meanings for each are:

  • Attribute – A property of a data entity that might be displayed on lists, detail pages, and so on. For example, size can be an attribute of a product variant and the product detail page shows the size variant as the text XL.
  • Facet – A search for data entities might return statistics about attribute values among the search result. These statistics are called a facet. For example, a category has 12 product variants with the color red and 8 product variants with the color blue.
  • Filter – A filter is used to restrict the results of a search for data entities, typically using one of its attributes. For example, a customer could filter all the products that have a size equal to XL.

Usually, the filter possibilities displayed are facets and the user can filter by selecting a facet value which is then applied as a filter to the previously shown result. In the below example, the facets are the count of results per attribute value:

Within the Facets area of the Frontastic studioFrontastic studio - The interface you use to manage, build, and edit all areas of your project and commerce sites. Previously known as `backstage`., a user can select which attributes should be available to users for facets and filters. They can also change the order that they appear to users. All attributes marked as searchable in the API can be used but they must be activated in the Facet area in the Frontastic studioFrontastic studio - The interface you use to manage, build, and edit all areas of your project and commerce sites. Previously known as `backstage`.. However, both of these can be overridden if it's written into your code, make sure you talk to your team to make sure there's no overlap. See the Using facets article for more info on how to use Facets in the Frontastic studioFrontastic studio - The interface you use to manage, build, and edit all areas of your project and commerce sites. Previously known as `backstage`..

Whenever someone opens the Facets area in the Frontastic studioFrontastic studio - The interface you use to manage, build, and edit all areas of your project and commerce sites. Previously known as `backstage`., it asks the API hubAPI hub - The API hub combines our code, your code, and APIs to create the backend of a commerce site. Any backend development or extensions are done here. Known as `Catwalk` in code. for all searchable attributes with the ProjectAPI and syncs these to the studio, these are then stored in the Frontastic studioFrontastic studio - The interface you use to manage, build, and edit all areas of your project and commerce sites. Previously known as `backstage`. database of the project as potential facets in the Facet area and are automatically updated each time it's opened. Any new facets will be added as deactivated by default.

The API hubAPI hub - The API hub combines our code, your code, and APIs to create the backend of a commerce site. Any backend development or extensions are done here. Known as `Catwalk` in code. hosts the implementation of the ProjectAPI and it has a database of all enabled facets from the Facet area in the Frontastic studioFrontastic studio - The interface you use to manage, build, and edit all areas of your project and commerce sites. Previously known as `backstage`. so it can communicate with your API providers. This means that your API hubAPI hub - The API hub combines our code, your code, and APIs to create the backend of a commerce site. Any backend development or extensions are done here. Known as `Catwalk` in code. also hosts a database of your facets.

Data sourcesData sources - The data provided from an API to display products, content, or anything from an integration or extension. A data source filter is created and can be selected for a Frontastic component that will display the data source filter on your commerce site. can be parametrized on top of what is defined in the Frontastic studioFrontastic studio - The interface you use to manage, build, and edit all areas of your project and commerce sites. Previously known as `backstage`., for example, with paging (offset/limit) and filtering (facets). This allows end-users to interact with data sourcesdata sources - The data provided from an API to display products, content, or anything from an integration or extension. A data source filter is created and can be selected for a Frontastic component that will display the data source filter on your commerce site. on a page by filtering its results or scrolling to later results.

The data fetching from the backend through streams depends on the URL requested by the end-user through the browser (which identifies a page folderpage folder - A way of structuring the pages within a project and forms the URL structure – they can contain sub-folders. Known as `node` in code. or dynamic pagedynamic page - A type of page that creates the default layout of pages that use the same structure but with different data, for example, a Product Details Page, wishlist, cart, search, and so on. Previously known as `master page`.). The Frontastic frontend uses the same URL presented in the browser to fetch the data. Therefore, in order to parametrize a stream, the URL needs to be manipulated.

The above image shows this flow:

  • The URL of a page folder is requested from the browser
  • The URL change in the frontend app results in a request for data to the API hubAPI hub - The API hub combines our code, your code, and APIs to create the backend of a commerce site. Any backend development or extensions are done here. Known as `Catwalk` in code. backend
  • A data source filter of items is returned together with metadata such as total, offset, limit, and the Facets statistics
  • The facets from the data source filder can be used to render drill-down functionality for the end-user
  • If the user selects a filter from such a drill-down selection, the URL changes which results in a data request to update the information state of the data source filter
  • Paging works exactly the same, based on the other data source metadata (see the Pagination and sorting with commerce backends article for more info on pagination)

Reading and displaying facets

A product-list data source doesn't only contain items but also facets. This property is filled by the commerce backend with statistics about the products in the data sourcedata source - The data provided from an API to display products, content, or anything from an integration or extension. A data source filter is created and can be selected for a Frontastic component that will display the data source filter on your commerce site.. The facets array contains an object for each attribute that has facet calculation enabled (through the Facets area). In the below code, there are the facets for color and size extracted from this array as examples. In a real-world application, you'll probably iterate all facets to render all possible filters.

import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux'
import { compose } from 'redux'
import PropTypes from 'prop-types'
import _ from 'lodash'
import Entity from '../../app/entity'
import UrlHandler from '../../app/urlHandler'

import app from '../../app/app'
import facetConnector from '../../app/connector/facet'
import categoryConnector from '../../app/connector/category'
import urlHandlerConnector from '../../app/connector/urlHandler'

class ExampleProductListFilterTastic extends Component {

    render () {
        const productList = this.props.data.stream

        // ... sanity checks ...

        const colorFacet = _.find(productList.facets, { handle: 'variants.attributes.color' })

        const streamId = this.props.tastic.configuration.stream

        return (<Fragment>
            <div style={{ width: '50%', 'float': 'left' }}>
            <h1>Color Filter:</h1>
            <ul>
                {colorFacet.terms.map((term) => {
                    return this.renderTerm(term, (event) => {
                        event.preventDefault()
                        this.toggleFacetTerm(streamId, colorFacet, term)
                    })
                })}
            </ul>
            </div>
        </Fragment>)
    }

    renderTerm = (term, toggleCallback) => {
        return (<li>
            <a href='#' onClick={toggleCallback}>{term.value}</a>
            ({term.count}) {term.selected ? '+' : ''}
        </li>)
    }

    // ... handlers, see below ...
}

ExampleProductListFilterTastic.propTypes = {
    data: PropTypes.object.isRequired,
    tastic: PropTypes.object.isRequired,

    // ... more props needed for handling, see below ...

    // From facetConnector
    facets: PropTypes.instanceOf(Entity).isRequired,
}

export default compose(
    connect(facetConnector),
    // ... more connectors, see below ...
)(ExampleProductListFilterTastic)

The basic data structure of a facet looks like this:

{
    "type": "range",
    "handle": "variants.price",
    "key":"variants.price",
    // ... more type specific properties
}

When querying the ProductAPI the API hubAPI hub - The API hub combines our code, your code, and APIs to create the backend of a commerce site. Any backend development or extensions are done here. Known as `Catwalk` in code. returns facets that can be grouped into 2 counting facets. Term counting is used when you have a finite number of values that it can count the number of times that term comes into play, for example, color or brand, which is represented as an object:

{
    "terms": [ 
          {
              "handle": "Damen",
            "name": "Damen",
            "value": "Damen",
            "count": 903,
            "selected": false
          }
    ]
}

Where handle is the identifier by which the server recognizes the term and value is the (localized) value to be displayed to the user. count is the number of items in the stream that satisfy a filter for this term and selected indicates if the term is applied as filter right now.

Range counting is when there's an infinite number of values that can be displayed so you can group them together, for example, price:

{
     "min":3125,
     "max":86125,
     "step":null,
     "value": {
         "min":0,
         "max":0
     },
     "selected": false
}

Where the min and max values on root level determine what values are possible in general for that facet (depending on current filters) and the value property contains the currently selected range values.

These should then be applied to your attribute types. For example, a boolean attribute type will most likely be a term facet and a number attribute type will most likely be a range facet. If you don't specify this, it will become a term facet by default.

The example shows how to simply display a list of all facet terms including their count and if the term is currently selected for filtering. Displaying a range would work accordingly, mostly with advanced slider elements or input fields.

Filtering by facet

The above example used a function toggleFacetTerm to select/deselect a facet term for filtering. This function and the required boilerplate code are shown below:

🚧

To determine if a filter is applied or not, we don't use the server result but rather the URL parameters. The reason is to avoid race conditions when a user clicks faster than the server handles the requests. It also gives faster feedback after the user has clicked.

import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux'
import { compose } from 'redux'
import PropTypes from 'prop-types'
import _ from 'lodash'
import Entity from '../../app/entity'
import UrlHandler from '../../app/urlHandler'

import app from '../../app/app'
import facetConnector from '../../app/connector/facet'
import categoryConnector from '../../app/connector/category'
import urlHandlerConnector from '../../app/connector/urlHandler'

class ExampleProductListFilterTastic extends Component {
    // ... render method ...

    toggleFacetTerm = (streamId, facet, term) => {
        const parameters = this.props.urlHandler.deriveParameters((urlState) => {
            const streamState = urlState.getStream(streamId)
            let filterTerms = this.getSelectedTerms(streamState, facet)

            if (filterTerms.includes(term.handle)) {
                filterTerms = filterTerms.splice(filterTerms.indexOf(term.handle), 1)
            } else {
                filterTerms.push(term.handle)
            }

            streamState.setFilter(
                facet.handle,
                _.isEmpty(filterTerms) ? null : { terms: filterTerms })
        })

        app.getRouter().push(this.props.route.route, parameters)
    }

    getSelectedTerms = (streamState, facet) => {
        return ((streamState.getParameters().facets || {})[facet.handle] || {}).terms || []
    }
}

ExampleProductListFilterTastic.propTypes = {
    data: PropTypes.object.isRequired,
    tastic: PropTypes.object.isRequired,

    // From connector
    route: PropTypes.object.isRequired,

    // From urlHandlerConnector
    urlHandler: PropTypes.instanceOf(UrlHandler),

    // ... more props ...
}

export default compose(
    // ... more connectors ...
    connect(urlHandlerConnector),
    connect((globalState) => {
        let streamParameters = globalState.app.route.parameters.s || {}

        return {
            route: globalState.app.route,
            streamParameters: streamParameters,
        }
    }),
)(ExampleProductListFilterTastic)

Working with stream URL parameters is done through an object called urlHandler. This encapsulates the parameters from the URL for all data sourcesdata sources - The data provided from an API to display products, content, or anything from an integration or extension. A data source filter is created and can be selected for a Frontastic component that will display the data source filter on your commerce site. in a page folderpage folder - A way of structuring the pages within a project and forms the URL structure – they can contain sub-folders. Known as `node` in code.. In order to change data sourcedata source - The data provided from an API to display products, content, or anything from an integration or extension. A data source filter is created and can be selected for a Frontastic component that will display the data source filter on your commerce site. parameters, you call it method deriveParameters() giving it a callback that will actually do the mutation on a clone of the current parameter urlState. The reason for this is to not change the parameters directly but to receive a new set of (clean) parameters for either direct use (router push) or link creation.

The urlState has methods to access the parameters of a specific data sourcedata source - The data provided from an API to display products, content, or anything from an integration or extension. A data source filter is created and can be selected for a Frontastic component that will display the data source filter on your commerce site. (there can be an arbitrary amount of data sourcesdata sources - The data provided from an API to display products, content, or anything from an integration or extension. A data source filter is created and can be selected for a Frontastic component that will display the data source filter on your commerce site. in a page folderpage folder - A way of structuring the pages within a project and forms the URL structure – they can contain sub-folders. Known as `node` in code.). You need to get hold of the ID of your data sourcedata source - The data provided from an API to display products, content, or anything from an integration or extension. A data source filter is created and can be selected for a Frontastic component that will display the data source filter on your commerce site. in question in order to access its parameters: this is possible through the Frontastic componentFrontastic component - A customizable building block that's used together with other components to create a commerce site. Known as `tastic` for short in code. configuration (see the previous example).

You'll still need to extract the currently selected terms of a facet filter from the parameters yourself. After updating the selection you can call methods on the streamState to manipulate the parameters.

For working with offset and limit there are corresponding methods available on the streamState.

🚧

The urlHandler only takes care of the parameters of data sources! If you have custom parameters in the URL (such as q for search), you need to merge them to the derived parameters before updating the route, otherwise, they'll be lost!

Be sure to use something to keep the deriveParameters(), for example:

const parameters = this.props.urlHandler.deriveParameters((urlState) => {
    // ...
})

// Conserve another parameter:
parameters.phrase = this.props.route.parameters.phrase

app.getRouter().push(this.props.route.route, parameters)

Did this page help you?