Skip to main content

Categoria e Pesquisa

Seguindo o mesmo padrão das telas de Home e Detalhe de produto, as páginas 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 funcionalidades essenciais para as telas de listagens. Algumas delas não serão compatíveis em ambas páginas, mas apontaremos quando não houver compatibilidade.

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 realiza outras ações que afetam a listagem de produtos, é necessário que a página mantenha a sincronia de elementos como paginação, filtros, contador de resultados, filtros já marcados e listagem de produtos. Nesse caso, temos cinco elementos importantes que foram importados nas 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 exista nenhum elemento, não há necessidade de criar esse campo, mas é 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.

Adicionamos 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 importância a aplicação do código acima nas páginas de listagem.

O breadcrumb funcionará apenas na página category.html, uma vez que ele trará o caminho de categorias e subcategorias. Na página search.html temos apenas a busca do cliente e não um caminho de fato, não sendo possível a aplicação do breadcrumb.

<div class="breadcrumb container">
<a {{ _href('/') }}>Home</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. A lista dos 'caminhos' do breadcrumb é percorrida por {% 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 as validações dos itens do breadcrumb.

Título da página

Existem 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, utilizamos o atributo {{ category.name }}
  <h1>{{ category.name }}</h1>
  • search.html: para a tela de busca, utilizamos 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 }} é 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

Filtros são uma parte importante da tela de listagem. Vamos mostrar um pouco sobre o 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 filtros nessa página, que o método _filter() aparecerá diversas vezes. Ele é 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 que poderão ser utilizados em seu HTML como mostra o exemplo acima. Indo um pouco mais a fundo, o _filter() possui alguns parâmetros importantes, e vamos explicar sobre cada um deles abaixo:

  • TIPO: Temos alguns tipos que podem ser utilizados na busca dos filtros, são eles:

    • get: Faz a busca padrão retornando uma lista de filtros de determinado atributo. Por 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. Por 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. Um 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 de uma categoria em específico. Por exemplo: camisetas com o filtro da cor "azul", já seriam selecionadas caso fosse detectado que já existe esse filtro marcado na URL, ou seja, a busca dos filtros será baseada nos resultados da categoria/termo_de_busca da 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 a 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: "cores", "tamanhos", entre outros. Para ficar mais fácil, você pode aplicar o código {{_debug(product)}} na página de detalhe de produto. Ao salvar você poderá ver na página do seu produto um botão chamado "debug". Clique no botão e você verá a lista de atributos disponíveis para serem 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"
    },
    ]

    Podemos dizer ao nosso motor de busca qual campo deve ser ultilizado e não retornarmos filtros duplicados. Por exemplo: podemos usar como base o atributo value do objeto, uma vez que ele identifica melhor as cores do que os valores hexadecimais em 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, mesmo que ambos sejam White.

Métodos essenciais

Antes de começarmos a falar sobre os filtros em particular, temos aqui alguns métodos que são necessários para a seleção dos filtros. Geralmente esses métodos são chamados nos scripts e importados nas páginas category.html e search.html. Vamos disponibilizá-los aqui para que você possa importá-los nas páginas de listagem 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 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 no formato de um 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, aplicamos 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 é um objeto { order: { 'name.raw': 'asc' } } 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 usamos o método _refresh(), que tem dois parâmetros: o elementosParaAtualizar, que são os elementos atualizados na tela após o filtro ser executado e 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 no filtro de ordenação. Lembrando que é necessário estilizar com css o html exibido acima.

Filtros de ordenação

Filtrar por preço

O filtro de preço é um pouco mais trabalhoso, mas não há o que temer. Nesse caso utilizaremos um multirange com <input> do tipo range:

Lembramos que o elemento de filtro de preço está localizado em Elementos > listing page > price filter.html.

{% 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 atributos fundamentais para o funcionamento do filtro de preço, são eles: maiorPreco e menorPreco, declarados antes do código HTML através de um {% set.. %}. Note que neles há a chamada do método padrão da PWA _filter(), onde é feito 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 corretamente.

O método _price() é padrão da PWA. Ele auxilia na busca do preço dos produtos, portanto pode ser usado em qualquer lugar. Assim 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 o funcionamento dos filtros.

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 %}

Aqui temos um método extra selectFilter(). Ele está sendo declarado na sessão métodos essenciais.

A base para poder filtrar por categorias, está nos atributos setados inicialmente pelo código {% set ... %}, onde temos os campos: categorias e categoriasSelecionadas, que utilizam 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 o 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(), retornamos para esses campos as cores e as cores selecionados caso existam.

É importante lembrarmos que o método selectFilter() está sendo declarado em métodos essenciais.

Aplicando o código acima e uma estilização css, teremos um resultado semelhante a este:

Filtro de cores

info

O filtro acima é apenas um exemplo de como podem estar sendo retornadas essas "cores" no produto. Para descobrir o formato correto do seu campo "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.

Filtros diversos

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

A lista (objeto) será criada utilizando o formato key:value.

A key são os campos que você deseja filtrar. Vale reforçar que a key representa o atributo onde está setado o valor real do filtro. Existem atributos do produto que são objetos e o seu valor geralmente é encontrado em um "atributo 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 daquele filtro pelo usuário.

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 quando queremos adicionar vários filtros na página de listagem de produtos, bastando apenas adicionar na lista do objeto atributosParaFiltrar os filtros necessários para a sua loja.

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

Filtros diversos

Filtros selecionados

Além de listar os filtros precisamos também exibir os filtros marcados. Seguindo a documentação, falamos sobre os filtros de ordenação, preços, subcategorias, cores e filtros diversos. Você verá que estamos validando cada tipo de filtro para que sejam corretamentes adicionados aos filtros marcados.

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 campos 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 ao 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 utilizando 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. 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 criamos uma cópia dele 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. Você também pode notar o formato dos filtros nos códigos acima, observando 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 à parte principal das páginas category.html e search.html, que é 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 o principal elemento para a busca dos produtos. Esse objeto é padrão da PWA, basta você inserí-lo no código HTML e ele será reconhecido automaticamente. 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 utilizando esse 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 manter o padrão e para que o elemento cartao de produto possa reconhecer e usar o campo para listar os dados do produto.

Botão 'carregar mais'

A listagem de produtos traz a quantidade de produtos que você configurou no seu painel da PWA em Layout > Configurações > Catálogo. Precisamos então 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: 'listing page/results products' } %}

<!-- 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 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 declará-lo em seu código. Com ele, estamos fazendo uma validação para detectar se a página atual é menor do que a última página da listagem de produtos, caso seja, não há mais a necessidade de aplicar o botão 'carregar mais'.

O append recebe o caminho do elemento listing page/results products 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 e o atributo loadMoreFilter que traz o objeto com os filtros, para que o _refresh entenda o que precisa ser buscado e traga 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 na array de elementos.