En el siguiente tutorial, explicamos cómo agregar el calculador de envíos y los locales físicos en tu diseño:
El código en este tutorial incluye:
- El calculador de envíos, tanto en el detalle del producto como en el carrito
- El filtro entre opciones de envio similares. Este destaca la mejor opción de envío y oculta el resto en un link de “Ver más opciones”. No aplica a medios de envío personalizados.
- Fechas de envío exactas en lugar de tiempo estimado en días
- Selector de locales físicos (solo para tiendas que no son de Brasil)
- Mensaje sobre el envío gratis (solo aplica a tiendas de argentina que usan el medio de envío OCA)
Debajo del subtotal se puede ver un ejemplo del calculador y los locales.
A la izquierda las opciones de envío desplegadas y a la derecha las opciones menos relevantes, visibles al hacer click en “Ver más opciones”.
HTML
Lo primero que vamos a hacer es crear los tpls necesarios para la funcionalidad.
1. Agregamos la carpeta con el nombre shipping dentro de la carpeta snipplets
2. Dentro de esta carpeta creamos los siguientes archivos, cada uno con su respectivo código. Es importante mantener los IDs y las clases “js-...” para garantizar su funcionamiento cuando agreguemos el JavaScript.
branches.tpl
Este archivo representa el selector de locales físicos que sólo están visibles para tiendas que no son de Brasil. El código es el siguiente:
<div class="js-toggle-branches w-100"> <span class="form-row"> <div class="col-auto"> {% include "snipplets/svg/store.tpl" with {svg_custom_class: "icon-inline icon-lg link-module-icon svg-icon-text"} %} </div> <div class="col-6"> <div {% if store.branches|length > 1 %}class="mb-1"{% endif %}> {% if store.branches|length > 1 %} {{ 'Nuestros locales' | translate }} {% else %} {{ 'Nuestro local' | translate }} {% endif %} </div> {% if store.branches|length > 1 %} <div class="btn-link float-left"> <span class="js-see-branches"> {{ 'Ver opciones' | translate }} </span> {% include "snipplets/svg/chevron-down.tpl" with {svg_custom_class: "js-see-branches icon-inline ml-1"} %} <span class="js-hide-branches" style="display: none;"> {{ 'Ocultar opciones' | translate }} {% include "snipplets/svg/chevron-up.tpl" with {svg_custom_class: "icon-inline ml-1"} %} </span> </div> {% endif %} </div> </span> </div> {# Store branches #} {% if not product_detail %} <ul class="js-store-branches-container list-unstyled radio-button-container mt-4" {% if store.branches|length > 1 %}style="display: none;"{% endif %}> {# Selectable branches #} {% for branch in store.branches %} <li class="radio-button-item"> <label class="js-shipping-radio js-branch-radio radio-button" data-loop="branch-radio-{{loop.index}}"> <input class="js-branch-method {% if cart.shipping_data.code == branch.code %} js-selected-shipping-method {% endif %} shipping-method" data-price="0" {% if cart.shipping_data.code == branch.code %}checked{% endif %} type="radio" value="{{branch.code}}" data-name="{{ branch.name }} - {{ branch.extra }}" data-code="{{branch.code}}" data-cost="{{ 'Gratis' | translate }}" name="option" style="display:none"> <span class="shipping-option row-fluid radio-button-content"> <span class="radio-button-icons"> <span class="radio-button-icon unchecked"></span> <span class="radio-button-icon checked"></span> <span class="radio-button-icon checked checked-invert"></span> </span> <span class="radio-button-label"> <h6 class="text-primary mb-1 d-inline-block">{{ 'Gratis' | translate }}</h6> <span class="radio-button-text"> {{ branch.name }} - {{ branch.extra }} </span> </span> </span> </label> </li> {% endfor %} </ul> {% else %} <ul class="js-store-branches-container list-unstyled list mt-4" {% if store.branches|length > 1 %}style="display: none;"{% endif %}> {% for branch in store.branches %} <li class="list-item"> <span class="list-item-content"> <h6 class="text-primary mb-1">{{ 'Gratis' | translate }}</h6> <div>{{ branch.name }} - {{ branch.extra }}</div> </span> </li> {% endfor %} </ul> {% endif %}
shipping-calculator.tpl
Es el componente para el calculador de envíos. Incluye un input, un botón, el mensaje de error y un mensaje que muestra el monto mínimo para lograr envío gratis, así como un mensaje notificando que el envío gratis fue alcanzado al superar este monto (esto solo aplica a tiendas que usen OCA).
<div class="{% if product_detail %}product-shipping-calculator{% endif %} mb-2 w-100" data-store="shipping-calculator"> <div class="js-shipping-calculator-head shipping-calculator-head position-relative transition-soft {% if cart.shipping_zipcode %}with-zip{% else %}with-form{% endif %}"> <div class="js-shipping-calculator-with-zipcode {% if cart.shipping_zipcode %}js-cart-saved-zipcode transition-up-active{% endif %} mt-3 mb-4 w-100 transition-up position-absolute"> <div class="container p-0"> <div class="row align-items-center"> <span class="col pr-0"> <span class="font-small align-sub"> <span>{{ "Entregas para el CP:" | translate }}</span> <strong class="js-shipping-calculator-current-zip">{{ cart.shipping_zipcode }}</strong> </span> </span> <div class="col-auto pl-0"> <a class="js-shipping-calculator-change-zipcode btn btn-secondary btn-small float-right py-1 px-2 px-sm-3" href="#">{{ "Cambiar CP" | translate }}</a> </div> </div> </div> </div> <div class="js-shipping-calculator-form shipping-calculator-form transition-up position-absolute"> {# Shipping calcualtor input #} {% embed "snipplets/forms/form-input.tpl" with{type_tel: true, input_value: cart.shipping_zipcode, input_name: 'zipcode', input_custom_class: 'js-shipping-input', input_placeholder: "Tu código postal" | translate, input_aria_label: 'Tu código postal' | translate, input_label: false, input_append_content: true, input_group_custom_class: 'form-row align-items-center mb-3', form_control_container_custom_class: 'col-5'} %} {% block input_prepend_content %} <div class="col-12 mb-2"> {% include "snipplets/svg/truck.tpl" with {svg_custom_class: "icon-inline icon-w-18 icon-lg svg-icon-text mr-2"} %} {# Free shipping achieved label #} <span class="js-free-shipping-message font-weight-bold text-accent" {% if not cart.free_shipping.cart_has_free_shipping %}style="display: none;"{% endif %}> {{ "¡Genial! Tenés envío gratis" | translate }} </span> {# Free shipping with min price label #} <span class="js-shipping-calculator-label font-weight-bold" {% if cart.free_shipping.cart_has_free_shipping or not cart.free_shipping.min_price_free_shipping.min_price %}style="display: none;"{% endif %}> {{ "<strong class='text-accent'>Envío gratis</strong> superando los" | translate }} <span>{{ cart.free_shipping.min_price_free_shipping.min_price }}</span> </span> {# Shipping default label #} <span class="js-shipping-calculator-label-default" {% if cart.free_shipping.cart_has_free_shipping or cart.free_shipping.min_price_free_shipping.min_price %}style="display: none;"{% endif %}> {# Regular shipping calculator label #} {{ 'Medios de envío' | translate }} </span> </div> {% endblock input_prepend_content %} {% block input_form_alert %} {% if store.country == 'BR' or 'AR' or 'MX' %} {% set zipcode_help_ar = 'https://www.correoargentino.com.ar/formularios/cpa' %} {% set zipcode_help_br = 'http://www.buscacep.correios.com.br/sistemas/buscacep/' %} {% set zipcode_help_mx = 'https://www.correosdemexico.gob.mx/datosabiertos/gobmx/gobmx_Descarga.html' %} <div class="col-12"> <a class="font-small text-primary mt-3 mb-2 d-block" href="{% if store.country == 'AR' %}{{ zipcode_help_ar }}{% elseif store.country == 'BR' %}{{ zipcode_help_br }}{% elseif store.country == 'MX' %}{{ zipcode_help_mx }}{% endif %}" target="_blank">{{ "No sé mi código postal" | translate }}</a> </div> {% endif %} <div class="col-12"> <div class="js-ship-calculator-error invalid-zipcode alert alert-danger" style="display: none;"> {# Specific error message considering if store has multiple languages #} {% for language in languages %} {% if language.active %} {% if languages | length > 1 %} {% set wrong_zipcode_wording = ' para ' | translate ~ language.country_name ~ '. Podés intentar con otro o' | translate %} {% else %} {% set wrong_zipcode_wording = '. ¿Está bien escrito?' | translate %} {% endif %} {{ "No encontramos este código postal{1}" | translate(wrong_zipcode_wording) }} {% if languages | length > 1 %} <a href="#" data-toggle="#{% if product_detail %}product{% else %}cart{% endif %}-shipping-country" class="js-modal-open btn-link btn-link-primary text-lowercase"> {{ 'cambiar tu país de entrega' | translate }} </a> {% endif %} {% endif %} {% endfor %} </div> <div class="js-ship-calculator-error js-ship-calculator-common-error alert alert-danger" style="display: none;">{{ "Ocurrió un error al calcular el envío. Por favor intentá de nuevo en unos segundos." | translate }}</div> <div class="js-ship-calculator-error js-ship-calculator-external-error alert alert-danger" style="display: none;">{{ "El calculo falló por un problema con el medio de envío. Por favor intentá de nuevo en unos segundos." | translate }}</div> </div> {% endblock input_form_alert %} {% block input_append_content %} <span class="col-6"> <button class="js-calculate-shipping btn btn-default btn-block" aria-label="{{ 'Calcular envío' | translate }}"> <span class="js-calculate-shipping-wording">{{ "Calcular" | translate }}</span> <span class="js-calculating-shipping-wording" style="display: none;">{{ "Calculando" | translate }}</span> </button> {% if shipping_calculator_variant %} <input type="hidden" name="variant_id" id="shipping-variant-id" value="{{ shipping_calculator_variant.id }}"> {% endif %} </span> {% endblock input_append_content %} {% endembed %} </div> </div> <div class="js-shipping-calculator-spinner shipping-spinner-container mb-3 float-left w-100 transition-soft text-center" style="display: none;"> <div class="spinner-ellipsis"> <div class="point"></div> <div class="point"></div> <div class="point"></div> <div class="point"></div> </div> </div> <div class="js-shipping-calculator-response mb-3 float-left w-100 {% if product_detail %}list list-readonly{% endif %}" style="display: none;"></div> </div> {# Shipping country modal #} {% if languages | length > 1 %} {% if product_detail %} {% set country_modal_id = 'product-shipping-country' %} {% else %} {% set country_modal_id = 'cart-shipping-country' %} {% endif %} {% embed "snipplets/modal.tpl" with{modal_id: country_modal_id, modal_class: 'bottom modal-centered-small js-modal-shipping-country', modal_position: 'center', modal_transition: 'slide', modal_header: true, modal_footer: true, modal_width: 'centered', modal_zindex_top: true, modal_mobile_full_screen: false} %} {% block modal_head %} {{ 'País de entrega' | translate }} {% endblock %} {% block modal_body %} {% embed "snipplets/forms/form-select.tpl" with{select_label: true, select_label_name: 'País donde entregaremos tu compra' | translate, select_aria_label: 'País donde entregaremos tu compra' | translate, select_custom_class: 'js-shipping-country-select', select_group_custom_class: 'mt-4' } %} {% block select_options %} {% for language in languages %} <option value="{{ language.country }}" data-country-url="{{ language.url }}" {% if language.active %}selected{% endif %}>{{ language.country_name }}</option> {% endfor %} {% endblock select_options%} {% endembed %} {% endblock %} {% block modal_foot %} <a href="#" class="js-save-shipping-country btn btn-primary float-right">{{ 'Aplicar' | translate }}</a> {% endblock %} {% endembed %} {% endif %}
shipping-calculator-item.tpl
Este es el item dentro del listado de opciones de envío. Dentro de este se puede mostrar:
Variable | Descripción |
---|---|
option.short_name | Nombre del medio de envío |
option.cost | Precio. Si option.cost == 0 se muestra la palabra grátis. |
option.old_cost | Precio viejo cuando es una opción de OCA con envío gratis |
option.time | Tiempo con formato en días, por ejemplo “5 días hábiles” |
option.time | Tiempo con formato en fechas, por ejemplo “Llega entre el martes 07/08 y el miércoles 09/08”. Recomendamos usar está opción ya que es más fácil de entender para el usuario. |
option.payment_rules | Un mensaje si el medio de envío está disponible solo para un determinado medio de pago |
Además si el medio de envío tiene subopciones incluimos el tpl de la carpeta shipping_suboptions (que vamos a agregar luego). Aplica solo a tiendas de argentina con el medio de envío OCA por ahora.
Y también hay un mensaje de alerta en caso que necesitemos mostrar alguna condición extra en el medio de envío. Esto es definido desde el PHP de Tiendanube.
También el condicional featured_option sobre el input radio. Este sirve para diferenciar las opciones de envío destacadas de las menos prioritarias a la hora de incluirlo en el tpl shipping_options.tpl (que incluiremos luego). Recordemos que este filtrado no incluye locales ni medios de envío personalizados.
El código es el siguiente:
<li class="js-shipping-list-item radio-button-item"> <label class="js-shipping-radio radio-button list-item" data-loop="shipping-radio-{{loop.index}}"> <input id="{% if featured_option %}featured-{% endif %}shipping-{{loop.index}}" class="js-shipping-method {% if not featured_option %}js-shipping-method-hidden{% endif %} shipping-method" data-price="{{option.cost.value}}" data-code="{{option.code}}" data-name="{{option.name}}" data-cost="{% if option.show_price %} {% if option.cost.value == 0 %}{{ 'Gratis' | translate }}{% else %}{{option.cost}}{% endif %}{% else %} {{ 'A convenir' | translate }} {% endif %}" type="radio" value="{{option.code}}" {% if featured_option and loop.first %}checked="checked"{% endif %} name="option" style="display:none" /> <span class="radio-button-content"> <span class="radio-button-icons"> <span class="radio-button-icon unchecked"></span> <span class="radio-button-icon checked"></span> </span> <span class="radio-button-label"> {# Improved shipping option with no carrier img and ordered shipping info #} <div class="radio-button-text"> {% if option.show_price %} <div class="mb-1 d-inline-block"> <span class="text-primary h6"> {% if option.cost.value == 0 %} {{ 'Gratis' | translate }} {% else %} {{option.cost}} {% endif %} </span> {% if option.cost.value == 0 and option.old_cost.value %} <span class="price-compare text-foreground font-small ml-1">{{option.old_cost}}</span> {% endif %} </div> {% endif %} {% if option.time %} <div> <strong> {% if store.has_smart_dates %} {{option.dates}} {% else %} {{option.time}} {% endif %} </strong> </div> {% endif %} </div> <div class="radio-button-text"> {{option.short_name}} {{ option.method == 'branch' ? option.extra.extra : '' }} </div> {% if option.payment_rules %} <div> {% include "snipplets/svg/info-circle.tpl" with {svg_custom_class: "icon-inline svg-icon-text"} %} <i>{{option.payment_rules}}</i> </div> {% endif %} {% if option.suboptions is not empty %} {% include "snipplets/shipping_suboptions/#{option.suboptions.type}.tpl" with {'suboptions': option.suboptions} %} {% endif %} {% if option.warning['enable'] %} <div class="alert alert-warning"> <p>{{ option.warning['message'] }}</p> </div> {% endif %} </span> </span> </label> </li>
3. Creamos el archivo shipping_options.tpl dentro de la carpeta snipplets. No debe cambiar su nombre ni ubicación ya que lo usamos desde el PHP de Tiendanube para mostrarlo dentro del div con la clase js-shipping-calculator-response en el tpl shipping-calculator.tpl
Dentro de este snipplet se construyen ambos listados de opciones de envío, la priorizadas y la que se ocultan en el link de “Ver más opciones”.
{% if options %} {% if store.show_shipping_emergency_message %} <div class="alert alert-warning">{{ store.shipping_emergency_message }}</div> {% endif %} <div class="{% if cart.items_count > 0 %}js-product-shipping-label{% endif %} font-small mb-4 pb-1" style="display: none;"> {{ 'Opciones para tu compra <strong>si sumás este producto</strong>.' | translate }} </div> {# Check for only shipping featured options #} {% set has_featured_shipping = false %} {% for option in options_to_show if option.shipping_type == 'ship' or option.shipping_type == 'delivery' or (option.method == 'table' and option.shipping_type == 'custom') %} {% if option |length >= 1 %} {% set has_featured_shipping = true %} {% endif %} {% endfor %} {# Check for only non featured shipping options #} {% set has_non_featured_shipping = false %} {% for option in options_to_hide if option.shipping_type == 'ship' or option.shipping_type == 'delivery' or (option.method == 'table' and option.shipping_type == 'custom') %} {% if option |length >= 1 %} {% set has_non_featured_shipping = true %} {% endif %} {% endfor %} {# Pickup featured options #} {% set has_non_featured_pickup = false %} {% set has_featured_pickup = false %} {# Check for only pickup featured options #} {% for option in options_to_show if option.shipping_type == 'pickup' and option.method != 'branch' %} {% if option |length >= 1 %} {% set has_featured_pickup = true %} {% endif %} {% endfor %} {# Check for only non featured pickup options #} {% for option in options_to_hide if option.shipping_type == 'pickup' and option.method != 'branch' %} {% if option |length >= 1 %} {% set has_non_featured_pickup = true %} {% endif %} {% endfor %} {# Shipping options #} {% if has_featured_shipping %} <div class="full-width-container {% if has_featured_pickup %}mb-4{% endif %}"> <div class="form-label mb-2"> {% include "snipplets/svg/truck.tpl" with {svg_custom_class: "icon-inline icon-lg svg-icon-text mr-2 align-bottom"} %} {{ "Envío a domicilio" | translate }} </div> <ul class="box radio-button-container p-0 mb-0 list-unstyled"> {# Smart shipping hides similar shipping options on a toggle div and also shows an improved shipping item #} {# Check if smart shipping is needed #} {# Include branch options inside calculador #} {% for option in options_to_show if option.shipping_type == 'ship' or option.shipping_type == 'delivery' or (option.method == 'table' and option.shipping_type == 'custom') %} {% include "snipplets/shipping/shipping-calculator-item.tpl" with {'featured_option': true} %} {% endfor %} {% if has_non_featured_shipping %} <div class="js-other-shipping-options w-100 float-left shipping-extra-options" style="display: none;"> {# Smart shipping hides similar shipping options on a toggle div and also shows an improved shipping item #} {# Check if smart shipping is needed #} {# Include branch options inside calculador #} {% for option in options_to_hide if option.shipping_type == 'ship' or option.shipping_type == 'delivery' or (option.method == 'table' and option.shipping_type == 'custom') %} {% include "snipplets/shipping/shipping-calculator-item.tpl" %} {% endfor %} </div> {% endif %} </ul> {% if has_non_featured_shipping %} <div class="js-toggle-more-shipping-options js-show-more-shipping-options w-100 float-left text-center mt-2"> <a href="#" class="btn-link"> <span class="js-shipping-see-more"> {{ 'Ver más opciones de envío' | translate }} </span> <span class="js-shipping-see-less" style="display: none;"> {{ 'Ver menos opciones de envío' | translate }} </span> </a> </div> {% endif %} </div> {% endif %} {# Pickup featured options #} {% if has_featured_pickup %} <div class="full-width-container mb-2"> <div class="form-label mb-2"> {% include "snipplets/svg/map-marker-alt.tpl" with {svg_custom_class: "icon-inline icon-lg svg-icon-text mr-2 align-bottom"} %} {{ "Retirar por" | translate }} </div> <ul class="list-unstyled box radio-button-container p-0 mb-0"> {# Smart shipping hides similar shipping options on a toggle div and also shows an improved shipping item #} {# List only pickup featured options #} {% for option in options_to_show if option.shipping_type == 'pickup' and option.method != 'branch' %} {% include "snipplets/shipping/shipping-calculator-item.tpl" with {'featured_option': true, 'pickup' : true} %} {% endfor %} {% if has_non_featured_pickup %} <div class="js-other-pickup-options w-100 float-left shipping-extra-options" style="display: none;"> {# Smart shipping hides similar shipping options on a toggle div and also shows an improved shipping item #} {# List only pickup featured options: same logic as for featured pickups but for non featured #} {% for option in options_to_hide if option.shipping_type == 'pickup' and option.method != 'branch' %} {% include "snipplets/shipping/shipping-calculator-item.tpl" with {'pickup' : true} %} {% endfor %} </div> {% endif %} </ul> {% if has_non_featured_pickup %} <div class="js-toggle-more-shipping-options js-show-other-pickup-options w-100 float-left text-center mt-2"> <a href="#" class="btn-link"> <span class="js-shipping-see-more"> {{ 'Ver más opciones de retiro' | translate }} </span> <span class="js-shipping-see-less" style="display: none;"> {{ 'Ver menos opciones de retiro' | translate }} </span> </a> </div> {% endif %} </div> {% endif %} {% if store.has_smart_dates and show_time %} <div class="font-small float-left w-100 mb-3">{{"El tiempo de entrega <strong>no considera feriados</strong>." | translate}}</div> {% endif %} {% else %} <span>{{"No hay costos de envío para el código postal dado." | translate}}</span> {% endif %} {# Don't remove this #} <input type="hidden" name="after_calculation" value="1"/> <input type="hidden" name="zipcode" value="{{zipcode}}"/>
4. Agregamos la carpeta shipping_suboptions dentro de la carpeta snipplet y por último creamos un tpl llamado select.tpl.
Este muestra subopciones en forma de un select para los medios de envío que necesiten mostrarlas, por ahora solo aplica a las sucursales de OCA.
{% set selected_option = loop.first or cart.shipping_option == option.name %} <div class="js-shipping-suboption {{suboptions.name}}"> {% if suboptions.options %} {# Read only suboptions inside popup #} {% set modal_id_val = (suboptions.name | sanitize) ~ '-pickup-modal-' ~ random() %} <div data-toggle="#{{ modal_id_val }}" class="js-modal-open mt-2"> {% include "snipplets/svg/map-marker-alt.tpl" with {svg_custom_class: "icon-inline icon-lg mr-1"} %} <span class="btn-link btn-link-primary align-bottom">{{ 'Ver puntos de retiro' | translate }}</span> </div> {% embed "snipplets/modal.tpl" with{modal_id: modal_id_val, modal_class: 'bottom modal-centered-small js-modal-shipping-suboptions', modal_position: 'center', modal_transition: 'slide', modal_header: true, modal_footer: false, modal_width: 'centered', modal_zindex_top: true} %} {% block modal_head %} {{ 'Puntos de retiro' | translate }} {% endblock %} {% block modal_body %} <ul class="list-unstyled py-2"> {% for option in suboptions.options %} <li class="text-capitalize mb-3">{% include "snipplets/svg/map-marker-alt.tpl" with {svg_custom_class: "icon-inline svg-icon-primary d-flex float-left mr-2"} %} <span class="d-flex">{{ option.name | lower }}</span></li> {% endfor %} </ul> <div class="mt-4"><span class="opacity-50">{{ 'Cercanos al CP:'}}</span> <span class="text-primary font-weight-bold">{{cart.shipping_zipcode}}</span></div> <div class="mt-2 font-small"> {% include "snipplets/svg/info-circle.tpl" with {svg_custom_class: "icon-inline svg-icon-text"} %} <i>{{ "Vas a poder elegir estas opciones antes de finalizar tu compra" | translate }}</i> </div> {% endblock %} {% endembed %} {% else %} <input type="hidden" name="{{suboptions.name}}"/> <div>{{ suboptions.no_options_message | translate }}</div> {% endif %} </div>
5. Ahora necesitamos llamar al calculador de envíos y los locales tanto en el carrito como en el detalle del producto.
Para el detalle del producto agregamos lo siguiente en product-form.tpl o el archivo donde tengas el formulario de producto, dentro del mismo:
{% set show_product_fulfillment = settings.shipping_calculator_product_page and (store.has_shipping or store.branches) and not product.free_shipping and not product.is_non_shippable %} {% if show_product_fulfillment %} <div class="divider"></div> {# Shipping calculator and branch link #} <div id="product-shipping-container" class="product-shipping-calculator list" {% if not product.display_price or not product.has_stock %}style="display:none;"{% endif %} data-shipping-url="{{ store.shipping_calculator_url }}"> {# Shipping Calculator #} {% if store.has_shipping %} {% include "snipplets/shipping/shipping-calculator.tpl" with {'shipping_calculator_variant' : product.selected_or_first_available_variant, 'product_detail': true} %} {% endif %} {% if store.branches %} {# Link for branches #} {% include "snipplets/shipping/branches.tpl" with {'product_detail': true} %} {% endif %} </div> <div class="divider"></div> {% endif %}
Luego tenemos que agregar el calculador en el carrito, en el theme Base se encuentra en el snipplet cart-totals.tpl pero en tu diseño puede que esté en cart.tpl o cart-panel-ajax.tpl
{# Define contitions to show shipping calculator and store branches on cart #} {% set show_calculator_on_cart = settings.shipping_calculator_cart_page and store.has_shipping %} {% set show_cart_fulfillment = settings.shipping_calculator_cart_page and (store.has_shipping or store.branches) %} {% if show_cart_fulfillment %} <div class="js-fulfillment-info js-allows-non-shippable" {% if not cart.has_shippable_products %}style="display: none"{% endif %}> <div class="js-visible-on-cart-filled divider {% if cart_page %}d-md-none{% endif %}" {% if cart.items_count == 0 %}style="display:none;"{% endif %}></div> <div class="js-visible-on-cart-filled js-has-new-shipping js-shipping-calculator-container container-fluid"> {# Saved shipping not available #} <div class="js-shipping-method-unavailable alert alert-warning row" style="display: none;"> <div> <strong>{{ 'El medio de envío que habías elegido ya no se encuentra disponible para este carrito. ' | translate }}</strong> </div> <div> {{ '¡No te preocupes! Podés elegir otro.' | translate}} </div> </div> {# Shipping calculator and branch link #} <div id="cart-shipping-container" class="row" {% if cart.items_count == 0 %} style="display: none;"{% endif %} data-shipping-url="{{ store.shipping_calculator_url }}"> {# Used to save shipping #} <span id="cart-selected-shipping-method" data-code="{{ cart.shipping_data.code }}" class="hidden">{{ cart.shipping_data.name }}</span> {# Shipping Calculator #} {% if store.has_shipping %} {% include "snipplets/shipping/shipping-calculator.tpl" with {'product_detail': false} %} {% endif %} {# Store branches #} {% if store.branches %} {# Link for branches #} {% include "snipplets/shipping/branches.tpl" with {'product_detail': false} %} {% endif %} </div> </div> <div class="js-visible-on-cart-filled divider {% if cart_page %}d-md-none{% endif %} {% if not store.branches %} mt-0{% endif %}" {% if cart.items_count == 0 %}style="display:none;"{% endif %}></div> </div> </div> {% endif %}
6. Necesitamos agregar dentro del template product.tpl en el div padre de todo el detalle de producto el ID “single-product” y las clases js-has-new-shipping js-product-detail js-product-container, js-shipping-calculator-container. Quedando de la siguiente forma:
<div id="single-product" class="js-has-new-shipping js-product-detail js-product-container js-shipping-calculator-container" data-variants="{{product.variants_object | json_encode }}" itemscope itemtype="http://schema.org/Product"> HTML del detalle de producto </div>
7. Creamos la carpeta forms dentro de la carpeta snipplets. Acá necesitamos sumar un archivo nuevo con el nombre form-input.tpl que vamos a usar para el input del calculador.
{# /*============================================================================ #Form input ==============================================================================*/ #Properties #Group //input_group_custom_class for custom CSS classes #Label // input_label_id for ID // input_for for label for // input_label_custom_class for custom CSS classes // input_label_text for label text #Prepend // input_prepend_content to add content before input #Container (Only if has prepend or append) // form_control_container_custom_class for container custom class. E.g: col #Input // Can be text_area or input // input_type to define type (text, tel, number or passowrd) // input_id for id // input_name for name // input_value for val // input_placeholder for placeholder // input_custom_class for custom CSS classes // input_rows for textarea rows // input_data_attr for data attributes // input_data_val for input_data_attr value // input_aria_label for aria-label attribute #Append // input_append_content to add content after input #Alerts // input_form_alert to insert alerts #} <div class="form-group {{ input_group_custom_class }}"> {% if input_label_text %} <label {% if input_label_id %}id="{{ input_label_id }}"{% endif %} class="form-label {{ input_label_custom_class }}" {% if input_for %}for="{{ input_name }}"{% endif %}>{{ input_label_text }}</label> {% endif %} {% block input_prepend_content %} {% endblock input_prepend_content %} {% if input_append_content or input_prepend_content %} <div class="form-control-container {{ form_control_container_custom_class }}"> {% endif %} {% if text_area %} <textarea {% if input_id %}id="{{ input_id }}"{% endif %} class="form-control form-control-area {{ input_custom_class }} {% if input_append_content %}form-control-inline{% endif %}" autocorrect="off" autocapitalize="off" {% if input_name %}name="{{ input_name }}"{% endif %} {% if input_value %}value="{{ input_value }}"{% endif %} {% if input_rows %}rows="{{ input_rows }}"{% endif %} {% if input_placeholder %}placeholder="{{ input_placeholder }}"{% endif %} {% if input_data_attr %}data-{{ input_data_attr }}="{{ input_data_val }}"{% endif %}></textarea> {% else %} <input type="{% if type_text %}text{% elseif type_number %}number{% elseif type_tel %}tel{% elseif type_password %}password{% elseif type_hidden %}hidden{% endif %}" {% if input_id %}id="{{ input_id }}"{% endif %} class="form-control {{ input_custom_class }} {% if input_append_content %}form-control-inline{% endif %}" autocorrect="off" autocapitalize="off" {% if type_password %}autocomplete="off"{% endif %} {% if input_name %}name="{{ input_name }}"{% endif %} {% if input_value %}value="{{ input_value }}"{% endif %} {% if input_min %}min="{{ input_min }}"{% endif %} {% if input_placeholder %}placeholder="{{ input_placeholder }}"{% endif %} {% if input_data_attr %}data-{{ input_data_attr }}="{{ input_data_val }}"{% endif %} {% if input_aria_label %}aria-label="{{ input_aria_label }}"{% endif %}/> {% endif %} {% if input_append_content or input_prepend_content %} </div> {% endif %} {% block input_append_content %} {% endblock input_append_content %} {% if input_help %} <div class="mt-4 text-center"> <a href="{{ input_help_link }}" class="btn-link {{ input_link_class }}">{% block input_help_text %}{% endblock input_help_text %}</a> </div> {% endif %} {% block input_form_alert %} {% endblock input_form_alert %} </div>
8. Ahora necesitamos crear el snipplet para el componente modal o popup dentro de la carpeta snipplets. Este tpl se llama modal.tpl y el código es:
{# /*============================================================================ #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>
9. Por último para la parte de HTML, necesitamos agregar una carpeta SVG dentro de la carpeta snipplets. Acá vamos sumar los SVGs que usamos para los iconos en el calculador.
store.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 616 512"><path d="M602 118.6L537.1 15C531.3 5.7 521 0 510 0H106C95 0 84.7 5.7 78.9 15L14 118.6c-29.6 47.2-10 110.6 38 130.8v227.4c0 19.4 14.3 35.2 32 35.2h448c17.7 0 32-15.8 32-35.2V249.4c48-20.2 67.6-83.6 38-130.8zM516 464H100v-96h416zm-.2-144.2H100v-64.7c24-3.3 45.1-15.2 60.3-32.2 18 20.1 44.3 33.1 73.8 33.1 29.6 0 55.8-13 73.8-33.1 18 20.1 44.3 33.1 73.8 33.1 29.6 0 55.8-13 73.8-33.1 15.3 17 36.3 28.9 60.3 32.2zm47.9-133c-3.2 6.8-10.9 18.6-27 20.8-2.4.3-4.8.5-7.2.5-14.7 0-28.2-6.1-38.1-17.2L455.7 151 420 190.8c-9.9 11.1-23.5 17.2-38.1 17.2s-28.2-6.1-38.1-17.2L308 151l-35.7 39.8c-9.9 11.1-23.5 17.2-38.1 17.2-14.7 0-28.2-6.1-38.1-17.2L160.3 151l-35.7 39.8c-9.9 11.1-23.5 17.2-38.1 17.2-2.5 0-4.9-.2-7.2-.5-16-2.2-23.8-13.9-27-20.8-5-10.8-7.1-27.6 2.3-42.6L114.8 48h386.3l60.2 96.1c9.5 15.1 7.5 31.9 2.4 42.7z"/></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>
chevron-up.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M6.101 359.293L25.9 379.092c4.686 4.686 12.284 4.686 16.971 0L224 198.393l181.13 180.698c4.686 4.686 12.284 4.686 16.971 0l19.799-19.799c4.686-4.686 4.686-12.284 0-16.971L232.485 132.908c-4.686-4.686-12.284-4.686-16.971 0L6.101 342.322c-4.687 4.687-4.687 12.285 0 16.971z"/></svg>
info-circle.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 448c-110.532 0-200-89.431-200-200 0-110.495 89.472-200 200-200 110.491 0 200 89.471 200 200 0 110.53-89.431 200-200 200zm0-338c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/></svg>
truck.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M624 368h-16V251.9c0-19-7.7-37.5-21.1-50.9L503 117.1C489.6 103.7 471 96 452.1 96H416V56c0-30.9-25.1-56-56-56H56C25.1 0 0 25.1 0 56v304c0 30.9 25.1 56 56 56h8c0 53 43 96 96 96s96-43 96-96h128c0 53 43 96 96 96s96-43 96-96h48c8.8 0 16-7.2 16-16v-16c0-8.8-7.2-16-16-16zm-464 96c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48zm208-96H242.7c-16.6-28.6-47.2-48-82.7-48s-66.1 19.4-82.7 48H56c-4.4 0-8-3.6-8-8V56c0-4.4 3.6-8 8-8h304c4.4 0 8 3.6 8 8v312zm48-224h36.1c6.3 0 12.5 2.6 17 7l73 73H416v-80zm64 320c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48zm80-100.9c-17.2-25.9-46.6-43.1-80-43.1-24.7 0-47 9.6-64 24.9V272h144v91.1z"/></svg>
sync-alt.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M483.515 28.485L431.35 80.65C386.475 35.767 324.485 8 256 8 123.228 8 14.824 112.338 8.31 243.493 7.971 250.311 13.475 256 20.301 256h28.045c6.353 0 11.613-4.952 11.973-11.294C66.161 141.649 151.453 60 256 60c54.163 0 103.157 21.923 138.614 57.386l-54.128 54.129c-7.56 7.56-2.206 20.485 8.485 20.485H492c6.627 0 12-5.373 12-12V36.971c0-10.691-12.926-16.045-20.485-8.486zM491.699 256h-28.045c-6.353 0-11.613 4.952-11.973 11.294C445.839 370.351 360.547 452 256 452c-54.163 0-103.157-21.923-138.614-57.386l54.128-54.129c7.56-7.56 2.206-20.485-8.485-20.485H20c-6.627 0-12 5.373-12 12v143.029c0 10.691 12.926 16.045 20.485 8.485L80.65 431.35C125.525 476.233 187.516 504 256 504c132.773 0 241.176-104.338 247.69-235.493.339-6.818-5.165-12.507-11.991-12.507z"/></svg>
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:
{# This mixin adds browser prefixes to a CSS property #} @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; } &-default{ padding: 10px 15px; background-color: rgba($main-foreground, .2); color: $main-foreground; fill: $main-foreground; font-weight: bold; } &-primary{ padding: 15px; background-color: $primary-color; color: $main-background; fill: $main-background; letter-spacing: 4px; @extend %body-font; &:hover{ color: $main-background; fill: $main-background; } } &-secondary{ padding: 10px 15px; background-color: $main-background; color: $main-foreground; fill: $main-foreground; border: 1px solid $main-foreground; } &-block{ float: left; width: 100%; } } {# /* // Links */ #} .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); } } {# /* // 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; } .radio-button { input[type="radio"]{ & + .radio-button-content .unchecked{ border:2px solid $main-foreground; } & + .radio-button-content .checked{ background-color: $main-foreground; } } } .spinner-ellipsis .point { background-color: rgba($main-foreground, 0.2); }
2. Agregar los estilos dentro del archivo static/style-critical.tpl
Si en tu diseño usas una hoja de estilos para el CSS crítico, vamos a necesitar agregar el siguiente código debajo dentro de la misma, pero si no es el caso podés unificar el CSS de los pasos 2 y 3 en un solo archivo.
{# /* // Placeholders and preloaders */ #} .spinner-ellipsis { position: relative; display: inline-block; width: 64px; height: 40px; } .spinner-ellipsis .point { position: absolute; top: 15px; width: 11px; height: 11px; border-radius: 50%; animation-timing-function: cubic-bezier(0, 1, 1, 0); } .spinner-ellipsis .point:nth-child(1) { left: 6px; animation: spinner-ellipsis1 0.6s infinite; } .spinner-ellipsis .point:nth-child(2) { left: 6px; animation: spinner-ellipsis2 0.6s infinite; } .spinner-ellipsis .point:nth-child(3) { left: 26px; animation: spinner-ellipsis2 0.6s infinite; } .spinner-ellipsis .point:nth-child(4) { left: 45px; animation: spinner-ellipsis3 0.6s infinite; } @keyframes spinner-ellipsis1 { 0% { transform: scale(0); } 100% { transform: scale(1); } } @keyframes spinner-ellipsis3 { 0% { transform: scale(1); } 100% { transform: scale(0); } } @keyframes spinner-ellipsis2 { 0% { transform: translate(0, 0); } 100% { transform: translate(19px, 0); } } {# /* // 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; } /*============================================================================ #Cart detail ==============================================================================*/ {# /* // Shipping Calculator */ #} .free-shipping-title { position: relative; width: 100%; height: 55px; } .shipping-calculator-head.with-zip { height: 65px; } .shipping-calculator-head.with-zip.with-free-shipping { height: 110px; } .shipping-calculator-head.with-form { height: 110px; } .shipping-calculator-head.with-form + .shipping-spinner-container { margin-top: -20px; } .shipping-calculator-head.with-error { height: 155px; }
3. Agregar los estilos dentro del archivo static/style-async.tpl
Si en tu diseño usas una hoja de estilos para CSS asíncrono, vamos a necesitar agregar el siguiente código dentro de la misma, pero si no es el caso podés unificar el CSS de los pasos 2 y 3 en un solo archivo.
{# /* // Margin and Padding */ #} %section-margin { margin-bottom: 70px; } %element-margin { margin-bottom: 35px; } %element-margin-small { margin-bottom: 20px; } {# /* // Mixins */ #} {# This mixin adds browser prefixes to a CSS property #} @mixin prefix($property, $value, $prefixes: ()) { @each $prefix in $prefixes { #{'-' + $prefix + '-' + $property}: $value; } #{$property}: $value; } {# /* // Animations */ #} .transition-up { position: relative; top: -8px; z-index: 10; @include prefix(transition, all 0.5s ease, webkit ms moz o); pointer-events: none; &-active { top: 0; opacity: 1; z-index: 100; pointer-events: all; } } {# /* // Forms */ #} .form-group{ @extend %element-margin; .form-label{ float: left; width: 100%; margin-bottom: 10px; } .alert{ margin: 10px 0 0 0; } } .radio-button{ width: 100%; float: left; clear: both; text-align: left; -webkit-tap-highlight-color: rgba(0,0,0,0); @extend %element-margin-small; cursor: pointer; &.disabled{ opacity: 0.6; cursor: not-allowed; input[type="radio"] { cursor: not-allowed; } } &-icons{ position: relative; float: left; display: table; width: 20px; margin:0 5px 0 0; } &-icon{ border-radius: 50%; width: 18px; height: 18px; } input[type="radio"]{ display: none; & + .radio-button-content .unchecked{ float: left; } & + .radio-button-content .checked{ position: absolute; left:9px; top:9px; width:0; height: 0; @include prefix(transform, translate(-50%,-50%), webkit ms moz o); @include prefix(transition, all 0.2s , webkit ms moz o); } &:checked + .radio-button-content .checked{ width: 6px; height: 6px; } } &-label{ display: table; padding-top: 1px; } &-text{ display: table; margin-bottom: 2px; } } .radio-button-item:last-of-type .radio-button{ margin-bottom: 0; } .form-select { display: block; width: 100%; &:focus{ outline:0; } &::-ms-expand { display: none; } } {# /* Disabled controls */ #} input, select, textarea{ &[disabled], &[disabled]:hover, &[readonly], &[readonly]:hover{ background-color: #DDD; cursor: not-allowed; } }
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.
1. Necesitamos agregar el JS que hace funcionar al componente del modal en general en el archivo store.js.tpl
{#/*============================================================================ #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(); } });
2. También necesitamos agregar en el archivo store.js.tpl (o donde tengas tus funciones de JS). El código que necesitamos es el siguiente:
{#/*============================================================================ #Shipping calculator ==============================================================================*/ #} window.urls = { "shippingUrl": "{{ store.shipping_calculator_url | escape('js') }}" } {# /* // Select and save shipping function */ #} selectShippingOption = function(elem, save_option) { jQueryNuvem(".js-shipping-method, .js-branch-method").removeClass('js-selected-shipping-method'); jQueryNuvem(elem).addClass('js-selected-shipping-method'); if (save_option) { LS.saveCalculatedShipping(true); } if(jQueryNuvem(elem).hasClass("js-shipping-method-hidden")){ {# Toggle other options visibility depending if they are pickup or delivery for cart and product at the same time #} if(jQueryNuvem(elem).hasClass("js-pickup-option")){ jQueryNuvem(".js-other-pickup-options, .js-show-other-pickup-options .js-shipping-see-less").show(); jQueryNuvem(".js-show-other-pickup-options .js-shipping-see-more").hide(); }else{ jQueryNuvem(".js-other-shipping-options, .js-show-more-shipping-options .js-shipping-see-less").show(); jQueryNuvem(".js-show-more-shipping-options .js-shipping-see-more").hide() } } }; {# Apply zipcode saved by cookie if there is no zipcode saved on cart from backend #} if (cookieService.get('calculator_zipcode')) { {# If there is a cookie saved based on previous calcualtion, add it to the shipping input to triggert automatic calculation #} var zipcode_from_cookie = cookieService.get('calculator_zipcode'); {% if settings.ajax_cart %} {# If ajax cart is active, target only product input to avoid extra calulation on empty cart #} jQueryNuvem('#product-shipping-container .js-shipping-input').val(zipcode_from_cookie); {% else %} {# If ajax cart is inactive, target the only input present on screen #} jQueryNuvem('.js-shipping-input').val(zipcode_from_cookie); {% endif %} jQueryNuvem(".js-shipping-calculator-current-zip").text(zipcode_from_cookie); {# Hide the shipping calculator and show spinner #} jQueryNuvem(".js-shipping-calculator-head").addClass("with-zip").removeClass("with-form"); jQueryNuvem(".js-shipping-calculator-with-zipcode").addClass("transition-up-active"); jQueryNuvem(".js-shipping-calculator-spinner").show(); } else { {# If there is no cookie saved, show calcualtor #} jQueryNuvem(".js-shipping-calculator-form").addClass("transition-up-active"); } {# Remove shipping suboptions from DOM to avoid duplicated modals #} removeShippingSuboptions = function(){ var shipping_suboptions_id = jQueryNuvem(".js-modal-shipping-suboptions").attr("id"); jQueryNuvem("#" + shipping_suboptions_id).remove(); jQueryNuvem('.js-modal-overlay[data-modal-id="#' + shipping_suboptions_id + '"').remove(); }; {# /* // Calculate shipping function */ #} jQueryNuvem(".js-calculate-shipping").on("click", function (e) { e.preventDefault(); {# Take the Zip code to all shipping calculators on screen #} let shipping_input_val = jQueryNuvem(e.currentTarget).closest(".js-shipping-calculator-form").find(".js-shipping-input").val(); jQueryNuvem(".js-shipping-input").val(shipping_input_val); {# Calculate on page load for both calculators: Product and Cart #} {% if template == 'product' %} if (!vanillaJS) { LS.calculateShippingAjax( jQueryNuvem('#product-shipping-container').find(".js-shipping-input").val(), '{{store.shipping_calculator_url | escape('js')}}', jQueryNuvem("#product-shipping-container").closest(".js-shipping-calculator-container") ); } {% endif %} if (jQueryNuvem(".js-cart-item").length) { LS.calculateShippingAjax( jQueryNuvem('#cart-shipping-container').find(".js-shipping-input").val(), '{{store.shipping_calculator_url | escape('js')}}', jQueryNuvem("#cart-shipping-container").closest(".js-shipping-calculator-container") ); } jQueryNuvem(".js-shipping-calculator-current-zip").html(shipping_input_val); removeShippingSuboptions(); }); {# /* // Calculate shipping by submit */ #} jQueryNuvem(".js-shipping-input").on('keydown', function (e) { var key = e.which ? e.which : e.keyCode; var enterKey = 13; if (key === enterKey) { e.preventDefault(); jQueryNuvem(e.currentTarget).closest(".js-shipping-calculator-form").find(".js-calculate-shipping").trigger('click'); if (window.innerWidth < 768) { jQueryNuvem(e.currentTarget).trigger('blur'); } } }); {# /* // Shipping and branch click */ #} jQueryNuvem(document).on("change", ".js-shipping-method, .js-branch-method", function (e) { selectShippingOption(this, true); jQueryNuvem(".js-shipping-method-unavailable").hide(); }); {# /* // Select shipping first option on results */ #} jQueryNuvem(document).on('shipping.options.checked', '.js-shipping-method', function (e) { let shippingPrice = jQueryNuvem(this).attr("data-price"); LS.addToTotal(shippingPrice); let total = (LS.data.cart.total / 100) + parseFloat(shippingPrice); jQueryNuvem(".js-cart-widget-total").html(LS.formatToCurrency(total)); selectShippingOption(this, false); }); {# /* // Toggle branches link */ #} jQueryNuvem(document).on("click", ".js-toggle-branches", function (e) { e.preventDefault(); jQueryNuvem(".js-store-branches-container").slideToggle("fast"); jQueryNuvem(".js-see-branches, .js-hide-branches").toggle(); }); {# /* // Toggle more shipping options */ #} jQueryNuvem(document).on("click", ".js-toggle-more-shipping-options", function(e) { e.preventDefault(); {# Toggle other options depending if they are pickup or delivery for cart and product at the same time #} if(jQueryNuvem(this).hasClass("js-show-other-pickup-options")){ jQueryNuvem(".js-other-pickup-options").slideToggle(600); jQueryNuvem(".js-show-other-pickup-options .js-shipping-see-less, .js-show-other-pickup-options .js-shipping-see-more").toggle(); }else{ jQueryNuvem(".js-other-shipping-options").slideToggle(600); jQueryNuvem(".js-show-more-shipping-options .js-shipping-see-less, .js-show-more-shipping-options .js-shipping-see-more").toggle(); } }); {# /* // Calculate shipping on page load */ #} {# Only shipping input has value, cart has saved shipping and there is no branch selected #} calculateCartShippingOnLoad = function(){ if(jQueryNuvem("#cart-shipping-container .js-shipping-input").val()){ // If user already had calculated shipping: recalculate shipping setTimeout(function() { LS.calculateShippingAjax( jQueryNuvem('#cart-shipping-container').find(".js-shipping-input").val(), '{{store.shipping_calculator_url | escape('js')}}', jQueryNuvem("#cart-shipping-container").closest(".js-shipping-calculator-container") ); removeShippingSuboptions(); }, 100); } if(jQueryNuvem(".js-branch-method").hasClass('js-selected-shipping-method')){ {% if store.branches|length > 1 %} jQueryNuvem(".js-store-branches-container").slideDown("fast"); jQueryNuvem(".js-see-branches").hide(); jQueryNuvem(".js-hide-branches").show(); {% endif %} } }; {% if cart.has_shippable_products %} calculateCartShippingOnLoad(); {% endif %} {# /* // Calculate product detail shipping on page load */ #} {% if template == 'product' %} if (!vanillaJS) { if(jQueryNuvem('#product-shipping-container').find(".js-shipping-input").val()){ setTimeout(function() { LS.calculateShippingAjax( jQueryNuvem('#product-shipping-container').find(".js-shipping-input").val(), '{{store.shipping_calculator_url | escape('js')}}', jQueryNuvem("#product-shipping-container").closest(".js-shipping-calculator-container") ); removeShippingSuboptions(); }, 100); } } {% endif %} {# /* // Change CP */ #} jQueryNuvem(document).on("click", ".js-shipping-calculator-change-zipcode", function(e) { e.preventDefault(); jQueryNuvem(".js-shipping-calculator-response").fadeOut(100); jQueryNuvem(".js-shipping-calculator-head").addClass("with-form").removeClass("with-zip"); jQueryNuvem(".js-shipping-calculator-with-zipcode").removeClass("transition-up-active"); jQueryNuvem(".js-shipping-calculator-form").addClass("transition-up-active"); }); {# /* // Shipping provinces */ #} {% if provinces_json %} jQueryNuvem('select[name="country"]').on("change", function (e) { var provinces = {{ provinces_json | default('{}') | raw }}; LS.swapProvinces(provinces[jQueryNuvem(e.currentTarget).val()]); }).trigger('change'); {% endif %} {# /* // Change store country: From invalid zipcode message */ #} changeLang = function(element) { var selected_country_url = element.find("option").filter((el) => el.selected).attr("data-country-url"); location.href = selected_country_url; }; jQueryNuvem('.js-lang-select').on("change", function (e) { lang_select_option = jQueryNuvem(this); changeLang(lang_select_option); }); jQueryNuvem(document).on("click", ".js-save-shipping-country", function(e) { e.preventDefault(); {# Change shipping country #} lang_select_option = jQueryNuvem(this).closest(".js-modal-shipping-country"); changeLang(lang_select_option); jQueryNuvem(this).text('{{ "Aplicando..." | translate }}').addClass("disabled"); });
2. Dentro de la función para cambiar de variantes function changeVariant antes de su cierre agregamos los siguiente:
LS.updateShippingProduct(); zipcode_on_changevariant = jQueryNuvem("#product-shipping-container .js-shipping-input").val(); jQueryNuvem("#product-shipping-container .js-shipping-calculator-current-zip").text(zipcode_on_changevariant);
3. Si tu diseño tiene carrito de compras rápida, necesitamos agregar el siguiente código dentro del callback cuando se agrega un producto con éxito. Esto podemos encontrarlo en la función disparada por el botón js-addtocart . Ubicado esto agregamos lo siguiente:
{# Update shipping input zipcode on add to cart #} {# Use zipcode from input if user is in product page, or use zipcode cookie if is not #} if (jQueryNuvem("#product-shipping-container .js-shipping-input").val()) { zipcode_on_addtocart = jQueryNuvem("#product-shipping-container .js-shipping-input").val(); jQueryNuvem("#cart-shipping-container .js-shipping-input").val(zipcode_on_addtocart); jQueryNuvem(".js-shipping-calculator-current-zip").text(zipcode_on_addtocart); } else if (cookieService.get('calculator_zipcode')){ var zipcode_from_cookie = cookieService.get('calculator_zipcode'); jQueryNuvem('.js-shipping-input').val(zipcode_from_cookie); jQueryNuvem(".js-shipping-calculator-current-zip").text(zipcode_from_cookie); }
Configuraciones
En el archivo config/settings.txt vamos a agregar el checkbox para poder activar o desactivar el calculador y los locales desde la sección de Personalizar tu diseño actual en el Administrador nube
Dentro de la sección de Detalle de producto agregamos lo siguiente:
title title = Medios de envío checkbox_global name = shipping_calculator_product_page description = Mostrar calculador de costos de envío en la página de producto
Y hacemos lo mismo para la sección de Carrito de compras
title title = Medios de envío checkbox_global name = shipping_calculator_cart_page description = Mostrar calculador de costos de envío en el carrito
Traducciones
En este paso agregamos los textos para las traducciones en el archivo config/translations.txt
es "Formas de envío" pt "Formas de envio" en "Shipping methods" es_mx "Opciones de envío" es "FORMAS DE ENVÍO" pt "FORMAS DE ENVIO" en "SHIPPING METHODS" es_mx "OPCIONES DE ENVÍO" es "Ejemplos de formas de envío" pt "Exemplos de formas de envio" en "Shipping methods examples" es_mx "Ejemplos de opciones de envío" es "Ingrese aquí su código postal para calcular su costo de envío" pt "Digite aqui o seu CEP para calcular o frete" en "Enter your zipcode to calculate your shipping cost" es_mx "Ingresa tu código postal para calcular el costo de envío" es "Calculá el costo de tu envío" pt "Cálculo de frete" en "Enter your zipcode to calculate your shipping cost" es_mx "Calcula el costo de tu envío" es "Calcular envío" pt "Calcular frete" en "Calculate shipping" es_mx "Calcular envío" es "Calcular" pt "Calcular" en "Calculate" es_mx "Calcular" es "Código postal" pt "CEP" en "Zipcode" es_mx "Código postal" es "Tu código postal" pt "Seu CEP" en "Your zipcode" es_mx "Tu código postal" es "No sé mi código" pt "Não sei meu CEP" en "I don't know my code" es_mx "No sé mi código" es "Calcular costo de envío" pt "Calcular Frete" en "Calculate shipping" es_mx "Calcular costo de envío" es "El código postal es inválido." pt "O CEP está inválido." en "The zipcode is not valid." es_mx "El código postal es inválido." es " (sin envío)" pt " (sem frete)" en " (without shipping cost)" es_mx " (sin envío)" es "El código postal es inválido. Por favor intentá de nuevo usando otro." pt "CEP inválido. Por favor, digite novamente ou informe outro." en "The zipcode is not valid. Please try again using another." es_mx "El código postal es inválido. Intenta de nuevo ingresando otro." es "Ocurrió un error al calcular el envío. Por favor intentá de nuevo en unos segundos." pt "Erro no cálculo. Por favor, tente novamente em alguns segundos." en "An error ocurred while calculating the shipping. Please try again in a few seconds." es_mx "Hubo un error al calcular el envío. Intenta de nuevo en unos segundos." es "El calculo falló por un problema con el medio de envío. Por favor intentá de nuevo en unos segundos." pt "Erro no meio de envio. Por favor, tente novamente em alguns segundos." en "The calculation failed due to a problem with the shipping method. Please try again in a few seconds." es_mx "El cálculo falló por un problema con el forma de envío. Intenta de nuevo en unos segundos." es "Vea las opciones de envío para su código postal abajo" pt "Veja os valores para o seu CEP abaixo" en "Your shipping options are the following" es_mx "Conoce las opciones de entrega para tu código postal" es "No hay costos de envío para el código postal dado." pt "Não há frete disponível para o CEP informado" en "Shipping is not available for your zipcode." es_mx "No hay costos de envío para ese código postal." es "Retirá gratis en nuestros locales" pt "Retire grátis em nossas lojas físicas" en "Free pickup on our stores" es_mx "Recoge gratis en nuestras tiendas" es "Retirá gratis en nuestro local" pt "Retire grátis em nossa loja física" en "Free pickup on our store" es_mx "Recoge gratis en nuestra tienda" es "Nuestros locales" pt "Nossas lojas" en "Our stores" es_mx "Nuestras tiendas" es "Nuestro local" pt "Nossa loja" en "Our store" es_mx "Nuestra tienda" es "Ver opciones" pt "Ver opções" en "See options" es_mx "Ver opciones" es "Ocultar opciones" pt "Esconder opções" en "Hide options" es_mx "Ocultar opciones" es "Ver locales" pt "Ver lojas físicas" en "See our stores" es_mx "Ver tiendas" es "Ver local" pt "Ver loja física" en "See our store" es_mx "Ver tienda" es "Elegir local" pt "Escolher loja física" en "Select store" es_mx "Elegir una tienda" es "Nuestros locales" pt "Nossas lojas" en "Our stores" es_mx "Nuestras tiendas" es "Nuestro local" pt "Nossa loja" en "Our store" es_mx "Nuestra tienda" es "Ver opciones" pt "Ver opções" en "See options" es_mx "Ver opciones" es "Ocultar opciones" pt "Esconder opções" en "Hide options" es_mx "Ocultar opciones" es "Conocé nuestras opciones de envío" pt "Conheça nossas opções de frete" en "See our shipping options" es_mx "Conoce nuestras opciones de entrega" es "Elegí nuestras opciones de envío" pt "Escolha a opção de frete" en "Choose our shipping options" es_mx "Conoce las opciones de entrega" es "Calculando" pt "Calculando" en "Calculating" es_mx "Calculando" es "El medio de envío que habías elegido ya no se encuentra disponible " pt "O frete escolhido não está mais disponível " en "The shipping method selected is no longer available " es_mx "La forma de envío que elegiste ya no está disponible " es "porque el total de los items del carrito superan el peso máximo." pt "porque o total de itens no carrinho excede o peso máximo." en "because the total of the items in the cart surpass the maximum weight." es_mx "porque el peso total del carrito supera el peso máximo." es "¡No te preocupes! Podés elegir otro medio de envío." pt "Mas não se preocupe! Você pode escolher outro meio de envio." en "Do not worry! You can select another method." es_mx "¡No te preocupes! Puedes elegir otra forma de envío." es "Ocultar locales" pt "Esconder lojas físicas" en "Hide our stores" es_mx "Ocultar tiendas" es "Ocultar local" pt "Esconder loja física" en "Hide our store" es_mx "Ocultar tienda" es "Seguí acá" pt "Acompanhe aqui" en "Track here" es_mx "Sigue aquí" es "tu última compra" pt "seu último pedido" en "your last order" es_mx "tu última compra" es "El precio de envío incluye este producto y todos los que agregaste al carrito." pt "O custo de frete inclui este produto e outros adicionados ao carrinho." en "The shipping cost includes this product and all the products added to the cart." es_mx "El precio de envío incluye este producto y todos los que has agregado al carrito." es "Vas a poder elegir alguna de las siguientes opciones antes de finalizar la compra:" pt "Você pode escolher uma das seguintes opções antes de finalizar a compra" en "You will be able to choose some of the following options before ending the purchase" es_mx "Podrás elegir alguna de las siguientes opciones antes de finalizar la compra:" es "No encontramos este código postal. ¿Está bien escrito?" pt "Não conseguimos encontrar esse CEP. Está bem escrito?" en "We couldn't find this zipcode. Is it written right?" es_mx "No encontramos este código postal. ¿Está bien escrito?" es "El tiempo de entrega no considera feriados." pt "O prazo de entrega não contabiliza feriados." en "The delivery time does not consider holidays." es_mx "El tiempo de entrega no considera feriados." es "Ingresa aquí tu código postal para calcular tu costo de envío" pt "Digite aqui o seu CEP para calcular o frete" en "Enter your zipcode to calculate your shipping cost" es_mx "Ingresa aquí tu código postal para calcular tu costo de envío" es "Costos de envío" pt "Custos de frete" en "Shipping costs" es_mx "Costos de envío" es "Calcular" pt "Calcular" en "Calculate" es_mx "Calcular" es "Envío:" pt "Frete:" en "Shipping:" es_mx "Envío:" es "Calcular para ver" pt "Calcule para visualizar" en "Calculate to view" es_mx "Calcular para ver" es "El costo del envío se calculará después." pt "O frete será calculado depois." en "Shipping cost will be calculated at checkout" es_mx "El costo del envío se calculará después." es "<strong>Tenés envío gratis</strong> en esta compra. ¡Aprovechalo!" pt "Você ganhou <strong>frete grátis</strong> para esta compra. Aproveite!" en "You have <strong>free shipping</strong> in this order. Go for it!" es_mx "<strong>Tienes envío gratis</strong> en esta compra. ¡Aprovéchalo!" es "¡Genial! <strong class='text-primary'>Tenés envío gratis</strong>" pt "Sucesso! Você <strong class='text-primary'>tem frete grátis</strong>" en "You have <strong class='text-primary'>free shipping</strong>" es_mx "¡Genial! <strong class='text-primary'>Tienes envío gratis</strong>" es "¡Estas a <strong class='js-cart-free-shipping-amount'></strong> de tener <strong class='text-primary m-quarter-bottom'>envío gratis</strong> en tu compra!" pt "<strong>Ganhe frete grátis</strong> com mais <strong class='js-cart-free-shipping-amount'></strong> em compras" en "You need <strong class='js-cart-free-shipping-amount'></strong> to have <strong class='text-primary m-quarter-bottom'>free shipping</strong> on your order" es_mx "¡Estas a <strong class='js-cart-free-shipping-amount'></strong> de tener <strong class='text-primary m-quarter-bottom'>envío gratis</strong> en tu compra!" es "Ver más productos" pt "Escolher mais produtos" en "See more products" es_mx "Ver más productos" es "¡Todos los productos de esta categoría <strong>tienen envío gratis!</strong>" pt "Todos os produtos desta categoria <strong>tienen envío gratis!</strong>" en "All the products in this category <strong>have free shipping!</strong>" es_mx "¡Todos los productos de esta categoría <strong>tienen envío gratis!</strong>" es "<strong class='text-primary'>Envío gratis</strong> superando los" pt "<strong class='text-primary'>Frete grátis</strong> a partir de" en "<strong class='text-primary'>Free shipping</strong> buying more than" es_mx "<strong class='text-primary'>Envío gratis</strong> superando los" es "<strong class='text-primary'>¡Envío gratis!</strong> con todos los medios de envío" pt "<strong class='text-primary'>Frete grátis!</strong> com todos os meios de envio" en "<strong class='text-primary'>Free shipping!</strong> with all shipping options" es_mx "<strong class='text-primary'>¡Envío gratis!</strong> con todos los medios de envío" es "Medios de envío" pt "Meios de envio" en "Shipping Methods" es_mx "Opciones de envío" es "Mostrar calculador de costos de envío en la página de producto" pt "Mostrar o Cálculo de Frete na página de produto" en "Show shipping cost calculator on product page" es_mx "Mostrar el calculador de costos de envío en la página de producto" es "Mostrar calculador de costos de envío en el carrito" pt "Mostrar o Cálculo de Frete no carrinho" en "Show shipping cost calculator to cart" es_mx "Mostrar el calculador de costos de envío en el carrito" es "Mostrar medios de envío en tu tienda" pt "Mostrar as opções de frete na sua loja" en "Show Shipping Methods in your store" es_mx "Mostrar las opciones de envío en tu sitio" es "Mostrar medios de pago en tu tienda" pt "Mostrar as opções de pagamento na sua loja" en "Show payment methods in your store" es_mx "Mostrar los métodos de pago en tu sitio" es "Ver puntos de retiro" pt "Ver pontos de retirada" en "See pickup points" es_mx "Ver puntos de retiro" es "Puntos de retiro" pt "Pontos de retirada" en "Pickup points" es_mx "Puntos de retiro" es "Cercanos al CP:" pt "Próximos ao CEP:" en "Near zipcode:" es_mx "Cercanos al CP:" es "Vas a poder elegir estas opciones antes de finalizar tu compra" pt "Você poderá escolher essas opções antes de finalizar sua compra" en "You will be able to select this options before you finish your purchase" es_mx "Podrás elegir estas opciones antes de finalizar tu compra" es "Entregas para el CP:" pt "Entregas para o CEP:" en "Shipping for zipcode:" es_mx "Entregas para el CP:" es "Cambiar CP" pt "Alterar CEP" en "Change zipcode" es_mx "Cambiar CP"
Activación
Por último podés activar el calculador tanto para el detalle del producto como para el carrito desde el Administrador nube, en la sección de Personalizar tu diseño actual dentro de las partes Detalles de producto y Carrito de compras:
Listo, ya tenés en tu diseño la funcionalidad aplicada ¡Excelente!