EDITED - Added AjaxCart Build function to the bottom of the post
I have a CSS switchbox, which adds a product to the cart drawer in Shopify. The cart drawer obviously ajax refresh, so I use localstorage to save the checkbox state. This part works fine, but on the ajax refresh, the Slider animates to checked state twice (probably some animation firing double?). However, I can't seem to be able to find a solution for it.
Check out preview of the site here:
https://0avs2fkcnxno3m87-2600108078.shopifypreview.com
Reproduce error
Go to the preview link
Add random product to cart
Open cart drawer
(It should open automatically when adding a product, otherwise click the cart icon at the top right corner)In the bottom of the drawer, you'll see two switches. Only the first one is working
(we want the code to work before implementing on both switches)Switch to add product to cart
When ajax refreshing happens, you'll see the switch animates twice (on click and on ajax refresh)
The specific questions:
How do we fix the sliders animation glitch on ajax refresh?
(both the slight opacity change and the movement)How do we check if one of the upsell items are in cart, so we can set the checkbox state correctly?
(Right now, it only listens to the switch when setting checkbox state, however customers would continue to be able to add the product from other parts of the page. We need to make sure it checks for that)
Our CSS, Switchbox HTML, and Javascript / jQuery:
Switchbox HTML:
<div class="cart__cross-sell-cta">
<label class="cart__cross-sell-btn">
<input type="checkbox" id="cartCrossSellCheckboxFirst" class="cart__cross-sell-btn-input cart__cross-sell-btn-input-first">
<span class="slider round"></span>
</label>
</div>
CSS:
.cart__cross-sell-btn {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
}
.cart__cross-sell-btn .cart__cross-sell-btn-input {
opacity: 0;
width: 0;
height: 0;
line-height: 0;
}
.cart__cross-sell-btn .slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
padding: 0;
}
.cart__cross-sell-btn .slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
top: 3px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
.cart__cross-sell-btn-input:checked + .slider {
background-color: #000;
}
.cart__cross-sell-btn-input:checked + .slider:before {
-webkit-transform: translateX(20px);
-ms-transform: translateX(20px);
transform: translateX(20px);
}
/* Rounded sliders */
.cart__cross-sell-btn .slider.round {
border-radius: 34px;
}
.cart__cross-sell-btn .slider.round:before {
border-radius: 50%;
}
Javascript / jQuery:
$('body').on('change', '.cart__cross-sell-btn-input', function(e) {
e.preventDefault();
var plusCrossSellItem1inCartID = $('.cart__cross-sell-item-one').data('variant-id'),
plusCrossSellItem2inCartID = $('.cart__cross-sell-item-second').data('variant-id')
if (e.target.checked) {
var crossSellItemId = $(this).closest('.cart__cross-sell-item').data('variant-id');
var params = {
type: 'POST',
url: '/cart/add.js',
data: 'quantity=' + 1 + '&id=' + crossSellItemId,
dataType: 'json',
success: function() {
if ((typeof callback) === 'function') {
callback(cart);
}
else {
ShopifyAPI.getCart(cartUpdateCallback);
}
},
error: function(XMLHttpRequest, textStatus) {
if ((typeof errorCallback) === 'function') {
errorCallback(XMLHttpRequest, textStatus);
}
else {
ShopifyAPI.onError(XMLHttpRequest, textStatus);
}
}
};
jQuery.ajax(params);
console.log(plusCrossSellItem1inCartID == crossSellItemId);
console.log(plusCrossSellItem2inCartID == crossSellItemId);
localStorage.setItem("plusCrossSellItem1inCart", true);
} else {
var crossSellItemId = $(this).closest('.cart__cross-sell-item').data('variant-id');
var params = {
type: 'POST',
url: '/cart/change.js',
data: { quantity: 0, id: crossSellItemId},
dataType: 'json',
success: function() {
if ((typeof callback) === 'function') {
callback(cart);
}
else {
ShopifyAPI.getCart(cartUpdateCallback);
}
},
error: function(XMLHttpRequest, textStatus) {
if ((typeof errorCallback) === 'function') {
errorCallback(XMLHttpRequest, textStatus);
}
else {
ShopifyAPI.onError(XMLHttpRequest, textStatus);
}
}
};
jQuery.ajax(params);
localStorage.setItem("plusCrossSellItem1inCart", false);
}
});
EDITED - Added AjaxCart Build function:
buildCart = function(cart) {
// Start with a fresh cart div
$cartContainer.empty();
// Show empty cart
if (cart.item_count === 0) {
$cartContainer
.append('<p>' + {{ 'cart.general.empty' | t | json }} + '</p>');
cartCallback(cart);
return;
}
// Handlebars.js cart layout
var items = [],
item = {},
data = {},
source = $('#CartTemplate').html(),
template = Handlebars.compile(source);
// Define cross sell items vars
var plusCrossSellItem1inCart = false,
plusCrossSellItem2inCart = false
// Add each item to our handlebars.js data
$.each(cart.items, function(index, cartItem) {
// Check if cross sell items are already in the cart
if (cartItem.handle == '{{ settings.plus_cross_sell_item_1 }}') {
plusCrossSellItem1inCart = true;
} else if (cartItem.handle == '{{ settings.plus_cross_sell_item_2 }}') {
plusCrossSellItem2inCart = true;
}
/* Hack to get product image thumbnail
* - If image is not null
* - Remove file extension, add _small, and re-add extension
* - Create server relative link
* - A hard-coded url of no-image
*/
var prodImg;
if (cartItem.image !== null) {
prodImg = cartItem.image
.replace(/(\.[^.]*)$/, '_small$1')
.replace('http:', '');
} else {
prodImg =
'//cdn.shopify.com/s/assets/admin/no-image-medium-cc9732cb976dd349a0df1d39816fbcc7.gif';
}
if (cartItem.properties !== null) {
$.each(cartItem.properties, function(key, value) {
if (key.charAt(0) === '_' || !value) {
delete cartItem.properties[key];
}
});
}
if (cartItem.properties !== null) {
$.each(cartItem.properties, function(key, value) {
if (key.charAt(0) === '_' || !value) {
delete cartItem.properties[key];
}
});
}
if (cartItem.line_level_discount_allocations.length !== 0) {
for (var discount in cartItem.line_level_discount_allocations) {
var amount =
cartItem.line_level_discount_allocations[discount].amount;
cartItem.line_level_discount_allocations[
discount
].formattedAmount = Shopify.formatMoney(
amount,
settings.moneyFormat
);
}
}
if (cart.cart_level_discount_applications.length !== 0) {
for (var cartDiscount in cart.cart_level_discount_applications) {
var cartAmount =
cart.cart_level_discount_applications[cartDiscount]
.total_allocated_amount;
cart.cart_level_discount_applications[
cartDiscount
].formattedAmount = Shopify.formatMoney(
cartAmount,
settings.moneyFormat
);
}
}
// Create item's data object and add to 'items' array
item = {
key: cartItem.key,
line: index + 1, // Shopify uses a 1+ index in the API
url: cartItem.url,
img: prodImg,
name: cartItem.product_title,
variation: cartItem.variant_title,
productId: cartItem.product_id,
properties: cartItem.properties,
productNameClass: cartItem.product_title.replace(/\s+/g, '-').toLowerCase().substring(0, 13),
itemAdd: cartItem.quantity + 1,
itemMinus: cartItem.quantity - 1,
itemQty: cartItem.quantity,
price: Shopify.formatMoney(
cartItem.original_line_price,
settings.moneyFormat
),
discountedPrice: Shopify.formatMoney(
cartItem.final_line_price,
settings.moneyFormat
),
discounts: cartItem.line_level_discount_allocations,
discountsApplied:
cartItem.line_level_discount_allocations.length === 0 ? false : true,
vendor: cartItem.vendor
};
var productNameClass = item.name.replace(/\s+/g, '-').toLowerCase().substring(0, 13);
console.log(productNameClass); // "sonic-free-games"
items.push(item);
});
// Gather all cart data and add to DOM
data = {
items: items,
note: cart.note,
subTotalPrice: Shopify.formatMoney(
cart.items_subtotal_price,
settings.moneyFormat
),
totalPrice: Shopify.formatMoney(
cart.total_price,
settings.moneyFormat
),
cartTotalDiscounts: Shopify.formatMoney(
cart.total_discount,
settings.moneyFormat
),
cartDiscounts: cart.cart_level_discount_applications,
cartDiscountsApplied: cart.cart_level_discount_applications.length === 0 ? false : true,
cartTotalSavings: cart.cart_level_discount_applications.length === 0 && cart.total_discount > 0,
plusCrossSellItem1inCart,
plusCrossSellItem2inCart,
cartFreeShipping: ((cart.total_price / 100) >= parseInt({{ settings.free_shipping_threshold }})) ? true : false,
amountRemaining: ((parseInt({{ settings.free_shipping_threshold }}) - cart.total_price / 100)).toFixed(2),
progressBarWidth: ((cart.total_price / 100) / parseInt({{ settings.free_shipping_threshold }})).toFixed(2) * 100
};
$cartContainer.append(template(data));
// Hide cross sell items if already in the cart
$('.cart__cross-sell-item.hide').hide();
cartCallback(cart);
var crossSellItem1Checked = JSON.parse(localStorage.getItem('plusCrossSellItem1inCart'));
document.getElementById("cartCrossSellCheckboxFirst").checked = crossSellItem1Checked;
};
cartCallback = function(cart) {
$body.removeClass('drawer--is-loading');
$body.trigger('ajaxCart.afterCartLoad', cart);
if (window.Shopify && Shopify.StorefrontExpressButtons) {
Shopify.StorefrontExpressButtons.initialize();
}
};
Any thoughts on how solve the above? Any help is deeply appreciated!
$cartContainer.empty();and rerender cart withtemplate(data)function, the check box value becomesfalseand then updating itscheckedattribute with the data inside localStorage, causes the slider to animate twice. Instead of updating checkbox value, pass its value totemplatefunction and render it with proper value in the first place. - AbbasEbadian