# Commercetools adapter
The commercetools adapter provides many features you may need in your Vue Storefront application that will prevent you from repeating the code in Vue templates.
# Features
# Automatic page number transformation
Adapter decrements page number before each request - so in the frontend, we have a human-friendly page's numeration starting from 1, while Algolia receives the page's number starting from 0.
# Category tree
An agnostic category tree (opens new window) is accessible using the getCategoryTree
getter. The adapter makes an additional request to get proper slugs for categories in the tree.
import { useSearch, searchGetters } from '@vsf-enterprise/algolia';
import { AgnosticCategoryTree } from '@vue-storefront/core';
const { result } = useSearch();
const categoryTree: AgnosticCategoryTree = searchGetters.getCategoryTree(result.value);
# Pagination
An agnostic pagination object (opens new window) is accessible using the getPagination
getter.
import { useSearch, searchGetters } from '@vsf-enterprise/algolia';
import { AgnosticPagination } from '@vue-storefront/core';
const { result } = useSearch();
const pagination: AgnosticPagination = searchGetters.getPagination(result.value);
# Sorting
An agnostic sort object (opens new window) is accessible using the getSortOptions
getter.
import { useSearch, searchGetters } from '@vsf-enterprise/algolia';
import { AgnosticSort } from '@vue-storefront/core';
const { result } = useSearch();
const pagination: AgnosticSort = searchGetters.getSortOptions(result.value);
# Breadcrumbs for Product Listing Page
An agnostic breadcrumbs object (opens new window) is accessible using the getBreadcrumbs
getter.
import { useSearch, searchGetters } from '@vsf-enterprise/algolia';
import { AgnosticBreadcrumb } from '@vue-storefront/core';
const { result } = useSearch();
const breadcrumbs: AgnosticBreadcrumb[] = searchGetters.getBreadcrumbs(result.value);
# Filters
An agnostic filters object (opens new window) is accessible using the getFilters
getter.
import { useSearch, searchGetters } from '@vsf-enterprise/algolia';
import { AgnosticFilter } from '@vue-storefront/core'; // It's in the @vsf-enterprise/algolia till VSF2.4 release
const { result } = useSearch();
const pagination: AgnosticFilter[] = searchGetters.getFilters(result.value);
# Hits
An agnostic hits object (opens new window) is accessible using the getItems
getter.
import { useSearch, searchGetters } from '@vsf-enterprise/algolia';
import { AgnosticHit } from '@vsf-enterprise/algolia-types';
const { result } = useSearch();
const pagination: AgnosticHit[] = searchGetters.getItems(result.value);
# Requirements
# Strings
The adapter supports single- and multi-language structures.
The single-language structure is just a plain string:
const name = 'Pants';
However, the multi-language structure has a type of MultilangString
, where the language key is stored as a key and translated string as a value.
type MultilangString = Record<string, string>;
const name: MultilangString = {
en: 'Pants',
de: 'Hose',
pl: 'Spodnie'
};
# Product index
The most important index containing every product from commercetools. Thanks to it, it's possible to use every Algolia feature on the product's catalog.
WARNING
It's important to save each product variant as an individual record in Algolia. Their only shared trait is the parentId
field.
const rawProduct = {
name: {
en: 'White Dress',
pl: 'Biała sukienka'
},
categories: {
en: [
{
slug: 'sale/women/dresses'
},
// ...
],
pl: [
{
slug: 'wyprzedaz/damskie/suknie'
},
// ...
]
},
hierarchicalCategories: {
en: [
{
lvl0: 'Sale',
lvl1: 'Sale > Women',
lvl2: 'Sale > Women > Dresses'
},
// ...
],
pl: [
{
lvl0: 'Wyprzedaż',
lvl1: 'Wyprzedaż > Damskie',
lvl2: 'Wyprzedaż > Damskie > Suknie'
},
// ...
]
},
parentId: 'fdgf-408e-656-a127-dasd',
slug: {
en: 'white-dress',
pl: 'biala-sukienka'
},
sku: 'MOE200000QD4',
price: {
type: 'centPrecision',
currencyCode: 'USD',
centAmount: 191950,
fractionDigits: 2
},
images: [
{
url: 'https://some-website/abc.jpg'
}
],
color: 'white'
}
hierarchicalCategories
- (Required) Path to the category with names joined with >
parentId
- ID of variant's ancestor
sku
- Variant SKU
price
- currently, we support only one price so you have to apply logic of picking it in your indexer
After indexing, you have to add fields you want to filter by or populate facets from them to the attributesForFaceting (opens new window). For index shown above it will be:
color
,hierarchicalCategories.en.lvl0
,hierarchicalCategories.pl.lvl0
,hierarchicalCategories.en.lvl1
,hierarchicalCategories.pl.lvl1
,hierarchicalCategories.en.lvl2
,hierarchicalCategories.pl.lvl2
,categories.en.slug
,categories.pl.slug
.
It's required to set the attributeForDistinct (opens new window) to the value of parentId
.
# Category index
After fetching products, the adapter checks the index for details of products' categories. Based on the response, it builds a category tree with names, paths, and slugs.
const rawCategory = {
name: {
en: 'Dresses',
pl: 'Suknie'
},
path: {
en: 'Sale > Women > Dresses',
pl: 'Wyprzedaż > Damskie > Suknie'
},
slug: {
en: 'sale/sale-women/sale-women-dresses',
pl: 'wyprzedaz/wyprzedaz-damskie/wyprzedaz-damskie-suknie'
}
};
After indexing, you have to add the name
, path
, and slug
for each language to the attributesForFaceting (opens new window). For index shown above it would be:
name.en
,name.pl
,path.en
,path.pl
,slug.en
,slug.pl
.
# Commercetools Algolia Indexer
Instead of writing your own indexer, you might use an OpenSource commercetools-algolia-indexer (opens new window) library from ChangeCX
.
# Adapter configuration
Open the middleware.config.js
file and add filters to the attributesForFilters
array, commercetools
to the adapters
, category
entity with it's index to the entities
, and defaultLocale
. The latter is a code of the languages used as a fallback when locale cookie is missing.
To use disjunctive faceting (opens new window) (perfect for multi-select filters), you can add them to the disjunctiveFacets
array.
{
algolia: {
location: '@vsf-enterprise/algolia-api/server',
configuration: {
// ...
entities: {
product: {/*...*/},
category: {
name: {
index: '<your_category_index>',
default: true
}
}
},
defaultLocale: 'en',
attributesForFilters: [
'color'
],
adapters: [
'commercetools'
],
disjunctiveFacets: [
'color',
'categories.en.slug',
'categories.de.slug',
]
}
}
}
Then, open the nuxt.config.js
file and add levels of hierarchical categories together with available filters' attributes.
['@vsf-enterprise/algolia/nuxt', {
attributesForFilters: [
'hierarchicalCategories.en.lvl0',
'hierarchicalCategories.pl.lvl0',
'hierarchicalCategories.en.lvl1',
'hierarchicalCategories.pl.lvl1',
'hierarchicalCategories.en.lvl2',
'hierarchicalCategories.pl.lvl2',
'color'
]
}]
# Example usage
# Query for Product Listing Page
import { useVSFContext } from '@vue-storefront/core';
import { useSearch } from '@vsf-enterprise/algolia';
import { useUiHelpers } from '~/composables';
//...
setup (context) {
const th = useUiHelpers();
const { $route } = context.root;
const { search } = useSearch('cat-page');
const { app: { i18n: { locale }} } = useVSFContext();
onSSR(async () => {
const { filters, page } = th.getFacetsFromURL();
const facetFilters = Object.entries(filters).reduce((total, [ filterType, values ]) => {
const filterValues = [];
for (let value of values) {
filterValues.push(`${filterType}:${value}`)
}
if (filterValues.length) {
total.push(filterValues.length === 1 ? filterValues[0]: filterValues)
}
return total;
}, []);
await search({
query: '',
parameters: {
page,
facetFilters: [
...facetFilters,
`categories.${locale}.slug:${Object.values($route.params).filter(Boolean).join('/')}`
]
},
sort: $route.query.sort
});
});
}
# Query in searchbar
import { useSearch } from '@vsf-enterprise/algolia';
import { useVSFContext } from '@vue-storefront/core';
//...
setup (context) {
const { search } = useSearch('search-bar-query');
const { app: { i18n: { locale }} } = useVSFContext();
const onSearch = async (textQueryInSearchbar) => {
await search({
query: textQueryInSearchbar,
parameters: {
facets: [`hierarchicalCategories.${locale}.lvl0`, `hierarchicalCategories.${locale}.lvl1`]
}
});
};
}