Ajax Cart Drawer for Shopify [No APP or external library]

Ajax Cart Drawer for Shopify [No APP or external library]

How to see the code

1. Please log in/create an account first. This is to ensure that all purchases are connected to an account and can be viewed on different devices.

2. Purchase the code using the "purchase box" on the specific page of the desired code.

3. After the purchase, please refresh the page.

CompatibilityDawn 2.0 to Shopify 3.0 - See video

I have multiple people asking for ajax cart drawer for Shopify. It is easy to create a side bar but not a cart that updates without refreshing the page. That is what we called ajax cart drawer. Add a cart drawer without any app or external library. Pure javascript code and Shopify Section Rendering API. 

Features:

- change the background of the drawer

- add a cart note

- have an option to open the cart whenever the customer goes to the cart url

- cart updates without refreshing the browser

Check DEMO store here 💻. Password: made4uo

 

To start:

1. Go to Admin store > Themes > Actions > Edit code. 

2. Go to Sections folder and create a new section, name it "cart-drawer", then paste the code below. 

 {{ 'component-cart.css' | asset_url | stylesheet_tag }}
{{ 'component-cart-items.css' | asset_url | stylesheet_tag }}
{{ 'component-totals.css' | asset_url | stylesheet_tag }}
{{ 'component-price.css' | asset_url | stylesheet_tag }}
{{ 'component-discounts.css' | asset_url | stylesheet_tag }}
{{ 'component-loading-overlay.css' | asset_url | stylesheet_tag }}

<link rel="stylesheet" href="{{ 'component-card.css' | asset_url }}" media="print" onload="this.media='all'">
<link rel="stylesheet" href="{{ 'section-product-recommendations.css' | asset_url }}" media="print" onload="this.media='all'">

<noscript>{{ 'component-card.css' | asset_url | stylesheet_tag }}</noscript>
<noscript>{{ 'section-product-recommendations.css' | asset_url | stylesheet_tag }}</noscript>

<script src="{{ 'cart.js' | asset_url }}" defer="defer"></script>
<div class="cart-overlay">&nbsp</div>
<cart-items class="close-drawer{% if cart == empty %} is-empty{% endif %}" data-open-cart-with-url= {{section.settings.open_with_url}}>
{% include "icon-close" %}
<div class="title-wrapper-with-link">
<h2 class="title title--primary">{{ 'sections.cart.title' | t }}</h2>
<a href="{{ routes.all_products_collection_url }}" class="underlined-link">{{ 'general.continue_shopping' | t }}</a>
</div>

<div class="cart__warnings">
<h2 class="cart__empty-text">{{ 'sections.cart.empty' | t }}</h2>
<a href="{{ routes.all_products_collection_url }}" class="button">
{{ 'general.continue_shopping' | t }}
</a>

{%- if shop.customer_accounts_enabled and customer == nil -%}
<h2 class="cart__login-title">{{ 'sections.cart.login.title' | t }}</h2>
<p class="cart__login-paragraph">
{{ 'sections.cart.login.paragraph_html' | t: link: routes.account_login_url }}
</p>
{%- endif -%}
</div>

<form action="{{ routes.cart_url }}" class="cart__contents critical-hidden" method="post" id="cart">
<div class="cart__items" id="cart-drawer" data-id="{{ section.id }}">
<div class="js-contents">
{%- if cart != empty -%}
<table class="cart-items">
<tbody>

{%- for item in cart.items -%}
<tr class="cart-item" id="CartItem-{{ item.index | plus: 1 }}">
<td class="cart-item__details">
<div class="cart-item__media">
{% if item.image %}
{% comment %} Leave empty space due to a:empty CSS display: none rule {% endcomment %}
<a href="{{ item.url }}" class="cart-item__link" aria-hidden="true" tabindex="-1"> </a>
<div class="cart-item__image-container gradient global-media-settings">
<img src="{{ item.image | image_url: width: 300 }}"
class="cart-item__image"
alt="{{ item.image.alt | escape }}"
loading="lazy"
width="150"
height="{{ 150 | divided_by: item.image.aspect_ratio | ceil }}"
>
</div>
{% endif %}
</div>
{%- if section.settings.show_vendor -%}
<p class="caption-with-letter-spacing">{{ item.product.vendor }}</p>
{%- endif -%}
</td>

<td class="cart-item__totals right medium-hide large-up-hide">
<div class="loading-overlay hidden">
<div class="loading-overlay__spinner">
<svg aria-hidden="true" focusable="false" role="presentation" class="spinner" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
<circle class="path" fill="none" stroke-width="6" cx="33" cy="33" r="30"></circle>
</svg>
</div>
</div>
<div class="cart-item__price-wrapper">
{%- if item.original_line_price != item.final_line_price -%}
<dl class="cart-item__discounted-prices">
<dt class="visually-hidden">
{{ 'products.product.price.regular_price' | t }}
</dt>
<dd>
<s class="cart-item__old-price price price--end">
{{ item.original_line_price | money }}
</s>
</dd>
<dt class="visually-hidden">
{{ 'products.product.price.sale_price' | t }}
</dt>
<dd class="price price--end">
{{ item.final_line_price | money }}
</dd>
</dl>
{%- else -%}
<span class="price price--end">
{{ item.original_line_price | money }}
</span>
{%- endif -%}

{%- if item.variant.available and item.unit_price_measurement -%}
<div class="unit-price caption">
<span class="visually-hidden">{{ 'products.product.price.unit_price' | t }}</span>
{{ item.variant.unit_price | money }}
<span aria-hidden="true">/</span>
<span class="visually-hidden">&nbsp;{{ 'accessibility.unit_price_separator' | t }}&nbsp;</span>
{%- if item.variant.unit_price_measurement.reference_value != 1 -%}
{{- item.variant.unit_price_measurement.reference_value -}}
{%- endif -%}
{{ item.variant.unit_price_measurement.reference_unit }}
</div>
{%- endif -%}
</div>
</td>

<td class="cart-item__totals right small-hide">
<div class="cart-item__quantity">
<div class="cart-item__quantity-wrapper">
<label class="visually-hidden" for="Quantity-{{ item.index | plus: 1 }}">
{{ 'products.product.quantity.label' | t }}
</label>
<quantity-input class="quantity">
<button class="quantity__button no-js-hidden" name="minus" type="button">
<span class="visually-hidden">{{ 'products.product.quantity.decrease' | t: product: item.product.title | escape }}</span>
{% render 'icon-minus' %}
</button>
<input class="quantity__input"
type="number"
name="updates[]"
value="{{ item.quantity }}"
min="0"
aria-label="{{ 'products.product.quantity.input_label' | t: product: item.product.title | escape }}"
id="Quantity-{{ item.index | plus: 1 }}"
data-index="{{ item.index | plus: 1 }}">
<button class="quantity__button no-js-hidden" name="plus" type="button">
<span class="visually-hidden">{{ 'products.product.quantity.increase' | t: product: item.product.title | escape }}</span>
{% render 'icon-plus' %}
</button>
</quantity-input>

<cart-remove-button id="Remove-{{ item.index | plus: 1 }}" data-index="{{ item.index | plus: 1 }}">
<a href="{{ item.url_to_remove }}" class="button button--tertiary" aria-label="{{ 'sections.cart.remove_title' | t: title: item.title }}">
{% render 'icon-remove' %}
</a>
</cart-remove-button>
</div>
<div class="cart-item__error" id="Line-item-error-{{ item.index | plus: 1 }}" role="alert">
<small class="cart-item__error-text"></small>
<svg aria-hidden="true" focusable="false" role="presentation" class="icon icon-error" viewBox="0 0 13 13">
<circle cx="6.5" cy="6.50049" r="5.5" stroke="white" stroke-width="2"/>
<circle cx="6.5" cy="6.5" r="5.5" fill="#EB001B" stroke="#EB001B" stroke-width="0.7"/>
<path d="M5.87413 3.52832L5.97439 7.57216H7.02713L7.12739 3.52832H5.87413ZM6.50076 9.66091C6.88091 9.66091 7.18169 9.37267 7.18169 9.00504C7.18169 8.63742 6.88091 8.34917 6.50076 8.34917C6.12061 8.34917 5.81982 8.63742 5.81982 9.00504C5.81982 9.37267 6.12061 9.66091 6.50076 9.66091Z" fill="white"/>
<path d="M5.87413 3.17832H5.51535L5.52424 3.537L5.6245 7.58083L5.63296 7.92216H5.97439H7.02713H7.36856L7.37702 7.58083L7.47728 3.537L7.48617 3.17832H7.12739H5.87413ZM6.50076 10.0109C7.06121 10.0109 7.5317 9.57872 7.5317 9.00504C7.5317 8.43137 7.06121 7.99918 6.50076 7.99918C5.94031 7.99918 5.46982 8.43137 5.46982 9.00504C5.46982 9.57872 5.94031 10.0109 6.50076 10.0109Z" fill="white" stroke="#EB001B" stroke-width="0.7"/>
</svg>
</div>
</div>

<div class="loading-overlay hidden">
<div class="loading-overlay__spinner">
<svg aria-hidden="true" focusable="false" role="presentation" class="spinner" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
<circle class="path" fill="none" stroke-width="6" cx="33" cy="33" r="30"></circle>
</svg>
</div>
</div>

<div class="cart-item__price-wrapper">
{%- if item.original_line_price != item.final_line_price -%}
<dl class="cart-item__discounted-prices">
<dt class="visually-hidden">
{{ 'products.product.price.regular_price' | t }}
</dt>
<dd>
<s class="cart-item__old-price price price--end">
{{ item.original_line_price | money }}
</s>
</dd>
<dt class="visually-hidden">
{{ 'products.product.price.sale_price' | t }}
</dt>
<dd class="price price--end">
{{ item.final_line_price | money }}
</dd>
</dl>
{%- else -%}
<span class="price price--end">
{{ item.original_line_price | money }}
</span>
{%- endif -%}

{%- if item.variant.available and item.unit_price_measurement -%}
<div class="unit-price caption">
<span class="visually-hidden">{{ 'products.product.price.unit_price' | t }}</span>
{{ item.variant.unit_price | money }}
<span aria-hidden="true">/</span>
<span class="visually-hidden">&nbsp;{{ 'accessibility.unit_price_separator' | t }}&nbsp;</span>
{%- if item.variant.unit_price_measurement.reference_value != 1 -%}
{{- item.variant.unit_price_measurement.reference_value -}}
{%- endif -%}
{{ item.variant.unit_price_measurement.reference_unit }}
</div>
{%- endif -%}
</div>
</td>
</tr>

<tr>
<td colspan="2" style="padding-top: 1rem">
<a href="{{ item.url }}" class="cart-item__name h4 break">{{ item.product.title | escape }}</a>

<div class="product-price">
{%- if item.original_price != item.final_price -%}
<div class="cart-item__discounted-prices">
<span class="visually-hidden">
{{ 'products.product.price.regular_price' | t }}
</span>
<s class="cart-item__old-price product-option">
{{- item.original_price | money -}}
</s>
<span class="visually-hidden">
{{ 'products.product.price.sale_price' | t }}
</span>
<strong class="cart-item__final-price product-option">
{{ item.final_price | money }}
</strong>
</div>
{%- else -%}
<div class="product-option">
{{ item.original_price | money }}
</div>
{%- endif -%}
</div>

{%- if item.product.has_only_default_variant == false or item.properties.size != 0 or item.selling_plan_allocation != nil -%}
{%- if item.product.has_only_default_variant == false -%}
{%- for option in item.options_with_values -%}
<div class="product-option">
<dt>{{ option.name }}: </dt>
<dd>{{ option.value }}</dd>
</div>
{%- endfor -%}
{%- endif -%}

{%- for property in item.properties -%}
{%- assign property_first_char = property.first | slice: 0 -%}
{%- if property.last != blank and property_first_char != '_' -%}
<div class="product-option">
<dt>{{ property.first }}: </dt>
<dd>
{%- if property.last contains '/uploads/' -%}
<a href="{{ property.last }}" class="link" target="_blank">
{{ property.last | split: '/' | last }}
</a>
{%- else -%}
{{ property.last }}
{%- endif -%}
</dd>
</div>
{%- endif -%}
{%- endfor -%}

<p class="product-option">{{ item.selling_plan_allocation.selling_plan.name }}</p>
{%- endif -%}

<ul class="discounts list-unstyled" role="list" aria-label="{{ 'customer.order.discount' | t }}">
{%- for discount in item.discounts -%}
<li class="discounts__discount">
{%- render 'icon-discount' -%}
{{ discount.title }}
</li>
{%- endfor -%}
</ul>
</td>
</tr>
{%- endfor -%}
</tbody>
</table>
{%- endif -%}

<div class="cart__footer">
{%- if section.settings.show_cart_note -%}
<cart-note class="cart__note field">
<label for="Cart-note">{{ 'sections.cart.note' | t }}</label>
<textarea class="text-area field__input" name="note" form="cart" id="Cart-note" placeholder="{{ 'sections.cart.note' | t }}">{{ cart.note }}</textarea>
</cart-note>
{%- endif -%}

<div class="cart__blocks">
<div class="js-contents" {{ block.shopify_attributes }}>
<div class="totals">
<h3 class="totals__subtotal">{{ 'sections.cart.subtotal' | t }}</h3>
<p class="totals__subtotal-value">{{ cart.total_price | money_with_currency }}</p>
</div>

<div>
{%- if cart.cart_level_discount_applications.size > 0 -%}
<ul class="discounts list-unstyled" role="list" aria-label="{{ 'customer.order.discount' | t }}">
{%- for discount in cart.cart_level_discount_applications -%}
<li class="discounts__discount discounts__discount--position">
{%- render 'icon-discount' -%}
{{ discount.title }}
(-{{ discount.total_allocated_amount | money }})
</li>
{%- endfor -%}
</ul>
{%- endif -%}
</div>

<small class="tax-note caption-large rte">
{%- if cart.taxes_included and shop.shipping_policy.body != blank -%}
{{ 'sections.cart.taxes_included_and_shipping_policy_html' | t: link: shop.shipping_policy.url }}
{%- elsif cart.taxes_included -%}
{{ 'sections.cart.taxes_included_but_shipping_at_checkout' | t }}
{%- elsif shop.shipping_policy.body != blank -%}
{{ 'sections.cart.taxes_and_shipping_policy_at_checkout_html' | t: link: shop.shipping_policy.url }}
{%- else -%}
{{ 'sections.cart.taxes_and_shipping_at_checkout' | t }}
{%- endif -%}
</small>
</div>

<div class="cart__ctas" {{ block.shopify_attributes }}>
<noscript>
<button type="submit" class="cart__update-button button button--secondary" form="cart">
{{ 'sections.cart.update' | t }}
</button>
</noscript>

<button type="submit" id="checkout" class="cart__checkout-button button" name="checkout"{% if cart == empty %} disabled{% endif %} form="cart">
{{ 'sections.cart.checkout' | t }}
</button>
</div>

{%- if additional_checkout_buttons -%}
<div class="cart__dynamic-checkout-buttons additional-checkout-buttons">
{{ content_for_additional_checkout_buttons }}
</div>
{%- endif -%}
<div id="cart-errors"></div>
</div>
</div>
</div>
</div>
<p class="visually-hidden" id="cart-live-region-text" aria-live="polite" role="status"></p>
<p class="visually-hidden" id="shopping-cart-line-item-status" aria-live="polite" aria-hidden="true" role="status">{{ 'accessibility.loading' | t }}</p>
</form>

<cart-recommendations class="product-recommendations" data-products="{{ section.settings.number_of_products }}">
{% if recommendations.performed and recommendations.products_count > 0 %}
<h2 class="product-recommendations__heading">{{ section.settings.heading | escape }}</h2>
<slider-component>
<ul class="cart-slider" id="Slider-{{ section.id }}" role="list">
{% for recommendation in recommendations.products %}
<li id="Slide-{{ section.id }}">
{% if section.settings.old_version %}
{% render 'product-card',
product_card_product: recommendation,
media_size: section.settings.image_ratio,
show_secondary_image: section.settings.show_secondary_image,
add_image_padding: section.settings.add_image_padding,
show_vendor: section.settings.show_vendor,
show_image_outline: section.settings.show_image_outline,
show_rating: section.settings.show_rating
%}
{% else %}
{% render "card-product",
card_product: recommendation,
media_aspect_ratio: section.settings.image_ratio,
show_vendor: section.settings.show_vendor,
show_rating: section.settings.show_rating
%}
{% endif %}

</li>
{% endfor %}
</ul>
{% endif %}
{% if recommendations.products_count > 2 %}
<div class="slider-buttons no-js-hidden">
<button type="button" class="slider-button slider-button--prev" name="previous" aria-label="{{ 'general.slider.previous_slide' | t }}" aria-controls="Slider-{{ section.id }}">{% render 'icon-caret' %}</button>
<div class="slider-counter caption">
<span class="slider-counter--current">1</span>
<span aria-hidden="true"> / </span>
<span class="visually-hidden">{{ 'general.slider.of' | t }}</span>
<span class="slider-counter--total">{{ recommendations.products_count }}</span>
</div>
<button type="button" class="slider-button slider-button--next" name="next" aria-label="{{ 'general.slider.next_slide' | t }}" aria-controls="Slider-{{ section.id }}">{% render 'icon-caret' %}</button>
</div>
{% endif %}
</slider-component>
</cart-recommendations>
</cart-items>

{% javascript %}
class CartNote extends HTMLElement {
constructor() {
super();

this.addEventListener('change', debounce((event) => {
const body = JSON.stringify({ note: event.target.value });
fetch(`${routes.cart_update_url}`, {...fetchConfig(), ...{ body }});
}, 300))
}
}

customElements.define('cart-note', CartNote);
{% endjavascript %}
<script>
document.addEventListener('DOMContentLoaded', function() {
function isIE() {
const ua = window.navigator.userAgent;
const msie = ua.indexOf('MSIE ');
const trident = ua.indexOf('Trident/');

return (msie > 0 || trident > 0);
}

if (!isIE()) return;
const cartSubmitInput = document.createElement('input');
cartSubmitInput.setAttribute('name', 'checkout');
cartSubmitInput.setAttribute('type', 'hidden');
document.querySelector('#cart').appendChild(cartSubmitInput);
document.querySelector('#checkout').addEventListener('click', function(event) {
document.querySelector('#cart').submit();
});
});
</script>

<style>
.cart-overlay {
position: fixed;
display: none;
background: transparent;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 5;
}

.show-overlay {
display: block !important;
}

cart-items {
position: fixed;
height: 100%;
width: 100%;
max-width: 40rem;
background: {{section.settings.background}};
z-index: 10;
right: 0;
padding: 3rem;
overflow-y: scroll;
transition: right .7s cubic-bezier(.94,.28,1,-0.08);
}

cart-items .icon-close {
position: absolute;
height: 2rem;
width: 2rem;
right: 3rem;
}

cart-items .icon-close:hover {
stroke: rgba(var(--color-foreground),.75);
stroke-width: 1px;
}

.cart__contents {
margin: auto;
width: 100%;
}
.cart-item__price-wrapper .price--end {
text-align: left;
}

cart-items .cart-item {
display: flex;
}

cart-items .cart-item__media {
width: 13rem;
}

.cart-item cart-remove-button{
margin: 0 !important;
height: unset !important;
}

cart-remove-button svg.icon-remove {
height: 2.5rem;
width: 2.5rem;
}

cart-items .totals {
display: flex;
align-items: center;
justify-content: space-between;
}

cart-items .quantity {
width: 8rem;
min-height: 2rem;
height: 3.5rem;
}


quantity-input .quantity__button {
width: 2rem;
}

.empty-cart {
display: none !important;
}

.close-drawer {
right: -60rem;
}

cart-items .cart__footer {
flex-direction: column;
margin-top: 2rem;
}

cart-items .title-wrapper-with-link {
margin-bottom: 0;
}

cart-items .cart__footer>*+* {
margin-top: 4rem;
margin-left: 0;
}

cart-items .cart-items {
table-layout: fixed;
}

.cart__footer > * {
width: 100%
}

.cart-items .cart-item__totals {
text-align: left;
width: 100%;
}

.cart-item .cart-item__quantity, .cart-items .cart-items__heading--wide {
padding-left: 0 !important;
}

.is-empty cart-recommendations {
display: none !important;
}

cart-recommendations slider-component {
width: 100%;
}

.cart-slider {
position: relative;
display: flex;
width: 100%;
flex-wrap: inherit;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
scroll-padding-left: 1.5rem;
-webkit-overflow-scrolling: touch;
margin-bottom: 1rem;
padding-inline-start: 0;
list-style: none;
}

.cart-slider li {
min-width: 50%
}
.cart-slider li:not(:first-child) {
margin-left: 1rem
}

.cart-slider {
scrollbar-color: rgb(var(--color-foreground)) rgba(var(--color-foreground), 0.04);
-ms-overflow-style: none;
scrollbar-width: none;
}

.cart-slider::-webkit-scrollbar {
height: 0.4rem;
width: 0.4rem;
display: none;
}

cart-items .cart__warnings {
position: relative;
margin: auto;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

</style>

{% schema %}
{
"name": "Cart drawer",
"settings": [
{
"type": "checkbox",
"id": "show_vendor",
"default": false,
"label": "t:sections.main-cart-items.settings.show_vendor.label"
},
{
"type": "checkbox",
"id": "show_cart_note",
"default": false,
"label": "t:sections.main-cart-footer.settings.show_cart_note.label"
},
{
"type": "checkbox",
"id": "open_with_url",
"default": false,
"label": "Open drawer with cart url",
"info": "Cart drawer opens whenever the url is ends with /cart"
},
{
"type": "color",
"id": "background",
"default": "#F27272",
"label": "Drawer's background color"
},
{
"type": "header",
"content": "Product Recommendation"
},
{
"type": "paragraph",
"content": "t:sections.product-recommendations.settings.paragraph__1.content"
},
{
"type": "text",
"id": "heading",
"default": "You may also like",
"label": "t:sections.product-recommendations.settings.heading.label"
},
{
"type": "range",
"id": "number_of_products",
"min": 2,
"max": 10,
"step": 1,
"default": 4,
"label": "Maximum products to show"
},
{
"type": "header",
"content": "t:sections.product-recommendations.settings.header__2.content"
},
{
"type": "checkbox",
"id": "old_version",
"default": false,
"label": "Check if you have Dawn 2.5 version and below"
},
{
"type": "select",
"id": "image_ratio",
"options": [
{
"value": "adapt",
"label": "t:sections.product-recommendations.settings.image_ratio.options__1.label"
},
{
"value": "portrait",
"label": "t:sections.product-recommendations.settings.image_ratio.options__2.label"
},
{
"value": "square",
"label": "t:sections.product-recommendations.settings.image_ratio.options__3.label"
}
],
"default": "adapt",
"label": "t:sections.product-recommendations.settings.image_ratio.label"
},
{
"type": "checkbox",
"id": "show_rating",
"default": false,
"label": "t:sections.product-recommendations.settings.show_rating.label",
"info": "t:sections.product-recommendations.settings.show_rating.info"
}
]
}
{% endschema %}
      

3. In the same folder, Sections, find the header.liquid. Find the word "cart_url".  Replace the highlighted code (Refer to the image below) with the code below.

header.liquid file

<cart-toggle class="header__icon header__icon--cart link focus-inset" id="cart-icon-bubble">
{%- liquid
if cart == empty
render 'icon-cart-empty'
else
render 'icon-cart'
endif
-%}
<span class="visually-hidden">{{ 'templates.cart.cart' | t }}</span>
{%- if cart != empty -%}
<div class="cart-count-bubble">
{%- if cart.item_count < 100 -%}
<span aria-hidden="true">{{ cart.item_count }}</span>
{%- endif -%}
<span class="visually-hidden">{{ 'sections.header.cart_count' | t: count: cart.item_count }}</span>
</div>
{%- endif -%}
</cart-toggle>

 

4. In the same area, you can see the "cart-notification." Please refer to the image below. 

notification

5. Go to Asset folder, and open the product-form.js, then replace the code with the code below. 

if (!customElements.get('product-form')) {
customElements.define('product-form', class ProductForm extends HTMLElement {
constructor() {
super();

this.form = this.querySelector('form');
this.form.querySelector('[name=id]').disabled = false;
this.form.addEventListener('submit', this.onSubmitHandler.bind(this));
this.cartItems = document.querySelector('cart-items');
this.submitButton = this.querySelector('[type="submit"]');
}

onSubmitHandler(evt) {
evt.preventDefault();
if (this.submitButton.getAttribute('aria-disabled') === 'true') return;

this.handleErrorMessage();

this.submitButton.setAttribute('aria-disabled', true);
this.submitButton.classList.add('loading');
this.querySelector('.loading-overlay__spinner').classList.remove('hidden');

const config = fetchConfig('javascript');
config.headers['X-Requested-With'] = 'XMLHttpRequest';
delete config.headers['Content-Type'];

const formData = new FormData(this.form);
formData.append('sections', this.cartItems.getSectionsToRender().map((section) => section.id));
formData.append('sections_url', window.location.pathname);
config.body = formData;

fetch(`${routes.cart_add_url}`, config)
.then((response) => response.json())
.then((response) => {
if (response.status) {
this.handleErrorMessage(response.description);

const soldOutMessage = this.submitButton.querySelector('.sold-out-message');
if (!soldOutMessage) return;
this.submitButton.setAttribute('aria-disabled', true);
this.submitButton.querySelector('span').classList.add('hidden');
soldOutMessage.classList.remove('hidden');
this.error = true;
return;
}

this.error = false;
const quickAddModal = this.closest('quick-add-modal');
if (quickAddModal) {
document.body.addEventListener('modalClosed', () => {
setTimeout(() => { this.cartItems.renderContents(response) });
}, { once: true });
quickAddModal.hide(true);
} else {
this.cartItems.renderContents(response);
}
this.cartItems.viewDrawer();
})

.catch((e) => {
console.error(e);
})
.finally(() => {
this.submitButton.classList.remove('loading');
if (!this.error) this.submitButton.removeAttribute('aria-disabled');
this.querySelector('.loading-overlay__spinner').classList.add('hidden');
});
}

handleErrorMessage(errorMessage = false) {
this.errorMessageWrapper = this.errorMessageWrapper || this.querySelector('.product-form__error-message-wrapper');
if (!this.errorMessageWrapper) return;
this.errorMessage = this.errorMessage || this.errorMessageWrapper.querySelector('.product-form__error-message');

this.errorMessageWrapper.toggleAttribute('hidden', !errorMessage);

if (errorMessage) {
this.errorMessage.textContent = errorMessage;
}
}
});
}

 

6. Still at the same folder, Asset, find the cart.js and replace the code with the code below. 

class CartRemoveButton extends HTMLElement {
constructor() {
super();
this.addEventListener('click', (event) => {
event.preventDefault();
this.closest('cart-items').updateQuantity(this.dataset.index, 0);
});
}
}

customElements.define('cart-remove-button', CartRemoveButton);

class CartItems extends HTMLElement {
constructor() {
super();
this.closeBtn = this.querySelector('.icon-close');
this.openBtn = document.querySelector('cart-toggle');
this.overlay = document.querySelector('.cart-overlay');

this.closeBtn.addEventListener('click', this.viewDrawer.bind(this));
this.openBtn.addEventListener('click', this.viewDrawer.bind(this));
this.overlay.addEventListener('click', this.overlayListener.bind(this))


this.lineItemStatusElement = document.getElementById('shopping-cart-line-item-status');
this.currentItemCount = Array.from(this.querySelectorAll('[name="updates[]"]'))
.reduce((total, quantityInput) => total + parseInt(quantityInput.value), 0);

this.debouncedOnChange = debounce((event) => {
this.onChange(event);
}, 300);

this.addEventListener('change', this.debouncedOnChange.bind(this));
this.cartFooter = document.getElementById('main-cart-footer');
this.parsedId;

this.productRecommendation = this.querySelector('cart-recommendations');
this.openWithUrl = this.dataset.openCartWithUrl;

if(this.openWithUrl == 'true') {
if(location.href.includes('/cart')) {
this.viewDrawer();
}
}
}

overlayListener() {
if(this.overlay.classList.contains('show-overlay')) {
this.viewDrawer();
}
}

getProductRecommendation(parsedId) {
this.parsedId = parsedId;
const numberOfProducts = this.productRecommendation.dataset.products;
const url = `/recommendations/products?section_id=cart-drawer&product_id=${this.parsedId}&limit=${numberOfProducts}`;

fetch(url)
.then(response => response.text())
.then(text => {
const html = document.createElement('div');
html.innerHTML = text;
const recommendations = html.querySelector('cart-recommendations');

if (recommendations && recommendations.innerHTML.trim().length) {
this.productRecommendation.innerHTML = recommendations.innerHTML;
}

if (html.querySelector('.grid__item')) {
this.productRecommendation.classList.add('product-recommendations--loaded');
}
})
.catch(e => {
console.error(e);
});
}

viewDrawer() {
this.classList.toggle('close-drawer');
this.overlay.classList.toggle('show-overlay');
}

onChange(event) {
this.updateQuantity(event.target.dataset.index, event.target.value, document.activeElement.getAttribute('name'));
}

getSectionsToRender() {
return [
{
id: 'cart-drawer',
section: document.getElementById('cart-drawer').dataset.id,
selector: '.js-contents',
},
{
id: 'cart-icon-bubble',
section: 'cart-icon-bubble',
selector: '.shopify-section'
},
{
id: 'cart-live-region-text',
section: 'cart-live-region-text',
selector: '.shopify-section'
}
];
}

renderContents(parsedState) {
this.parsedId = parsedState.product_id;
this.getProductRecommendation(parsedState.product_id)

this.classList.toggle('is-empty', parsedState.item_count === 0);
if (this.cartFooter) this.cartFooter.classList.toggle('is-empty', parsedState.item_count === 0);

this.getSectionsToRender().forEach((section => {
const elementToReplace =
document.getElementById(section.id).querySelector(section.selector) || document.getElementById(section.id);

elementToReplace.innerHTML =
this.getSectionInnerHTML(parsedState.sections[section.section], section.selector);
}));

this.updateLiveRegions(1, parsedState.item_count);
const lineItem = document.getElementById(`CartItem-1`);
if (lineItem && lineItem.querySelector(`[name="${name}"]`)) lineItem.querySelector(`[name="${name}"]`).focus();
this.disableLoading();
}

updateQuantity(line, quantity, name) {
this.enableLoading(line);

const body = JSON.stringify({
line,
quantity,
sections: this.getSectionsToRender().map((section) => section.section),
sections_url: window.location.pathname
});

fetch(`${routes.cart_change_url}`, {...fetchConfig(), ...{ body }})
.then((response) => {
return response.text();
})
.then((state) => {
const parsedState = JSON.parse(state);
this.parsedId = parsedState.product_id;
this.classList.toggle('is-empty', parsedState.item_count === 0);

if (this.cartFooter) this.cartFooter.classList.toggle('is-empty', parsedState.item_count === 0);

this.getProductRecommendation(parsedState.items[0].product_id);

this.getSectionsToRender().forEach((section => {
const elementToReplace =
document.getElementById(section.id).querySelector(section.selector) || document.getElementById(section.id);

elementToReplace.innerHTML =
this.getSectionInnerHTML(parsedState.sections[section.section], section.selector);
}));

this.updateLiveRegions(line, parsedState.item_count);
const lineItem = document.getElementById(`CartItem-${line}`);
if (lineItem && lineItem.querySelector(`[name="${name}"]`)) lineItem.querySelector(`[name="${name}"]`).focus();
this.disableLoading();
}).catch(() => {
this.querySelectorAll('.loading-overlay').forEach((overlay) => overlay.classList.add('hidden'));
document.getElementById('cart-errors').textContent = window.cartStrings.error;
this.disableLoading();
});
}

updateLiveRegions(line, itemCount) {
if (this.currentItemCount === itemCount) {
document.getElementById(`Line-item-error-${line}`)
.querySelector('.cart-item__error-text')
.innerHTML = window.cartStrings.quantityError.replace(
'[quantity]',
document.getElementById(`Quantity-${line}`).value
);
}

this.currentItemCount = itemCount;
this.lineItemStatusElement.setAttribute('aria-hidden', true);

const cartStatus = document.getElementById('cart-live-region-text');
cartStatus.setAttribute('aria-hidden', false);

setTimeout(() => {
cartStatus.setAttribute('aria-hidden', true);
}, 1000);
}

getSectionInnerHTML(html, selector) {
return new DOMParser()
.parseFromString(html, 'text/html')
.querySelector(selector).innerHTML;
}

enableLoading(line) {
document.getElementById('cart-drawer').classList.add('cart__items--disabled');
this.querySelectorAll(`#CartItem-${line} .loading-overlay`).forEach((overlay) => overlay.classList.remove('hidden'));
document.activeElement.blur();
this.lineItemStatusElement.setAttribute('aria-hidden', false);
}

disableLoading() {
document.getElementById('cart-drawer').classList.remove('cart__items--disabled');
}
}

customElements.define('cart-items', CartItems);

 7. Go to the layout folder, and open the theme.liquid. Find the "announcement-bar", then paste the code below before it. 

   {% section 'cart-drawer' %}

 

8. Almost done, all we have to do is to clean up. We need to delete this following section files to remove the errors. Open the Section folder, and find the "main-cart-items.liquid", open the file and click "delete file." Same with the "main-cart-footer.liquid." 

That's it. (",)

Copied!
Voltar para o blogue

Deixe um comentário