Carrito de compras rápidas

Esta funcionalidad permite agregar productos al carrito de compras de manera asincrónica (sin necesidad de redireccionar al usuario a la página del carrito).

1. Configuración

Agregar las siguientes líneas en el archivo /config/settings.txt :

Carrito de compras
 checkbox
   name = ajax_cart
   description = Carrito de compras rápidas. Tus clientes pueden agregar y ver los productos que van a comprar sin necesidad de ir a otra página, ya que la información está siempre visible.

Agregar esta línea en el archivo /config/defaults.txt :

ajax_cart = 0

Agregar estas líneas en el archivo /config/translations.txt :

es "Editar carrito"
pt "Editar carrinho"
en "Edit my cart"

es "Carrito de compras rápidas. Tus clientes pueden agregar y ver los productos que van a comprar sin necesidad de ir a otra página, ya que la información está siempre visible."
pt "Carrinho de compra rápida. Seus clientes podem escolher produtos e conferir o que vão comprar sem a necessidade abrir outra página, já que a informação está sempre visível."
en "AJAX Cart"

2. Layout del theme (/layouts/layout.tpl)

Modificar la inclusión del sniplet del carrito, reemplazando este código:

<div class="cart-snipplet hidden-phone">
    {% if not store.is_catalog and template != 'cart' %}
        {% snipplet "cart.tpl" as "cart" %}
    {% endif %}

Por el siguiente:

<div class="cart-snipplet {% if not settings.ajax_cart %}hidden-phone{% endif %}">
{% if not store.is_catalog and template != 'cart' %}
    {% if settings.ajax_cart %}
        {% snipplet "cart_ajax.tpl" as "cart" %}
    {% else %}
        {% snipplet "cart.tpl" as "cart" %}
    {% endif %}
{% endif %}

Eliminar la inclusión del plugin livequery del condicional {% if template= 'cart' %} e incluirlo con el resto de los plugins, para que esté disponible en todo el sitio:

{{ 'js/jquery.livequery.min.js' | static_url | script_tag }}

Adicionar el siguiente script de javascript dentro de unos tags <script>:

{% if settings.ajax_cart %}
    $(document).on("click", ".js-addtocart", function(e) {
        if(!$(this).hasClass('contact')) {
            e.preventDefault();
            $prod_form = $(this).closest("form");
            LS.addToCart(
                $prod_form,
                '{{ "Agregar al carrito" | translate }}',
                '{{ "Agregando..." | translate }}',
                '{{ "No hay suficiente stock para agregar este producto al carrito." | translate }}' );
        }
    });

    $(document).on("click", ".js-toggleCart", function(e) {
        e.preventDefault();
        LS.toggleCart();
    });

    $('input.shipping-method:checked').livequery(function(){
        var shippingPrice = $(this).attr("data-price");
        LS.addToTotal(shippingPrice);
    });

    $(document).on( "click", "input.shipping-method", function() {
        var elem = $(this);
        var shippingPrice = elem.attr("data-price");
        elem.click(function() {
            LS.addToTotal(shippingPrice);
        });
    });

{% endif %}

Reemplazar el selector de jQuery en la función changeVariant:

function changeVariant(variant){
    $("#shipping-calculator-response").hide();
    …
}

por el siguiente:

function changeVariant(variant){
    $("#single-product .shipping-calculator-response").hide();  
    …
}

Modificar los selectores de jQuery con ID (#) por selectores con clases (.):

$("#calculate-shipping-button").click(function(e) {
    …
});

Así debe quedar la función:

$(".calculate-shipping-button").click(function(e) {
    e.preventDefault();
    LS.calculateShippingAjax(
        $(this).parent().find("input.shipping-zipcode").val(),
        '{{store.shipping_calculator_url | escape('js')}}',
        $(this).closest(".shipping-calculator") );
});

3. Sniplets del carrito

Agregar el archivo /snipplets/cart_ajax.tpl (ejemplo del código en el theme Luxury) :

<div id="ajax-cart" class="cart-summary">
    <a href="#" class="js-toggleCart">
      <span class="items">
        <span id="cart-amount">{{ "{1}" | translate(cart.items_count ) }}</span> 
        <small>x</small> 
        <span id="cart-total">{{ cart.total | money }}</span>
      </span>
      <span class="item-img"><i class="fa fa-shopping-cart"></i></span>
    </a>
</div>
<div id="ajax-cart-details" style="display: none;">
	<button type="button" class="button close-cart js-toggleCart secondary-button">
		<i class="fa fa-angle-left" aria-hidden="true"></i> 
		<span class="hidden-phone">{{ "Seguir comprando" | translate }}</span>
		<span class="visible-phone">{{ "Volver" | translate }}</span>
	</button>
	<h2 class="m-bottom">{{ "Resumen del carrito de compras" | translate }}</h2>
	<div class="overflow-none ajax-cart_titles p-half-top p-half-bottom">
	  <div class="ajax-cart_titles_product-col p-half-left pull-left" style="width: 45%">
	      <h5 class="ajax-cart_titles_header">{{ "Producto" | translate }}</h5>
	    </div>
	    <div class="ajax-cart_titles_subtotal-col text-right p-half-right pull-right" style="width: 45%">
	      <h5 class="ajax-cart_titles_header">{{ "Subtotal" | translate }}</h5>
	    </div>
	</div>
	<div class="ajax-cart">
	  {% if cart.items %}
	    {% for item in cart.items %}
	    <div class="overflow-none ajax-cart-item">
	      <div class="ajax-cart-item_image-col p-half-top p-half-bottom p-quarter-left p-quarter-right text-center ajax-cart-item_item-row">
	        <img src="{{ item.featured_image | product_image_url('original') }}" class="ajax-cart-item_img full-width" />
	      </div>
	      <div class="ajax-cart-item_desc-col p-half-all ajax-cart-item_item-row full-width text-wrap">
	        <a class="ajax-cart-item-link" href="{{ item.url }}">{{ item.name }}</a><br/>
	            {{ item.unit_price | money }}<br/>
	            x {{ item.quantity }}
	      </div>
	      <div class="ajax-cart-item_subtotal-col text-right p-half-all ajax-cart-item_item-row">
	        {{ item.subtotal | money }}
	      </div>
	    </div>
	    {% endfor %}
	  {% endif %}
	</div>
	<div class="ajax-cart-total overflow-none p-half-top text-right">
	  <h3 class="m-none p-quarter-right d-inline-block">{{ "Total" | translate }}:</h3>
	  <h3 id="cart-table-total" class="m-none d-inline-block text-primary">{{ cart.total | money }}</h3>
	</div>
	<div id="ajax-cart-shipping">
		{% snipplet "shipping_cost_calculator.tpl" with shipping_calculator_show = settings.shipping_calculator_cart_page %}
	</div>
	<div id="ajax-cart-totalwshipping" class="total-price d-inline-block full-width text-right text-center-xs m-half-top"></div>
	<div class="full-width pull-left ajax-cart-bottom m-top">
		{% set cart_total = (settings.cart_minimum_value * 100) %}
		<div class="ajax-cart_edit-cart-container text-left pull-left text-center-xs">
			<a href="{{ store.cart_url }}" class="btn btn-link m-half-top m-bottom-xs p-left-none p-right-none">{{ 'Editar carrito' | translate }}</a>
		</div>
		<div class="pull-right ajax-cart_finish-buy-container" {{ cart.total < cart_total ? 'style="display:none"' }} id="ajax-cart-submit-div">
			<form action="{{ store.cart_url }}" method="post">
				<input class="button pull-right ajax-cart_finish-buy-btn" type="submit" name="go_to_checkout" value="{{ 'Iniciar Compra' | translate }}"/>
			</form>
		</div>
		<div class="clear-both p-half-top" {{ cart.total >= cart_total ? 'style="display:none"' }} id="ajax-cart-minumum-div">
			<div class="alert alert-warning" role="alert">
		  	<h4 class="text-center">{{ "El monto mínimo de compra (subtotal) es de" | translate }} {{ cart_total | money }}</h4>
			</div>
		</div>
		<input type="hidden" id="ajax-cart-minimum-value" value="{{ cart_total }}"/>
	</div>
</div>
<div id="ajax-cart-backdrop" class="js-toggleCart" style="display: none;"></div>

En el archivo /snipplets/shipping_cost_calculator.tpl reemplazar los ids por clases.

Debería quedar de la siguiente manera:

{% if shipping_calculator_show %}
    <div class="shipping-calculator">
        <div class="shipping-calculator-form" {% if shipping_calculator_variant and not shipping_calculator_variant.available %}style="display: none;" {% endif %}>
            <div class="ssb">{{ "Ingrese aquí su código postal para calcular su costo de envío" | translate }}:</div>
            <input type="text" name="zipcode" value="{{ cart.shipping_zipcode }}" class="shipping-zipcode">
            {% if shipping_calculator_variant %}
                <input type="hidden" name="variant_id" id="shipping-variant-id" value="{{ shipping_calculator_variant.id }}">
            {% endif %}
            <button class="button calculate-shipping-button">{{ "Calcular costo de envío" | translate }}</button>
            <span class="loading" style="display: none;"><i class="fa fa-circle-o-notch fa-spin"></i></span>
            <span class='alert alert-error invalid-zipcode' style="display: none;">{{ "El código postal es inválido." | translate }}</span>
        </div>
        <div class="shipping-calculator-response" style="display: none;"></div>
    </div>
{% endif %}

En el archivo /snipplets/shipping_options.tpl eliminar el siguiente condicional:

{% if not variant %}

4. Templates

En el archivo /templates/product.tpl agregar la clase js-addtocart de la siguiente manera:

<input type="submit" class="button addToCart js-addtocart {{state}}" value="{{ texts[state] | translate }}" {% if state == 'nostock' %}disabled{% endif %} />

En el archivo /snipplets/quick-shop.tpl agregar la clase js-addtocart de la siguiente manera:

<input type="submit" class="button addToCart js-addtocart {{ state }}" value="{{ texts[state] | translate }}" {% if state == 'nostock' %}disabled{% endif %}

5. CSS

Adicionar el CSS correspondiente al theme. Ejemplo del código en Luxury:

#ajax-cart-details {
    position: fixed;
    top: 0;
    right: 0;
    width: 500px;
    height: 100%;
    z-index: 99999999;
    padding: 20px;
    border-left: 1px solid black;
    overflow-y: scroll;
    text-align: left;
}
#ajax-cart-details h2{
    font-size: 24px;
    line-height: initial;
}
@media (max-width: 480px){
    #ajax-cart-details {
        width: 90%;
    }
}
#ajax-cart-backdrop {
    background-color: rgba(0,0,0,0.8);
    position: fixed;
    top: 0;
    height: 120%;
    width:100%;
    left: 0;
    z-index: 999999;
}
.close-cart {
    margin: 0 0 20px 0;
    padding: 4px 10px 0 10px;
}
.close-cart:hover i {
    color: #fff;
}
table#cart-table {
    margin: 20px 0;
}
#ajax-cart-shipping {
    width: 100%;
    margin-top: 20px;
}
#ajax-cart-shipping .shipping-option {
    width: 100%;
}
#ajax-cart-shipping .shipping-calculator li input[type="radio"] + .shipping-option {
    border: 2px solid transparent;
}
#ajax-cart-shipping .shipping-calculator li input[type="radio"]:checked + .shipping-option {
    border: 2px solid red;
}
#ajax-cart-totalwshipping {
    font-weight: bold;
    font-size: 20pt;
    float: right;
}
#ajax-cart-shipping .shipping-calculator ul.shipping-list li {
    margin-bottom: 20px;
    clear: both;
    width: 100%;
}
#ajax-cart-shipping .shipping-calculator ul.shipping-list li:last-child{
    margin-bottom: 0;
}
#ajax-cart-shipping .shipping-calculator-form {
    margin-top: 0;
}
div.addToCartButton div.alert {
    margin-top: 20px;
}
.edit-cart {
    margin: 0 0 10px 0;
    text-align: left;
}
.ajax-cart-bottom{
    margin-bottom: 50px
}
.ajax-cart-shipping .shipping-option_image{
    margin-left: 0;
    width: 100px;
}
.ajax-cart-shipping .shipping-option_text{
    width: 60%;
}
.ajax-cart-bottom_edit-cart-link, 
.ajax-cart-bottom_start-order-btn { 
    width: 50% 
}

/****** PROPERTIES HELPERS ******/

/*CSS properties helpers minified, to unminify it you have to copy the code and paste it here http://unminify.com/, after that paste the unminified code here */

.text-danger{color:red}.border-box{box-sizing:border-box;}.c-pointer{cursor:pointer}.f-none{float:none!important}.d-none{display:none}.d-inline{display:inline}.d-block{display:block}.d-inline-block{display:inline-block}.p-relative{position:relative}.p-absolute{position:absolute}.p-fixed{position:fixed}.clear-both{clear:both}.opacity-80{opacity:.8}.opacity-50{opacity:.5}.full-height{height:100%}.full-width{width:100%}.z-index-above{z-index:999999}.m-top{margin-top:20px}.m-bottom{margin-bottom:20px}.m-right{margin-right:20px}.m-left{margin-left:20px}.m-all{margin:20px}.m-half-top{margin-top:10px}.m-half-bottom{margin-bottom:10px}.m-half-right{margin-right:10px}.m-half-left{margin-left:10px!important}.m-half-all{margin:10px}.m-quarter-top{margin-top:5px}.m-quarter-right{margin-right:5px}.m-quarter-bottom{margin-bottom:5px}.m-quarter-left{margin-left:5px}.m-none-left{margin-left:0!important}.m-quarter-all{margin:5px}.m-double-top{margin-top:40px}.m-double-right{margin-right:40px}.m-double-bottom{margin-bottom:40px}.m-auto{margin:auto}.m-none{margin:0!important}.m-none-bottom{margin-bottom:0}.m-none-top{margin-top:0!important}.m-center{margin:0 auto;position:relative;display:block}.p-double-top{padding-top:40px!important}.p-double-right{padding-right:40px!important}.p-double-bottom{padding-bottom:40px!important}.p-double-left{padding-left:40px!important}.p-top{padding-top:20px!important}.p-none-top{padding-top:0!important}.p-right{padding-right:20px!important}.p-right-none{padding-right:0!important}.p-left-none{padding-left:0!important}.p-bottom{padding-bottom:20px!important}.p-none-bottom{padding-bottom:0!important}.p-left{padding-left:20px!important}.p-all{padding:20px!important}.p-half-top{padding-top:10px!important}.p-half-right{padding-right:10px!important}.p-half-bottom{padding-bottom:10px!important}.p-half-left{padding-left:10px!important}.p-half-all{padding:10px!important}.p-quarter-top{padding-top:5px}.p-quarter-right{padding-right:5px}.p-quarter-bottom{padding-bottom:5px}.p-quarter-left{padding-left:5px}.p-quarter-all{padding:5px}.p-none{padding:0!important}.col-tight{padding-left:8px;padding-right:8px}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-wrap{-ms-word-break:break-all;word-wrap:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}.font-weight-normal{font-weight:400}.text-decoration-none{text-decoration:none}.text-line-through{text-decoration:line-through}.text-underline{text-decoration:underline}.font-italic{font-style:italic}.font-normal{font-weight:normal}.font-bold{font-weight:700}.line-height-inherit{line-height:inherit}.line-height-initial{line-height:initial}ul.list-style-none li{list-style:none}.mail-to a,.mail-to a:hover,.no-link,.no-link:focus,.no-link:hover{text-decoration:none}.border-radius-none{border-radius:0}.overflow-none{overflow:hidden}.overflow-y{overflow-y:auto}

/* Mobile Helpers */
@media (max-width: 767px) {
    .full-width-xs{width: 100%}.clear-both-xs{clear:both}.f-none-xs{float:none!important}.pull-left-xs{float: left!important;}.d-inline-block-xs{display:inline-block!important}.p-none-xs{padding:0!important}.p-left-none-xs{padding-left:0}.p-right-none-xs{padding-right:0}.p-half-left-xs{padding-left:10px!important}.p-quarter-left-xs{padding-left:5px}.p-quarter-right-xs{padding-right:5px}.p-right-xs{padding-right: 20px!important}.p-half-right-xs{padding-right:10px!important}.p-double-right-xs{padding-right:40px!important}.p-top-xs{padding-top:20px!important}.p-half-top-xs{padding-top:10px}.p-bottom-xs{padding-bottom:20px}.p-half-bottom-xs{padding-bottom:10px}.p-double-bottom-xs{padding-bottom:40px}.m-none-xs{margin:0!important}.m-bottom-xs{margin-bottom:20px}.m-half-bottom-xs{margin-bottom:10px}.m-quarter-bottom-xs{margin-bottom:5px!important}.m-top-xs{margin-top:20px}.m-half-top-xs{margin-top:10px}.m-quarter-top-xs{margin-top:5px}.m-none-top-xs{margin-top:0}.m-none-bottom-xs{margin-bottom:0;}.text-center-xs{text-align:center}.text-left-xs{text-align:left}.col-tight-xs{padding-left:8px;padding-right:8px}.drop-shadow-xs{-moz-box-shadow:0 0 3px #ccc;-webkit-box-shadow:0 0 3px #ccc;box-shadow:0 0 3px #ccc}.border-top-none-xs{border-top:0!important}.border-bottom-none-xs{border-bottom:0!important}.horizontal-container{overflow-x:scroll;width:100%;}.horizontal-container::-webkit-scrollbar{width:1px;height:0}.horizontal-container::-webkit-scrollbar-track{background:0 0;border-radius:10px}.horizontal-container::-webkit-scrollbar-thumb{border-radius:1px}.horizontal-container ul, .horizontal-products-scroller{white-space:nowrap}
}

6. SASS

Adicionar el SASS correspondiente al theme. Ejemplo del código en Luxury:

#ajax-cart-details{
    background: $back; 
 }
.ajax-cart_titles, .ajax-cart-item:last-child { 
    font-family: $fonthead;
    border-top: 1px solid rgba($secondary, $opac2);
    border-bottom: 1px solid rgba($secondary, $opac2);
}
.ajax-cart_titles .ajax-cart_titles_header { 
    font-weight: normal; 
    text-transform: uppercase; 
    font-size: 12px; 
    letter-spacing: 1px 
}
.ajax-cart-item { 
    font-family: $fonthead; 
    border-bottom: 1px solid rgba($secondary, $opac2); 
    background: rgba(150, 150, 150, 0.06); 
}
.ajax-cart-item:last-child { 
    font-family: $fonthead; 
    border-top: none; 
    border-bottom: 2px solid rgba($secondary, $opac2) 
}
.ajax-cart-item_image-col {
    width:100px; 
    min-width: 35px;
}
.ajax-cart-item_subtotal-col { 
    width: 40%; 
}
.ajax-cart-item .ajax-cart-item_img { 
    max-height: 60px; 
    max-width: 60px; 
    object-fit: contain; 
}
.ajax-cart-item .ajax-cart-item-link { 
    color: $primary 
}
.ajax-cart-item { 
    display: table; 
}
.ajax-cart-item .ajax-cart-item_item-row {
    float: none;
    display: table-cell;
    vertical-align: top;
}
.ajax-cart-total { 
    font-family: $fonthead; 
}
.ajax-cart-total h3 { 
    font-size: 20px; 
}
.ajax-cart-shipping{
    border-top: 1px solid rgba($secondary, $opac2);
    border-bottom: 1px solid rgba($secondary, $opac2);
}

7. Activación

Una vez incluida la funcionalidad, es necesario activarla desde el administrador de la tienda en la página Personalizar tu diseño, dentro de la opción Carrito de compras.