1
votes

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

  1. Go to the preview link

  2. Add random product to cart

  3. Open cart drawer
    (It should open automatically when adding a product, otherwise click the cart icon at the top right corner)

  4. 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)

  5. Switch to add product to cart

  6. When ajax refreshing happens, you'll see the switch animates twice (on click and on ajax refresh)

The specific questions:

  1. How do we fix the sliders animation glitch on ajax refresh?
    (both the slight opacity change and the movement)

  2. 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!

1
Can you share the function that gets called when drawer gets opened? I mean the code that updates values(checkboxes section specifically) - AbbasEbadian
Hi @AbbasEbadian - sure. One moment, I'll update the OG post. - Magnus Pilegaard
When you do $cartContainer.empty(); and rerender cart with template(data) function, the check box value becomes false and then updating its checked attribute with the data inside localStorage, causes the slider to animate twice. Instead of updating checkbox value, pass its value to template function and render it with proper value in the first place. - AbbasEbadian
@AbbasEbadian Thank you. However, what you are referring to isn't my code. It's part of an old programmers script, as i havent made that. However, i suppose moving away from localstorage and set the value directly on ajaxcart load. However, i can't figure how to do so atm? - Magnus Pilegaard
@AbbasEbadian My original thought was to check if each of the two Upsell items are in cart (on buildcart) and solely set each checkbox state according to the correct state. - Magnus Pilegaard

1 Answers

0
votes

Here, next to line 456 add another key-value:

crossSellItem1Checked:JSON.parse(localStorage.getItem('plusCrossSellItem1inCart'))

and Here add this to end of the input tag (line 180) :

{% if crossSellItem1Checked %} checked {% endif %}

then it should be like :

<input type="checkbox" id="cartCrossSellCheckboxFirst" class="cart__cross-sell-btn-input cart__cross-sell-btn-input-first" {% if crossSellItem1Checked %} checked {% endif %}>

repeat this for the other checkbox aswell. let me now if it worked.