Filtros e ordenamiento de produtos

Neste tutorial, veremos como adicionar filtros de categoria e propriedade, bem como a capacidade de ordenar produtos.

Entre as propriedades a filtrar estão:

  • Variantes do produto
  • Marca
  • Preço
  • Campos personalizados

Caso você tenha interesse apenas em ter o filtro de preços, recomendamos que consulte este artigo.

HTML

1. A primeira coisa que faremos é atualizar o código snipplet filters.tpl dentro da pasta snipplets/grid com o seguinte código:

{% if applied_filters %}
    
    {# Applied filters chips #}

    {% if has_applied_filters %}
        <div class="col-12 mb-3">
            <div class="d-md-inline-block mr-md-2 mb-3">{{ 'Filtrado por:' | translate }}</div>
            {% for product_filter in product_filters %}
                {% for value in product_filter.values %}

                    {# List applied filters as tags #}
                    
                    {% if value.selected %}
                        <button class="js-remove-filter chip" data-filter-name="{{ product_filter.key }}" data-filter-value="{{ value.name }}">
                            {{ value.pill_label }}
                            {% include "snipplets/svg/times.tpl" with {svg_custom_class: "icon-inline chip-remove-icon"} %}
                        </button>
                    {% endif %}
                {% endfor %}
            {% endfor %}
            <a href="#" class="js-remove-all-filters d-inline-block px-0">{{ 'Borrar filtros' | translate }}</a> 
        </div>
    {% endif %}
{% else %}
    {% if product_filters is not empty %}
        <div id="filters" data-store="filters-nav">
            {% for product_filter in product_filters %}
                {% if product_filter.type == 'price' %}

                    {{ component(
                        'price-filter',
                        {'group_class': 'filters-container mb-5', 'title_class': 'h6 mb-3', 'button_class': 'btn btn-default px-2 px-md-3 align-bottom' }
                    ) }}

                {% else %}
                    {% if product_filter.has_products %}
                        <div class="filters-container mb-5" data-store="filters-group">
                            <h6 class="mb-3">{{product_filter.name}}</h6>
                            {% set index = 0 %}
                            {% for value in product_filter.values %}
                                {% if value.product_count > 0 %}
                                    {% set index = index + 1 %}
                                    <label class="js-filter-checkbox {% if not value.selected %}js-apply-filter{% else %}js-remove-filter{% endif %} checkbox-container font-weight-bold {% if mobile %}mb-3{% else %}mb-2{% endif %}" data-filter-name="{{ product_filter.key }}" data-filter-value="{{ value.name }}">
                                        <span class="checkbox">
                                            <input type="checkbox" autocomplete='off' {% if value.selected %}checked{% endif %}>
                                            <span class="checkbox-icon"></span>
                                            <span class="checkbox-text">{{ value.name }} ({{ value.product_count }})</span>
                                            {% if product_filter.type == 'color' and value.color_type == 'insta_color' %}
                                                <span class="checkbox-color" style="background-color: {{ value.color_hexa }};"></span>
                                            {% endif %}
                                        </span>
                                    </label>
                                    {% if index == 8 and product_filter.values_with_products > 8 %}
                                        <div class="js-accordion-container" style="display: none;">
                                    {% endif %}
                                {% endif %}
                                {% if loop.last and product_filter.values_with_products > 8 %}
                                    </div>
                                    <a href="#" class="js-accordion-toggle btn-link d-inline-block mt-1 pl-0">
                                        <span class="js-accordion-toggle-inactive">
                                            {{ 'Ver todos' | translate }}
                                        </span>
                                        <span class="js-accordion-toggle-active" style="display: none;">
                                            {{ 'Ver menos' | translate }}
                                        </span>
                                    </a>
                                {% endif %}
                            {% endfor %}
                        </div>
                    {% endif %}
                {% endif %}
            {% endfor %}
        </div>
    {% endif %}
{% endif %}

2. Em seguida, vamos criar um novo snipplet categories.tpl dentro da mesma pasta snipplets/grid, onde usamos o seguinte código:

{% if filter_categories %}
    <div class="filters-container mb-5">
        <h6 class="mb-3">{{ "Categorías" | translate }}</h6>
        <ul class="list-unstyled"> 
            {% for category in filter_categories %}
                <li data-item="{{ loop.index }}" class="mb-3">
                    <a href="{{ category.url }}" title="{{ category.name }}" class="text-primary">
                        {{ category.name }}
                    </a>
                </li>

                {% if loop.index == 8 and filter_categories | length > 8 %}
                    <div class="js-accordion-container" style="display: none;">
                {% endif %}
                {% if loop.last and filter_categories | length > 8 %}
                    </div>
                    <a href="#" class="js-accordion-toggle btn-link d-inline-block mt-1 pl-0">
                        <span class="js-accordion-toggle-inactive">
                            {{ 'Ver más' | translate }}
                        </span>
                        <span class="js-accordion-toggle-active" style="display: none;">
                            {{ 'Ver menos' | translate }}
                        </span>
                    </a>
                {% endif %}
            {% endfor %}
        </ul>
    </div>
{% endif %}

3. Agora precisamos criar o snipplet para o componente modal ou pop-up dentro da pasta de snipplets. Este tpl é denominado modal.tpl e o código é:

{# /*============================================================================
  #Modal
==============================================================================*/
 
#Properties
    // ID
    // Position - Top, Right, Bottom, Left
    // Transition - Slide and Fade
    // Width - Full and Box
    // modal_form_action - For modals that has a form
 
 
#Head
    // Block - modal_head
#Body
    // Block - modal_body
#Footer
    // Block - modal_footer
 
#}
 
{% set modal_overlay = modal_overlay | default(true) %}
 
<div id="{{ modal_id }}" class="js-modal {% if modal_mobile_full_screen %}js-fullscreen-modal{% endif %} modal modal-{{ modal_class }} modal-{{modal_position}} transition-{{modal_transition}} modal-{{modal_width}} transition-soft" style="display: none;">
    {% if modal_form_action %}
    <form action="{{ modal_form_action }}" method="post" class="{{ modal_form_class }}" {% if modal_form_hook %}data-store="{{ modal_form_hook }}"{% endif %}>
    {% endif %}
    <div class="js-modal-close {% if modal_mobile_full_screen %}js-fullscreen-modal-close{% endif %} modal-header">
        <span class="modal-close">
            {% include "snipplets/svg/times.tpl" with {svg_custom_class: "icon-inline svg-icon-text"} %}
        </span>
        {% block modal_head %}{% endblock %}
    </div>
    <div class="modal-body">
        {% block modal_body %}{% endblock %}
    </div>
    {% if modal_footer %}
        <div class="modal-footer d-md-block">
            {% block modal_foot %}{% endblock %}
        </div>
    {% endif %}
    {% if modal_form_action %}
    </form>
    {% endif %}
</div>

4. Vamos adicionar o arquivo sort-by.tpl dentro da pasta snipplets/grid para a funcionalidade de ordenamento dos produtos.

{% set sort_text = {
'user': 'Destacado',
'price-ascending': 'Precio: Menor a Mayor',
'price-descending': 'Precio: Mayor a Menor',
'alpha-ascending': 'A - Z',
'alpha-descending': 'Z - A',
'created-ascending': 'Más Viejo al más Nuevo',
'created-descending': 'Más Nuevo al más Viejo',
'best-selling': 'Más Vendidos',
} %}
{% embed "snipplets/forms/form-select.tpl" with{select_label: false, select_custom_class: 'js-sort-by', select_group_custom_class: 'mb-0', select_aria_label: 'Ordenar por:' | translate } %}
    {% block select_options %}
        {% for sort_method in sort_methods %}
            {# This is done so we only show the user sorting method when the user chooses it #}
            {% if sort_method != 'user' or category.sort_method == 'user' %}
                <option value="{{ sort_method }}" {% if sort_by == sort_method %}selected{% endif %}>{{ sort_text[sort_method] | t }}</option>
            {% endif %}
        {% endfor %}
    {% endblock select_options%}
{% endembed %}

5. Para usar o snipplet que criamos no passo anterior, teremos que criar um novo componente para o select ou dropdown. Para isso, criamos o arquivo form-select.tpl dentro da pasta snipplets/forms com o seguinte código

{# /*============================================================================
  #Form select
==============================================================================*/
 
#Properties
 
#Group
    //select_group_custom_class for custom CSS classes
#Label 
    // select_label_name for name
    // select_label_id for ID
    // select_for for label for
    // select_label_custom_class for custom CSS classes
#Select 
    // select_id for id
    // select_name for name
    // select_custom_class for custom CSS classes 
    // input_rows for textarea rows
    // select_options to insert select options
    // select_aria_label for aria-label attribute
 
#}
 
<div class="form-group {{ select_group_custom_class }}">
    {% if select_label %}
        <label {% if select_label_id%}id="{{ select_label_id }}"{% endif %} class="form-label {{ select_label_custom_class }}" {% if select_for %}for="{{ select_for }}"{% endif %}>{{ select_label_name }}</label>
    {% endif %}
    <select 
        {% if select_id %}id="{{ select_id }}"{% endif %}
        class="form-select {{ select_custom_class }} {% if select_inline %}form-control-inline{% endif %}"
        {% if select_data %}data-{{select_data}}="{{select_data_value}}"{% endif %}
        {% if select_name %}name="{{ select_name }}"{% endif %}
        {% if select_aria_label %}aria-label="{{ select_aria_label }}"{% endif %}>
        {% block select_options %}
        {% endblock select_options %}
    </select>
    <div class="form-select-icon">
        {% include "snipplets/svg/chevron-down.tpl" with {svg_custom_class: "icon-inline icon-w-14 icon-lg svg-icon-text"} %}
    </div>
</div>

6. Quase terminado vamos incluir os dois arquivos criados nas etapas 1 e 2, no template category.tpl por meio do modal criado na etapa 3. Acima da parte onde incluímos a grade de produtos, que começa com {% if products %}, incluiremos o seguinte:

{% set has_filters_available = products and has_filters_enabled and (filter_categories is not empty or product_filters is not empty) %}
<div class="js-category-controls-prev category-controls-sticky-detector"></div>
<div class="js-category-controls row align-items-center mb-md-3 category-controls">
    {% if products %}
        {% set columns = settings.grid_columns %}
        <div class="col-6{% if columns == 2 %} col-md-9{% else %} col-md-9{% endif %}">
        {% if has_filters_available %}
            <a href="#" class="js-modal-open filter-link" data-toggle="#nav-filters">
                {{ 'Filtrar' | t }} {% include "snipplets/svg/filter.tpl" with {svg_custom_class: "icon-inline icon-w-16"} %} 
            </a>           
            {% embed "snipplets/modal.tpl" with{modal_id: 'nav-filters', modal_class: 'filters modal-docked-small', modal_position: 'left', modal_transition: 'slide', modal_width: 'full'} %}
                {% block modal_head %}
                    {{'Filtros' | translate }}
                {% endblock %}
                {% block modal_body %}
                    {% if filter_categories is not empty %}
                        {% snipplet "grid/categories.tpl" %}
                    {% endif %}
                    {% if product_filters is not empty %}
                        {% snipplet "grid/filters.tpl" %}
                    {% endif %}
                    <div class="js-filters-overlay filters-overlay" style="display: none;">
                        <div class="filters-updating-message">
                            <h3 class="js-applying-filter" style="display: none;">{{ 'Aplicando filtro...' | translate }}</h3>
                            <h3 class="js-removing-filter" style="display: none;">{{ 'Borrando filtro...' | translate }}</h3>
                        </div>
                    </div>
                {% endblock %}
            {% endembed %}
        {% endif %}
        </div>
        <div class="col-6{% if columns == 2 %} col-md-3{% else %} col-md-3{% endif %} text-right">
            {% include 'snipplets/grid/sort-by.tpl' %}
        </div>
    {% endif %}
</div>
<div class="row">
    {% include "snipplets/grid/filters.tpl" with {applied_filters: true} %}
</div>    

7. Por fim, para a parte HTML, dentro da pasta snipplets/SVG adicionaremos os SVGs que usamos para o select, o ícone de filtros e o modal.

times.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>

chevron-left.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><path d="M231.293 473.899l19.799-19.799c4.686-4.686 4.686-12.284 0-16.971L70.393 256 251.092 74.87c4.686-4.686 4.686-12.284 0-16.971L231.293 38.1c-4.686-4.686-12.284-4.686-16.971 0L4.908 247.515c-4.686 4.686-4.686 12.284 0 16.971L214.322 473.9c4.687 4.686 12.285 4.686 16.971-.001z"/></svg>

chevron-down.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M441.9 167.3l-19.8-19.8c-4.7-4.7-12.3-4.7-17 0L224 328.2 42.9 147.5c-4.7-4.7-12.3-4.7-17 0L6.1 167.3c-4.7 4.7-4.7 12.3 0 17l209.4 209.4c4.7 4.7 12.3 4.7 17 0l209.4-209.4c4.7-4.7 4.7-12.3 0-17z"/></svg>

filter.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M463.952 0H48.057C5.419 0-16.094 51.731 14.116 81.941L176 243.882V416c0 15.108 7.113 29.335 19.2 40l64 47.066c31.273 21.855 76.8 1.538 76.8-38.4V243.882L497.893 81.941C528.042 51.792 506.675 0 463.952 0zM288 224v240l-64-48V224L48 48h416L288 224z"/></svg>

CSS

Requisito:

Ter adicionado helper classes em seu layout. Você pode seguir este pequeno tutorial para fazer isso (é só copiar e colar algumas classes, não leva mais que 1 minuto).

1. Adicionamos o seguinte SASS de cores em style-colors.scss.tpl (ou na stylesheet do seu layout que possui as cores e fontes da loja). Lembre-se de que as variáveis de cores e fontes podem variar em relação ao seu layout:

{# /* // Mixins */ #}
{# This mixin adds browser prefixes to a CSS property #}
@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
      #{'-' + $prefix + '-' + $property}: $value;
  }
    #{$property}: $value;
}
 
{# /* // Links */ #}
 
a {
  color: $main-foreground;
  fill: $main-foreground;
  @include prefix(transition, all 0.4s ease, webkit ms moz o);
  &:hover,
  &:focus{
    color: rgba($main-foreground, .5);
    fill: rgba($main-foreground, .5);
  }
}
 
.link-contrast {
  color: $main-background;
  fill: $main-background;
  &:hover,
  &:focus{
    color: rgba($main-background, .8);
    fill: rgba($main-background, .8);
  }
}
 
.btn-link{
  color: $primary-color;
  fill: $primary-color;
  text-transform: uppercase;
  border-bottom: 1px solid;
  font-weight: bold;
  cursor: pointer;
  &:hover,
  &:focus{
    color: rgba($primary-color, .5);
    fill: rgba($primary-color, .5);
  }
}
 
{# /* // Chips */ #}
 
.chip {
  color: $main-foreground;
  background: rgba($main-foreground,0.1);
  border: 0;
 
  &-remove-icon {
    fill: $main-foreground;
  }
}
 
{# /* // Modals */ #}
 
.modal{
  color: $main-foreground;
  background-color:$main-background;
}
 
{# /* // Forms */ #}
 
input,
textarea {
  font-family: $body-font;
}
 
.form-control {
  display: block;
  padding: 8px;
  width: 100%;
  font-size: 16px; /* Hack to avoid autozoom on IOS */
  border: 0;
  border-bottom: 1px solid rgba($main-foreground, .5);
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  color: $main-foreground;
  background-color: $main-background;
  &:focus{
    outline: 0;
  }
  &-inline{
    display: inline;
  }
}
 
.form-control::-webkit-input-placeholder { 
  color: $main-foreground;
}
.form-control:-moz-placeholder {
  color: $main-foreground;
}
.form-control::-moz-placeholder {
  color: $main-foreground;
}
.form-control:-ms-input-placeholder {
  color: $main-foreground;
}
 
.form-select{
  display: block;
  padding: 10px 0;
  width: 100%;
  font-size: 16px; /* Hack to avoid autozoom on IOS */
  border: 0;
  border-bottom: 1px solid rgba($main-foreground, .5);
  border-radius: 0;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  color: $main-foreground;
  background-color: $main-background;
  @extend %body-font;
  &-icon{
    background: $main-background;
  }
}
 
.checkbox-container{
  .checkbox {
    color: $main-foreground;
    &-color {
      border: 1px solid rgba($main-foreground,.1);
    }
    &-icon {
      background: $main-background;
      border: 1px solid $main-foreground;
      &:after {
        border: solid $main-foreground;
        border-width: 0 2px 2px 0;
      }
    }
    &:hover {
      color: rgba($main-foreground,.8);
    }
  }
}

2. Adicione os estilos no arquivo static/style-critical.tpl

Se em seu layout você usar um stylesheet para o CSS crítico, precisaremos do seguinte código dentro dele, do contrário, você pode unificar o CSS dos passos 2 e 3 em um único arquivo.

{# /* // Forms */ #}
 
.form-group {
  position: relative;
  width: 100%;
}
.form-group .form-select-icon{
  position: absolute;
  bottom: 12px;
  right: 0;
  pointer-events: none;
}
 
{# /* // Category header */ #}
 
.filter-link {
  display: inline-block;
  width: 100%;
  padding: 10px 0;
}

3.  Adicione os estilos no arquivo static/style-async.tpl

Se em seu layout você usar um stylesheet CSS assíncrono, precisaremos do seguinte código dentro dela, do contrário, você pode unificar o CSS dos passos 2 e 3 em um único arquivo.

{# /* // Mixins */ #}
 
{# This mixin adds browser prefixes to a CSS property #}
 
@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
    #{'-' + $prefix + '-' + $property}: $value;
  }
  #{$property}: $value;
}
 
{# /* // Modals */ #}
 
.modal {
  position: fixed;
  top: 0;
  display: block;
  width: 80%;
  height: 100%;
  padding: 10px;
  -webkit-overflow-scrolling: touch;
  overflow-y: auto;
  transition: all .2s cubic-bezier(.16,.68,.43,.99);
  z-index: 20000;
  &-header{
    width: calc(100% + 20px);
    margin: -10px 0 10px -10px;
    padding: 10px 15px;
    font-size: 20px;
  }
  &-footer{
    padding: 10px;
    clear: both;
  }
  &-full {
    width: 100%;
  }
  &-docked-md{
    width: 100%;
  }
  &-docked-small{
    width: 80%;
  }
  &-top{
    top: -100%;
    left: 0;
  }
  &-bottom{
    top: 100%;
    left: 0;
  }
  &-left{
    left: -100%;
  }
  &-right{
    right: -100%;
  }
  &-centered{
    height: 100%;
    width: 100%;
  }
  &-top.modal-show,
  &-bottom.modal-show {
    top: 0;
  }
  &-bottom-sheet {
    top: initial;
    bottom: -100%;
    height: auto;
    &.modal-show {
      top: initial;
      bottom: 0;
      height: auto;
    }
  }
  &-left.modal-show {
    left: 0;
  }
  &-right.modal-show {
    right: 0;
  }
  &-close { 
    display: inline-block;
    padding: 1px 5px 5px 0;
    margin-right: 5px;
    vertical-align: middle;
    cursor: pointer;
  }
  .tab-group{
    margin:  0 -10px 20px -10px;
  }
}
 
.modal-overlay{
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #00000047;
  z-index: 10000;
}
 
{# /* // Forms */ #}
 
.form-group{
  @extend %element-margin;
  .form-label{
    float: left;
    width: 100%;
    margin-bottom: 10px;
  }
  .alert{
    margin: 10px 0 0 0;
  }
}
 
.checkbox-container{
  .checkbox {
    position: relative;
    display: block;
    margin-bottom: 15px;
    padding-left: 30px;
    line-height: 20px;
    cursor: pointer;
    @include prefix(user-select, none, webkit ms moz o);
 
    &-color {
      display: inline-block;
      width: 10px;
      height: 10px;
      margin: 0 0 2px 5px;
      vertical-align: middle;
      border-radius: 100%;
    }
 
    input {
      display: none;
      &:checked ~ .checkbox-icon:after {
        display: block;
      }
    }
 
    &-icon {
      position: absolute;
      top: -1px;
      left: 0;
      width: 20px;
      height: 20px;
 
      &:after {
        position: absolute;
        top: 1px;
        left: 6px;
        display: none;
        width: 7px;
        height: 12px;
        content: '';
        @include prefix(transform, rotate(45deg), webkit ms moz o);
      }
    }
  }
}
 
.form-select {
  display: block;
  width: 100%;
  &:focus{
    outline:0;
  }
  &::-ms-expand {
    display: none;
  }
}

.filter-input-price-container {
  display: inline-block;
  width: 85px;
  margin-right: 5px;
  .filter-input-price {
    padding: 10px;
  }
}
 
{#/*============================================================================
  #Media queries
==============================================================================*/ #}
 
{# /* // Min width 768px */ #}
 
@media (min-width: 768px) { 
 
  {# /* //// Components */ #}
 
  {# /* Modals */ #}
 
  .modal{
    &-centered{
      height: 80%;
      width: 80%;
      left: 10%;
      margin: 5% auto;
    }
    &-docked-md{
      width: 500px;
      &-centered{
        left: calc(50% - 250px);
        bottom: auto;
        height: auto;
      }
    }
    &-bottom-sheet {
      top: 100%;
      &.modal-show {
        top: 0;
        bottom: auto;
      }
    }
    &-docked-small{
      width: 350px;
    }
  }
}

JS

⚠️ A partir do dia 30 de janeiro de 2023, a biblioteca jQuery será removida do código de nossas lojas, portanto, a função "$" não poderá ser utilizada.

JavaScript precisam ser adicionados no arquivo store.js.tpl (ou onde você tem suas funções JS). O código que precisamos é o seguinte:

{#/*============================================================================
  #Modals
==============================================================================*/ #}
 
{# Full screen mobile modals back events #}

if (window.innerWidth < 768) {

    {# Clean url hash function #}

    cleanURLHash = function(){
        const uri = window.location.toString();
        const clean_uri = uri.substring(0, uri.indexOf("#"));
        window.history.replaceState({}, document.title, clean_uri);
    };

    {# Go back 1 step on browser history #}

    goBackBrowser = function(){
        cleanURLHash();
        history.back();
    };

    {# Clean url hash on page load: All modals should be closed on load #}

    if(window.location.href.indexOf("modal-fullscreen") > -1) {
        cleanURLHash();
    }

    {# Open full screen modal and url hash #}

    jQueryNuvem(document).on("click", ".js-fullscreen-modal-open", function(e) {
        e.preventDefault();
        var modal_url_hash = jQueryNuvem(this).data("modalUrl");
        window.location.hash = modal_url_hash;
    });

    {# Close full screen modal: Remove url hash #}

    jQueryNuvem(document).on("click", ".js-fullscreen-modal-close", function(e) {
        e.preventDefault();
        goBackBrowser();
    });

    {# Hide panels or modals on browser backbutton #}

    window.onhashchange = function() {
        if(window.location.href.indexOf("modal-fullscreen") <= -1) {

            {# Close opened modal #}

            if(jQueryNuvem(".js-fullscreen-modal").hasClass("modal-show")){

                {# Remove body lock only if a single modal is visible on screen #}

                if(jQueryNuvem(".js-modal.modal-show").length == 1){
                    jQueryNuvem("body").removeClass("overflow-none");
                }

                var $opened_modal = jQueryNuvem(".js-fullscreen-modal.modal-show");
                var $opened_modal_overlay = $opened_modal.prev();

                $opened_modal.removeClass("modal-show");
                setTimeout(() => $opened_modal.hide(), 500);
                $opened_modal_overlay.fadeOut(500);

            }
        }
    }
}

jQueryNuvem(document).on("click", ".js-modal-open", function(e) {
    e.preventDefault(); 
    var modal_id = jQueryNuvem(this).data('toggle');
    var $overlay_id = jQueryNuvem('.js-modal-overlay[data-modal-id="' + modal_id + '"]');
    if (jQueryNuvem(modal_id).hasClass("modal-show")) {
        let modal = jQueryNuvem(modal_id).removeClass("modal-show");
        setTimeout(() => modal.hide(), 500);
    } else {

        {# Lock body scroll if there is no modal visible on screen #}
        
        if(!jQueryNuvem(".js-modal.modal-show").length){
            jQueryNuvem("body").addClass("overflow-none");
        }
        $overlay_id.fadeIn(400);
        jQueryNuvem(modal_id).detach().appendTo("body");
        $overlay_id.detach().insertBefore(modal_id);
        jQueryNuvem(modal_id).show().addClass("modal-show");
    }             
});

jQueryNuvem(document).on("click", ".js-modal-close", function(e) {
    e.preventDefault();  
    {# Remove body lock only if a single modal is visible on screen #}

    if(jQueryNuvem(".js-modal.modal-show").length == 1){
        jQueryNuvem("body").removeClass("overflow-none");
    }
    var $modal = jQueryNuvem(this).closest(".js-modal");
    var modal_id = $modal.attr('id');
    var $overlay_id = jQueryNuvem('.js-modal-overlay[data-modal-id="#' + modal_id + '"]');
    $modal.removeClass("modal-show");
    setTimeout(() => $modal.hide(), 500);
    $overlay_id.fadeOut(500);

    {# Close full screen modal: Remove url hash #}

    if ((window.innerWidth < 768) && (jQueryNuvem(this).hasClass(".js-fullscreen-modal-close"))) {
        goBackBrowser();
    }    
});

jQueryNuvem(document).on("click", ".js-modal-overlay", function(e) {
    e.preventDefault();
    {# Remove body lock only if a single modal is visible on screen #}

    if(jQueryNuvem(".js-modal.modal-show").length == 1){
        jQueryNuvem("body").removeClass("overflow-none");
    }
    var modal_id = jQueryNuvem(this).data('modalId');
    let modal = jQueryNuvem(modal_id).removeClass("modal-show");
    setTimeout(() => modal.hide(), 500); 
    jQueryNuvem(this).fadeOut(500);   

    if (jQueryNuvem(this).hasClass("js-fullscreen-overlay") && (window.innerWidth < 768)) {
        cleanURLHash();
    }
});
 
{#/*============================================================================
  #Product grid
==============================================================================*/ #}
 
var $category_controls = jQueryNuvem(".js-category-controls");
var mobile_nav_height = jQueryNuvem(".js-head-main").innerHeight();

{% if template == 'category' %}

    {# /* // Fixed category controls */ #}

    if (window.innerWidth < 768) {
        {% if settings.head_fix %}
            $category_controls.css("top" , mobile_nav_height.toString() + 'px');
        {% else %}
            jQueryNuvem(".js-category-controls").css("top" , "0px");
        {% endif %}

        {# Detect if category controls are sticky and add css #}

        var observer = new IntersectionObserver(function(entries) {
            if(entries[0].intersectionRatio === 0)
                jQueryNuvem(".js-category-controls").addClass("is-sticky");
            else if(entries[0].intersectionRatio === 1)
                jQueryNuvem(".js-category-controls").removeClass("is-sticky");
            }, { threshold: [0,1] 
        });

        observer.observe(document.querySelector(".js-category-controls-prev"));
    }

    {# /* // Filters */ #}

    jQueryNuvem(document).on("click", ".js-apply-filter, .js-remove-filter", function(e) {
        e.preventDefault();
        var filter_name = jQueryNuvem(this).data('filterName');
        var filter_value = jQueryNuvem(this).data('filterValue');
        if(jQueryNuvem(this).hasClass("js-apply-filter")){
            jQueryNuvem(this).find("[type=checkbox]").prop("checked", true);
            LS.urlAddParam(
                filter_name,
                filter_value,
                true
            );
        }else{
            jQueryNuvem(this).find("[type=checkbox]").prop("checked", false);
            LS.urlRemoveParam(
                filter_name,
                filter_value
            );
        }

        {# Toggle class to avoid adding double parameters in case of double click and show applying changes feedback #}

        if (jQueryNuvem(this).hasClass("js-filter-checkbox")){
            if (window.innerWidth < 768) {
                jQueryNuvem(".js-filters-overlay").show();
                if(jQueryNuvem(this).hasClass("js-apply-filter")){
                    jQueryNuvem(".js-applying-filter").show();
                }else{
                    jQueryNuvem(".js-removing-filter").show();
                }
            }
            jQueryNuvem(this).toggleClass("js-apply-filter js-remove-filter");
        }
    });

    jQueryNuvem(document).on("click", ".js-remove-all-filters", function(e) {
        e.preventDefault();
        LS.urlRemoveAllParams();
    });

    {# /* // Sort by */ #}

    jQueryNuvem('.js-sort-by').on("change", function (e) {
        var params = LS.urlParams;
        params['sort_by'] = jQueryNuvem(e.currentTarget).val();
        var sort_params_array = [];
        for (var key in params) {
            if (!['results_only', 'page'].includes(key)) {
                sort_params_array.push(key + '=' + params[key]);
            }
        }
        var sort_params = sort_params_array.join('&');
        window.location = window.location.pathname + '?' + sort_params;
    });

{% endif %}

{#/*============================================================================
  #Accordions
==============================================================================*/ #}

jQueryNuvem(document).on("click", ".js-accordion-toggle", function(e) {
    e.preventDefault();
    if(jQueryNuvem(this).hasClass("js-accordion-show-only")){
        jQueryNuvem(this).hide();
    }else{
        jQueryNuvem(this).find(".js-accordion-toggle-inactive").toggle();
        jQueryNuvem(this).find(".js-accordion-toggle-active").toggle();
    }
    jQueryNuvem(this).prev(".js-accordion-container").slideToggle();
});

Configurações

No arquivo config/settings.txt, adicionaremos os checkboxes para ativar e desativar as funcionalidades. Vamos colocá-lo na seção Lista de produtos.

title
        title = Filtros
    description
        description = Elegí qué filtros vas a mostrar en el listado de productos.
    checkbox
        name = product_filters
        description = Variantes del producto
    checkbox
        name = brand_filters
        description = Marca
    checkbox
        name = price_filters
        description = Precio

Traduções

Nesta etapa, adicionamos os textos para as traduções no arquivo  config/translations.txt

--- --- Config

es "Elegí qué filtros vas a mostrar en el listado de productos."
pt "Escolha quais filtros serão exibidos na lista de produtos."
es_mx "Elige qué filtros vas a mostrar en el listado de productos."

es "Variantes del producto"
pt "Variações do produto"
es_mx "Variantes del producto"

es "Marca"
pt "Marca"
es_mx "Marca"

--- --- Sort By

es "Ordenar por:"
pt "Ordenar por:"
en "Sort by:"
es_mx "Ordenar por:"

es "Destacado"
pt "Destaque"
en "Featured"
es_mx "Destacado"

es "Precio: Menor a Mayor"
pt "Preço: Menor ao Maior"
en "Price: Low to High"
es_mx "Precio: Menor a Mayor"

es "Precio: Mayor a Menor"
pt "Preço: Maior ao Menor"
en "Price: High to Low"
es_mx "Precio: Mayor a Menor"

es "A - Z"
pt "A - Z"
en "A - Z"
es_mx "A - Z"

es "Z - A"
pt "Z - A"
en "Z - A"
es_mx "Z - A"

es "Más Viejo al más Nuevo"
pt "Mais Antigo ao mais Novo"
en "Oldest to Newest"
es_mx "Más Viejo al más Nuevo"

es "Más Nuevo al más Viejo"
pt "Mais Novo ao mais Antigo"
en "Newest to Oldest"
es_mx "Más Nuevo al más Viejo"

es "Más Vendidos"
pt "Mais Vendidos"
en "Best Selling"
es_mx "Más Vendidos"

--- --- Filtros

es "Filtrar por:"
pt "Filtrar por:"
en "Filter by:"
es_mx "Filtrar por:"

es "Filtros"
pt "Filtros"
en "Filters"
es_mx "Filtros"

es "Filtrado por:"
pt "Filtrado por:"
en "Filtered by:"
es_mx "Filtrado por:"

es "Filtro aplicado:"
pt "Filtro aplicado:"
en "Applied filter:"
es_mx "Filtro aplicado:"

es "Aplicando filtro..."
pt "Aplicando filtro..."
en "Applying filter..."
es_mx "Aplicando filtro..."

es "Borrando filtro..."
pt "Removendo filtro..."
en "Removing filter..."
es_mx "Borrando filtro..."

es "Filtrar"
pt "Filtrar"
en "Filter"
es_mx "Filtrar"

es "Ver todos"
pt "Ver todos"
en "View all"
es_mx "Ver todos"

es "Ver más"
pt "Ver mais"
en "View more"
es_mx "Ver más"

es "Ver menos"
pt "Ver menos"
en "View less"
es_mx "Ver menos"

es "Borrar filtros"
pt "Limpar filtros"
en "Clean filters"
es_mx "Borrar filtros"

es "No tenemos productos en esas variantes. Por favor, intentá con otros filtros."
pt "Não temos produtos com estas variações. Por favor, tente com outros filtros"
en "We don&#39;t have any product with those variants. Please, try with other filters"
es_mx "No tenemos productos con esas variables. Intenta con otros filtros."

es "Más colores"
pt "Mais cores"
en "More colors"
es_mx "Más colores"

es "Categorías"
pt "Categorias"
en "Categories"
es_mx "Categorías"

es "Categorías principales"
pt "Categorias principais"
en "Main categories"
es_mx "Categorías principales"

es "Mostrar más categorías"
pt "Mostrar mais categorias"
en "Show more categories"
es_mx "Mostrar más categorías"

es "Talle"
pt "Tamanho"
en "Size"
es_mx "Talla"

es "Color"
pt "Cor"
en "Color"
es_mx "Color"

Ativação

Por fim, para ativar os filtros, você pode fazer isso no Administrador Nuvem, na seção “Personalizar meu layout atual” na Lista de produtos.