Pular para o conteúdo

Filtros de Produto

O Filtros de Produto é o componente que atende as necessidades de filtrar os produtos de um dado conjunto, definido por uma Tag. Na grande maioria dos casos esse componente é implementado na página de listagem de produtos. Nesse artigo você confere como adicionar esse componente na sua loja, e como adaptar os parâmetros para seu negócio.

📘 Demonstração do componente

Visualize como é o componente acessando o Filtros de Produto Demo. Recomendamos utilizar a versão mais atualizada do componente: (v18).

Por padrão, na Olist Ecommerce, à medida que vamos selecionando os filtros, as opções vão se mantendo mesmo que no momento não tenhamos produtos em determinada tag/atributo.

Temos a opção de ir “afunilando” os filtros conforme as opções vão sendo selecionadas. Para isso em admin/config/gerais/configuracoes-adicionais inserir o seguinte comando search_use_api: true.

Para desativar, no mesmo local inserir search_use_api: false.

Por padrão na plataforma sempre ao fazer a chamada de produtos, o {% load_products %} serão trazidos também os produtos indisponíveis.

Para excluir os produtos indisponíveis do front do site em /admin/config/gerais/extras marque a opção Listar somente produtos disponíveis?.

Os filtros de produtos, como todos os outros componentes, já vêm implementados nos templates da Olist Ecommerce. Caso você precise fazer esta implementação manualmente, ou alterar o seu funcionamento, deve-se, primeiro, dar atenção à importação dos arquivos JavaScript e CSS do componente. Junto a isso, você deve adicionar o objeto {{ aggregations }}.

🚧 Objeto {{ aggregations }}

Para que o filtro de produtos comporte-se de forma dinâmica, utilizamos o objeto {{ aggregations }}. Os atributos types e properties desse objeto serão utilizados no código JavaScript de declaração do componente.

Entre todos os componentes, os filtros são os que possuem maior diversidade de formas, de acordo com as escolhas selecionadas nos seus parâmetros. O parâmetro mode é responsável por grande parte desta variação, pois controla a forma de exibição do filtro, podendo variar entre horizontal, vertical, popup ou aba lateral.

Para cada uma dessas opções do parâmetro mode, os templates contam com um conjunto específico de configurações no seu arquivo SCSS (assets/sass/content/tag/_filters.scss). Ao escolher uma das opções para usar, aconselha-se buscar em qual dos templates ela já foi implementada e utilizar o seu arquivo _filters.scss como ponto de partida do processo de personalização.

Caso opte-se por usar a aba lateral (drawer) no parâmetro mode, deve-se incluir um botão para controlar a exibição do componente, semelhante ao exemplo abaixo.

<button aria-label="Abrir filtro" class="button-default -outlined" id="open-filter-mobile">
<svg class="icon">
<use xlink:href="#icon-filter"/>
</svg>
FILTROS
</button>
const mobileButton = document.querySelector('#open-filter-mobile')
if (mobileButton) mobileButton.addEventListener('click', () => {
FilterComponent.show()
})

Além do mode, há uma série de outros parâmetros que podem ser configurados de acordo com as necessidades da loja. Essas configurações se concentram na função setFilters do arquivo filters.js.

setFilters: function() {
const { root } = FilterComponent;
const tags = FilterComponent.setTagTypes()
const properties = FilterComponent.setProperties()
const customProperties = FilterComponent.setCustomProperties()
const priceSettings = FilterComponent.setPricesRanges()
const mode = (window.innerWidth <= 1024) ? 'drawer' : 'vertical'
const componentFilters = new Vnda.Component.ProductsFilter({
mode,
filtersStartOpen: false,
showAppliedFilters: false,
hasSort: true,
filterOnClick: false,
resetMode: 'all',
tags,
properties,
customProperties,
price: priceSettings.price,
priceProps: priceSettings.priceProps
});
componentFilters.render(root);
FilterComponent.instance = componentFilters
root.dispatchEvent(new Event('vnda:filter-component-loaded'))
},

Para que o JavaScript monte o componente corretamente, o Liquid precisa renderizar a raiz do filtro e definir window.filterSettings antes da inicialização.

A raiz do componente pode ser renderizada desta forma:

<div id="component-products-filter-root"></div>

O objeto window.filterSettings segue este formato:

window.filterSettings = {
script: "{{ 'products-filter.v[version].js' | replace: '[version]', component_version | component_path }}",
styles: "{{ 'products-filter.v[version].css' | replace: '[version]', component_version | component_path }}",
aggregations: {{ aggregations | to_json }},
patterns: {{ patterns_list | to_json }},
has_sort: {{ has_sort }},
prices_range: [
{% if ranges %}
{% for range in ranges %}
{% assign values = range | strip | split: '-' %}
[{{ values[0] | times: 1.00 }}, {{ values[1] | times: 1.00 }}],
{% endfor %}
{% endif %}
]
}

Esse objeto fornece ao componente:

  • os assets que serão carregados
  • as agregações da listagem
  • os dados auxiliares para filtros visuais
  • as faixas prontas de preço
  • a flag de ordenação

O trecho abaixo mostra um exemplo completo de como preparar os dados do filtro no Liquid antes da inicialização do JavaScript.

{% load_banners position: 'filtro-preco' limit: 1 %}
{% for banner in loaded_banners %}
{% if banner.description %}
{% assign ranges = banner.description | strip_html | split: ',' %}
{% endif %}
{% endfor %}
{% assign patterns_list = '' %}
{% assign patterns = '' %}
{% assign attributes_to_check = aggregations.properties.property1 %}
{% if aggregations.variant_attributes.size > 0 %}
{% for custom_attribute in aggregations.variant_attributes %}
{% assign custom_attributes = custom_attributes | concat: custom_attribute[1] %}
{% endfor %}
{% assign attributes_to_check = attributes_to_check | concat: custom_attributes %}
{% endif %}
{% for attribute in attributes_to_check %}
{% if attribute.value contains '|' %}
{% unless attribute.value contains '#' %}
{% if patterns == blank %}
{% load_tags types: 'estampa' %}
{% assign patterns = loaded_tags %}
{% endif %}
{% assign pattern_image = '' %}
{% for pattern in patterns %}
{% if attribute.value contains pattern.name %}
{% assign pattern_image = pattern.image_url | resize: '25x25' %}
{% endif %}
{% endfor %}
{% capture patterns_list %}
{{ patterns_list }},{
"value": "{{ attribute.value }}",
"pattern": "{{ pattern_image }}"
}
{% endcapture %}
{% endunless %}
{% endif %}
{% endfor %}
{% assign patterns_list = patterns_list | prepend: '[' | append: ']' | remove_first: ',' | from_json %}
{% assign component_version = '19' %}
{% if current_shop.settings contains 'component_version_products_filter' %}
{% assign settings_version = current_shop.settings.component_version_products_filter %}
{% if settings_version != blank %}
{% assign component_version = settings_version %}
{% endif %}
{% endif %}
{% unless has_sort %}
{% assign has_sort = false %}
{% endunless %}
<div id="component-products-filter-root"></div>
<script>
window.filterSettings = {
script: "{{ 'products-filter.v[version].js' | replace: '[version]', component_version | component_path }}",
styles: "{{ 'products-filter.v[version].css' | replace: '[version]', component_version | component_path }}",
aggregations: {{ aggregations | to_json }},
patterns: {{ patterns_list | to_json }},
has_sort: {{ has_sort }},
prices_range: [
{% if ranges %}
{% for range in ranges %}
{% assign values = range | strip | split: '-' %}
[{{ values[0] | times: 1.00 }}, {{ values[1] | times: 1.00 }}],
{% endfor %}
{% endif %}
]
}
</script>

Nesse fluxo:

  • aggregations fornece categorias, atributos e preço
  • patterns_list alimenta filtros visuais de cor ou estampa
  • ranges alimenta prices_range
  • component_version define quais assets do componente serão carregados

Como o JavaScript monta categorias, atributos e preço

Seção intitulada “Como o JavaScript monta categorias, atributos e preço”

Depois que window.filterSettings estiver disponível, o componente pode ser inicializado:

FilterComponent.init();

A montagem dos filtros no JavaScript acontece em etapas.

As categorias são montadas por setTagTypes(), a partir de settings.aggregations.types.categoria.

setTagTypes: function() {
const { settings } = FilterComponent;
const tags = [];
if (settings.aggregations) {
const aggregations = settings.aggregations
if (aggregations.types.categoria) tags.push({
title: 'Categoria',
type: 'categoria',
style: 'list',
options: FilterComponent.sortArrayValues(aggregations.types.categoria, 'title')
})
}
return tags
}

Os atributos principais são montados por setProperties(), a partir de:

  • aggregations.properties.property1
  • aggregations.properties.property2
  • aggregations.properties.property3

Exemplo de montagem de property1:

if (aggregations.properties.property1.length > 0) properties.push({
title: 'Cor',
property: 'property1',
style: 'colors',
colorsProps: {
showTitle: true,
showColor: true,
},
options: FilterComponent.sortArrayValues(aggregations.properties.property1, 'value'),
patterns: settings.patterns
})

Exemplo de montagem de property2:

if (aggregations.properties.property2.length > 0) properties.push({
title: 'Tamanho',
property: 'property2',
style: 'list',
options: FilterComponent.sortArrayValues(aggregations.properties.property2, 'value'),
patterns: settings.patterns
});

Exemplo de montagem de property3:

if (aggregations.properties.property3.length > 0) properties.push({
title: 'Fragrância',
property: 'property3',
style: 'list',
options: FilterComponent.sortArrayValues(aggregations.properties.property3, 'value'),
patterns: settings.patterns
});

Os atributos adicionais são montados por setCustomProperties(), a partir de aggregations.variant_attributes.

setCustomProperties: function () {
const { settings } = FilterComponent;
const customProperties = [];
if (settings.aggregations) {
const aggregations = settings.aggregations
if (aggregations.variant_attributes) {
for (const [key, value] of Object.entries(aggregations.variant_attributes)) {
const options = FilterComponent.sortArrayValues(value, 'value')
const hasAnyColor = options.some(option => option.value.includes('|'));
const style = hasAnyColor ? 'colors' : 'list';
customProperties.push({
title: key,
style,
...(hasAnyColor && {
colorsProps: {
showTitle: true,
showColor: true,
}
}),
property: key,
options,
patterns: settings.patterns
});
};
}
}
return customProperties
}

Quando algum valor contém |, o atributo é tratado como filtro visual. Nos demais casos, ele é tratado como lista.

O preço é montado por setPricesRanges(), combinando os limites mínimo e máximo com as faixas prontas, quando existirem.

setPricesRanges: function() {
const { settings, showPriceSlider } = FilterComponent
let priceSettings = {
price: false,
priceProps: false
}
if (showPriceSlider) {
priceSettings.price = [
settings.aggregations.min_price,
settings.aggregations.max_price
]
}
if (settings.prices_range.length > 0) {
priceSettings.priceProps = {
mode: 'options',
options: settings.prices_range
}
priceSettings.price = [
settings.aggregations.min_price,
settings.aggregations.max_price
]
}
return priceSettings
}

Depois de montar categorias, atributos e preço, setFilters() reúne essas estruturas e instancia o Vnda.Component.ProductsFilter.

setFilters: function() {
const { root, settings } = FilterComponent;
const tags = FilterComponent.setTagTypes();
const properties = FilterComponent.setProperties();
const customProperties = FilterComponent.setCustomProperties();
const priceSettings = FilterComponent.setPricesRanges();
const hasSort = settings.has_sort || false;
const componentFilters = new Vnda.Component.ProductsFilter({
tags,
properties,
customProperties,
price: priceSettings.price,
priceProps: priceSettings.priceProps
});
componentFilters.render(root);
FilterComponent.instance = componentFilters
root.dispatchEvent(new Event('vnda:filter-component-loaded'))
}

🚧 Importante

setFilters() não busca os dados do filtro. Ela apenas reúne categorias, atributos e preço que já foram preparados anteriormente a partir de window.filterSettings.

Os parâmetros definem as características dos Filtros de Produto. Veja na tabela abaixo os parâmetros e o que eles determinam no componente. Note que os parâmetros que possuem valores padrões estão sinalizados na coluna Default, e as opções de entrada que o parâmetro pode receber estão apresentados na coluna Tipos/Opções de entrada.

ParâmetroDescriçãoTipos/OpçõesDefaultObrigatório
modeDefine como o componente é exibido.drawer, horizontal ou verticaldrawerNão
filtersStartOpenDefine se as opções de filtro iniciarão abertas. Aplica-se aos modos drawer e vertical.true ou falsefalseNão
showAppliedFiltersDefine se a seção de filtros aplicados será exibida.true ou falsefalseNão
sideDrawerDefine a posição do componente quando o modo for drawer.right ou leftrightNão
hasInputSearchHabilita o campo de busca.true ou falsefalseNão
hasSortHabilita a ordenação dos resultados.true ou falsefalseNão
filterOnClickCarregamento automático ao clicar em uma opção do filtro.true ou falsefalseNão
resetModeDefine como limpar os filtros: um botão para tudo (all), para cada (each) ou nenhum (none).all, each ou nonenoneNão
tagsDefine o filtro de categorias (tags). Veja parâmetro tags.**Não
propertiesDefine o filtro de propriedades (permite múltiplos tipos).**Não
sortForma de ordenação (requer hasSort ativo). Veja parâmetro sort.grid ou list*Não
priceCarrega valores de limite inferior e superior para o filtro de preço.**Não
pricePropsDefine opções de valores para o filtro de preço. Veja parâmetro priceProps.**Não
patternsURLs que definem estampas para filtros de propriedade. Veja parâmetro patterns.**Não
tags = aggregations.types.categoria && [{
title: "Categoria",
type: "categoria",
style: "list",
options: aggregations.types.categoria
}];
sort: {
style: "grid", // estilo de como as opções são mostradas ( 'grid' ou 'list')
};
priceProps: {
mode: 'options',
options: [
[0, 99.99],
[100, 199.99],
[200, 299.99]
]
}
patterns: [
{
"value": "Xadrez | estampa-xadrez",
"pattern": "http://via.placeholder.com/30x30"
}
],

A partir da versão 15, os filtros disparam eventos toda vez que: o filtro for carregado, uma opção de filtro for aberta, uma opção de filtro for fechada ou quando o filtro for fechado (caso o mode seja drawer). O trecho abaixo indica os eventos:

// Evento disparado sempre que uma opção de filtro for aberta
window.addEventListener('vnda:filter-component-item-open', (event) => {
console.log(event)
})
// Evento disparado sempre que uma opção de filtro for fechada
window.addEventListener('vnda:filter-component-item-close', (event) => {
console.log(event)
})
// Evento disparado quando o filtro for carregado
window.addEventListener('vnda:filter-component-loaded', (event) => {
console.log(event)
})
// Evento disparado quando o filtro for fechado. Somente para o mode drawer
window.addEventListener('vnda:filter-component-closed', (event) => {
console.log(event)
})

O método disponível para esse componente é o toggle(). Ele abre o filtro se ele estiver fechado e fecha o drawer se ele estiver aberto (para os casos de mode drawer).

Confira como foi feita a implementação do filtro de produtos no Template1 acessando os arquivos: