Filtros e ordenação dos produtos

Neste artigo vamos adicionar duas funcionalidades:

Filtros

Possibilita adicionar e remover filtros na página de categorias.

 











Adicione também na versão desktop uma barra com as categorias à esquerda do grid.


Ordem dos produtos

Deixe o usuário solicitar os produtos com os seguintes critérios:

  • Preço: maior para menor e menor para maior.
  • Alfabética: A -Z e Z - A.
  • Tempo: do mais recente para o mais antigo e do mais antigo para o mais recente.
  • Os mais vendidos

HTML

A primeira coisa que faremos é criar os tpls necessários para as funcionalidades.

1. Adicione a pasta com o nome grid dentro da pasta snipplets

2. Na pasta grid criaremos três snipplets:

categories.tpl  

Representa as categorias mostradas em uma coluna à esquerda do grid, visível apenas em desktop:

<div class="mb-4 pb-1">
    <div class="js-append-filters pb-5" style="display: none;">
        <div class="d-none d-sm-block" >
            <h5 class="mb-2">{{ 'Filtro aplicado' | translate }}</h5>
        </div>
    </div>


    {% if parent_category and parent_category.id!=0 %}
        <a href="{{ parent_category.url }}" title="{{ parent_category.name }}" class="category-back d-block{% if filter_categories %} mb-4{% endif %}">{% include "snipplets/svg/chevron-left.tpl" with {svg_custom_class: "icon-inline mr-2 svg-icon-text"} %}{{ parent_category.name }}</a>
    {% endif %}


    {% if filter_categories %}
        <div class="d-none d-md-block">
            <h3 class="title-section mb-4">{{ category.id!=0 ? category.name :("Categorías" | translate) }}</h3>
            <ul class="list-unstyled"> 
                {% for category in filter_categories %}
                    <li data-item="{{ loop.index }}" class="js-category-sidebar-item mb-2"><a href="{{ category.url }}" title="{{ category.name }}">{{ category.name }}</a></li>
                {% endfor %}
            </ul>
        </div>
    {% endif %}
</div>

filters.tpl

Como su nombre indica, son los filtros. Estos están divididos en 4 tipos:

FiltroDescrição
filter_colorsSão as cores principais como vermelho, verde, amarelo, etc.
filter_more_colorsSão as cores mais personalizadas, por exemplo, "Salmon"
filter_sizesSão os tamanhos, por exemplo, para numeração de roupas.
variants_propertySão os outros filtros que não se aplicam aos 3 anteriores


{% set default_lang = current_language.lang %}
{% set filter_colors = insta_colors|length > 0 %}
{% set filter_more_colors = other_colors|length > 0 %}
{% set filter_sizes = size_properties_values|length > 0 %}
{% set filter_other = variants_properties|length > 0 %}


{% if default_lang == 'pt' %}
    {% set color_name = 'Cor' %}
    {% set size_name = 'Tamanho' %}
{% endif %}
{% if default_lang == 'es' %}
    {% set color_name = 'Color' %}
    {% set size_name = 'Talle' %}
{% endif %}
{% if default_lang == 'en' %}
    {% set color_name = 'Color' %}
    {% set size_name = 'Size' %}
{% endif %}
<div id="filters">
    {% if filter_colors %}
        <div class="mb-4">
            <h6 class="mb-2">{{ 'Color' | translate }}</h6>
            {% for name,color in insta_colors %}
                <button type="button" class="btn btn-secondary btn-circle mr-2 mb-2" style="background-color: {{ color[name] }};" title="{{ name }}" onclick="LS.urlAddParam('{{ color_name|replace("'","%27") }}', '{{ name|replace("'","%27") }}');">
                </button>
            {% endfor %}
        </div>
    {% endif %}
    {% if filter_more_colors %}
        <div class="mb-4">
            <h6 class="mb-2">
                {% if filter_colors %}
                    {{ 'Más colores' | translate }}
                {% else %}
                    {{ 'Color' | translate }}
                {% endif %}
            </h6>
            {% for color in other_colors %}
                <button type="button" class="btn btn-secondary mr-2 mb-2" onclick="LS.urlAddParam('{{ color_name|replace("'","%27") }}', '{{ color|replace("'","%27") }}');">{{ color }}
                </button>
            {% endfor %}
        </div>
    {% endif %}
    {% if filter_sizes %}
        <div class="mb-4">
            <h6 class="mb-2">{{ 'Talle' | translate }}</h6>
            {% for size in size_properties_values %}
                <button type="button" class="btn btn-secondary mr-2 mb-2" onclick="LS.urlAddParam('{{ size_name|replace("'","%27") }}', '{{ size|replace("'","%27") }}');">{{ size }}
                </button>
            {% endfor %}
        </div>
    {% endif %}


    {% for variants_property in variants_properties %}
        {% if filter_other %}
            <div class="mb-4">
                <h6 class="mb-2">{{ variants_property }}</h6>
                {% for value in variants_properties_values[variants_property] %}
                    <button type="button" class="btn btn-secondary mr-2 mb-2" onclick="LS.urlAddParam('{{ variants_property|replace("'","%27") }}', '{{ value|replace("'","%27") }}');">{{value}}
                    </button>
                {% endfor %}
            </div>
        {% endif %}
    {% endfor %}
</div>

sort-by.tpl

É o select para ordenar 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',
} %}


<div class="form-group">
    <select class="form-select js-sort-by">
        {% 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 %}
    </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>

No layout Base, para o select usamos o snipplet form-select.tpl mas neste exemplo usamos o select sem snipplet para simplificar.

3. Nós chamamos estes snipplets dentro da categoria template.tpl

A primeira coisa que fazemos é criar a seguinte variável que é usada para determinar se a categoria na qual o usuário está, tem filtros para mostrar ou não. Nós o adicionamos assim que o template começar:

{% set has_filters = insta_colors|length > 0 or other_colors|length > 0 or size_properties_values|length > 0 or variants_properties|length > 0 %}

Em seguida, criamos a coluna onde os filtros e as categorias são mostrados à esquerda do grid de produtos.

<div class="js-product-table row">
    <div class="d-none d-sm-block col-sm-2">
        {% snipplet "grid/categories.tpl" %}
        {% if has_filters %}
            <h3 class="mb-4">{{ "Filtros" | translate }}</h3>
            {% snipplet "grid/filters.tpl" %}
        {% endif %}
    </div>
    <div class="col-12 col-sm-10">
        <div class="row">
            {% include 'snipplets/product_grid.tpl' %}
        </div>
    </div>
</div>

Em seguida, adicionamos uma linha acima da coluna e do grid, o botão para filtrar (que só é visto em telefones celulares) e o select para ordenar produtos:

<div class="row mb-3 align-items-center">
    {% if products %}
        {% set columns = settings.grid_columns %}
        <div class="col-6{% if columns == 2 %} col-sm-9{% else %} col-sm-9{% endif %}">
        {% if has_filters %}
            <a href="#" class="js-modal-open filter-link d-sm-none" 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 %}
                    {% snipplet "grid/filters.tpl" %}
                {% endblock %}
            {% endembed %}
        {% endif %}
        </div>
        <div class="col-6{% if columns == 2 %} col-sm-3{% else %} col-sm-3{% endif %} text-right">
            {% include 'snipplets/grid/sort-by.tpl' %}
        </div>
    {% endif %}
</div>
<div class="row d-sm-none">
    <div class="js-append-filters col-12 mb-3 mt-3" style="display: none;">
    </div>
</div>

Neste código, o seguinte deve ser destacado:

  • ss-append-filters é uma classe que usamos para exibir os filtros aplicados, que são adicionados usando JavaScript
  • Embora os filtros sejam exibidos em apenas uma coluna em desktops, para dispositivos móveis, eles são exibidos em um modal. É por isso que incorporamos o snipplet filters.tpl usando uma incorporação do snipplet modal.tpl

4. Agora precisamos criar o snipplet para o componente modal ou pop-up dentro da pasta snipplets. Este tpl é chamado 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 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 }}">
    {% endif %}
    <div class="js-modal-close 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>

5. Por último, para o HTML, precisamos adicionar uma pasta SVG dentro da pasta snipplets. Aqui vamos adicionar os SVGs que usamos para o select, o ícone do filtro 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. Adicione os estilos no arquivo static/style-colors.scss.tpl

Adicionamos o seguinte SASS de cores em style-colors.scss.tpl (ou 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:

@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
      #{'-' + $prefix + '-' + $property}: $value;
  }
    #{$property}: $value;
}


/* // Modals */


.modal{
  color: $main-foreground;
  background-color:$main-background;
}


{# /* // Buttons */ #}


.btn{
  text-decoration: none;
  text-align: center;
  border: 0;
  cursor: pointer;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  text-transform: uppercase;
  background: none;
  @include prefix(transition, all 0.4s ease, webkit ms moz o);
  &:hover,
  &:focus{
    outline: 0;
    opacity: 0.8;
  }
  &[disabled],
  &[disabled]:hover{
    opacity: 0.5;
    cursor: not-allowed;
    outline: 0;
  }
  &-default{
    padding: 10px 15px; 
    background-color: rgba($main-foreground, .2);
    color: $main-foreground;
    fill: $main-foreground;
    font-weight: bold;
  }
  &-secondary{
    padding: 10px 15px; 
    background-color: $main-background;
    color: $main-foreground;
    fill: $main-foreground;
    border: 1px solid $main-foreground;
  }
  &-block{
    float: left;
    width: 100%;
  }
  &-small{
    display: inline-block;
    padding: 10px;
    font-size: 10px;
    letter-spacing: 2px;
  }
  &-circle{
    height: 32px;
    border-radius: 50%;
  }
}


button{
  @extend %body-font;
  cursor: pointer;
  &:focus{
    outline: 0;
    opacity: 0.8;
  }
}
{# /* // Forms */ #}


input,
textarea {
  font-family: $body-font;
}


.form-control {
  display: block;
  padding: 10px 8px;
  width: 100%;
  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%;
  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;
  &-icon{
    background: $main-background;
  }
}

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, mas se não for o caso, você poderá 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;
}
.form-row {
  width: auto;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -ms-flex-wrap: wrap;
  flex-wrap: wrap;
  margin-right: -5px;
  margin-left: -5px;
  clear: both;
}


.form-row > .col,
.form-row > [class*=col-]{
  padding-right: 5px;
  padding-left: 5px;
}


.form-label {
  display: block;
  font-size: 10px;
  text-transform: uppercase;
}

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

@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
      #{'-' + $prefix + '-' + $property}: $value;
  }
    #{$property}: $value;
}


{# /* // Forms */ #}


.form-group{
  .form-label{
    float: left;
    width: 100%;
    margin-bottom: 10px;
  }
  .alert{
    margin: 10px 0 0 0;
  }
}




/* // 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-sm{
    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;
  }
  &-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;
}


/* // Min width 768px */


@media (min-width: 768px) { 


/* //// Components */


 /* Modals */


  .modal{
    &-centered{
      height: 80%;
      width: 80%;
      left: 10%;
      margin: 5% auto;
    }
    &-docked-sm{
      width: 500px;
    }
    &-docked-small{
      width: 350px;
    }
  }
}

JS

1. O JavaScript deve ser adicionado no arquivo store.js.tpl (ou onde você tem suas funções JS). Adicionamos o seguinte código para modals:

{#/*============================================================================
      #Modals
    ==============================================================================*/ #}


    var $modal_close = $('.js-modal-close');
    var $modal_open = $('.js-modal-open');
    
    $modal_open.click(function (e) {
        e.preventDefault(); 
        var $modal_id = $(this).data('toggle');
        $(".js-modal-overlay").fadeToggle();
        if ($($modal_id).hasClass("modal-show")) {
            $($modal_id).removeClass("modal-show").delay(200).hide(0);
        } else {
            $($modal_id).detach().insertAfter(".js-modal-overlay").show(0).addClass("modal-show");
        }             
    });


    $modal_close.click(function (e) {
        e.preventDefault();  
        $(this).closest(".js-modal").removeClass("modal-show").delay(200).hide(0); 
        $(".js-modal-overlay").fadeOut(300);     
    });


    $(".js-modal-overlay").click(function (e) {
        e.preventDefault();  
        $(".js-modal.modal-show").removeClass("modal-show").delay(200).hide(0);   
        $(this).fadeOut(300);   
    });

2. Finalmente, adicionamos essa parte do código para o JS que aplica os filtros e para o select de ordenação dos produtos:

{% if template == 'category' %}

    {# /* // Show filters */ #}

    LS.showWhiteListedFilters("{{ filters|json_encode() }}");

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

    $('.js-sort-by').change(function () {
        var params = LS.urlParams;
        params['sort_by'] = $(this).val();
        var sort_params_array = [];
        for (var key in params) {
            if ($.inArray(key, ['results_only', 'page']) == -1) {
                sort_params_array.push(key + '=' + params[key]);
            }
        }
        var sort_params = sort_params_array.join('&');
        window.location = window.location.pathname + '?' + sort_params;
    });

{% endif %}

Traduções

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

--- --- 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 "Filtro aplicado:"
pt "Filtro aplicado:"
en "Applied filter:"
es_mx "Filtro aplicado:"


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


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"

Pronto, você já tem em seu layout ambas funcionalidades aplicadas.