Cartel de porcentaje de descuento

En este tutorial vamos a ver como mostrar un cartel que calcula el porcentaje de descuento cuando un producto tiene un precio original y un precio promocional, tanto en el detalle del producto como en el listado:

Este porcentaje se calcula cuando el usuario cambia alguna variable en el detalle del producto para los casos donde tengan precio diferente.

HTML

Lo primero que vamos a hacer es crear el tpl general para todos los carteles en relación a un producto: descuento, envío gratis y sin stock. Si no necesitas todos podés borrar el código de lo que no precises.

1. Agregamos en la carpeta snipplets el archivo labels.tpl 

Vamos a notar dos cosas importantes acá:

  • El condicional {% if product_detail %} que usamos a la hora de incluir el snipplet para preguntar si aplica al detalle del producto o no, de esta forma podemos usar clases “js-...”
  • La variable price_discount_percentage que hace la cuenta para calcular cual es el porcentaje de descuento.
{% if product.compare_at_price > product.price %}
{% set price_discount_percentage = ((product.compare_at_price) - (product.price)) * 100 / (product.compare_at_price) %}
{% endif %}

{% if not product.has_stock or product.free_shipping or product.compare_at_price %}
  <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.compare_at_price %}
        <div class="js-offer-label label label-primary" {% if (not product.compare_at_price) or not product.display_price %}style="display:none;"{% endif %}>
          <span {% if product_detail %}class="js-offer-percentage"{% endif %}>{{ price_discount_percentage |round }}</span>% OFF
        </div>
      {% endif %}
      {% if product.free_shipping %}
        <div class="label label-secondary">{{ "Envío gratis" | translate }}</div>
      {% endif %}
    {% endif %}
  </div>
{% endif %}

2. Con labels.tpl creado, tenemos que incluirlo tanto en el detalle del producto como en el item del listado. 

Para el item del listado lo incluimos en el snipplet item.tpl dentro de la carpeta snipplets/grid, puede ser que en tu diseño este snipplet se llame single_product.tpl

Llamamos al snipplet de la siguiente forma (idealmente dentro del componente de la imagen del producto):

{% include 'snipplets/labels.tpl' %}

Por otro lado vamos a incluir el mismo snipplet en el detalle del producto. En el theme Base lo hacemos en el archivo product-image.tpl en la carpeta snipplets/product, en tu caso puede ser que tengas que incluirlo en el template product.tpl. Lo importante es que esté dentro del contexto de la imagen del producto ya que el componente de los labels tiene una posición absoluta de CSS.

{% include 'snipplets/labels.tpl' with {'product_detail': true} %}

3. Continuando con el detalle del producto aplicamos los siguientes cambios:

En el template product.tpl en el div principal que engloba a todo el contenido de esta página agregamos los siguientes IDs y selectores “js-...”, quedando de la siguiente forma:

<div id="single-product" class=”js-product-detail js-product-container" data-variants="{{product.variants_object | json_encode }}" itemscope itemtype="http://schema.org/Product">
…
</div>

Por otro lado reemplazamos el HTML que muestra el precio y el precio promocional dentro del snipplet product-form.tpl o quizás en tu caso sea en el propio product.tpl,  por el siguiente código:

{# Product price #}

<div class="price-container text-center text-sm-left" itemprop="offers" itemscope itemtype="http://schema.org/Offer">
    <span class="d-inline-block">
       <h4 id="compare_price_display" class="js-compare-price-display price-compare" {% if not product.compare_at_price or not product.display_price %}style="display:none;"{% else %} style="display:block;"{% endif %}>{% if product.compare_at_price and product.display_price %}{{ product.compare_at_price | money }}{% endif %}</h4>
    </span>
    <spa class="d-inline-block">
        <h4 class="js-price-display" id="price_display" itemprop="price"{% if product.display_price %} content="{{ product.price / 100 }}"{% endif %} {% if not product.display_price %}style="display:none;"{% endif %}>{% if product.display_price %}{{ product.price | money }}{% endif %}</h4>
    </span>
    <meta itemprop="priceCurrency" content="{{ product.currency }}" />
    {% if product.stock_control %}
        <meta itemprop="inventoryLevel" content="{{ product.stock }}" />
        <meta itemprop="availability" href="http://schema.org/{{ product.stock ? 'InStock' : 'OutOfStock' }}" content="{{ product.stock ? 'In stock' : 'Out of stock' }}" />
    {% endif %}
</div>

Por último agregamos la clase js-variation-option dentro del select para las variantes dentro del detalle del producto. En el theme Base esto está en el tpl product-variants.tpl dentro de la carpeta snipplets/product. En tu diseño puede que este archivo se llame variants.tpl o tengas que aplicar el cambio directamente en el template product.tpl

CSS

Requisito:

Tener agregados en tu diseño las clases helpers. Podés seguir este este pequeño tutorial para hacerlo (simplemente es copiar y pegar algunas clases, no toma más de 1 minuto).

1. Agregamos el siguiente SASS de colores en style-colors.scss.tpl (o la hoja de tu diseño que tenga los colores y tipografías de la tienda). Recordá que las variables de colores y tipografías pueden variar respecto a tu diseño:

{# /* // Labels */ #}

.label {
  background: darken($main-background, 1%);
  &.label-primary{
    background: $main-foreground;
    color: $main-background;
  }
}

2. Agregar los estilos dentro del archivo static/style-critical.tpl 

{# /* // Labels */ #}

.labels {
  position: absolute;
  top: 0;
  z-index: 9;
}

.label {
  margin-bottom: 10px;
  padding: 5px 10px; 
  font-size: 12px;
  text-align: left;
}

JS

⚠️ A partir del día 30 de enero de 2023, la librería jQuery será removida del código de nuestras tiendas, por lo tanto la función "$" no podrá ser utilizada.

El JavaScript necesitamos agregarlo en el archivo store.js.tpl (o donde tengas tus funciones de JS).  Agregamos el siguiente código para ejecutar el cambio de variante (y los carteles del producto) :

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);
    } 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();
    }
});

Listo, ya tienen en su tienda la funcionalidad aplicada ¡Excelente!