Skip to main content

Categoria e Pesquisa

Seguindo o mesmo padrão das telas de Home e Detalhe de produto, a página de categoria category.html e busca search.html (lembrando que são duas páginas diferentes porém utilizam os mesmos métodos com layouts parecidos) possuem algumas funcionalidades importantes para o seu funcionamento, além também de importar elementos para uma organização melhor das páginas.

info
  • Tela de Categoria: Responsável por trazer os produtos de uma categoria.

  • Tela de busca: Responsável por trazer os produtos de uma busca feita pelo usuário através do campo de busca (geralmente localizado no header, ou até mesmo links de redirecionamentos feitos pela loja).

Vamos então falar sobre as funcionalidades essenciais para as telas de listagens. Algumas delas não serão compatíveis entre ambas as páginas, mas será apontado em qual delas não será compatível.

Para simplificar o processo, caso você tenha seguido o tutorial de forma sequencial, não vamos disponibilizar o código de toda a página de categoria ou busca, apenas blocos das funcionalidades essenciais para que sejam reaproveitados copiando e colando em seu projeto.

Array de elementos

Como a página possui vários elementos que precisam ser atualizados no momento em que o usuário filtra, troca de página ou outras ações que afetam a listagem de produtos, é necessário que a página mantenha a sincronia dos elementos como paginação, filtros, contador de resultados, filtros ja marcados e listagem de produtos. Nesse caso, temos cinco elementos importantes que foram importados em ambas as páginas category.html e search.html. Note que o atributo está sendo criado antes da tag <main> da página.

Aviso

Esses elementos são exemplos que usamos no nosso layout base, você precisa analisar seu código para saber quais são os nomes dos elementos que você está chamando nas telas de category.html e search.html. Caso não tenha nenhum elemento, não há necessidade de criar esse campo. Mais é importante analisar bem a forma como estamos chamando o campo elementosParaAtualizar no decorrer dos códigos abaixo, para que você consiga aplicar em seu projeto.

Foi adicionado também uma variável global window.elementosParaAtualizar, para que o atributo seja acessível em outros arquivos.

{%
set elementosParaAtualizar = [
'listing page/paginator', 'listing page/results products',
'listing page/marked filters', 'listing page/order filter',
'listing page/results counter'
]
%}

<script>
window.elementosParaAtualizar = {{ _object(elementosParaAtualizar) }};
</script>

<main class="pagina-de-categoria">
...
</main>

É de extrema importancia que o código acima seja aplicado nas páginas de listagens.

O breadcrumb irá funcionar apenas na página de category.html visto que ele trará o caminho de categorias e subcategorias, já na página de search.html temos apenas a busca do cliente, não sendo reconhecido como um caminho de fato.

<div class="breadcrumb container">
<a {{ _href('/') }}>Increazy</a>
<span class="arrow-right" data-grid="center-center">
{% include 'icons/arrow-left' %}
</span>

{% for path in category.breadcrumb %}
<a {{ _href(path.path) }} class="{{ loop.last ? 'item-active' : '' }}">
{{ path.name }}
</a>

{% if loop.last == false %}
<span class="arrow-right" data-grid="center-center">
{% include 'icons/arrow-left' %}
</span>
{% endif %}
{% endfor %}
</div>

De forma similar ao breadcrumb da página de produto, o atributo category.breadcrumb é quem faz a maior parte do trabalho, onde a lista dos 'caminhos' do breadcrumb é percorrida pelo {% for path in category.breadcrumb %}. Temos também o auxílio de um atributo extra chamado loop, que é reconhecido por padrão pelo core da PWA para facilitar nas validações dos itens do breadcrumb.

Título da página

Teremos duas formas de exibir o título da página, um formato para category.html e outro para search.html:

  • category.html: Para a tela de categoria, é utilizado o atributo {{ category.name }}, como mostra abaixo:
  <h1>{{ category.name }}</h1>
  • search.html: Para a tela de busca, é utilizado o atributo {{ term | url_decode }}.
  <h1>{{  term | url_decode }}</h1>

Contador de resultados

Caso você queira exibir a quantidade de itens encontrados pela busca, basta seguir o código abaixo:

<p>Foram encontrados {{ pagination.total }} produtos</p>

O atributo {{ pagination.total }} é o responsável por trazer a quantidade total de produtos encontrados. O objeto pagination é padrão do core da PWA, e livre para ser usado nas páginas de category.html e search.html.

Filtros

Os filtros é uma das partes mais importantes da tela de listagem, vamos mostrar um pouco sobre o seu funcionamento e as formas mais simples de se aplicar um filtro na PWA.

Método _filter()

Você irá notar ao longo das explicações sobre os filtros dessa página, que o método _filter() aparecerá diversas vezes, ele é o responsável por buscar a lista de filtros que será utilizada para filtrar seus produtos baseado nos campos selecionados trazidos por ele.

Vamos exemplificar de uma forma bem simples o funcionamento do método _filter(). Veja o código abaixo:

{% set filtros = _filter('TIPO', 'ATRIBUTO_PRODUTO', 'TIPO') %}

{% for filtro in filtros %}
{% set filtro_label = filtro.label ? filtro.label : filtro %}
{% set filtro = filtro.value ? filtro.value : filtro %}

<li onclick="selectFilter(event, { match: { '{{ name }}': '{{ filtro|trim }}' } })">
<label data-filter="{{ 'ATRIBUTO_PRODUTO' }}:{{ filtro|trim }}" for="{{ filtro }}">
<input type="checkbox" id="{{ filtro }}"
{{ filtro in selecionados ? 'checked' : '' }}
/>

<span>{{ filtro_label | raw }}</span>
</label>
</li>
{% endfor %}

Note que estamos chamando o método _filter('TIPO', 'ATRIBUTO_PRODUTO', 'UNIQUE') na primeira linha, ele trará uma lista de filtros para que você possa montar em seu HTML como mostra o exemplo acima. Indo um pouco mais a fundo, o _filter() possui alguns parâmetros importante, e vamos explicar sobre cada um deles abaixo:

  • TIPO: Temos alguns tipos a serem utilizados na busca dos filtros, sendo eles:

    • get: Faz a busca padrão retornando uma lista de filtros de determinado atributo. Exemplo, trará uma lista de todas as opções do campo produto.estampa (nome do atributo do produto), que poderia retornar: 'florido', 'liso', 'listrado' e etc..

    • selected: Faz a busca dos filtros selecionados de um determinado campo. Exemplo, ainda no atributo de produto.estampa o cliente poderá ter selecionado 'liso' e 'florido'. Então o selected trará a lista das opções selecionados pelo cliente.

    • max e min: São específicos para filtros de preços. Exemplo de como usar seria _filter('max', 'ATRIBUTO_PRECO'). Note que aqui não é passado o terceiro parâmetro, saiba mais em filtrar por preços.

    • get:url_based: Similar ao get, porém acrescentamos o :url_based para dizer ao motor de busca que queremos 'afunilar' a busca para trazer os filtros dos produtos da categoria ex.: Camisetas, com o filtro da cor "azul" já seleciona (pois é aí que entra o :url_base), pois ele detecta que já existe um filtro marcado na URL, então a busca dos filtros será baseado nos resultados da categoria/termo_de_busca na URL além dos filtros marcados. Ou seja, além de buscar os filtros baseados na categoria Camisetas ele também levará em consideração a query de filtros selecionados que seria cor "azul" para retornar a lista de filtros.

    • get:asc: Além de trazer os filtros com o get, adicionando o :asc estamos dizendo que queremos o retorno dos filtros em ordem crescente/alfabética.

    • get:desc: Além de trazer os filtros com o get, adicionando o :desc estamos dizendo que queremos o retorno dos filtros em ordem decrescente/alfabética-invertida.

    • Também podemos aplicar combinações usando o :asc e :desc para get:url_based:asc e get:url_based:desc.

  • ATRIBUTO_PRODUTO: Como o próprio nome diz, você precisa chamar o atributo do produto da sua loja que deseja filtrar como "cores", "tamanhos" entre outros. Para ficar mais fácil, na página de detalhe de produto no HTML aplique o código {{_debug(product)}}, salva e veja lá na página do seu produto um botão chamado "debug", clique no botão que você verá a lista de atributos disponível para ser filtrado.

  • UNIQUE: Para atributos que são objetos, por exemplo: O campo do produto chamado product.colors é um objeto que possui os seguintes valores:

    product.colors: [
    {
    label: "#000",
    value: "Black"
    },
    {
    label: "#fff",
    value: "White"
    },
    ]

    Para dizer ao nosso motor de busca qual o campo único que podemos utilizar na lista de filtros para que não retorne filtros duplicados, exemplo: Nesse caso podemos usar como base o atributo value do objeto, pois ele identifica melhor as cores do que os valores em hexadecimal do label. Assim, nosso filtro não irá repetir as cores Black e White. Reforçando, o White por exemplo poderia ter dois hexadecimais diferentes como #fff e #fffff, porém ambos são White.

Métodos essenciais

Antes de começarmos a falar sobre os filtros em particular, temos aqui alguns métodos necessários para serem usados nos filtros. Geralmente esses métodos são chamados em algum script e importado na página de category.html e search.html. Nesse caso vamos disponibilizá-los aqui para que você possa importá-los nas páginas de listagens da forma que o atenda melhor.

function selectFilter(event, filter) {
event.preventDefault();
event.stopPropagation();
var input = event.target.querySelector('input');

if (input) {
input.checked = !input.checked;
_refresh(window.elementosParaAtualizar, filter, event);
}
}

function unselectFilter(event, filter) {
event.preventDefault();
event.stopPropagation();

if (filter.clean == '*') {
return _refresh(window.elementosParaAtualizar, filter, event);
}

var input = document.querySelector('*[data-filter="' + filter.clean + '"] input');

if (input) {
input.checked = false;
}

_refresh(window.elementosParaAtualizar, filter, event);
}

Lembrando que o atributo elementosParaAtualizar está sendo chamado na sessão de Array de elementos. Também temos o método padrão da PWA _refresh() que é utilizado para atualizar o conteúdo da página de acordo com o resultado retornado.

Filtro de ordenação

O filtro de ordenação é bem simples, o código abaixo foi feito em um formato de dropdown 'customizado'.

<ul>
<li>
<input type="radio" id="ordering1" value="1" name="ordering" {{ queryString.order == null ? 'checked' : '' }}/>
<span>Nenhum</span>
</li>

<li>
<input type="radio" id="ordering2" value="2" name="ordering" {{ queryString.order['name.raw'] == 'asc' ? 'checked' : '' }}/>
<span>Nome</span>
</li>

<li>
<input type="radio" id="ordering4" value="4" name="ordering" {{ queryString.order['prices.sale_price'] == 'asc' ? 'checked' : '' }}/>
<span>Menor preço</span>
</li>

<li>
<input type="radio" id="ordering5" value="5" name="ordering" {{ queryString.order['prices.sale_price'] == 'desc' ? 'checked' : '' }}/>
<span>Maior preço</span>
</li>

<span class="filter__selector--options__icon" data-grid="center-center">
{% include 'icons/arrow-left' %}
</span>
</ul>

<ul>
<li onclick="orderFilter({})">
<label for="ordering2">Nenhum</label>
</li>

<li onclick="orderFilter({{ _object({ order: { 'name.raw': 'asc' } }) }})">
<label for="ordering2">Nome</label>
</li>

<li onclick="orderFilter({{ _object({ order: { 'prices.sale_price': 'asc' } }) }})">
<label for="ordering4">Menor preço</label>
</li>

<li onclick="orderFilter({{ _object({ order: { 'prices.sale_price': 'desc' } }) }})">
<label for="ordering5">Maior preço</label>
</li>
</ul>

<script>
async function orderFilter(filter) {
await _refresh(elementosParaAtualizar, { clean: 'order', ...filter} )
}
</script>

O atributo elementosParaAtualizar foi criado em Array de elementos.

Na lista <ul> ... </ul>, temos o filtro atual selecionado, ou seja, ficará como destaque o item selecionado. Para detectar o item que está selecionado, nele é aplicado um checked na tag <input> através da chamada do código {{ queryString .. }}, assim você poderá exibir o conteúdo via css de acordo com o atributo checked aplicado.

Já na segunda lista temos as opções que o cliente deseja selecionar para filtrar. Ao clicar em uma delas, a ação ocorrerá através do método orderFilter({{ _object({ order: { 'name.raw': 'asc' } }) }}) que está sendo chamado dentro da tag <li>. No método é passado um parâmetro que trata-se de um objeto { order: { 'name.raw': 'asc' } } que é filtrado pelo _object() (uma espécia de JSON.parse).

<li onclick="orderFilter({{ _object({ order: { 'name.raw': 'asc' } }) }})">
<label for="ordering2">Nome</label>
</li>

Temos então o método orderFilter() que faz o trabalho de ordenar os produtos da tela, dentro dele é usado um método chamado _refresh(), que tem dois parâmetros, o elementosParaAtualizar, que serão os elementos atualizados na tela após o filtro ser executado, e temos o atributo dos filtros que serão aplicados que no caso é um objeto { clean: 'order', ...filter}. O clean: 'order' serve para limpar o filtro sempre que for aplicado, ou seja, primeiro ele limpa a ordenação atual e depois aplica a ordenação desejada, que no caso seria o objeto filter. Veja abaixo:

async function orderFilter(filter) {
await _refresh(elementosParaAtualizar, { clean: 'order', ...filter} )
}

Por fim, teremos o seguinte resultado para o filtro de ordenação. Lembrando que é necessário estilizar com css o html exibido acima.

An image


Filtrar por preços

O filtro de preço já é um pouco mais trabalhoso, mas nada o que temer. Nesse caso vamos utilizar multirange com <input> do tipo range, como mostra abaixo:

Lembrando que o elemento de filtro de preço está localizado em Elementos > listing page > price filter.html, do layout que veio junto ao seu projeto PWA.

{% set maiorPreco = _filter('max', 'prices.sale_price') %}
{% set menorPreco = _filter('min', 'prices.sale_price') %}

{% if menorPreco < maiorPreco %}
<div class="filters__list">
<p>Preço</p>

<div class="price-range">
<div class="multi-range">
<input id="min" type="range" min="0" max="100" value="0" step="0.0001" />
<input id="max" type="range" min="0" max="100" value="100" step="0.0001" />
</div>

<div class="price-value">
<small id="from">{{ _price('format', menorPreco) }}</small>
<small id="to">{{ _price('format', maiorPreco) }}</small>
</div>
</div>
</div>

<script>
var minDollars = {{ menorPreco }};
var maxDollars = {{ maiorPreco }};

window.__lastMinValue = minDollars;
window.__lastMaxValue = maxDollars;

var minSlider = document.querySelector('#min');
var maxSlider = document.querySelector('#max');

function updateDollars() {
var fromValue = Math.floor((maxDollars - minDollars) * minSlider.value / 100 + minDollars);
var toValue = Math.floor((maxDollars - minDollars) * maxSlider.value / 100 + minDollars);

document.querySelector('#from').textContent = _price('format', fromValue);
document.querySelector('#to').textContent = _price('format', toValue);

window.__lastMinValue = fromValue;
window.__lastMaxValue = toValue;
}

maxSlider.addEventListener('input', () => {
var minValue = parseInt(minSlider.value);
var maxValue = parseInt(maxSlider.value);

if (maxValue < minValue + 10) {
minSlider.value = maxValue - 10;

if (minValue === parseInt(minSlider.min)) {
maxSlider.value = 10;
}
}

updateDollars();
});

minSlider.addEventListener('change', () => {
_refresh({{ elementosParaAtualizar | json_encode | raw }}, {
range: {
'prices.sale_price' : {
gte: window.__lastMinValue,
lte: window.__lastMaxValue,
}
}
});
});

maxSlider.addEventListener('change', () => {
_refresh({{ elementosParaAtualizar | json_encode | raw }}, {
range: {
'prices.sale_price' : {
gte: window.__lastMinValue,
lte: window.__lastMaxValue,
}
}
});
});

minSlider.addEventListener('input', () => {
var minValue = parseInt(minSlider.value);
var maxValue = parseInt(maxSlider.value);

if (minValue > maxValue - 10) {
maxSlider.value = minValue + 10;

if (maxValue === parseInt(maxSlider.max)) {
minSlider.value = parseInt(maxSlider.max) - 10;
}
}

updateDollars();
});
</script>
{% endif %}

Temos dois principais atributos que fazem funcionar o filtro de preço, que são: maiorPreco e menorPreco, que estão sendo declarados antes do código HTML através de um {% set.. %}. Note que nele há a chamada do método padrão da PWA _filter(), onde nele é feito todo o tratamento para buscar corretamente o maior e o menor preço dos produtos listados, feito isso, basta apenas criarmos alguns scripts de validação e sincronização para que tudo funcione nos eixos.

O método _price() é padrão da PWA que auxilia na busca do preço dos produtos, portanto pode ser usado em qualquer lugar. Assim também como o método _refresh(), que ajuda na sincronização dos elementos da página, trazendo os resultados buscados.

Vale lembrar que você pode organizar os scripts como achar melhor, desde que o HTML tenha acesso aos métodos que executam para o funcionamento do filtro.


Filtrar por subcategoria

O Filtro de subcategoria é bem simples de entender, vamos ao código:

{% set categorias = category.subcategories %}
{% set categoriasSelecionadas = _filter('selected', 'categories.id') %}

{% if categorias | length > 0 %}
<div class="filters__list">
<p>Categoria</p>
<ul class="filters__list--options" id="category">
{% for filtro in categorias %}
<li>
<label
onclick="selectFilter(event, { match: { 'categories.id': '{{ filtro.id }}' } })"
for="{{ filtro.name }}"
data-filter="categories.id:{{ filtro.id }}"
>
<input type="checkbox" {{ filtro.id in categoriasSelecionadas ? 'checked' : '' }} id="{{ filtro.name }}">
<div class="filters__checkbox"></div>
<small>{{ filtro.name | raw }}</small>
</label>
</li>
{% endfor %}
</ul>
</div>
{% endif %}

Temos um método extra selectFilter(), ele está sendo declarado na sessão de Métodos essenciais.

A base para poder filtrar por categoria, estão nos dois atributos setados inicialmente pelo código {% set ... %}, onde temos os campos: categorias e categoriasSelecionadas, nas quais estão usando o método padrão da PWA _filter() para trazer as categorias, e também as categorias selecionadas.


Filtrar por cores

Seguindo o mesmo exemplo dos filtros de categoria, vamos falar sobre o filtro de cores, que é um dos filtros mais utilizados na listagem de produtos. Veja no código abaixo:

{% set cores = _filter('get', 'variations.color.values', 'label') %}
{% set coresSelecionadas = _filter('selected', 'variations.color.values.value') %}

{% if cores | length > 0 %}
<div class="filters__list">
<p>Cor</p>
<ul class="filters__list--options" id="color">
{% for filtro in cores %}
<li>
<label
onclick="selectFilter(event, { match: { 'variations.color.values.value': '{{ filtro.value }}' } })"
for="{{ filtro.label }}"
data-filter="variations.color.values.value:{{ filtro.value }}"
>
<div>
<input type="checkbox" {{ filtro.value in categoriasSelecionadas ? 'checked' : '' }} id="{{ filtro.label }}">
<div class="filters__checkbox"></div>
<small>{{ filtro.label | raw }}</small>
</div>
<span class="filters__list--color" style="background-color: {{ filtro.source }}"></span>
</label>
</li>
{% endfor %}
</ul>
</div>
{% endif %}

Iniciamos então a criação dos campos cores e coresSelecionadas através do {% set .. %}, e com a ajuda do método padrão da PWA _filter(), é retornado para esses campos as cores, e as cores selecionados caso haja.

Vale lembrar que o método selectFilter() está sendo declarado em Métodos essenciais.

Aplicando o código acima, teremos o seguinte resultado, vale lembrar que o código está com css aplicado.

info

Vale lembrar que o filtro de cores acima é apenas um exemplo de como é retornado esse campo de "cores" no produto, para descobrir qual o formato correto do seu campo de "cores" basta debugar algum produto na tela de detalhe de produto com {{ _debug(product) }}, dessa forma você irá conseguir descobrir o formato do campo e montar seu filtro corretamente, assim como também montar o filtro de tamanhos por exemplo, que segue o mesmo formato do filtro de cores.

An image


Filtros diversos

Seguindo o padrão de código dos filtros acima, podemos também filtrar por outros campos dos produtos. No exemplo mostrado, vamos criar uma lista de campos que queremos incluir nos filtros das páginas de categoria e pesquisa.

Crie abaixo uma lista (objeto) com o key:value.

O key são os campos que você deseja filtrar, vale reforçar que o key representa o atributo onde está setado o valor real do do filtro, ou seja, existem atributos do produto que são objetos e o seu valor geralmente é encontrado em um "atirbuto filho" chamado .value ficando atributo.value. Você encontrará esses campos debugando algum produto na tela de detalhe de produto usando {{ _debug(product) }}.

O value serão as labels que facilitarão a leitura do usuário daquele filtro.

Seguimos então com o nosso exemplo de filtros diversos. Primeiro vamos criar a nossa lista de filtros atributosParaFiltrar. E na sequência o código que irá trazer os filtros da nossa lista.

{% 
set atributosParaFiltrar = {
'tipo_veiculo.value': 'Tipo de Veículo',
'pneu_marca.value' : 'Marca',
'modelo': 'Modelo',
'produto_largura.value': 'Largura',
'produto_perfil.value': 'Perfil',
'produto_aro.value': 'Aro',
'pneu_vias': 'Tipo de terreno'
}
%}

{% for name, label in atributosParaFiltrar %}
{% set _name = name %}
{% if '.' in name %}
{% set _name = name|split('.') %}
{% set _name = _name[0] %}
{% endif %}

{% set type = '.' in name ? 'value' : '' %}
{% set filtros = _filter('get', _name, type) %}

{% set selecionados = _filter('selected', name) %}

<div>
<h3>{{ label }}</h3>

<ul>
<div>
{% for filtro in filtros %}
{% set filtro_label = filtro.label ? filtro.label : filtro %}
{% set filtro = filtro.value ? filtro.value : filtro %}

<li onclick="selectFilter(event, { match: { '{{ name }}': '{{ filtro|trim }}' } })">
<label data-filter="{{ name }}:{{ filtro|trim }}" for="{{ filtro }}">
<input type="checkbox" id="{{ filtro }}" {{ filtro in selecionados ? 'checked' : '' }}/>
<span>{{ filtro_label | raw }}</span>
</label>
</li>
{% endfor %}
</div>
</ul>
</div>
{% endfor %}

Fazendo uma leitura de todo o código, você verá que não é muito diferente dos filtros de cores e subcategorias, onde temos o mesmo método padrão da PWA _filter() para buscar os filtros, a única diferença é que estamos fazendo tudo de forma dinâmica e mais prática, para caso queira adicionar vários filtros para a sua página de listagem de produtos, basta apenas adicionar na lista do objeto atributosParaFiltrar quantos filtros forem necessários para a sua loja.

Por fim, teremos um resultado similar a este do print abaixo:

An image


Filtros selecionados

Além de listar os filtros precisamos exibir os valores desses filtros marcados. E seguindo a documentação, falamos sobre os filtros de Ordenação, Preços, Subategorias, Cores e Filtros diversos. Você verá que estamos validando cada tipo de filtro para que seja detectado que foram acionados.

Antes de exibir o código que traz os filtros selecionados, vamos criar um atributo chamado window.allFilters, esse atributo recebe os valores criados nas sessões de cada filtro (subcategoria, cores e os filtros diversos).

<script>   
window.allFilters = Object.assign({}, {{ _object(filtrosDinamicos) }});
window.atributosParaFiltrar = {{ _object(atributosParaFiltrar) }};
</script>

Os campos citados ( subcategorias, cores e filtros diversos), são os campos que foram criados pelo {% set .. %} na sessão de seus respectivos filtros, portanto, para que o window.allFilters tenha acesso à eles, é necessário que o código acima esteja posicionado logo abaixo da criação desses filtros, assim, não teremos problemas em buscar todos os filtros da página.

Não há necessidade de chamar os filtros de ordenação e preços dentro de window.allFilters, pois a validação deles dentro de Filtros selecionados é diferente.

Vamos então ao código de Filtros selecionados:

O código abaixo está localizado em Elementos > listing page > marked filters.html (caso você esteja com o projeto inicial criado por padrão pela plataforma).

 {% if matchsList | length > 0 or queryString.range %}
<div class="filters__list filters__list--selected">
<p>Filtrado por:</p>

<ul class="filters__list--options">
{% for filtro in matchsList %}
{% set nomeCompleto = filtro.name ~ ':' ~ filtro.value %}

<li>
<label
onclick="unselectFilter(event, { clean: '{{ nomeCompleto }}' })"
data-filter-tag="{{ nomeCompleto }}"
for="{{ nomeCompleto }}"
>
<input type="checkbox" checked id="{{ nomeCompleto }}">
<div class="filters__checkbox"></div>
<small>{{ filtro.value | raw }}</small>
</label>
</li>
{% endfor %}

{% if queryString.range['prices.sale_price'].gte %}
<li>
<label
for="price-gte"
onclick="unselectFilter(event, { clean: 'range:prices.sale_price:gte' })"
>
<input type="checkbox" checked id="price-gte">
<div class="filters__checkbox"></div>
<small>
>= {{ _price('format', queryString.range['prices.sale_price'].gte) }}
</small>
</label>
</li>
{% endif %}

{% if queryString.range['prices.sale_price'].lte %}
<li>
<label
for="price-lte"
onclick="unselectFilter(event, { clean: 'range:prices.sale_price:lte' })"
>
<input type="checkbox" checked id="price-lte">
<div class="filters__checkbox"></div>
<small>
<= {{ _price('format', queryString.range['prices.sale_price'].lte) }}
</small>
</label>
</li>
{% endif %}
</ul>

<a onclick="unselectFilter(event, { clean: '*' })" class="filters__clean">
Limpar tudo
</a>
</div>
{% endif %}


<script>
var matchsList = {{ matchsList | json_encode | raw }};

_dom('window.allFilters').waitVariable(function () {
matchsList.forEach(filter => {
const fullName = `${filter.name}:${filter.value}`
const elTag = document.querySelector(`*[data-filter-tag="${fullName}"] small`)

if (elTag) {
const attr = filter.name == 'categories.id' ? 'id' : 'value'
const obj = window.allFilters[filter.name].filter(o => o[attr] == filter.value)

if (obj.length > 0) {
elTag.innerHTML = obj[0].label || obj[0].name || elTag.innerHTML
}
}
return;
})
});
</script>

O método _dom() usado no script acima, é um método padrão da PWA que facilita a busca do elemento passado por parâmetro, e nele podem ser utilizados alguns callbacks como waitVariable que aguarda a variável ser encontrada e alimentada para que possa executar alguma ação.

O atributo matchsList vem por padrão da PWA, então foi criado uma cópia do mesmo no script sendo: var matchsList = {{ matchsList | json_encode | raw }};, e através dessa cópia foram feitos os tratamentos para listar todos os tipos de filtros exceto preços e ordenação, pois ambos tem um tratamento diferente dos demais filtros, mas você também pode notar o formato dos filtros pelos códigos acima, verá que temos as mesmas chamadas para os filtros de subcategoria, cores, filtros diversos.

Vale lembrar que temos alguns métodos como unselectFilter() que foram declarados na sessão Métodos essenciais.

Listagem de produtos

Chegamos então na parte principal das páginas de category.html e search.html, que seria a listagem dos produtos. É muito simples, veja o código abaixo:

{% if products | length > 0 %}
{% for product in products %}
{% include 'cartao de produto' %}
{% endfor %}
{% else %}
<p class="empty">Sem produtos para essa busca :c</p>
{% endif %}

O código acima mostra o objeto products como principal sujeito da busca dos produtos. Esse atributo é padrão da PWA, basta você inserir no código HTML que será reconhecido automáticamente. Inserindo o products em uma estrutura de repetição {% for.. %} representaremos então cada produto da estrutura com o atributo product, assim podemos fazer a listagem dos produtos através desse campo.

Feito isso, basta chamarmos o elemento cartao de produto, para que ele possa utilizar o campo product e exibir as informações do produto.

Aviso

É muito importante que o nome do atributo seja product para mater o padrão e o elemento cartao de produto reconheça e passe a usar o campo para listar os dados do produto.

Botão de carregar mais

A listagem de produtos traz a quantidade de produtos que você configurou no seu painel da PWA em Configurações > Avançado. Então precisamos aplicar uma paginação para que o cliente possa acessar todos os produtos da busca.

Vamos ao código:

{% set loadMoreFilter = { page: pagination.next, append: 'listagem/produtos do resultado' } %}

<!-- Botão carregar mais produtos.. -->
{% if pagination.current < pagination.pages %}
<div class="load-more">
<a onclick="searchMore()">
Carregar mais
</a>
</div>
{% endif %}

<script>
function searchMore() {
_refresh(window.elementosParaAtualizar, {{ _object(loadMoreFilter) }});
}
</script>

Iniciamos com a criação de um atributo chamado loadMoreFilter que basicamente recebe o page: pagination.next e append: 'produtos/listagem do resultado'.

O pagination é um objeto padrão da PWA, portanto pode ser usado sem a necessidade de declarar em seu código. Com ele, estamos fazendo uma validação para detectar se a página atual é menor do que a ultima página da listagem de produtos, caso seja, não há mais a necessidade de aplicar o botão de 'Carregar mais'.

O append recebe o caminho do elemento listagem/produtos do resultado que irá ser atualizado quando a busca do searchMore for executada.

Além disso, temos o método que criamos chamado searchMore(), dentro dele temos o _refresh(), que é responsável por atualizar o conteúdo da página trazendo os novos produtos, com a ajuda do campo window.elementosParaAtualizar, juntamente com o atributo loadMoreFilter que traz o objeto com os filtros para que o _refresh entenda o que precisa ser buscado para trazer os resultados. O método _object faz apenas o tratamento do objeto loadMoreFilter (semelhante à um JSON.parse) para enviá-lo ao método _refresh.

Lembrando que window.elementosParaAtualizar está sendo chamado no Array de elementos.