Skip to content

Instantly share code, notes, and snippets.

@marioloncarek
Created September 26, 2018 14:06
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save marioloncarek/93c8aaf2c9e2f29054b70e1c69105ae2 to your computer and use it in GitHub Desktop.
Save marioloncarek/93c8aaf2c9e2f29054b70e1c69105ae2 to your computer and use it in GitHub Desktop.
complete shopify ajax cart solution with drawer and modal, adding and removing products - ugly AF
const defaults = {
cartModal: '.js-ajax-cart-modal', // classname
cartModalContent: '.js-ajax-cart-modal-content', // classname
cartModalClose: '.js-ajax-cart-modal-close', // classname
cartDrawer: '.js-ajax-cart-drawer', // classname
cartDrawerContent: '.js-ajax-cart-drawer-content', // classname
cartDrawerClose: '.js-ajax-cart-drawer-close', // classname
cartDrawerTrigger: '.js-ajax-cart-drawer-trigger', // classname
cartOverlay: '.js-ajax-cart-overlay', // classname
cartCounter: '.js-ajax-cart-counter', // classname
addToCart: '.js-ajax-add-to-cart', // classname
removeFromCart: '.js-ajax-remove-from-cart', //classname
removeFromCartNoDot: 'js-ajax-remove-from-cart', //classname,
checkoutButton: '.js-ajax-checkout-button',
};
const cartModal = document.querySelector(defaults.cartModal);
const cartModalContent = document.querySelector(defaults.cartModalContent);
const cartModalClose = document.querySelector(defaults.cartModalClose);
const cartDrawer = document.querySelector(defaults.cartDrawer);
const cartDrawerContent = document.querySelector(defaults.cartDrawerContent);
const cartDrawerClose = document.querySelector(defaults.cartDrawerClose);
const cartDrawerTrigger = document.querySelector(defaults.cartDrawerTrigger);
const cartOverlay = document.querySelector(defaults.cartOverlay);
const cartCounter = document.querySelector(defaults.cartCounter);
const addToCart = document.querySelectorAll(defaults.addToCart);
let removeFromCart = document.querySelectorAll(defaults.removeFromCart);
const checkoutButton = document.querySelector(defaults.checkoutButton);
const htmlSelector = document.documentElement;
for (let i = 0; i < addToCart.length; i++) {
addToCart[i].addEventListener('click', function(event) {
event.preventDefault();
const formID = this.parentNode.getAttribute('id');
console.log(formID);
addProductToCart(formID);
});
}
function addProductToCart(formID) {
$.ajax({
type: 'POST',
url: '/cart/add.js',
dataType: 'json',
data: $('#' + formID)
.serialize(),
success: addToCartOk,
error: addToCartFail,
});
}
function fetchCart() {
$.ajax({
type: 'GET',
url: '/cart.js',
dataType: 'json',
success: function(cart) {
onCartUpdate(cart);
if (cart.item_count === 0) {
cartDrawerContent.innerHTML = 'Cart is empty';
checkoutButton.classList.add('is-hidden');
} else {
renderCart(cart);
checkoutButton.classList.remove('is-hidden');
}
},
});
}
function changeItem(line, callback) {
const quantity = 0;
$.ajax({
type: 'POST',
url: '/cart/change.js',
data: 'quantity=' + quantity + '&line=' + line,
dataType: 'json',
success: function(cart) {
if ((typeof callback) === 'function') {
callback(cart);
} else {
onCartUpdate(cart);
fetchCart();
removeProductFromCart();
}
},
});
}
function onCartUpdate(cart) {
console.log('items in the cart?', cart.item_count);
}
function addToCartOk(product) {
cartModalContent.innerHTML = product.title + ' was added to the cart!';
cartCounter.innerHTML = Number(cartCounter.innerHTML) + 1;
openAddModal();
openCartOverlay();
fetchCart();
}
function removeProductFromCart() {
cartCounter.innerHTML = Number(cartCounter.innerHTML) - 1;
}
function addToCartFail() {
cartModalContent.innerHTML = 'The product you are trying to add is out of stock.';
openAddModal();
openCartOverlay();
}
function renderCart(cart) {
console.log(cart);
clearCartDrawer();
cart.items.forEach(function(item, index) {
//console.log(item.title);
//console.log(item.image);
//console.log(item.line_price);
//console.log(item.quantity);
const productTitle = '<div class="ajax-cart-item__title">' + item.title + '</div>';
const productImage = '<img class="ajax-cart-item__image" src="' + item.image + '" >';
const productPrice = '<div class="ajax-cart-item__price">' + item.line_price + '</div>';
const productQuantity = '<div class="ajax-cart-item__quantity">' + item.quantity + '</div>';
const productRemove = '<div class="ajax-cart-item__remove ' + defaults.removeFromCartNoDot + '"></div>';
const concatProductInfo = '<div class="ajax-cart-item__single" data-line="' + Number(index + 1) + '">' + productTitle + productImage + productPrice + productQuantity + productRemove + '</div>';
cartDrawerContent.innerHTML = cartDrawerContent.innerHTML + concatProductInfo;
});
// document.querySelectorAll('.js-ajax-remove-from-cart')
// .forEach((element) => {
// element.addEventListener('click', function() {
// const lineID = this.parentNode.getAttribute('data-line');
// console.log('aa');
// });
// });
removeFromCart = document.querySelectorAll(defaults.removeFromCart);
for (let i = 0; i < removeFromCart.length; i++) {
removeFromCart[i].addEventListener('click', function() {
const line = this.parentNode.getAttribute('data-line');
console.log(line);
changeItem(line);
});
}
}
function openCartDrawer() {
cartDrawer.classList.add('is-open');
}
function closeCartDrawer() {
cartDrawer.classList.remove('is-open');
}
function clearCartDrawer() {
cartDrawerContent.innerHTML = '';
}
function openAddModal() {
cartModal.classList.add('is-open');
}
function closeAddModal() {
cartModal.classList.remove('is-open');
}
function openCartOverlay() {
cartOverlay.classList.add('is-open');
htmlSelector.classList.add('is-locked');
}
function closeCartOverlay() {
cartOverlay.classList.remove('is-open');
htmlSelector.classList.remove('is-locked');
}
cartModalClose.addEventListener('click', function() {
closeAddModal();
closeCartOverlay();
});
cartDrawerClose.addEventListener('click', function() {
closeCartDrawer();
closeCartOverlay();
});
// cart is empty stanje
cartOverlay.addEventListener('click', function() {
closeAddModal();
closeCartDrawer();
closeCartOverlay();
});
cartDrawerTrigger.addEventListener('click', function(event) {
event.preventDefault();
//fetchCart();
openCartDrawer();
openCartOverlay();
});
document.addEventListener('DOMContentLoaded', function() {
fetchCart();
});
<!--ajax cart modal-->
<div class="ajax-cart__modal js-ajax-cart-modal">
<div class="ajax-cart-modal">
<!--ajax cart modal close-->
<div class="ajax-cart-modal__close js-ajax-cart-modal-close">
{% include 'icon-close' %}
</div>
<!--end ajax cart modal close-->
<!--ajax cart modal content-->
<div class="ajax-cart-modal__content js-ajax-cart-modal-content"></div>
<!--end ajax cart modal content-->
</div>
</div>
<!--end ajax cart modal-->
<!--ajax cart drawer-->
<div class="ajax-cart__drawer js-ajax-cart-drawer">
<div class="ajax-cart-drawer">
<!--ajax cart drawer close-->
<div class="ajax-cart-drawer__close js-ajax-cart-drawer-close">
{% include 'icon-close' %}
</div>
<!--end ajax cart drawer close-->
<!--ajax cart drawer content-->
<div class="ajax-cart-drawer__content js-ajax-cart-drawer-content"></div>
<!--end ajax cart drawer content-->
<!--ajax cart drawer buttons-->
<div class="ajax-cart-drawer__buttons">
<a href="/cart/" class="button button--black button--full-width js-button">
<span>Go to cart</span>
</a>
<a href="/checkout/" class="button button--black button--full-width js-button js-ajax-checkout-button">
<span>Proceed to checkout</span>
</a>
</div>
<!--end ajax cart drawer buttons-->
</div>
</div>
<!--end ajax cart drawer-->
<!--ajax cart overlay-->
<div class="ajax-cart__overlay js-ajax-cart-overlay"></div>
<!--end ajax cart overlay-->
.ajax-cart {
&__modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 40;
max-width: 575px;
background: getColor('white', 'default');
border: 1px solid #e9e9e9;
padding: 50px 65px;
opacity: 0;
visibility: hidden;
will-change: opacity, visibility;
&.is-open {
opacity: 1;
visibility: visible;
}
}
&__overlay {
position: fixed;
z-index: 30;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: getColor('black-40', 'variations');
opacity: 0;
visibility: hidden;
will-change: opacity, visibility;
&.is-open {
opacity: 1;
visibility: visible;
}
}
&__drawer {
transition: getTransition();
position: fixed;
z-index: 40;
right: -400px;
top: 0;
width: 400px;
height: 100%;
background: #f6f6f6;
will-change: transform;
border-left: 1px solid #e9e9e9;
&.is-open {
transform: translateX(-100%);
}
}
}
.ajax-cart-modal {
position: relative;
&__close {
position: absolute;
right: 10px;
top: 10px;
}
&__content {
padding: 20px;
}
}
.ajax-cart-drawer {
position: relative;
height: 100%;
&__close {
position: absolute;
right: 10px;
top: 5px;
}
&__content {
padding: 25px 25px 190px;
height: 100%;
overflow: hidden;
overflow-y: scroll;
}
&__buttons {
position: absolute;
z-index: 10;
left: 0;
bottom: 0;
width: 100%;
height: 190px;
background: #f6f6f6;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: flex-end;
.button {
&:last-child {
margin-top: auto;
}
}
}
}
.ajax-cart-item {
&__single {
position: relative;
margin-bottom: 20px;
border-bottom: 2px solid red;
}
&__title {
}
&__image {
width: 100px;
}
&__price {
}
&__quantity {
}
&__remove {
@include center(vertical);
right: 5px;
width: 15px;
height: 15px;
background: url('data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiBpY29uLWNsb3NlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iMCAwIDIwIDIwIj4KICAgIDxwYXRoIGQ9Ik0xNS44OSAxNC42OTZsLTQuNzM0LTQuNzM0IDQuNzE3LTQuNzE3Yy40LS40LjM3LTEuMDg1LS4wMy0xLjQ4NXMtMS4wODUtLjQzLTEuNDg1LS4wM0w5LjY0MSA4LjQ0NyA0Ljk3IDMuNzc2Yy0uNC0uNC0xLjA4NS0uMzctMS40ODUuMDNzLS40MyAxLjA4NS0uMDMgMS40ODVsNC42NzEgNC42NzEtNC42ODggNC42ODhjLS40LjQtLjM3IDEuMDg1LjAzIDEuNDg1czEuMDg1LjQzIDEuNDg1LjAzbDQuNjg4LTQuNjg3IDQuNzM0IDQuNzM0Yy40LjQgMS4wODUuMzcgMS40ODUtLjAzcy40My0xLjA4NS4wMy0xLjQ4NXoiLz4KPC9zdmc+Cg==') center no-repeat;
background-size: cover;
cursor: pointer;
}
}
<!--cart-->
<div class="header__cart">
<a class="js-ajax-cart-drawer-trigger" href="/cart">
{% include 'icon-cart' %}
<span class="js-ajax-cart-counter">{{ cart.item_count }}</span>
</a>
</div>
<!--end cart-->
<!--product form-->
<form action="/cart/add" method="post" enctype="multipart/form-data" id="add-to-cart-{{ product.handle }}">
<!--product variants-->
{% unless product.has_only_default_variant %}
{% for option in product.options_with_values %}
<div class="single-product__option-wrapper js">
{% assign option-name = option.name | downcase %}
<div class="js-single-product-option-{{ option-name }} single-product-option-{{ option-name }}"
id="SingleOptionSelector-{{ forloop.index0 }}">
{% for value in option.values %}
{% assign product-handle = product.handle %}
{% assign is_color = false %}
{% assign stripped-value = value | split: ' ' | last | handle %}
{% if option-name contains 'color' or option-name contains 'colour' %}
{% assign is_color = true %}
{% endif %}
{% if is_color %}
<input type="radio" name="{{ option-name }}-{{ product-handle }}"
class="single-option-selector single-product-option-{{ option-name }}__input js-radio-button"
data-single-option-selector
data-index="option{{ option.position }}"
value="{{ value | escape }}"
data-color="{{ value | handleize }}"
{% if option.selected_value == value %}checked="checked"{% endif %}
id="variant_{{ option-name }}-{{ product-handle }}-{{ forloop.index0 }}"/>
<label for="variant_{{ option-name }}-{{ product-handle }}-{{ forloop.index0 }}"
class="single-product-option-{{ option-name }}__label {% if stripped-value contains 'white' %}single-product-option-{{ option-name }}__label--white{% endif %}"
style="background-color: {{ stripped-value }};">
{% include 'icon-check' %}
</label>
{% else %}
<input type="radio" name="{{ option-name }}-{{ product-handle }}"
class="single-option-selector single-product-option-{{ option-name }}__input"
data-single-option-selector
data-index="option{{ option.position }}"
value="{{ value | escape }}"
{% if option.selected_value == value %}checked="checked"{% endif %}
id="variant_{{ option-name }}-{{ product-handle }}-{{ forloop.index0 }}"/>
<label for="variant_{{ option-name }}-{{ product-handle }}-{{ forloop.index0 }}"
class="single-product-option-{{ option-name }}__label">{{ value }}</label>
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
{% endunless %}
<select name="id" class="no-js" data-product-select>
{% for variant in product.variants %}
<option
{% if variant == current_variant %}selected="selected"{% endif %}
{% unless variant.available %}disabled="disabled"{% endunless %}
value="{{ variant.id }}">
{{ variant.title }}
</option>
{% endfor %}
</select>
<!--end product variants-->
<!--product add to cart-->
<button
class="single-product__add-to-cart u-b6 js-ajax-add-to-cart"
type="submit"
name="add"
data-add-to-cart
{% unless current_variant.available %}disabled="disabled"{% endunless %}>
<span data-add-to-cart-text>
{% if current_variant.available %}
{{ 'products.product.add_to_cart' | t }}
{% else %}
{{ 'products.product.sold_out' | t }}
{% endif %}
</span>
</button>
<!--end product add to cart-->
</form>
<!--end product form-->
@marioloncarek
Copy link
Author

marioloncarek commented Sep 21, 2021

@sajalghoshui please try this - this is the production version https://github.com/bornfight/goCart.js

@sajalghoshui
Copy link

@sajalghoshui please try this - this is the production version https://github.com/bornfight/goCart.js

Thanks for your reply :)

@Anit2000
Copy link

@Deniz-Isso

But you have {% form 'product', product, id: product_form_id %} ? that is what you are looking for, you just copied it to me :))

after changing it's id as u mentioned it shows some liquid error how can i fix that

@sanjay-makwana-avidbrio
Copy link

@marioloncarek does this solution working with a prestige theme?

@marioloncarek
Copy link
Author

@Sanjayavidbrio i have no idea what a prestige theme is, sorry :/

i suggest you try and let us all know :) https://github.com/bornfight/goCart.js

@thanhkma1996
Copy link

Hi @marioloncarek
after i installed it, accessing the collection page and product both shows error 404 product not found i tried to uninstall but my product is still not showing please help me answer this question thanks
image

@marioloncarek
Copy link
Author

my plugin would not cause this kind of problem :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment