En este tutorial vamos a ver cómo agregar un popup con el formulario de producto, el cual puede ser visto desde el ítem en el listado de productos.
HTML
1. Lo primero que vamos a hacer, es agregar un nuevo snipplet llamado quick-shop.tpl dentro de la carpeta snipplets/grid con el siguiente código:
{% if settings.quick_shop %} {% embed "snipplets/modal.tpl" with{modal_id: 'quickshop-modal', modal_class: 'quickshop text-center', modal_position: 'bottom modal-bottom-sheet', modal_transition: 'slide', modal_header: true, modal_footer: true, modal_width: 'centered modal-docked-md modal-docked-md-centered', modal_mobile_full_screen: 'true' } %} {% block modal_body %} <div class="js-item-product" data-product-id=""> <div class="js-product-container js-quickshop-container js-quickshop-modal js-quickshop-modal-shell" data-variants="" data-quickshop-id=""> <div class="js-item-variants"> <div class="js-item-name h1 mb-1" data-store="product-item-name-{{ product.id }}"></div> <div class="item-price-container mb-4" data-store="product-item-price-{{ product.id }}"> <span class="js-compare-price-display h4 price-compare"></span> <span class="js-price-display h4"></span> </div> {# Image is hidden but present so it can be used on cart notification #} <img srcset="" class="js-item-image js-quickshop-img hidden"/> <div id="quickshop-form"></div> </div> </div> </div> {% endblock %} {% endembed %} {% endif %}
2. Luego, vamos a buscar el snipplet item.tpl dentro de la carpeta snipplets/grid, puede ser que en tu diseño este snipplet se llame single_product.tpl y usamos el siguiente código:
{% set slide_item = slide_item | default(false) %} {% set columns = settings.grid_columns %} {% set has_color_variant = false %} {% if settings.product_color_variants %} {% for variation in product.variations if variation.name in ['Color', 'Cor'] and variation.options | length > 1 %} {% set has_color_variant = true %} {% endfor %} {% endif %} <div class="js-item-product {% if slide_item %}js-item-slide swiper-slide{% else %}col{% if columns == 2 %}-6 col-md-3{% else %}-12 col-md-4{% endif %}{% endif %} item item-product{% if not product.display_price %} no-price{% endif %}" data-product-type="list" data-product-id="{{ product.id }}" data-store="product-item-{{ product.id }}"> {% if settings.quick_shop or settings.product_color_variants %} <div class="js-product-container js-quickshop-container {% if product.variations %}js-quickshop-has-variants{% endif %}" data-variants="{{ product.variants_object | json_encode }}" data-quickshop-id="quick{{ product.id }}{% if slide_item and section_name %}-{{ section_name }}{% endif %}"> {% endif %} {% set product_url_with_selected_variant = has_filters ? ( product.url | add_param('variant', product.selected_or_first_available_variant.id)) : product.url %} {% if has_color_variant %} {# Item image will be the first avaiable variant #} {% set item_img_spacing = product.featured_variant_image.dimensions['height'] / product.featured_variant_image.dimensions['width'] * 100 %} {% set item_img_srcset = product.featured_variant_image %} {% set item_img_alt = product.featured_variant_image.alt %} {% else %} {# Item image will be the first image regardless the variant #} {% set item_img_spacing = product.featured_image.dimensions['height'] / product.featured_image.dimensions['width'] * 100 %} {% set item_img_srcset = product.featured_image %} {% set item_img_alt = product.featured_image.alt %} {% endif %} <div class="item-image mb-2"> <div style="padding-bottom: {{ item_img_spacing }}%;" class="p-relative" data-store="product-item-image-{{ product.id }}"> <a href="{{ product_url_with_selected_variant }}" title="{{ product.name }}"> <img alt="{{ item_img_alt }}" data-sizes="auto" data-expand="-10" src="{{ 'images/empty-placeholder.png' | static_url }}" data-srcset="{{ item_img_srcset | product_image_url('small')}} 240w, {{ item_img_srcset | product_image_url('medium')}} 320w, {{ item_img_srcset | product_image_url('large')}} 480w" class="js-item-image lazyautosizes lazyload img-absolute img-absolute-centered fade-in" /> <div class="placeholder-fade"></div> </a> {% if settings.product_color_variants %} {% include 'snipplets/labels.tpl' with {color: true} %} {% include 'snipplets/grid/item-colors.tpl' %} {% else %} {% include 'snipplets/labels.tpl' %} {% endif %} </div> </div> {% if (settings.quick_shop or settings.product_color_variants) and product.variations %} {# Hidden product form to update item image and variants: Also this is used for quickshop popup #} <div class="js-item-variants hidden"> <form id="product_form" class="js-product-form" method="post" action="{{ store.cart_url }}"> <input type="hidden" name="add_to_cart" value="{{product.id}}" /> {% if product.variations %} {% include "snipplets/product/product-variants.tpl" with {quickshop: true} %} {% endif %} {% if product.available and product.display_price and settings.quick_shop %} {% include "snipplets/product/product-quantity.tpl" with {quickshop: true} %} {% endif %} {% set state = store.is_catalog ? 'catalog' : (product.available ? product.display_price ? 'cart' : 'contact' : 'nostock') %} {% set texts = {'cart': "Agregar al carrito", 'contact': "Consultar precio", 'nostock': "Sin stock", 'catalog': "Consultar"} %} {# Add to cart CTA #} <input type="submit" class="js-addtocart js-prod-submit-form btn btn-primary btn-block {{ state }}" value="{{ texts[state] | translate }}" {% if state == 'nostock' %}disabled{% endif %} /> {# Fake add to cart CTA visible during add to cart event #} {% include 'snipplets/placeholders/button-placeholder.tpl' with {custom_class: "btn-block"} %} </form> </div> {% endif %} <div class="item-description" data-store="product-item-info-{{ product.id }}"> <a href="{{ product_url_with_selected_variant }}" title="{{ product.name }}" class="item-link"> <div class="js-item-name item-name mb-1" data-store="product-item-name-{{ product.id }}">{{ product.name }}</div> {% if product.display_price %} <div class="item-price-container mb-1" data-store="product-item-price-{{ product.id }}"> <span class="js-compare-price-display price-compare" {% if not product.compare_at_price or not product.display_price %}style="display:none;"{% else %}style="display:inline-block;"{% endif %}> {{ product.compare_at_price | money }} </span> <span class="js-price-display item-price"> {{ product.price | money }} </span> </div> {% endif %} </a> </div> {% include 'snipplets/payments/installments.tpl' %} {% if settings.quick_shop and product.available and product.display_price %} {# Trigger quickshop actions #} <div class="item-actions mt-2"> {% if product.variations %} {# Open quickshop popup if has variants #} <a data-toggle="#quickshop-modal" data-modal-url="modal-fullscreen-quickshop" class="js-quickshop-modal-open {% if slide_item %}js-quickshop-slide{% endif %} js-modal-open js-fullscreen-modal-open btn btn-primary btn-small px-4" title="{{ 'Compra rápida de' | translate }} {{ product.name }}" aria-label="{{ 'Compra rápida de' | translate }} {{ product.name }}" >{{ 'Agregar al carrito' | translate }}</a> {% else %} {# If not variants add directly to cart #} <form id="product_form" class="js-product-form" method="post" action="{{ store.cart_url }}"> <input type="hidden" name="add_to_cart" value="{{product.id}}" /> {% set state = store.is_catalog ? 'catalog' : (product.available ? product.display_price ? 'cart' : 'contact' : 'nostock') %} {% set texts = {'cart': "Agregar al carrito", 'contact': "Consultar precio", 'nostock': "Sin stock", 'catalog': "Consultar"} %} <input type="number" name="quantity" value="1" class="js-quantity-input hidden" aria-label="{{ 'Cambiar cantidad' | translate }}" > <input type="submit" class="js-addtocart js-prod-submit-form btn btn-primary btn-small {{ state }} px-4 mb-1" value="{{ texts[state] | translate }}" {% if state == 'nostock' %}disabled{% endif %} /> {# Fake add to cart CTA visible during add to cart event #} {% include 'snipplets/placeholders/button-placeholder.tpl' with {custom_class: "js-addtocart-placeholder-inline btn-small mb-1"} %} </form> {% endif %} </div> {% endif %} {# Structured data to provide information for Google about the product content #} {% include 'snipplets/structured_data/item-structured-data.tpl' %} {% if settings.quick_shop or settings.product_color_variants %} </div> {% endif %} </div>
3. Vamos a crear un nuevo snipplet llamado button-placeholder.tpl dentro de la carpeta snipplets/placeholders para incluir las transiciones del botón al “Agregar al carrito”. El código es el siguiente:
<div class="js-addtocart js-addtocart-placeholder btn btn-primary btn-transition disabled {{ custom_class }}" style="display: none;"> <span class="js-addtocart-text transition-container btn-transition-start active"> {{ 'Agregar al carrito' | translate }} </span> <span class="js-addtocart-success transition-container btn-transition-success"> {{ '¡Listo!' | translate }} </span> <div class="js-addtocart-adding transition-container btn-transition-progress"> {{ 'Agregando...' | translate }} </div> </div>
4. Dentro de la carpeta snipplets/product, vamos a editar 2 archivos. Por un lado, el snipplet product-quantity.tpl con el siguiente código.
{% if not quickshop %} <div class="row"> <div class="col col-md-4"> {% endif %} {% embed "snipplets/forms/form-input.tpl" with{type_number: true, input_value: '1', input_name: 'quantity' ~ item.id, input_custom_class: 'js-quantity-input text-center', input_label: false, input_append_content: true, input_group_custom_class: 'js-quantity form-row align-items-center', form_control_container_custom_class: 'col-6', input_min: '1', input_aria_label: 'Cambiar cantidad' | translate } %} {% block input_prepend_content %} <span class="js-quantity-down col-3 text-center"> {% include "snipplets/svg/minus.tpl" with {svg_custom_class: "icon-inline icon-w-12 icon-lg svg-icon-text"} %} </span> {% endblock input_prepend_content %} {% block input_append_content %} <span class="js-quantity-up col-3 text-center"> {% include "snipplets/svg/plus.tpl" with {svg_custom_class: "icon-inline icon-w-12 icon-lg svg-icon-text"} %} </span> {% endblock input_append_content %} {% endembed %} {% if not quickshop %} </div> </div> {% endif %}
Y por otro, el product-variants.tpl con el siguiente código.
<div class="js-product-variants{% if quickshop %} js-product-quickshop-variants text-left{% endif %} form-row"> {% for variation in product.variations %} <div class="js-product-variants-group {% if loop.length == 3 %} {% if quickshop %}col-4{% else %}col-12{% endif %} col-md-4 {% elseif loop.length == 2 %} col-6 {% else %} col {% if quickshop %}col-md-12{% else %}col-md-6{% endif %}{% endif %}" data-variation-id="{{ variation.id }}"> {% embed "snipplets/forms/form-select.tpl" with{select_label: true, select_label_name: '' ~ variation.name ~ '', select_for: 'variation_' ~ loop.index , select_id: 'variation_' ~ loop.index, select_data_value: 'variation_' ~ loop.index, select_name: 'variation' ~ '[' ~ variation.id ~ ']', select_custom_class: 'js-variation-option js-refresh-installment-data'} %} {% block select_options %} {% for option in variation.options %} <option value="{{ option.id }}" {% if product.default_options[variation.id] == option.id %}selected="selected"{% endif %}>{{ option.name }}</option> {% endfor %} {% endblock select_options%} {% endembed %} </div> {% endfor %} </div>
5. Agregamos un parámetro de select_data en el snipplet form-select.tpl dentro de la carpeta snipplets/forms. El código quedaría asi:
<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>
6. 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 {% if modal_mobile_full_screen %}js-fullscreen-modal{% endif %} 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 }}" {% if modal_form_hook %}data-store="{{ modal_form_hook }}"{% endif %}> {% endif %} <div class="js-modal-close {% if modal_mobile_full_screen %}js-fullscreen-modal-close{% endif %} 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>
7. Por último, en layout.tpl agregamos el siguiente código para que levante el contenido del popup:
{# Quickshop modal #} {% snipplet "grid/quick-shop.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 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 el siguiente código dentro de la misma, pero si no es el caso entonces podés agregarlo en la hoja de tu CSS principal.
{# /* // Buttons */ #} .btn-transition { position: relative; overflow: hidden; .transition-container { position: absolute; top: 50%; left: 0; width: 100%; margin-top: -7px; opacity: 0; text-align: center; @include prefix(transition, all 0.5s ease, webkit ms moz o); cursor: not-allowed; pointer-events: none; &.active { opacity: 1; } } } {# /* // Modals */ #} .modal { position: fixed; top: 0; display: block; width: 80%; height: 100%; padding: 10px; -webkit-overflow-scrolling: touch; overflow-y: auto; transition: all .2s cubic-bezier(.16,.68,.43,.99); z-index: 20000; &-header{ width: calc(100% + 20px); margin: -10px 0 10px -10px; padding: 10px 15px; font-size: 20px; } &-footer{ padding: 10px; clear: both; } &-full { width: 100%; } &-docked-md{ width: 100%; } &-docked-small{ width: 80%; } &-top{ top: -100%; left: 0; } &-bottom{ top: 100%; left: 0; } &-left{ left: -100%; } &-right{ right: -100%; } &-centered{ height: 100%; width: 100%; } &-top.modal-show, &-bottom.modal-show { top: 0; } &-bottom-sheet { top: initial; bottom: -100%; height: auto; &.modal-show { top: initial; bottom: 0; height: auto; } } &-left.modal-show { left: 0; } &-right.modal-show { right: 0; } &-close { display: inline-block; padding: 1px 5px 5px 0; margin-right: 5px; vertical-align: middle; cursor: pointer; } .tab-group{ margin: 0 -10px 20px -10px; } } .modal-overlay{ position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #00000047; z-index: 10000; } @media (min-width: 768px) { {# /* Modals */ #} .modal{ &-centered{ height: 80%; width: 80%; left: 10%; margin: 5% auto; } &-docked-md{ width: 500px; &-centered{ left: calc(50% - 250px); bottom: auto; height: auto; } } &-bottom-sheet { top: 100%; &.modal-show { top: 0; bottom: auto; } } &-docked-small{ width: 350px; } } }
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. El JavaScript necesitamos agregarlo en el archivo store.js.tpl (o donde tengas tus funciones de JS). Agregamos el siguiente código :
El JS para que funcionen los modals
{#/*============================================================================ #Modals ==============================================================================*/ #} {% if settings.quick_shop %} restoreQuickshopForm = function(){ {# Restore form to item when quickshop closes #} {# Clean quickshop modal #} jQueryNuvem("#quickshop-modal .js-item-product").removeClass("js-swiper-slide-visible js-item-slide"); jQueryNuvem("#quickshop-modal .js-quickshop-container").attr( { 'data-variants' : '' , 'data-quickshop-id': '' } ); jQueryNuvem("#quickshop-modal .js-item-product").attr('data-product-id', ''); {# Wait for modal to become invisible before removing form #} setTimeout(function(){ var $quickshop_form = jQueryNuvem("#quickshop-form").find('.js-product-form'); var $item_form_container = jQueryNuvem(".js-quickshop-opened").find(".js-item-variants"); $quickshop_form.detach().appendTo($item_form_container); jQueryNuvem(".js-quickshop-opened").removeClass("js-quickshop-opened"); },350); }; {% endif %} {# 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); {% if settings.quick_shop %} restoreQuickshopForm(); {% endif %} } } } } 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); {% if settings.quick_shop %} restoreQuickshopForm(); {% endif %} {# 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 settings.quick_shop %} restoreQuickshopForm(); {% endif %} if (jQueryNuvem(this).hasClass("js-fullscreen-overlay") && (window.innerWidth < 768)) { cleanURLHash(); } });
El JS para actualizar la información del producto al cambiar de variante:
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(); } });
Luego, el JS para que el modal levante el contenido correspondiente y además quede sincronizado con el feature de Colores en el item de productos si es que lo tiene:
{% if settings.product_color_variants %} {# Product color variations #} jQueryNuvem(document).on("click", ".js-color-variant", function(e) { e.preventDefault(); $this = jQueryNuvem(this); var option_id = $this.data('option'); $selected_option = $this.closest('.js-item-product').find('.js-variation-option option').filter(function(el) { return el.value == option_id; }); $selected_option.prop('selected', true).trigger('change'); var available_variant = jQueryNuvem(this).closest(".js-quickshop-container").data('variants'); var available_variant_color = jQueryNuvem(this).closest('.js-color-variant-active').data('option'); for (var variant in available_variant) { if (option_id == available_variant[variant]['option'+ available_variant_color ]) { if (available_variant[variant]['stock'] == null || available_variant[variant]['stock'] > 0 ) { var otherOptions = getOtherOptionNumbers(available_variant_color); var otherOption = available_variant[variant]['option' + otherOptions[0]]; var anotherOption = available_variant[variant]['option' + otherOptions[1]]; changeSelect(jQueryNuvem(this), otherOption, otherOptions[0]); changeSelect(jQueryNuvem(this), anotherOption, otherOptions[1]); break; } } } $this.siblings().removeClass("selected"); $this.addClass("selected"); }); function getOtherOptionNumbers(selectedOption) { switch (selectedOption) { case 0: return [1, 2]; case 1: return [0, 2]; case 2: return [0, 1]; } } function changeSelect(element, optionToSelect, optionIndex) { if (optionToSelect != null) { var selected_option_attribute = element.closest('.js-item-product').find('.js-color-variant-available-' + (optionIndex + 1)).data('value'); var selected_option = element.closest('.js-item-product').find('#' + selected_option_attribute + " option").filter(function(el) { return el.value == optionToSelect; }); selected_option.prop('selected', true).trigger('change'); } } {% endif %} {% if settings.product_color_variants or settings.quick_shop %} {# Product quickshop for color variations #} LS.registerOnChangeVariant(function(variant){ {# Show product image on color change #} var current_image = jQueryNuvem('.js-item-product[data-product-id="'+variant.product_id+'"] .js-item-image'); current_image.attr('srcset', variant.image_url); }); {% endif %} {% 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"); } LS.fillQuickshop($this); }); {# Get width of the placeholder button #} var productButttonWidth = jQueryNuvem(".js-addtocart-placeholder-inline").prev(".js-addtocart").innerWidth(); jQueryNuvem(".js-addtocart-placeholder-inline").width(productButttonWidth-20); {% endif %}
Por último, el JS que actualiza el botón y la notificación al agregar al carrito:
jQueryNuvem(document).on("click", ".js-addtocart:not(.js-addtocart-placeholder)", function (e) { {# Button variables for transitions on add to cart #} var $productContainer = jQueryNuvem(this).closest('.js-product-container'); var $productVariants = $productContainer.find(".js-variation-option"); var $productButton = $productContainer.find("input[type='submit'].js-addtocart"); var $productButtonPlaceholder = $productContainer.find(".js-addtocart-placeholder"); var $productButtonText = $productButtonPlaceholder.find(".js-addtocart-text"); var $productButtonAdding = $productButtonPlaceholder.find(".js-addtocart-adding"); var $productButtonSuccess = $productButtonPlaceholder.find(".js-addtocart-success"); {# Define if event comes from quickshop or product page #} var isQuickShop = $productContainer.hasClass('js-quickshop-container'); if (!isQuickShop) { if(jQueryNuvem(".js-product-slide-img.js-active-variant").length) { var imageSrc = $productContainer.find('.js-product-slide-img.js-active-variant').data('srcset').split(' ')[0]; } else { var imageSrc = $productContainer.find('.js-product-slide-img').attr('srcset').split(' ')[0]; } var name = $productContainer.find('.js-product-name').text(); var price = $productContainer.find('.js-price-display').text(); } else { var imageSrc = jQueryNuvem(this).closest('.js-quickshop-container').find('img').attr('srcset'); var name = $productContainer.find('.js-item-name').text(); var price = $productContainer.find('.js-price-display').text().trim(); } var quantity = $productContainer.find('.js-quantity-input').val(); var addedToCartCopy = "{{ 'Agregar al carrito' | translate }}"; if (!jQueryNuvem(this).hasClass('contact')) { {% if settings.ajax_cart %} e.preventDefault(); {% endif %} {# Hide real button and show button placeholder during event #} $productButton.hide(); $productButtonPlaceholder.show().addClass("active"); $productButtonText.removeClass("active"); setTimeout(function(){ $productButtonAdding.addClass("active"); },300); {% if settings.ajax_cart %} var callback_add_to_cart = function(){ {# Animate cart amount #} jQueryNuvem(".js-cart-widget-amount").addClass("beat"); setTimeout(function(){ jQueryNuvem(".js-cart-widget-amount").removeClass("beat"); },4000); {# Fill notification info #} jQueryNuvem('.js-cart-notification-item-img').attr('srcset', imageSrc); jQueryNuvem('.js-cart-notification-item-name').text(name); jQueryNuvem('.js-cart-notification-item-quantity').text(quantity); jQueryNuvem('.js-cart-notification-item-price').text(price); if($productVariants.length){ var output = []; $productVariants.each( function(el){ var variants = jQueryNuvem(el); output.push(variants.val()); }); jQueryNuvem(".js-cart-notification-item-variant-container").show(); jQueryNuvem(".js-cart-notification-item-variant").text(output.join(', ')) }else{ jQueryNuvem(".js-cart-notification-item-variant-container").hide(); } {# Set products amount wording visibility #} var cartItemsAmount = jQueryNuvem(".js-cart-widget-amount").text(); if(cartItemsAmount > 1){ jQueryNuvem(".js-cart-counts-plural").show(); jQueryNuvem(".js-cart-counts-singular").hide(); }else{ jQueryNuvem(".js-cart-counts-singular").show(); jQueryNuvem(".js-cart-counts-plural").hide(); } {# Show button placeholder with transitions #} $productButtonAdding.removeClass("active"); setTimeout(function(){ $productButtonSuccess.addClass("active"); },300); setTimeout(function(){ $productButtonSuccess.removeClass("active"); setTimeout(function(){ $productButtonText.addClass("active"); },300); $productButtonPlaceholder.removeClass("active"); },2000); setTimeout(function(){ $productButtonPlaceholder.hide(); $productButton.css('display' , 'inline-block'); },4000); $productContainer.find(".js-added-to-cart-product-message").slideDown(); if (isQuickShop) { jQueryNuvem("#quickshop-modal").removeClass('modal-show'); jQueryNuvem(".js-modal-overlay[data-modal-id='#quickshop-modal']").hide(); jQueryNuvem("body").removeClass("overflow-none"); restoreQuickshopForm(); if (window.innerWidth < 768) { cleanURLHash(); } } {# Show notification and hide it only after second added to cart #} setTimeout(function(){ jQueryNuvem(".js-alert-added-to-cart").show().addClass("notification-visible").removeClass("notification-hidden"); },500); if (!cookieService.get('first_product_added_successfully')) { cookieService.set('first_product_added_successfully', 1, 7 ); } else{ setTimeout(function(){ jQueryNuvem(".js-alert-added-to-cart").removeClass("notification-visible").addClass("notification-hidden"); setTimeout(function(){ jQueryNuvem('.js-cart-notification-item-img').attr('src', ''); jQueryNuvem(".js-alert-added-to-cart").hide(); },2000); },8000); } {# 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); } } var callback_error = function(){ {# Restore real button visibility in case of error #} $productButtonPlaceholder.removeClass("active"); $productButtonText.fadeIn("active"); $productButtonAdding.removeClass("active"); $productButtonPlaceholder.hide(); $productButton.css('display' , 'inline-block'); } $prod_form = jQueryNuvem(this).closest("form"); LS.addToCartEnhanced( $prod_form, '{{ "Agregar al carrito" | translate }}', '{{ "Agregando..." | translate }}', '{{ "¡Uy! No tenemos más stock de este producto para agregarlo al carrito." | translate }}', {{ store.editable_ajax_cart_enabled ? 'true' : 'false' }}, callback_add_to_cart, callback_error ); {% endif %} } });
Configuraciones
En el archivo config/settings.txt vamos a agregar un checkbox para activar y desactivar la funcionalidad. Vamos a ubicarlo dentro de la sección Listado de productos.
title title = Compra rápida checkbox name = quick_shop description = Permitir que tus clientes puedan agregar productos a su carrito rápidamente desde el listado de productos
Traducciones
En este paso agregamos los textos para las traducciones en el archivo config/translations.txt
es "Compra rápida" pt "Compra rápida" es_mx "Compra rápida" es "Permitir que tus clientes puedan agregar productos a su carrito rápidamente desde el listado de productos" pt "Permitir que seus clientes possam agregar produtos ao seu carrinho rapidamente na lista de produtos" es_mx "Permitir que tus clientes puedan agregar productos a su carrito rápidamente desde el listado de productos" es "Compra rápida de" pt "Compra rápida de" en "Quickshop of" es_mx "Compra rápida de"
Activación
Por último podés activar el modal desde el Administrador nube, en la sección de Personalizar tu diseño actual dentro de las Listado de productos: