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 variation.name in ['Color', 'Cor'] %}js-color-variants-container{% endif %} {% if loop.length == 3 %} {% if quickshop %}col-4{% else %}col-12{% endif %} col-md-4 {% elseif loop.length == 2 %} col-6 {% else %} col {% if quickshop %}col-md-12{% else %}col-md-6{% endif %}{% endif %}"> {% embed "snipplets/forms/form-select.tpl" with{select_label: true, select_label_name: '' ~ variation.name ~ '', select_for: 'variation_' ~ loop.index , select_data: 'variant-id', 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
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 #} $("#quickshop-modal .js-item-product").removeClass("js-swiper-slide-visible js-item-slide"); $("#quickshop-modal .js-quickshop-container").attr( { 'data-variants' : '' , 'data-quickshop-id': '' } ); $("#quickshop-modal .js-item-product").attr('data-product-id', ''); {# Wait for modal to become invisible before removing form #} setTimeout(function(){ var $quickshop_form = $("#quickshop-form").find('.js-product-form'); var $item_form_container = $(".js-quickshop-opened").find(".js-item-variants"); $quickshop_form.detach().appendTo($item_form_container); $(".js-quickshop-opened").removeClass("js-quickshop-opened"); },350); }; {% endif %} {# Full screen mobile modals back events #} if ($(window).width() < 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 #} $(document).on("click", ".js-fullscreen-modal-open", function(e) { e.preventDefault(); var modal_url_hash = $(this).data("modal-url"); window.location.hash = modal_url_hash; }); {# Close full screen modal: Remove url hash #} $(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($(".js-fullscreen-modal").hasClass("modal-show")){ var $opened_modal = $(".js-fullscreen-modal.modal-show"); var $opened_modal_overlay = $opened_modal.prev(); $opened_modal.removeClass("modal-show").delay(500).hide(0); $opened_modal_overlay.fadeOut(500); {% if settings.quick_shop %} restoreQuickshopForm(); {% endif %} } } } } $(document).on("click", ".js-modal-open", function(e) { e.preventDefault(); var $modal_id = $(this).data('toggle'); $(".js-modal-overlay").fadeToggle(); if ($($modal_id).hasClass("modal-show")) { $($modal_id).removeClass("modal-show").delay(200).hide(0); } else { $($modal_id).detach().insertAfter(".js-modal-overlay").show(0).addClass("modal-show"); } }); closeModal = function(element){ $(element).closest(".js-modal").removeClass("modal-show").delay(200).hide(0); $(".js-modal-overlay").fadeOut(300); {# Close full screen modal: Remove url hash #} if (($(window).width() < 768) && ($(element).hasClass(".js-fullscreen-modal-close"))) { goBackBrowser(); } {% if settings.quick_shop %} restoreQuickshopForm(); {% endif %} }; $(document).on("click", ".js-modal-close", function(e) { e.preventDefault(); closeModal($(this)); }); {# Close modal on ESC keyboard #} $(document).keyup(function(e) { if (e.keyCode == 27) { closeModal($(".js-modal-close")); } }); $(".js-modal-overlay").click(function (e) { e.preventDefault(); $(".js-modal.modal-show").removeClass("modal-show").delay(200).hide(0); $(this).fadeOut(300); {% if settings.quick_shop %} restoreQuickshopForm(); {% endif %} if ($(window).width() < 768) { cleanURLHash(); } });
El JS para actualizar la información del producto al cambiar de variante:
$(document).on("change", ".js-variation-option", function(e) { var $parent = $(this).closest(".js-product-variants"); var $variants_group = $(this).closest(".js-product-variants-group"); var $quickshop_parent_wrapper = $(this).closest(".js-quickshop-container"); {# If quickshop is used from modal, use quickshop-id from the item that opened it #} var quick_id = $quickshop_parent_wrapper.attr("data-quickshop-id"); if($parent.hasClass("js-product-quickshop-variants")){ var $quickshop_parent = $(this).closest(".js-item-product"); {# Target visible slider item if necessary #} if($quickshop_parent.hasClass("js-item-slide")){ var $quickshop_variant_selector = '.js-swiper-slide-visible .js-quickshop-container[data-quickshop-id="'+quick_id+'"]'; }else{ var $quickshop_variant_selector = '.js-quickshop-container[data-quickshop-id="'+quick_id+'"]'; } LS.changeVariant(changeVariant, $quickshop_variant_selector); {% if settings.product_color_variants %} {# Match selected color variant with selected quickshop variant #} if(($variants_group).hasClass("js-color-variants-container")){ var selected_option_id = $(this).find("option:selected").val(); if($quickshop_parent.hasClass("js-item-slide")){ var $color_parent_to_update = $('.js-swiper-slide-visible .js-quickshop-container[data-quickshop-id="'+quick_id+'"]'); }else{ var $color_parent_to_update = $('.js-quickshop-container[data-quickshop-id="'+quick_id+'"]'); } $color_parent_to_update.find('.js-color-variant').removeClass("selected"); $color_parent_to_update.find('.js-color-variant[data-option="'+selected_option_id+'"]').addClass("selected"); } {% endif %} } else { LS.changeVariant(changeVariant, '#single-product'); } var $this_compare_price = $(this).closest(".js-product-container").find(".js-compare-price-display"); var $this_price = $(this).closest(".js-product-container").find(".js-price-display"); var $installment_container = $(this).closest(".js-product-container").find(".js-product-payments-container"); var $installment_text = $(this).closest(".js-product-container").find(".js-max-installments-container"); var $this_product_container = $(this).closest(".js-product-container"); var $this_add_to_cart = $(this).closest(".js-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(); // Filter prices to only have numbers old_price_value_filtered = parseInt(compare_price_value.replace(/[^0-9]/gi, ''), 10)/100; current_price_value_filtered = parseInt(price_value.replace(/[^0-9]/gi, ''), 10)/100; // Calculate new discount percentage based on difference between filtered old and new prices price_difference = (old_price_value_filtered-current_price_value_filtered); updated_discount_percentage = Math.round(((price_difference*100)/old_price_value_filtered)); $this_product_container.find(".js-offer-percentage").html(updated_discount_percentage); if ($this_compare_price.css("display") == "none") { $this_product_container.find(".js-offer-label").hide(); } else { $this_product_container.find(".js-offer-label").css("display" , "table"); } 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 #} $(document).on("click", ".js-color-variant", function(e) { e.preventDefault(); $this = $(this); var option_id = $this.data('option'); $selected_option = $this.closest('.js-item-product').find('.js-variation-option option').filter(function() { return this.value == option_id; }); $selected_option.prop('selected', true).trigger('change'); var available_variant = $(this).closest(".js-quickshop-container").data('variants'); var available_variant_color = $(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($(this), otherOption, otherOptions[0]); changeSelect($(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_parent_id = element.closest('.js-item-product').data("product-id"); var selected_option_attribute = $('.js-item-product[data-product-id="'+selected_option_parent_id+'"]').find('.js-color-variant-available-' + (optionIndex + 1)).data('value'); var selected_option = $('.js-item-product[data-product-id="'+selected_option_parent_id+'"]').find('.js-variation-option[data-variant-id="'+selected_option_attribute+'"] option').filter(function() { return this.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 $item_to_update_image = $('.js-item-product[data-product-id^="'+variant.product_id+'"].js-swiper-slide-visible'); var $item_to_update_image_cloned = $('.js-item-product[data-product-id^="'+variant.product_id+'"].js-swiper-slide-visible.swiper-slide-duplicate'); {# If item is cloned from swiper change only cloned item #} if($item_to_update_image.hasClass("swiper-slide-duplicate")){ var slide_item_index = $item_to_update_image_cloned.attr("data-swiper-slide-index"); var current_image = $('js-item-image', '.js-item-product[data-product-id="'+variant.product_id+'-clone-'+slide_item_index+'" ]'); }else{ var slide_item_index = $item_to_update_image.attr("data-swiper-slide-index"); var current_image = $('js-item-image', '.js-item-product[data-product-id="'+variant.product_id+'"]'); } current_image.attr('srcset', variant.image_url); }); {% endif %} {% if settings.quick_shop %} $(document).on("click", ".js-quickshop-modal-open", function (e) { e.preventDefault(); var $this = $(this); if($this.hasClass("js-quickshop-slide")){ $("#quickshop-modal .js-item-product").addClass("js-swiper-slide-visible js-item-slide"); } LS.fillQuickshop($this); }); {# Get width of the placeholder button #} var productButttonWidth = $(".js-addtocart-placeholder-inline").prev(".js-addtocart").innerWidth(); $(".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:
{# /* // Add to cart */ #} $(document).on("click", ".js-addtocart:not(.js-addtocart-placeholder)", function (e) { {# Button variables for transitions on add to cart #} var $productContainer = $(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"); var productButttonHeight = $productButton.height(); {# Define if event comes from quickshop or product page #} var isQuickShop = $productContainer.hasClass('js-quickshop-container'); if (!isQuickShop) { if($(".js-product-slide-img.js-active-variant").length) { var imageSrc = $($productContainer.find('.js-product-slide-img.js-active-variant')[0]).data('srcset').split(' ')[0]; } else { var imageSrc = $($productContainer.find('.js-product-slide-img')[0]).attr('srcset').split(' ')[0]; } var name = $productContainer.find('.js-product-name').text(); var price = $productContainer.find('.js-price-display').text(); } else { var imageSrc = $(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 (!$(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"); $productButtonPlaceholder.height(productButttonHeight); $productButtonText.removeClass("active"); setTimeout(function(){ $productButtonAdding.addClass("active"); },300); {% if settings.ajax_cart %} var callback_add_to_cart = function(){ {# Animate cart amount #} $(".js-cart-widget-amount").addClass("beat"); setTimeout(function(){ $(".js-cart-widget-amount").removeClass("beat"); },4000); {# Fill notification info #} $('.js-cart-notification-item-img').attr('srcset', imageSrc); $('.js-cart-notification-item-name').text(name); $('.js-cart-notification-item-quantity').text(quantity); $('.js-cart-notification-item-price').text(price); if($productVariants.length){ var output = []; $productVariants.each( function(){ var variants = $(this); output.push(variants.val()); }); $(".js-cart-notification-item-variant-container").show(); $(".js-cart-notification-item-variant").text(output.join(', ')) }else{ $(".js-cart-notification-item-variant-container").hide(); } {# Set products amount wording visibility #} var cartItemsAmount = $(".js-cart-widget-amount").first().text(); if(cartItemsAmount > 1){ $(".js-cart-counts-plural").show(); $(".js-cart-counts-singular").hide(); }else{ $(".js-cart-counts-singular").show(); $(".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.show(); },4000); $productContainer.find(".js-added-to-cart-product-message").slideDown(); if (isQuickShop) { closeModal($(".js-addtocart:not(.js-addtocart-placeholder)")); if ($(window).width() < 768) { cleanURLHash(); } } if ($(window).width() > 768) { $(".js-toggle-cart").click(); }else{ {# Show notification and hide it only after second added to cart #} setTimeout(function(){ $(".js-alert-added-to-cart").show().addClass("notification-visible").removeClass("notification-hidden"); },500); if (typeof $.cookie('first_product_added_successfully') === 'undefined') { $.cookie('first_product_added_successfully', true, { path: '/', expires: 7 }); } else{ setTimeout(function(){ $(".js-alert-added-to-cart").removeClass("notification-visible").addClass("notification-hidden"); setTimeout(function(){ $('.js-cart-notification-item-img').attr('src', ''); $(".js-alert-added-to-cart").hide(); },2000); },8000); } } } var callback_error = function(){ {# Restore real button visibility in case of error #} $productButtonPlaceholder.removeClass("active"); $productButtonText.fadeIn("active"); $productButtonAdding.removeClass("active"); $productButtonPlaceholder.hide(); $productButton.show(); } $prod_form = $(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: