Skip to main content

Overview

The Facets module manages faceted search functionality, including facet definitions, filters, filter selection state, and sticky filters. It handles fetching facets from the API and coordinating filter state with the search module.

State

The Facets module maintains the following state:
query
string
Current query for facets request
facets
Record<string, Facet>
Dictionary of facets by facet ID
filters
Record<string, Filter>
Dictionary of all filters by filter ID
groups
Record<string, string>
Mapping of facet IDs to group IDs
preselectedFilters
Filter[]
Filters that should be preselected on load
stickyFilters
Record<string, Filter>
Filters that persist across searches
status
'initial' | 'loading' | 'success' | 'error'
Current request status
origin
string | null
Origin of the facets request
params
Record<string, unknown>
Additional request parameters
config
FacetsConfig
Module configuration options

Configuration

filtersStrategyForRequest
'all' | 'selected' | 'none'
default:"'all'"
Which filters to include in facets request:
  • 'all' - Include all filters
  • 'selected' - Only include selected filters
  • 'none' - Don’t include filters

Getters

facets
Facet[]
Array of all facets with their filters populated
selectedFilters
Filter[]
Array of currently selected filters
selectedFiltersForRequest
Filter[]
Selected filters formatted for API request based on filtersStrategyForRequest
selectedFiltersByFacet
Record<string, Filter[]>
Selected filters grouped by facet ID
request
FacetsRequest
Computed facets request object

Mutations

setQuery
(state, query: string) => void
Update the query for facets
setFacet
(state, facet: Facet) => void
Add or update a single facet
removeFacet
(state, { id: string }) => void
Remove a facet by ID
setFilters
(state, filters: Filter[]) => void
Add or update multiple filters
mutateFilter
(state, { filter: Filter, newFilterState: Partial<Filter> }) => void
Update properties of an existing filter
removeFilter
(state, { id: string }) => void
Remove a single filter
removeFilters
(state, filters: Filter[]) => void
Remove multiple filters
setPreselectedFilters
(state, filters: Filter[]) => void
Set filters that should be preselected
setFacetGroup
(state, { facetId: string, groupId: string }) => void
Assign a facet to a group
setStickyFilter
(state, filter: Filter) => void
Mark a filter as sticky
removeStickyFilter
(state, filter: Filter) => void
Remove sticky status from a filter
clearStickyFilters
(state) => void
Clear all sticky filters
setStatus
(state, status: Status) => void
Update request status
setOrigin
(state, origin: string | null) => void
Set the origin of facets request
setParams
(state, params: Record<string, unknown>) => void
Update request parameters

Actions

fetchFacetsResponse
(context) => Promise<FacetsResponse>
Fetch facets from the API without updating state
fetchAndSaveFacetsResponse
(context) => Promise<FacetsResponse>
Fetch facets and update module state
cancelFetchAndSaveFacetsResponse
(context) => Promise<void>
Cancel any in-progress facets request
saveOrigin
(context, origin: string) => void
Save the origin of the facets request

Events Emitted

UserClickedAFilter
{ filter: Filter }
Emitted when user selects/deselects a filter
UserClearedAllFilters
{ facetId?: string }
Emitted when user clears filters
FacetsChanged
{ facets: Facet[] }
Emitted when facets data is updated
SelectedFiltersChanged
{ filters: Filter[] }
Emitted when selected filters change

Usage Examples

Basic Filter Selection

import { useStore } from 'vuex'
import type { Filter } from '@empathyco/x-types'

const store = useStore()

// Get filter from facets
const facets = store.getters['x/facets/facets']
const categoryFacet = facets.find(f => f.id === 'category')
const filter = categoryFacet.filters[0]

// Toggle filter selection
store.commit('x/facets/mutateFilter', {
  filter,
  newFilterState: { selected: !filter.selected }
})

// Fetch new search results with filter applied
await store.dispatch('x/search/fetchAndSaveSearchResponse')

Sticky Filters

// Mark a filter as sticky (persists across searches)
const filter = {
  id: 'brand:nike',
  facetId: 'brand',
  label: 'Nike',
  modelName: 'SimpleFilter',
  selected: true,
  totalResults: 150
}

store.commit('x/facets/setStickyFilter', filter)

// Sticky filter will remain selected even when query changes
store.commit('x/search/setQuery', 'running shoes')
await store.dispatch('x/search/fetchAndSaveSearchResponse')

// Remove sticky status
store.commit('x/facets/removeStickyFilter', filter)

Preselected Filters

// Set filters that should be preselected on page load
const preselectedFilters = [
  {
    id: 'category:shoes',
    facetId: 'category',
    label: 'Shoes',
    modelName: 'SimpleFilter',
    selected: true
  }
]

store.commit('x/facets/setPreselectedFilters', preselectedFilters)

Facet Groups

// Organize facets into groups
store.commit('x/facets/setFacetGroup', {
  facetId: 'color',
  groupId: 'attributes'
})

store.commit('x/facets/setFacetGroup', {
  facetId: 'size',
  groupId: 'attributes'
})

store.commit('x/facets/setFacetGroup', {
  facetId: 'brand',
  groupId: 'taxonomy'
})

Clear Filters

// Clear all selected filters
const selectedFilters = store.getters['x/facets/selectedFilters']

selectedFilters.forEach(filter => {
  store.commit('x/facets/mutateFilter', {
    filter,
    newFilterState: { selected: false }
  })
})

await store.dispatch('x/search/fetchAndSaveSearchResponse')

Component Integration

<template>
  <div class="facets">
    <div v-for="facet in facets" :key="facet.id" class="facet">
      <h3 class="facet-title">{{ facet.label }}</h3>
      
      <div class="facet-filters">
        <button
          v-for="filter in facet.filters"
          :key="filter.id"
          :class="{ selected: filter.selected }"
          @click="toggleFilter(filter)"
        >
          {{ filter.label }} ({{ filter.totalResults }})
        </button>
      </div>
    </div>
    
    <button 
      v-if="hasSelectedFilters" 
      @click="clearAllFilters"
    >
      Clear All Filters
    </button>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useStore } from 'vuex'
import type { Filter, Facet } from '@empathyco/x-types'

const store = useStore()

const facets = computed<Facet[]>(() => store.getters['x/facets/facets'])
const selectedFilters = computed<Filter[]>(() => store.getters['x/facets/selectedFilters'])
const hasSelectedFilters = computed(() => selectedFilters.value.length > 0)

const toggleFilter = async (filter: Filter) => {
  store.commit('x/facets/mutateFilter', {
    filter,
    newFilterState: { selected: !filter.selected }
  })
  
  // Reset to page 1 when filtering
  store.commit('x/search/setPage', 1)
  await store.dispatch('x/search/fetchAndSaveSearchResponse')
}

const clearAllFilters = async () => {
  selectedFilters.value.forEach(filter => {
    store.commit('x/facets/mutateFilter', {
      filter,
      newFilterState: { selected: false }
    })
  })
  
  store.commit('x/search/setPage', 1)
  await store.dispatch('x/search/fetchAndSaveSearchResponse')
}
</script>

Hierarchical Filters

<template>
  <div class="hierarchical-facet">
    <FilterTree :filters="rootFilters" @toggle="toggleFilter" />
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import type { HierarchicalFilter } from '@empathyco/x-types'
import FilterTree from './FilterTree.vue'

interface Props {
  facetId: string
}

const props = defineProps<Props>()
const store = useStore()

const facet = computed(() => {
  return store.state.x.facets.facets[props.facetId]
})

const rootFilters = computed(() => {
  return facet.value?.filters.filter(
    (f: HierarchicalFilter) => !f.parentId
  )
})
</script>

Filter Types

Simple Filter

import type { Filter } from '@empathyco/x-types'

interface SimpleFilter extends Filter {
  id: string
  facetId: string
  label: string
  selected: boolean
  totalResults: number
  modelName: 'SimpleFilter'
}

Hierarchical Filter

import type { Filter } from '@empathyco/x-types'

interface HierarchicalFilter extends Filter {
  id: string
  facetId: string
  label: string
  selected: boolean
  totalResults: number
  modelName: 'HierarchicalFilter'
  parentId: string | null
  children: HierarchicalFilter[]
}

Number Range Filter

import type { Filter } from '@empathyco/x-types'

interface NumberRangeFilter extends Filter {
  id: string
  facetId: string
  label?: string
  selected: boolean
  modelName: 'NumberRangeFilter'
  range: {
    min: number
    max: number
  }
}

Type Reference

Source: /home/daytona/workspace/source/packages/x-components/src/x-modules/facets/store/module.ts:1

Resources

x-types

Type definitions for facets and filters

Search Module

Related search functionality

Build docs developers (and LLMs) love