Compatibility: Dawn 5.0 and earlier version ONLY
Many of our customers have been inquiring about an Ajax Cart Drawer for Shopify. Creating a sidebar is straightforward, but developing a cart that updates in real-time without needing to refresh the page is more complex. This is where the Ajax Cart Drawer comes into play. This feature allows you to add a cart drawer using only pure JavaScript code with Shopify Ajax API and Shopify's Section Rendering API, without the need for any apps or external libraries.
Please note that this functionality is already included in the latest versions of Shopify 2.0 FREE themes , although the design and functionality might differ
Check DEMO store here 💻. Quickview button not included. Password: made4uo
What you are buying:
- 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
- You may also like feature is added
What makes our code better:
- We do not use external libraries, with that being said, our code will have no to minimal effect to your website's speed performance
- We do not leave or add codes use to advertise for our website
- Our code is mobile friendly
Any issues related to the code will be fix with no additional cost, excluding code customization requests. Simply contact us with "Chat with us." We are just a button away.
To start:
1. Go to Admin store > Themes > Actions > Edit code.
2. Go to Sections folder and create a new section, name it "shopify-ajax-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"> </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"> {{ 'accessibility.unit_price_separator' | t }} </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"> {{ 'accessibility.unit_price_separator' | t }} </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.

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

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 'shopify-ajax-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. (",)
2 comments
Hi @Sanjay,
Line item properties will need to be added to the code. Regarding the selling plan, the code is derive from Dawn cart, if the original code of Dawn supports it then yes.
IS this code support extra line items property and selling plan option ?