Variações sem estoque

Neste artigo vamos "riscar" as opções que não tiverem estoque quando as variações sejam mostradas como botões.

HTML

1. Dentro da pasta snipplets crie outra pasta com o nome product, dentro adicionamos um novo snipplet chamado product-variants.tpl (se você já tem esse snipplet basta substituí-lo) com o seguinte código:

<div class="js-product-variants{% if quickshop %} js-product-quickshop-variants text-left{% endif %} form-row">
    {% set has_size_variations = false %}
    {% if settings.bullet_variants %}
        {% set hidden_variant_select = ' d-none' %}
    {% endif %}
    {% for variation in product.variations %}
        <div class="js-product-variants-group {% if variation.name in ['Color', 'Cor'] %}js-color-variants-container{% endif %} {% if settings.bullet_variants %}col-12 mb-2 text-center {% if not quickshop %}text-md-left{% endif %}{% else %}{% 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 %}{% 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_group_custom_class:hidden_variant_select, 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] is same as(option.id) %}selected="selected"{% endif %}>{{ option.name }}</option>
                    {% endfor %}
                {% endblock select_options%}
            {% endembed %}
            {% if settings.bullet_variants %}
                    <div class="text-center {% if not quickshop %}text-md-left{% endif %}">
                        <label class="form-label mb-3">{{ variation.name }}</label>
                    </div>
                    <div class="text-center {% if not quickshop %}text-md-left{% endif %}">
                        {% for option in variation.options if option.custom_data %}
                            <a data-option="{{ option.id }}" class="js-insta-variant btn btn-variant{% if product.default_options[variation.id] is same as(option.id) %} selected{% endif %}{% if variation.name in ['Color', 'Cor'] %} btn-variant-color{% endif %}" title="{{ option.name }}" data-option="{{ option.id }}" data-variation-id="{{ variation.id }}">
                                <span class="btn-variant-content"{% if variation.name in ['Color', 'Cor'] %} style="background: {{ option.custom_data }}; border: 1px solid #eee"{% endif %} data-name="{{ option.name }}">
                                {% if not(variation.name in ['Color', 'Cor']) %}
                                    {{ option.name }}
                                {% endif %}
                                </span>
                            </a>
                        {% endfor %}
                        {% for option in variation.options if not option.custom_data %}
                            <a data-option="{{ option.id }}" class="js-insta-variant btn btn-variant{% if product.default_options[variation.id] is same as(option.id) %} selected{% endif %}" data-variation-id="{{ variation.id }}">
                                <span class="btn-variant-content" data-name="{{ option.name }}">{{ option.name }}</span>
                            </a>
                        {% endfor %}
                    </div>
            {% endif %}
        </div>
        {% if variation.name in ['Talle', 'Talla', 'Tamanho', 'Size'] %}
            {% set has_size_variations = true %}
        {% endif %}
    {% endfor %}
</div>

2. Temos que criar uma nova pasta com o nome forms, localizados na pasta snipplets. Dentro adicionamos o snipplet com o nome form-select.tpl, que usaremos para todos os componentes “select” dentro do layout (no layout Base ele já está adicionado).

{# /*============================================================================
  #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>

3. Para a parte do HTML, precisamos adicionar uma pasta SVG dentro da pasta snipplets. Aqui vamos adicionar o SVG que usamos para a seta dentro das variantes selecionadas com o nome 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>

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 no 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 design:

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


{# /* // 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;
  }
  &-variant{
    display: inline-block;
    font-size: 10px;
    line-height: 10px;
    min-width: 24px;
    min-height: 24px;
    margin: 0 10px 10px 0;
    border: 1px solid rgba($main-foreground, .3);
    color: rgba($main-foreground, .3);
    border-radius: 2px;
    vertical-align: top;
    &-content{
      display: block;
      min-width: 12px;
      min-height: 12px;
      margin: 5px;
      line-height: 12px;
    }
    &.selected {
      color: $main-foreground;
      border: 1px solid rgba($main-foreground, .8);
    }
    &-no-stock {
      position: relative;
      background: transparent;
      color: rgba($main-foreground, 0.5);
      overflow: hidden;
      &:after {
        position: absolute;
        top: 0;
        left: 0;
        z-index: 9;
        width: 100%;
        height: 100%;
        background: linear-gradient(to top left,transparent 49%,rgba($main-foreground, 0.5) ,transparent 52%);
        content:'';
      }
      &.selected:after {
        background: linear-gradient(to top left,transparent 49%,$main-foreground ,transparent 52%);
      }
      &.btn-variant-color:after {
        background: linear-gradient(-45deg,rgba($main-background, 0.3) calc(50% - .7px),rgba($main-foreground, 0.5) calc(50% - .7px),rgba($main-foreground, 0.5) 50%,rgba($main-foreground, 0.5) calc(50% + .7px),rgba($main-background, 0.3) calc(50% + .7px));
      }
      &.btn-variant-color.selected:after {
        background: linear-gradient(-45deg,rgba($main-background, 0.3) calc(50% - .7px),$main-foreground calc(50% - .7px),$main-foreground 50%,$main-foreground calc(50% + .7px),rgba($main-background, 0.3) calc(50% + .7px));
      }
    }
  }
}


{# /* // 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ê usa um stylesheet para o CSS crítico, precisaremos adicionar o código abaixo nele, mas se não for o caso, 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;
}
.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

Se em seu layout você usar um stylesheet de CSS assíncrono, precisaremos adicionar o seguinte código abaixo, mas se esse não for o caso, você poderá unificar o CSS dos passos 3 e 4 em um único arquivo.

{# /* // Forms */ #}


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

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 precisa ser adicionado no arquivo store.js.tpl (ou onde você tem suas funções JS). Adicionamos o seguinte código:

    {# /* // Variants without stock */ #}

    {% set is_button_variant = settings.bullet_variants %}

    {% if is_button_variant %}
        const noStockVariants = (container = null) => {

            {# Configuration for variant elements #}
            const config = {
                variantsGroup: ".js-product-variants-group",
                variantButton: ".js-insta-variant",
                noStockClass: "btn-variant-no-stock",
                dataVariationId: "data-variation-id",
                dataOption: "data-option"
            };

            {# Product container wrapper #}
            const wrapper = container ? container : jQueryNuvem('#single-product');
            if (!wrapper) return;

            {# Fetch the variants data from the container #}
            const dataVariants = wrapper.data('variants');
            const variantsLength = wrapper.find(config.variantsGroup).length;

            {# Get selected options from product variations #}
            const getOptions = (productVariationId, variantOption) => {
                if (productVariationId === 2) {
                    return {
                        option0: String(wrapper.find(`${config.variantsGroup}[${config.dataVariationId}="0"] select`).val()),
                        option1: String(wrapper.find(`${config.variantsGroup}[${config.dataVariationId}="1"] select`).val()),
                        option2: String(jQueryNuvem(variantOption).data('option')),
                    };
                } else if (productVariationId === 1) {
                    return {
                        option0: String(wrapper.find(`${config.variantsGroup}[${config.dataVariationId}="0"] select`).val()),
                        option1: String(jQueryNuvem(variantOption).data('option')),
                    };
                } else {
                    return {
                        option0: String(jQueryNuvem(variantOption).data('option')),
                    };
                }
            };

            {# Filter available variants based on selected options #}
            const filterVariants = (options) => {
                return dataVariants.filter(variant => {
                    return Object.keys(options).every(optionKey => variant[optionKey] === options[optionKey]) && variant.available;
                });
            };

            {# Update stock status for variant buttons #}
            const updateStockStatus = (productVariationId) => {
                const variationGroup = wrapper.find(`${config.variantsGroup}[${config.dataVariationId}="${productVariationId}"]`);
                variationGroup.find(`${config.variantButton}.${config.noStockClass}`).removeClass(config.noStockClass);

                variationGroup.find(config.variantButton).each((variantOption, item) => {
                    const options = getOptions(productVariationId, variantOption);
                    const itemsAvailable = filterVariants(options);
                    const button = wrapper.find(`${config.variantButton}[${config.dataOption}="${options[`option${productVariationId}`].replace(/"/g, '\\"')}"]`);
                    
                    if (!itemsAvailable.length) {
                        button.addClass(config.noStockClass);
                    }
                });
            };

            {# Iterate through all variant and update stock status #}
            for (let productVariationId = variantsLength - 1; productVariationId >= 0; productVariationId--) {
                updateStockStatus(productVariationId);
            }
        };

        noStockVariants();

    {% endif %}

2. Se tivermos a funcionalidade de QuickShop adicionamos este JS ao acionar a abertura do modal.

    {% if settings.quick_shop %}
        
        jQueryNuvem(document).on("click", ".js-quickshop-modal-open", function (e) {
            e.preventDefault();
            var $this = jQueryNuvem(this);
            if($this.hasClass("js-quickshop-slide")){
                jQueryNuvem("#quickshop-modal .js-item-product").addClass("js-swiper-slide-visible js-item-slide");
            }

            {% if is_button_variant %}
                {# Updates variants without stock #}
                let container = jQueryNuvem(this).closest('.js-quickshop-container');
                if (!container.length) return;
                noStockVariants(container);
            {% endif %}

            LS.fillQuickshop($this);
        });

    {% endif %}

3. Por fim, adicionamos o seguinte código ao alterar as variantes:

    function changeVariant(variant){

        var parent = jQueryNuvem("body");
        if (variant.element){
            parent = jQueryNuvem(variant.element);
        }

        {% if is_button_variant %}
            {# Updates variants without stock #}
            if(parent.hasClass("js-quickshop-container")){
                let container = parent.closest('.js-quickshop-container');
                noStockVariants(container);
            } else {
                noStockVariants();
            }
        {% endif %}

        (...)

    }

Configurações

No arquivo config/settings.txt, adicionaremos um input que ativa a funcionalidade na seção "Detalhe do produto".

title
    title = Variantes del producto
checkbox
    name = bullet_variants
    description = Mostrar como botones

Traduções

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

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

es "Mostrar como botones"
pt "Mostrar como botões"
es_mx "Mostrar como botones"

Ativação

Você pode ativar a funcionalidade no Administrador Nuvem na seção de Personalizar seu layout atual dentro de ‘Detalhe do produto’:

Pronto, você já tem em seu layout a funcionalidade aplicada.