Cores no item do produto

Neste tutorial, vamos ver como visualizar as cores de um produto na listagem, sem precisar inserir nos detalhes do produto.

HTML

1. A primeira coisa que vamos fazer é adicionar um novo snipplet chamado item-colors.tpl dentro da pasta snipplets/grid com o seguinte código:

{% if product.variations %}
    {% set own_color_variants = 0 %}
    {% set custom_color_variants = 0 %}


    {% for variation in product.variations %}
        <div class="js-color-variant-available-{{ loop.index }} {% if variation.name in ['Color', 'Cor'] %}js-color-variant-active{% endif %}" data-value="variation_{{ loop.index }}" data-option="{{ loop.index0 }}" >
            {% if variation.name in ['Color', 'Cor'] %}
                {% if variation.options | length > 1 %}
                    <div class="item-colors">
                        <a href="{{ product_url_with_selected_variant }}" class="item-colors-bullet item-colors-bullet-text d-md-none w-auto px-2">{{ variation.options | length }} {{ 'colores' | translate }}</a>
                        <div class="d-none d-md-block">
                            {% for option in variation.options | take(5) if option.custom_data %}
                                <span title="{{ option.name }}" data-option="{{ option.id }}" class="js-color-variant item-colors-bullet {% if product.default_options[variation.id] == option.id %}selected{% endif %}" style="background: {{ option.custom_data }}"></span>
                            {% endfor %}


                            {% for option in variation.options %}
                                {% if option.custom_data %}
                                    {# Quantity of our colors #}
                                    {% set own_color_variants = own_color_variants + 1 %}
                                {% else %}
                                    {# Quantity of custom colors #}
                                    {% set custom_color_variants = custom_color_variants + 1 %}
                                {% endif %}
                            {% endfor %}


                            {% set more_color_variants = (own_color_variants - 5) + custom_color_variants %}


                            {% if own_color_variants and custom_color_variants %}
                                <a href="{{ product_url_with_selected_variant }}" class="item-colors-bullet w-auto" title="{{ 'Ver más colores' | translate }}">
                                    {% if own_color_variants > 5 %}
                                        +{{ more_color_variants }}
                                    {% else %}
                                        +{{ custom_color_variants }}
                                    {% endif %}
                                </a>
                            {% elseif own_color_variants > 5 %}
                                <a href="{{ product_url_with_selected_variant }}" class="item-colors-bullet w-auto" title="{{ 'Ver más colores' | translate }}">+{{ own_color_variants - 5 }}</a>
                            {% elseif custom_color_variants %}
                                <a href="{{ product_url_with_selected_variant }}" class="item-colors-bullet item-colors-bullet-text w-auto px-2" title="{{ 'Ver más colores' | translate }}">{{ custom_color_variants }} {{ 'colores' | translate }}</a>
                            {% endif %}
                        </div>
                    </div>
                {% endif %}
            {% endif %}
        </div>
    {% endfor %}
{% endif %}

2. Depois vamos procurar o snipplet item.tpl dentro da pasta snipplets/grid, pode ser que em seu layout esse snipplet seja chamado single_product.tpl, e usamos o seguinte código:

{% set slide_item = slide_item | default(false) %}
{% set columns = settings.grid_columns %}
{% set has_color_variant = false %}
{% if settings.product_color_variants %}
    {% for variation in product.variations if variation.name in ['Color', 'Cor'] and variation.options | length > 1 %}
        {% set has_color_variant = true %}
    {% endfor %}
{% endif %}


<div class="js-item-product {% if slide_item %}swiper-slide{% else %}col{% if columns == 2 %}-6 col-md-3{% else %}-12 col-md-4{% endif %}{% endif %} item item-product{% if not product.display_price %} no-price{% endif %}" data-product-type="list" data-product-id="{{ product.id }}" data-store="product-item-{{ product.id }}">


    {% if settings.product_color_variants %}
        <div id="quick{{ product.id }}{% if slide_item and section_name %}-{{ section_name }}{% endif %}" class="js-product-container js-quickshop-container {% if product.variations %}js-quickshop-has-variants{% endif %}" data-variants="{{ product.variants_object | json_encode }}">
    {% endif %}


        {% set product_url_with_selected_variant = has_filters ?  ( product.url | add_param('variant', product.selected_or_first_available_variant.id)) : product.url  %}


        {% if has_color_variant %}


            {# Item image will be the first avaiable variant #}


            {% set item_img_spacing = product.featured_variant_image.dimensions['height'] / product.featured_variant_image.dimensions['width'] * 100 %}
            {% set item_img_srcset = product.featured_variant_image %}
            {% set item_img_alt = product.featured_variant_image.alt %}
        {% else %}


            {# Item image will be the first image regardless the variant #}


            {% set item_img_spacing = product.featured_image.dimensions['height'] / product.featured_image.dimensions['width'] * 100 %}
            {% set item_img_srcset = product.featured_image %}
            {% set item_img_alt = product.featured_image.alt %}
        {% endif %}


        <div class="item-image mb-2">
            <div style="padding-bottom: {{ item_img_spacing }}%;" class="p-relative" data-store="product-item-image-{{ product.id }}">
                <a href="{{ product_url_with_selected_variant }}" title="{{ product.name }}">
                    <img alt="{{ item_img_alt }}" data-sizes="auto" data-expand="-10" src="{{ 'images/empty-placeholder.png' | static_url }}" data-srcset="{{ item_img_srcset | product_image_url('small')}} 240w, {{ item_img_srcset | product_image_url('medium')}} 320w, {{ item_img_srcset | product_image_url('large')}} 480w" class="js-item-image lazyautosizes lazyload img-absolute img-absolute-centered fade-in" /> 
                    <div class="placeholder-fade"></div>
                </a>
                {% if settings.product_color_variants %}
                    {% include 'snipplets/labels.tpl' with {color: true} %}
                    {% include 'snipplets/grid/item-colors.tpl' %}
                {% else %}
                    {% include 'snipplets/labels.tpl' %}
                {% endif %}
            </div>
        </div>
        {% if settings.product_color_variants and product.variations %}


            {% for variation in product.variations if variation.name in ['Color', 'Cor'] and variation.options | length > 1 %}


                {# Hidden product form to update item image and variants #}
                
                <div class="js-item-variants hidden">
                    <form id="product_form" class="js-product-form" method="post" action="{{ store.cart_url }}">
                        {% if product.variations %}
                            {% include "snipplets/product/product-variants.tpl" with {quickshop: true} %}
                        {% endif %}
                        {% set state = store.is_catalog ? 'catalog' : (product.available ? product.display_price ? 'cart' : 'contact' : 'nostock') %}
                        {% set texts = {'cart': "Agregar al carrito", 'contact': "Consultar precio", 'nostock': "Sin stock", 'catalog': "Consultar"} %}


                        {# Add to cart CTA #}


                        <input type="submit" class="js-addtocart js-prod-submit-form {{ state }}" value="{{ texts[state] | translate }}" {% if state == 'nostock' %}disabled{% endif %} />
                    </form>
                </div>


            {% endfor %}
        {% endif %}
        <div class="item-description" data-store="product-item-info-{{ product.id }}">
            <a href="{{ product_url_with_selected_variant }}" title="{{ product.name }}" class="item-link">
                <div class="item-name mb-1" data-store="product-item-name-{{ product.id }}">{{ product.name }}</div>
                {% if product.display_price %}
                    <div class="item-price-container mb-1" data-store="product-item-price-{{ product.id }}">
                        <span class="js-compare-price-display price-compare" {% if not product.compare_at_price or not product.display_price %}style="display:none;"{% else %}style="display:inline-block;"{% endif %}>
                            {{ product.compare_at_price | money }}
                        </span>
                        <span class="js-price-display item-price">
                            {{ product.price | money }}
                        </span>


                    </div>
                {% endif %}
            </a>
        </div>
        {% include 'snipplets/payments/installments.tpl' %}


        {# Structured data to provide information for Google about the product content #}
        {% include 'snipplets/structured_data/item-structured-data.tpl' %}
    {% if settings.product_color_variants %}
        </div>
    {% endif %}
</div>

3. No snipplet labels.tpl, pode ser que em seu layout esteja diretamente dentro do snipplet single_product.tpl, utilizamos o seguinte código:

{% if product.compare_at_price > product.price %}
{% set price_discount_percentage = ((product.compare_at_price) - (product.price)) * 100 / (product.compare_at_price) %}
{% endif %}


{% if color %}
  {% set show_labels = settings.product_color_variants %}
{% else %}
  {% set show_labels = not product.has_stock or product.free_shipping or product.compare_at_price or product.promotional_offer %}
{% endif %}


{% if show_labels %}
  <div class="labels">
    {% if not product.has_stock %}
      <div class="{% if product_detail %}js-stock-label {% endif %}label label-default">{{ "Sin stock" | translate }}</div>
    {% else %}
      {% if product_detail or color %}
        <div class="js-stock-label label label-default" {% if product.has_stock %}style="display:none;"{% endif %}>{{ "Sin stock" | translate }}</div>
      {% endif %}
      {% if product.compare_at_price or product.promotional_offer %}
        <div class="{% if not product.promotional_offer and product %}js-offer-label{% endif %} label label-primary" {% if (not product.compare_at_price and not product.promotional_offer) or not product.display_price %}style="display:none;"{% endif %}>
          {% if product.promotional_offer.script.is_percentage_off %}
            {{ product.promotional_offer.parameters.percent * 100 }}% OFF
          {% elseif product.promotional_offer.script.is_discount_for_quantity %}
            <div>{{ product.promotional_offer.selected_threshold.discount_decimal_percentage * 100 }}% OFF</div>
            <div class="label-small p-right-quarter p-left-quarter">{{ "Comprando {1} o más" | translate(product.promotional_offer.selected_threshold.quantity) }}</div>
          {% elseif product.promotional_offer %}
            {% if store.country == 'BR' %}
              {{ "Leve {1} Pague {2}" | translate(product.promotional_offer.script.quantity_to_take, product.promotional_offer.script.quantity_to_pay) }}
            {% else %}
              {{ "Promo" | translate }} {{ product.promotional_offer.script.type }} 
            {% endif %}
          {% else %}
            <span {% if product_detail or color %}class="js-offer-percentage"{% endif %}>{{ price_discount_percentage |round }}</span>% OFF
          {% endif %}
        </div>
      {% endif %}
      {% if product.free_shipping %}
        <div class="label label-secondary">{{ "Envío gratis" | translate }}</div>
      {% endif %}
    {% endif %}
  </div>
{% endif %}

4. No snipplet installmets.tpl dentro da pasta snipplets/payments, para atualizar as informações de parcelas ao alterar uma cor, substituímos esta linha de código:

<div class="{% if product_detail %}js-max-installments-container js-max-installments text-center text-md-left{% else %}item-installments{% endif %}">

Pela seguinte:

<div class="js-max-installments-container js-max-installments {% if product_detail %}text-center text-md-left{% else %}item-installments{% endif %}">

5. Por úlitmo, no snipplet product-variants.tpl dentro da pasta snipplets/product, usamos:

<div class="js-product-variants{% if quickshop %} js-product-quickshop-variants text-left{% endif %} form-row">
    {% for variation in product.variations %}
        <div class="js-product-variants-group {% if variation.name in ['Color', 'Cor'] %}js-color-variants-container{% endif %} {% if loop.length == 3 %} {% if quickshop %}col-4{% else %}col-12{% endif %} col-md-4 {% elseif loop.length == 2 %} col-6 {% else %} col {% if quickshop %}col-md-12{% else %}col-md-6{% endif %}{% endif %}" data-variation-id="{{ variation.id }}">
            {% embed "snipplets/forms/form-select.tpl" with{select_label: true, select_label_name: '' ~ variation.name ~ '', select_for: 'variation_' ~ loop.index , select_id: 'variation_' ~ loop.index, select_data_value: 'variation_' ~ loop.index, select_name: 'variation' ~ '[' ~ variation.id ~ ']', select_custom_class: 'js-variation-option js-refresh-installment-data'} %}
                {% block select_options %}
                    {% for option in variation.options %}
                        <option value="{{ option.id }}" {% if product.default_options[variation.id] == option.id %}selected="selected"{% endif %}>{{ option.name }}</option>
                    {% endfor %}
                {% endblock select_options%}
            {% endembed %}
        </div>
    {% endfor %}
</div>

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-critical.tpl

Se em seu layout você usar um stylesheet para o CSS crítico, precisaremos do seguinte código dentro dele.

.item-colors {
  position: absolute;
  bottom: 0;
  z-index: 9;
  width: 100%;
  padding: 5px 0;
}
.item-colors-bullet {
  display: inline-block;
  min-width: 18px;
  height: 18px;
  margin: 0 3px;
  font-size: 10px;
  text-transform: uppercase;
  line-height: 19px;
  vertical-align: top;
  border-radius: 18px;
  cursor: pointer;
  opacity: 0.8;
  -webkit-transition: all 0.4s ease;
  -ms-transition: all 0.4s ease;
  -moz-transition: all 0.4s ease;
  -o-transition: all 0.4s ease;
  transition: all 0.4s ease;
}
.item-colors-bullet:hover,
.item-colors-bullet.selected {
  opacity: 1;
}

2. 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:

.item-colors {
  background: rgba($main-foreground, .6);
  &-bullet {
    color: $main-foreground;
  }
  &-bullet-text {
    color: $main-background;
  }
}

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.

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

{% if settings.product_color_variants %}

    {# Product color variations #}

    jQueryNuvem(document).on("click", ".js-color-variant", function(e) {
        e.preventDefault();
        $this = jQueryNuvem(this);


        var option_id = $this.data('option');
        $selected_option = $this.closest('.js-item-product').find('.js-variation-option option').filter(function(el) {
            return el.value == option_id;
        });
        
        $selected_option.prop('selected', true).trigger('change');
        var available_variant = jQueryNuvem(this).closest(".js-quickshop-container").data('variants');


        var available_variant_color = jQueryNuvem(this).closest('.js-color-variant-active').data('option');


        for (var variant in available_variant) {
            if (option_id == available_variant[variant]['option'+ available_variant_color ]) {

                if (available_variant[variant]['stock'] == null || available_variant[variant]['stock'] > 0 ) {

                    var otherOptions = getOtherOptionNumbers(available_variant_color);

                    var otherOption = available_variant[variant]['option' + otherOptions[0]];
                    var anotherOption = available_variant[variant]['option' + otherOptions[1]];


                    changeSelect(jQueryNuvem(this), otherOption, otherOptions[0]);
                    changeSelect(jQueryNuvem(this), anotherOption, otherOptions[1]);
                    break;

                }
            }
        }
        $this.siblings().removeClass("selected");
        $this.addClass("selected");
    });

    function getOtherOptionNumbers(selectedOption) {
        switch (selectedOption) {
            case 0:
                return [1, 2];
            case 1:
                return [0, 2];
            case 2:
                return [0, 1];
        }
    }

    function changeSelect(element, optionToSelect, optionIndex) {
        if (optionToSelect != null) {
            var selected_option_attribute = element.closest('.js-item-product').find('.js-color-variant-available-' + (optionIndex + 1)).data('value');
            var selected_option = element.closest('.js-item-product').find('#' + selected_option_attribute + " option").filter(function(el) {
                return el.value == optionToSelect;
            });

            selected_option.prop('selected', true).trigger('change');
        }
    }


    LS.registerOnChangeVariant(function(variant){
        {# Show product image on color change #}
        var current_image = jQueryNuvem('.js-item-product[data-product-id="'+variant.product_id+'"] .js-item-image');
        current_image.attr('srcset', variant.image_url);
    });

{% endif %}

2. Também precisamos adicionar o JS que atualiza os preços, estoque e rótulos correspondentes, como estoque e descontos, ao alterar as variantes.

Para isso, devemos procurar a seguinte função:

$(document).on("change", ".js-variation-option", function(e) {


    LS.changeVariant(changeVariant, '#single-product');

    (...)


});

E substituí-la pela seguinte:

jQueryNuvem(document).on("change", ".js-variation-option", function(e) {

    var $parent = jQueryNuvem(this).closest(".js-product-variants");
    var $variants_group = jQueryNuvem(this).closest(".js-product-variants-group");
    var $quickshop_parent_wrapper = jQueryNuvem(this).closest(".js-quickshop-container");

    {# If quickshop is used from modal, use quickshop-id from the item that opened it #}
    
    if($quickshop_parent_wrapper.hasClass("js-quickshop-modal")){
           var quick_id = jQueryNuvem(".js-quickshop-opened .js-quickshop-container").data("quickshopId");
    }else{
          var quick_id = $quickshop_parent_wrapper.data("quickshopId");
     }

    if($parent.hasClass("js-product-quickshop-variants")){

        var $quickshop_parent = jQueryNuvem(this).closest(".js-item-product");

        {# Target visible slider item if necessary #}
        
        if($quickshop_parent.hasClass("js-item-slide")){
            var $quickshop_variant_selector = '.js-swiper-slide-visible .js-quickshop-container[data-quickshop-id="'+quick_id+'"]';
        }else{
            var $quickshop_variant_selector = '.js-quickshop-container[data-quickshop-id="'+quick_id+'"]';
        }
        
        LS.changeVariant(changeVariant, $quickshop_variant_selector);

        {% if settings.product_color_variants %}
            {# Match selected color variant with selected quickshop variant #}


            if(($variants_group).hasClass("js-color-variants-container")){
                var selected_option_id = jQueryNuvem(this).find("option").filter((el) => el.selected).val();
                if($quickshop_parent.hasClass("js-item-slide")){
                    var $color_parent_to_update = jQueryNuvem('.js-swiper-slide-visible .js-quickshop-container[data-quickshop-id="'+quick_id+'"]');
                }else{
                    var $color_parent_to_update = jQueryNuvem('.js-quickshop-container[data-quickshop-id="'+quick_id+'"]');
                }
                $color_parent_to_update.find('.js-color-variant, .js-insta-variant').removeClass("selected");
                $color_parent_to_update.find('.js-color-variant[data-option="'+selected_option_id+'"], .js-insta-variant[data-option="'+selected_option_id+'"]').addClass("selected");
            }
        {% endif %} 
    } else {
        LS.changeVariant(changeVariant, '#single-product');
    }

    {# Offer and discount labels update #}

    var $this_product_container = jQueryNuvem(this).closest(".js-product-container");

    if($this_product_container.hasClass("js-quickshop-container")){
        var this_quickshop_id = $this_product_container.attr("data-quickshop-id");
        var $this_product_container = jQueryNuvem('.js-product-container[data-quickshop-id="'+this_quickshop_id+'"]');
    }
    var $this_compare_price = $this_product_container.find(".js-compare-price-display");
    var $this_price = $this_product_container.find(".js-price-display");
    var $installment_container = $this_product_container.find(".js-product-payments-container");
    var $installment_text = $this_product_container.find(".js-max-installments-container");
    var $this_add_to_cart = $this_product_container.find(".js-prod-submit-form");

    // Get the current product discount percentage value
    var current_percentage_value = $this_product_container.find(".js-offer-percentage");

    // Get the current product price and promotional price
    var compare_price_value = $this_compare_price.html();
    var price_value = $this_price.html();

    // Calculate new discount percentage based on difference between filtered old and new prices
    const percentageDifference = window.moneyDifferenceCalculator.percentageDifferenceFromString(compare_price_value, price_value);
    if(percentageDifference){
        $this_product_container.find(".js-offer-percentage").text(percentageDifference);
        $this_product_container.find(".js-offer-label").css("display" , "table");
    }

    if ($this_compare_price.css("display") == "none" || !percentageDifference) {
        $this_product_container.find(".js-offer-label").hide();
    }

    if ($this_add_to_cart.hasClass("nostock")) {
        $this_product_container.find(".js-stock-label").show();
    }
    else {
        $this_product_container.find(".js-stock-label").hide();
    }
    if ($this_price.css('display') == 'none'){
        $installment_container.hide();
        $installment_text.hide();
    }else{
        $installment_text.show();
    }
});

Configurações

No arquivo config/settings.txt, adicionaremos um checkbox que ativa a funcionalidade na seção "Lista de produtos".

    title
        title = Variantes de color
    checkbox
        name = product_color_variants
        description = Mostrar variantes de color en listado de productos

Traduções

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

es "colores"
pt "cores"
en "colors"
es_mx "colores"

es "Ver más colores"
pt "Ver mais cores"
en "See more colors"
es_mx "Ver más colores"

es "Variantes de color"
pt "Variações de cor"
en "Color variants"
es_mx "Variantes de color"

es "Mostrar variantes de color en listado de productos"
pt "Mostrar variações de cores na lista de produtos"
en "Show color variants in product list"
es_mx "Mostrar variantes de color en listado de productos"

Ativação

Por último, você pode ativar funcionalidade no Administrador Nuvem, na seção ‘Personalizar seu layout atual’ dentro de ‘Lista de produtos’:

Lembre-se, para que o produto funcione, ele deve ter pelo menos 2 opções da variante "Cor".