Skip to content

Instantly share code, notes, and snippets.

@tahmidbintaslim
Forked from marioloncarek/ajax-cart.js
Created March 11, 2025 03:13
Show Gist options
  • Save tahmidbintaslim/24ad0d5d603f89733c6ba35b60f510d7 to your computer and use it in GitHub Desktop.
Save tahmidbintaslim/24ad0d5d603f89733c6ba35b60f510d7 to your computer and use it in GitHub Desktop.

Revisions

  1. @marioloncarek marioloncarek created this gist Oct 14, 2018.
    478 changes: 478 additions & 0 deletions ajax-cart.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,478 @@
    import {formatMoney} from '@shopify/theme-currency/currency';
    import {Power3, TweenMax} from 'gsap/TweenMax';
    import $ from 'jquery';

    const defaults = {
    cartModal: '.js-ajax-cart-modal', // 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
    addBundleToCart: '.js-ajax-add-bundle-to-cart', // classname
    bundleError: '.js-ajax-bundle-error', // classname
    disabledBundleButton: '.js-ajax-disabled-bundle-button', // classname
    removeFromCart: '.js-ajax-remove-from-cart', // classname
    removeFromCartNoDot: 'js-ajax-remove-from-cart', // classname
    checkoutButton: '.js-ajax-checkout-button', // classname
    bodyClass: 'is-overlay-opened', // classname
    mainContent: '.js-main-content', // classname
    };

    const cartModal = document.querySelector(defaults.cartModal);
    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);
    const addBundleToCart = document.querySelector(defaults.addBundleToCart);
    const bundleError = document.querySelector(defaults.bundleError);
    const disabledBundleButton = document.querySelectorAll(defaults.disabledBundleButton);
    let removeFromCart = document.querySelectorAll(defaults.removeFromCart);
    const checkoutButton = document.querySelector(defaults.checkoutButton);
    const htmlSelector = document.documentElement;
    const mainContent = document.querySelector(defaults.mainContent);

    /**
    *
    * @param {function} callback
    */
    function fetchCart(callback) {
    $.ajax({
    type: 'GET',
    url: '/cart.js',
    dataType: 'json',
    success(cart) {
    onCartUpdate(cart);
    currentCartItemNumber(cart);

    if (cart.item_count === 0) {
    renderBlankCart();
    checkoutButton.classList.add('is-hidden');
    } else {
    renderCart(cart);
    checkoutButton.classList.remove('is-hidden');

    if (typeof callback === 'function') {
    callback();
    }

    }

    },
    error(error) {
    console.log(error);
    ajaxRequestFail();
    },

    });
    }

    /**
    *
    * @param formID
    * @param callback
    */
    function addProductToCart(formID, callback) {
    $.ajax({
    type: 'POST',
    url: '/cart/add.js',
    dataType: 'json',
    data: $(`#${formID}`)
    .serialize(),
    success(cart) {
    if ((typeof callback) === 'function') {
    callback(cart);
    } else {
    addToCartOk();
    }
    },
    error: ajaxRequestFail,
    });
    }

    function addAllItems(string) {

    const queue = [];
    const newArray = string.split(',');

    for (let i = 0; i < newArray.length; i++) {
    const product = newArray[i];
    queue.push({
    serializedForm: product,
    });
    }

    function moveAlong() {
    // If we still have requests in the queue, let's process the next one.
    if (queue.length) {
    const request = queue.shift();
    const data = `${request.serializedForm}`;
    $.ajax({
    type: 'POST',
    url: '/cart/add.js',
    dataType: 'json',
    data,
    success() {
    moveAlong();
    },
    error() {
    // if it's not last one Move Along else update the cart number with the current quantity
    if (queue.length) {
    moveAlong();
    } else {
    console.log('ERROR');
    }
    },
    });
    } else {
    // If the queue is empty, we add 1 to cart
    addToCartOk();
    }
    }

    moveAlong();
    }

    /**
    *
    * @param line
    * @param callback
    */
    function changeItem(line, callback) {
    const quantity = 0;
    $.ajax({
    type: 'POST',
    url: '/cart/change.js',
    data: `quantity=${quantity}&line=${line}`,
    dataType: 'json',
    success(cart) {
    if ((typeof callback) === 'function') {
    callback(cart);
    } else {
    fetchCart();
    }
    },
    error: ajaxRequestFail,
    });
    }

    /**
    *
    */
    addToCart.forEach((item) => {

    item.addEventListener('click', function(event) {

    event.preventDefault();
    const formID = this.parentNode.getAttribute('id');
    console.log(formID);

    addProductToCart(formID);

    });

    });

    /**
    *
    */
    if (addBundleToCart) {

    addBundleToCart.addEventListener('click', (event) => {

    event.preventDefault();

    function isAnyButtonDisabled() {
    return $('.js-ajax-disabled-bundle-button:disabled').length > 0;
    }

    if (isAnyButtonDisabled()) {

    bundleError.innerHTML = 'Error, some of the variants in bundle are sold out. Please chose available variant to add bundle to cart.';

    } else {

    const productForm = document.querySelectorAll('.js-ajax-bundle-form');

    const productFormString = '.js-ajax-bundle-form';

    let concatedFormSerializeString = '';

    for (let i = 1; i <= productForm.length; i++) {

    if (i < (productForm.length)) {
    concatedFormSerializeString += `${$(`${productFormString}-${i}`)
    .serialize()},`;
    } else {
    concatedFormSerializeString += `${$(`${productFormString}-${i}`)
    .serialize()}`;
    }

    }

    console.log(concatedFormSerializeString);

    bundleError.innerHTML = '';

    addAllItems(concatedFormSerializeString);

    }

    });

    }

    /**
    *
    */
    if (disabledBundleButton) {

    disabledBundleButton.forEach((button) => {

    button.addEventListener('click', (event) => {

    event.preventDefault();

    });

    });

    }

    /**
    *
    * @param cart
    */
    function onCartUpdate(cart) {
    console.log('items in the cart?', cart.item_count);
    }

    /**
    *
    * @param cart
    */
    function currentCartItemNumber(cart) {
    cartCounter.innerHTML = cart.item_count;
    }

    /**
    *
    */
    function addToCartOk() {
    fetchAndOpenCart();
    }

    /**
    *
    */
    function fetchAndOpenCart() {

    fetchCart(() => {
    openCartDrawer();
    openCartOverlay();
    });

    }

    /**
    *
    */
    function ajaxRequestFail() {
    openFailModal();
    openCartOverlay();
    }

    /**
    *
    * @param cart
    */
    function renderCart(cart) {

    console.log(cart);

    clearCartDrawer();

    cart.items.forEach((item, index) => {

    const productTitle = `<a href="${item.url}" class="ajax-cart-item__title u-b4">${item.product_title}</a>`;
    const productVariant = `<div class="ajax-cart-item__variant u-b4">${item.variant_title}</div>`;
    const productImage = `<div class="ajax-cart-item__image" style="background-image: url(${item.image});"></div>`;
    const productPrice = `<div class="ajax-cart-item__price u-b2 u-b2--medium">${formatMoney(item.line_price)}</div>`;
    const productQuantity = `<div class="ajax-cart-item__quantity u-b4">Quantity: ${item.quantity}</div>`;
    const productRemove = `<a class="ajax-cart-item__remove u-b4 link--underline ${defaults.removeFromCartNoDot}">Remove</a>`;

    const concatProductInfo = `<div class="ajax-cart-item__single" data-line="${Number(index + 1)}"><div class="ajax-cart-item__info-wrapper">${productImage}<div class="ajax-cart-item__info">${productTitle}${productVariant}${productQuantity}</div></div>${productPrice}${productRemove}</div>`;

    cartDrawerContent.innerHTML += concatProductInfo;

    });

    const cartSubTotal = `<div class="ajax-cart-drawer__subtotal"><span class="u-b2 u-b2--medium">Subtotal: </span><span class="ajax-cart-drawer__subtotal-price u-b2 u-b2--medium">${formatMoney(cart.total_price)}</span></div>`;

    cartDrawerContent.innerHTML += cartSubTotal;

    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 renderBlankCart() {

    console.log('Blank Cart');

    clearCartDrawer();

    const blankCartContent = '<div class="ajax-cart__empty u-a7 u-uppercase">Your Cart is currenty empty!</div>';

    cartDrawerContent.innerHTML = blankCartContent;

    }

    /**
    *
    */
    function openCartDrawer() {

    TweenMax.to(cartDrawer, 0.5, {
    x: -480,
    ease: Power3.easeInOut,
    });

    // TweenMax.to(mainContent, 0.5, {
    // x: -50,
    // ease: Power3.easeInOut,
    // });

    }

    /**
    *
    */
    function closeCartDrawer() {

    TweenMax.to(cartDrawer, 0.5, {
    x: 0,
    ease: Power3.easeInOut,
    });

    // TweenMax.to(mainContent, 0.5, {
    // x: 0,
    // ease: Power3.easeInOut,
    // });

    }

    /**
    *
    */
    function clearCartDrawer() {
    cartDrawerContent.innerHTML = '';
    }

    /**
    *
    */
    function openFailModal() {

    TweenMax.to(cartModal, 0.4, {
    autoAlpha: 1,
    ease: Power3.easeOut,
    });

    }

    /**
    *
    */
    function closeFailModal() {

    TweenMax.to(cartModal, 0.2, {
    autoAlpha: 0,
    ease: Power3.easeOut,
    });

    }

    /**
    *
    */
    function openCartOverlay() {

    htmlSelector.classList.add(defaults.bodyClass);

    TweenMax.to(cartOverlay, 0.5, {
    autoAlpha: 1,
    ease: Power3.easeInOut,
    });

    }

    /**
    *
    */
    function closeCartOverlay() {

    htmlSelector.classList.remove(defaults.bodyClass);

    TweenMax.to(cartOverlay, 0.5, {
    autoAlpha: 0,
    ease: Power3.easeInOut,
    });

    }

    /**
    *
    */
    cartOverlay.addEventListener('click', () => {
    closeFailModal();
    closeCartDrawer();
    closeCartOverlay();
    });

    /**
    *
    */
    cartDrawerClose.addEventListener('click', () => {
    closeCartDrawer();
    closeCartOverlay();
    });

    /**
    *
    */
    cartDrawerTrigger.addEventListener('click', (event) => {
    event.preventDefault();
    openCartDrawer();
    openCartOverlay();
    });

    /**
    *
    */
    cartModalClose.addEventListener('click', () => {
    closeFailModal();
    closeCartDrawer();
    closeCartOverlay();
    });

    /**
    *
    */
    document.addEventListener('DOMContentLoaded', () => {
    fetchCart();
    });
    69 changes: 69 additions & 0 deletions ajax-cart.liquid
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,69 @@
    <!--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">

    <span class="u-b2 u-b2--medium">Something went wrong, please contact us</span>

    </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-wrapper">

    <div class="ajax-cart-drawer__close js-ajax-cart-drawer-close">
    {% include 'icon-close' %}
    </div>

    </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="/checkout/" class="button button--black button--full-width js-button js-ajax-checkout-button">
    <span>Proceed to checkout</span>
    </a>

    <div class="ajax-cart-drawer__cart-button">

    <a href="/cart/" class="link--underline u-b2 u-b2--medium">
    View cart
    </a>

    </div>

    </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-->
    188 changes: 188 additions & 0 deletions ajax-cart.scss
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,188 @@
    $ajax-cart-header-height: 79px;
    $ajax-cart-footer-height: 155px;

    .ajax-cart {

    &__modal {
    @include center(both, fixed);
    z-index: 50;
    width: 100%;
    max-width: 7 * $settings-grid-column-width;
    margin: 0 auto;
    background-color: getColor('white', 'default');
    height: 220px;
    display: flex;
    align-items: center;
    opacity: 0;
    visibility: hidden;
    will-change: opacity, visibility;
    }

    &__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;
    }

    &__drawer {
    position: fixed;
    z-index: 40;
    right: -480px;
    top: 0;
    width: 480px;
    height: 100%;
    background: getColor('white', 'default');
    will-change: transform;
    }

    &__empty {
    border-top: 1px solid getColor('silver', 'secondary');
    text-align: center;
    padding: 40px 0;
    }

    }

    .ajax-cart-modal {
    position: relative;
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;

    &__close {
    position: absolute;
    right: 20px;
    top: 20px;
    line-height: 0;
    cursor: pointer;
    }

    &__content {
    padding: 20px;
    }

    }

    .ajax-cart-drawer {
    position: relative;
    height: 100%;

    &__close-wrapper {
    width: 100%;
    height: $ajax-cart-header-height;
    }

    &__close {
    position: absolute;
    right: 30px;
    top: 30px;
    cursor: pointer;
    line-height: 0;
    font-size: 17px;
    }

    &__content {
    padding: 0 30px;
    overflow: hidden;
    overflow-y: scroll;
    height: calc(100% - #{$ajax-cart-footer-height} - #{$ajax-cart-header-height});
    }

    &__subtotal {
    position: relative;
    margin: 25px 0;
    padding-left: 120px;
    line-height: 0;
    }

    &__subtotal-price {
    position: absolute;
    right: 10px;
    }

    &__buttons {
    width: 100%;
    height: $ajax-cart-footer-height;
    background: getColor('white', 'default');
    padding: 20px 20px 25px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    }

    &__cart-button {
    text-align: center;
    }

    }

    .ajax-cart-item {

    &__single {
    position: relative;
    padding: 15px 0;
    border-bottom: 1px solid getColor('silver', 'secondary');

    &:first-child {
    border-top: 1px solid getColor('silver', 'secondary');
    }

    }

    &__info-wrapper {
    display: flex;
    position: relative;
    }

    &__info {
    padding: 10px 10px 10px 30px;
    max-width: 250px;
    line-height: 1.18;
    }

    &__image {
    width: 90px;
    height: 110px;
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
    }

    &__title {
    vertical-align: top;
    }

    &__variant {
    position: relative;
    top: 5px;
    }

    &__quantity {
    position: absolute;
    bottom: 10px;
    }

    &__price {
    position: absolute;
    bottom: 25px;
    right: 10px;
    }

    &__remove {
    position: absolute;
    right: 10px;
    top: 25px;
    cursor: pointer;
    letter-spacing: 0.2px;
    }

    }
    359 changes: 359 additions & 0 deletions product.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,359 @@
    /**
    * Product Template Script
    * ------------------------------------------------------------------------------
    * A file that contains scripts highly couple code to the Product template.
    *
    * @namespace product
    */

    import $ from 'jquery';
    import Variants from '@shopify/theme-variants';
    import {formatMoney} from '@shopify/theme-currency';
    import {register} from '@shopify/theme-sections';

    const selectors = {
    addToCart: '[data-add-to-cart]',
    addToCartText: '[data-add-to-cart-text]',
    comparePrice: '[data-compare-price]',
    comparePriceText: '[data-compare-text]',
    originalSelectorId: '[data-product-select]',
    priceWrapper: '[data-price-wrapper]',
    productImageWrapper: '[data-product-image-wrapper]',
    productFeaturedImage: '[data-product-featured-image]',
    productJson: '[data-product-json]',
    productPrice: '[data-product-price]',
    productThumbs: '[data-product-single-thumbnail]',
    singleOptionSelector: '[data-single-option-selector]',
    bundleTemplate: '.template-pack-single',
    oosForm: '.js-OOS-form',
    };


    // JAKI
    const $bundleTemplate = $(selectors.bundleTemplate);
    const $oosForm = $(selectors.oosForm);

    if ($bundleTemplate.length > 0) {
    console.log($bundleTemplate);
    } else {
    console.log('ne postoji');
    }

    function isAnySizeOptionSelected() {
    return $('.p-single-product--bundle-single-active .c-size-picker__sizes input[type=radio]:checked').length > 0;
    }

    function isAnyButtonDisabled() {
    return $('.js-ajax-disabled-bundle-button:disabled').length > 0;
    }

    console.log(isAnySizeOptionSelected());
    console.log(isAnyButtonDisabled());

    // JAKI

    const cssClasses = {
    activeThumbnail: 'active-thumbnail',
    hide: 'hide',
    };

    const keyboardKeys = {
    ENTER: 13,
    };

    /**
    * Product section constructor. Runs on page load as well as Theme Editor
    * `section:load` events.
    * @param {string} container - selector for the section container DOM element
    */

    register('product', {
    onLoad() {
    this.$container = $(this.container);
    this.namespace = `.${this.id}`;

    // Stop parsing if we don't have the product json script tag when loading
    // section in the Theme Editor
    if (!$(selectors.productJson, this.$container)
    .html()) {
    return;
    }

    this.productSingleObject = JSON.parse(
    $(selectors.productJson, this.$container)
    .html(),
    );

    const options = {
    $container: this.$container,
    enableHistoryState: this.$container.data('enable-history-state') || false,
    singleOptionSelector: selectors.singleOptionSelector,
    originalSelectorId: selectors.originalSelectorId,
    product: this.productSingleObject,
    };

    this.settings = {};
    this.variants = new Variants(options);
    this.$featuredImage = $(selectors.productFeaturedImage, this.$container);

    this.$container.on(
    `variantChange${this.namespace}`,
    this.updateAddToCartState.bind(this),
    );
    this.$container.on(
    `variantPriceChange${this.namespace}`,
    this.updateProductPrices.bind(this),
    );

    if (this.$featuredImage.length > 0) {
    this.$container.on(
    `variantImageChange${this.namespace}`,
    this.updateImages.bind(this),
    );
    }

    this.initImageSwitch();
    },

    initImageSwitch() {
    const $productThumbs = $(selectors.productThumbs, this.$container);

    if (!$productThumbs.length) {
    return;
    }

    $productThumbs
    .on('click', (evt) => {
    evt.preventDefault();
    const imageId = $(evt.currentTarget)
    .data('thumbnail-id');
    this.switchImage(imageId);
    this.setActiveThumbnail(imageId);
    })
    .on('keyup', this.handleImageFocus.bind(this));
    },

    handleImageFocus(evt) {
    if (evt.keyCode !== keyboardKeys.ENTER) {
    return;
    }

    this.$featuredImage.filter(':visible')
    .focus();
    },

    setActiveThumbnail(imageId) {
    let newImageId = imageId;

    // If "imageId" is not defined in the function parameter, find it by the current product image
    if (typeof newImageId === 'undefined') {
    newImageId = $(
    `${selectors.productImageWrapper}:not('.${cssClasses.hide}')`,
    )
    .data('image-id');
    }

    const $thumbnail = $(
    `${selectors.productThumbs}[data-thumbnail-id='${newImageId}']`,
    );

    $(selectors.productThumbs)
    .removeClass(cssClasses.activeThumbnail)
    .removeAttr('aria-current');

    $thumbnail.addClass(cssClasses.activeThumbnail);
    $thumbnail.attr('aria-current', true);
    },

    switchImage(imageId) {
    const $newImage = $(
    `${selectors.productImageWrapper}[data-image-id='${imageId}']`,
    this.$container,
    );
    const $otherImages = $(
    `${selectors.productImageWrapper}:not([data-image-id='${imageId}'])`,
    this.$container,
    );
    $newImage.removeClass(cssClasses.hide);
    $otherImages.addClass(cssClasses.hide);
    },

    /**
    * Updates the DOM state of the add to cart button
    *
    * @param {boolean} enabled - Decides whether cart is enabled or disabled
    * @param {string} text - Updates the text notification content of the cart
    */
    updateAddToCartState(evt) {
    const variant = evt.variant;

    console.log(isAnySizeOptionSelected());

    if ($bundleTemplate.length > 0) {

    if (variant) {
    $(selectors.priceWrapper, this.$container)
    .removeClass(cssClasses.hide);
    } else {
    $(selectors.addToCart, this.$container)
    .prop('disabled', true);
    $(selectors.addToCartText, this.$container)
    .html(
    theme.strings.unavailable,
    );
    $(selectors.priceWrapper, this.$container)
    .addClass(cssClasses.hide);
    return;
    }

    if (isAnySizeOptionSelected()) {

    if (variant.available) {
    $(selectors.addToCart, this.$container)
    .prop('disabled', false);

    if ($(this.$container)
    .hasClass('p-single-product--bundle-single-last-step') && isAnySizeOptionSelected()) {
    $(selectors.addToCartText, this.$container)
    .html('Finish and add to cart');
    } else {
    $(selectors.addToCartText, this.$container)
    .html('Proceed to next step');
    }
    $(this.container)
    .find('.js-button')
    .on('click', () => {

    if ($(this.$container)
    .hasClass('p-single-product--bundle-single-step-done')) {
    return;
    } else {

    $(this.$container)
    .next()
    .next()
    .addClass('p-single-product--bundle-single-active')
    .removeClass('p-single-product--bundle-single-disabled')
    .find($(selectors.addToCartText))
    .html('Select your size');
    $(this.$container)
    .removeClass('p-single-product--bundle-single-active');
    $(this.$container)
    .addClass('p-single-product--bundle-single-step-done');

    }

    });
    } else {
    $(selectors.addToCart, this.$container)
    .prop('disabled', true);
    $(selectors.addToCartText, this.$container)
    .html(theme.strings.soldOut);
    }

    } else if ($(this.$container)
    .hasClass('p-single-product--bundle-single-step-done')) {
    if (variant.available) {
    $(selectors.addToCart, this.$container)
    .prop('disabled', false);
    $(selectors.addToCartText, this.$container)
    .html('Proceed to next step');
    } else {
    $(selectors.addToCart, this.$container)
    .prop('disabled', true);
    $(selectors.addToCartText, this.$container)
    .html(theme.strings.soldOut);
    }
    } else if (variant.available) {
    $(selectors.addToCart, this.$container)
    .prop('disabled', true);
    $(selectors.addToCartText, this.$container)
    .html('Select your size');
    } else {
    $(selectors.addToCart, this.$container)
    .prop('disabled', true);
    $(selectors.addToCartText, this.$container)
    .html('Select your size');
    }

    } else {

    if (variant) {
    $(selectors.priceWrapper, this.$container)
    .removeClass(cssClasses.hide);
    } else {
    $(selectors.addToCart, this.$container)
    .prop('disabled', true);
    $(selectors.addToCartText, this.$container)
    .html(
    theme.strings.unavailable,
    );
    $(selectors.priceWrapper, this.$container)
    .addClass(cssClasses.hide);
    return;
    }

    if (variant.available) {
    $(selectors.addToCart, this.$container)
    .prop('disabled', false);
    $(selectors.addToCartText, this.$container)
    .html(theme.strings.addToCart);
    $($oosForm)
    .removeClass('is-visible');
    } else {
    $(selectors.addToCart, this.$container)
    .prop('disabled', true);
    $(selectors.addToCartText, this.$container)
    .html(theme.strings.soldOut);
    $($oosForm)
    .addClass('is-visible');
    }

    }
    },

    updateImages(evt) {
    const variant = evt.variant;
    const imageId = variant.featured_image.id;

    this.switchImage(imageId);
    this.setActiveThumbnail(imageId);
    },

    /**
    * Updates the DOM with specified prices
    *
    * @param {string} productPrice - The current price of the product
    * @param {string} comparePrice - The original price of the product
    */
    updateProductPrices(evt) {
    const variant = evt.variant;
    const $comparePrice = $(selectors.comparePrice, this.$container);
    const $compareEls = $comparePrice.add(
    selectors.comparePriceText,
    this.$container,
    );

    $(selectors.productPrice, this.$container)
    .html(
    formatMoney(variant.price, theme.moneyFormat),
    );

    if (variant.compare_at_price > variant.price) {
    $comparePrice.html(
    formatMoney(variant.compare_at_price, theme.moneyFormat),
    );
    $compareEls.removeClass(cssClasses.hide);
    } else {
    $comparePrice.html('');
    $compareEls.addClass(cssClasses.hide);
    }
    },

    /**
    * Event callback for Theme Editor `section:unload` event
    */
    onUnload() {
    this.$container.off(this.namespace);
    },
    });
    434 changes: 434 additions & 0 deletions single-bundle-product.liquid
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,434 @@
    <div data-section-id="{{ section.id }}" data-section-type="product" data-enable-history-state="true"
    class="{% if forloop.first == true %}p-single-product--bundle-single-active{% endif %} {% unless forloop.first == true %}p-single-product--bundle-single-disabled{% endunless %} {% if forloop.last == true %}p-single-product--bundle-single-last-step{% endif %} js-scroll-to-{{ forloop.index }}">

    <section class="p-single-product p-single-product--bundle">

    <!--single product slideshow and config-->
    <div class="p-single-product__wrapper">

    <div class="c-product-showcase c-product-showcase--bundle js-product-showcase">

    <div class="c-product-showcase__main">
    <div class="o-row o-row--md-fixed">
    <div class="c-product-showcase__mobile-top-info">
    <div class="c-product-config c-product-config--condensed">
    <div class="c-product-config__top-wrap">
    <div class="c-product-config__title-wrap">
    <h2 class="u-a7 u-uppercase">

    {% assign pack_product_title = product.title %}
    {% assign stringSplit = pack_product_title | split:'-' %}

    {% for word in stringSplit %}

    {% unless forloop.first == true %}
    {{ word | strip }}
    {% endunless %}

    {% endfor %}

    </h2>
    </div>
    </div>
    </div>
    </div>
    </div>

    {%- comment -%} data-product-showcase-id must be unique number {%- endcomment -%}
    <div class="c-product-showcase__preview c-product-preview js-product-showcase-preview"
    data-product-showcase-id="{{ product-loop-index }}">

    {%- assign current_variant = product.selected_or_first_available_variant -%}
    {%- assign featured_image = current_variant.featured_image | default: product.featured_image -%}

    <div class="c-product-preview__main-images">
    {% for image in product.images %}
    {% capture wrapper_class %}
    {%- unless featured_image == image -%}
    hide
    {%- endunless -%}
    {% endcapture %}

    {% include 'responsive-image' with
    image: image,
    image_class: 'c-product-preview__big-image'
    max_width: 700,
    max_height: 800,
    image_attributes: "data-product-featured-image",
    wrapper_class: wrapper_class,
    wrapper_attributes: "data-product-image-wrapper", %}
    {% endfor %}
    </div>

    {% if product.images.size > 1 %}

    <div class="c-product-preview__product-list-group">

    {% for image in product.images %}

    {% if image.variants.size > 0 and forloop.first == true %}
    {% assign current_color = image.variants[0].option1 %}
    <ul class="c-product-preview__product-list js-product-preview-thumbnail-list is-current" data-color="{{ current_color | handleize }}">
    {% endif %}

    {% if image.variants.size > 0 and image.variants[0].option1 != current_color and forloop.last == false %}
    </ul>
    {% assign current_color = image.variants[0].option1 %}
    <ul class="c-product-preview__product-list js-product-preview-thumbnail-list" data-color="{{ current_color | handleize }}">
    {% endif %}

    <li class="c-product-preview__product-list-item">
    <a class="c-product-preview__product-link js-product-showcase-preview-link {% if forloop.first == true %} active-thumbnail {% endif %}"
    href="{{ image.src | img_url: '480x480' }}"
    data-color="{{ current_color | handleize }}"
    data-variant="{{ image.variants[0].option1 }}"
    data-thumbnail-id="{{ image.id }}"
    data-product-single-thumbnail>
    <img class="product-single__thumbnail-image c-product-preview__small-image"
    src="{{ image.src | img_url: '160x160' }}"
    alt="{{ image.alt | escape }}">
    </a>
    </li>

    {% if forloop.last == true %}
    </ul>
    {% endif %}

    {% endfor %}

    </div>

    {% endif %}

    </div>

    <div class="js-product-showcase-sliders-group c-product-showcase__sliders-group"
    data-product-showcase-id="{{ product-loop-index }}">

    {% if product.images.size > 1 %}

    {% for image in product.images %}

    {% if image.variants.size > 0 and forloop.first == true %}
    {% assign current_color = image.variants[0].option1 %}
    <div class="c-preview-slider c-slider js-preview-slider-component is-current" data-color="{{ current_color | handleize }}">
    <div class="c-preview-slider__slider-container swiper-container js-preview-slider-container">
    <div class="c-slider__wrapper swiper-wrapper">
    {% endif %}

    {% if image.variants.size > 0 and image.variants[0].option1 != current_color and forloop.last == false %}
    </div><!-- .c-slider__wrapper -->
    </div><!-- .c-preview-slider__slider-container -->

    <div class="c-preview-slider__bullets js-slider-bullets">

    </div><!-- .c-preview-slider__bullets -->
    </div><!-- .c-preview-slider -->

    {% assign current_color = image.variants[0].option1 %}
    <div class="c-preview-slider c-slider js-product-showcase-preview-slider js-preview-slider-component" data-color="{{ current_color | handleize }}">
    <div class="c-preview-slider__slider-container swiper-container js-preview-slider-container">
    <div class="c-slider__wrapper swiper-wrapper">
    {% endif %}

    <div class="c-slider__item swiper-slide">
    <div class="c-slider__image js-slide-image"
    style="background-image: url('{{ image.src | img_url: '480x480' }}')"></div>
    </div><!-- .c-slider__item -->


    {% if forloop.last == true %}
    </div><!-- .c-slider__wrapper -->
    </div><!-- .c-preview-slider__slider-container -->

    <div class="c-preview-slider__bullets js-slider-bullets">
    </div><!-- .c-preview-slider__bullets -->

    </div><!-- .c-preview-slider -->
    {% endif %}

    {% endfor %}

    {% endif %}

    </div><!-- .js-product-showcase-sliders-group -->

    </div>

    <div class="c-product-showcase__side">
    <div class="p-single-product__config p-single-product__config--bundle">
    {% assign color = 'black' %}
    {% assign mobileTitleHidden = true %}
    {% assign hideReviews = true %}
    {% assign firstChunk = 'data-product-showcase-id="' %}
    {% assign lastChunk = '"' %}
    {% assign colorPickerDataAttributes = firstChunk | append: product-loop-index | append: lastChunk %}

    <div class="c-product-config {% if mobileTitleHidden %} c-product-config--mobile-title-hidden {% endif %}">

    <div class="c-product-config__top-wrap">

    <div class="c-product-config__title-wrap c-product-config__title-wrap--bundle">

    <!--bundle info-->
    <div class="c-product-config__bundle-info">

    <!--bundle step-->
    <div class="c-product-config__bundle-step u-b4">
    step 0{{ product-loop-index }} / 0{{ product-loop-length }}
    </div>
    <!--end bundle step-->

    <!--bundle price-->
    <div class="c-product-config__bundle-price u-b4">
    Clinton Pack $75
    </div>
    <!--end bundle price-->

    </div>
    <!--end bundle info-->

    <h2 class="u-a7 u-uppercase">

    <a href="{{ product.url | within: collection }}">

    {% assign pack_product_title = product.title %}
    {% assign stringSplit = pack_product_title | split:'-' %}

    {% for word in stringSplit %}

    {% unless forloop.first == true %}
    {{ word | strip }}
    {% endunless %}

    {% endfor %}

    </a>

    </h2>

    </div>

    </div>

    <!--product form-->
    <form action="/cart/add" method="post" enctype="multipart/form-data"
    class="js-ajax-bundle-form js-ajax-bundle-form-{{ product-loop-index }}"
    data-product-id="{{ product.selected_or_first_available_variant.id }}">

    <!--product variants-->
    {% unless product.has_only_default_variant %}

    {% for option in product.options_with_values %}

    {% assign option-name = option.name | downcase %}

    {% assign is_color = false %}

    {% assign stripped-value = value | split: ' ' | last | handle %}

    {% assign product-handle = product.handle %}

    {% if option-name contains 'color' or option-name contains 'colour' %}

    {% assign is_color = true %}

    {% endif %}

    {% if is_color %}

    <div class="c-product-config__color-picker">
    {% include "color-picker" with option-name, stripped-value, product-handle %}
    </div>

    {% endif %}

    {% assign is_size = false %}

    {% if option-name contains 'size' %}

    {% assign is_size = true %}

    {% endif %}

    {% if is_size %}

    <div class="c-product-config__size-picker">
    {% include "size-picker-bundle" with option-name, stripped-value, product-handle %}
    </div>

    {% endif %}

    {% 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-->

    {% if forloop.last == true %}

    <!--product add to cart-->
    <button
    class="button button--black button--full-width c-product-config__add-to-cart js-button js-ajax-add-bundle-to-cart"
    type="submit"
    name="add"
    data-add-to-cart
    disabled="disabled">
    <span data-add-to-cart-text>
    {% if current_variant.available %}
    Complete previous step
    {% else %}
    {{ 'products.product.sold_out' | t }}
    {% endif %}
    </span>

    </button>
    <!--end product add to cart-->

    <div class="c-form-errors js-ajax-bundle-error"></div>

    {% elsif forloop.first == true %}

    <!--product next step-->
    <button
    class="button button--black button--full-width c-product-config__add-to-cart js-button js-ajax-disabled-bundle-button"
    type="submit"
    name="add"
    data-scroll-to="on"
    data-scroll-to-target=".js-scroll-to-{{ forloop.index | plus:1 }}"
    data-add-to-cart
    disabled="disabled">
    <span data-add-to-cart-text>
    {% if current_variant.available %}
    Select your size
    {% else %}
    {{ 'products.product.sold_out' | t }}
    {% endif %}
    </span>

    </button>
    <!--end product next step-->

    {% else %}

    <!--product next step-->
    <button
    class="button button--black button--full-width c-product-config__add-to-cart js-button js-ajax-disabled-bundle-button"
    type="submit"
    name="add"
    data-scroll-to="on"
    data-scroll-to-target=".js-scroll-to-{{ forloop.index | plus:1 }}"
    data-add-to-cart
    disabled="disabled">
    <span data-add-to-cart-text>
    {% if current_variant.available %}
    Complete previous step
    {% else %}
    {{ 'products.product.sold_out' | t }}
    {% endif %}
    </span>

    </button>
    <!--end product next step-->

    {% endif %}

    </form>
    <!--end product form-->

    <p class="c-product-config__additional-info u-b4">
    Free shipping on all domestic orders
    </p>

    </div>


    </div>
    </div>

    </div><!-- .c-product-showcase -->

    </div>
    <!--end single product slideshow and config-->

    {% unless product == empty %}
    <script type="application/json" data-product-json>{{ product | json }}</script>
    {% endunless %}

    </section>


    </div>

    <script type="application/ld+json">
    {
    "@context": "http://schema.org/",
    "@type": "Product",
    "name": "{{ product.title | escape }}",
    "url": "{{ shop.url }}{{ product.url }}",
    {% if product.featured_image %}
    {% assign image_size = product.featured_image.width | append: 'x' %}
    "image": [
    "https:{{ product.featured_image.src | img_url: image_size }}"
    ],
    {% endif %}
    "description": "{{ product.description | strip_html | escape }}",
    {% if current_variant.sku != blank %}
    "sku": "{{ current_variant.sku }}",
    {% endif %}
    "brand": {
    "@type": "Thing",
    "name": "{{ product.vendor | escape }}"
    },
    {% if product.variants %}
    "offers": [
    {% for variant in product.variants %}
    {
    "@type" : "Offer",
    "availability" : "http://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
    "price" : "{{ variant.price | divided_by: 100.00 }}",
    "priceCurrency" : "{{ shop.currency }}",
    "url" : "{{ shop.url }}{{ variant.url }}",
    "itemOffered" :
    {
    "@type" : "Product",
    {% if variant.image %}
    {% assign variant_image_size = variant.image.width | append: 'x' %}
    "image": "http:{{ variant.image.src | img_url: variant_image_size }}",
    {% endif %}
    {% if variant.title != blank %}
    "name" : "{{ variant.title | escape }}",
    {% endif %}
    {% if variant.sku != blank %}
    "sku": "{{ variant.sku }}",
    {% endif %}
    {% if variant.weight != blank %}
    "weight": {
    "@type": "QuantitativeValue",
    {% if variant.weight_unit != blank %}
    "unitCode": "{{ variant.weight_unit }}",
    {% endif %}
    "value": "{{ variant.weight | weight_with_unit: variant.weight_unit }}"
    },
    {% endif %}
    "url": "{{ shop.url }}{{ variant.url }}"
    }
    }{% unless forloop.last %},{% endunless %}
    {% endfor %}
    ]
    {% endif %}
    }
    </script>
    27 changes: 27 additions & 0 deletions size-picker-bundle.liquid
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,27 @@
    <div class="c-size-picker js-size-picker {% if color == 'white' %} c-size-picker--white {% endif %}">

    <div class="c-size-picker__sizes u-b2">

    {% for value in option.values %}

    <input type="radio" name="{{ option-name }}-{{ product-handle }}"
    class="c-size-picker__radio-btn js-size-picker-radio"
    data-single-option-selector
    data-index="option{{ option.position }}"
    value="{{ value | escape }}"
    id="variant_{{ option-name }}-{{ product-handle }}-{{ forloop.index0 }}"/>

    <label for="variant_{{ option-name }}-{{ product-handle }}-{{ forloop.index0 }}"
    class="c-size-picker__radio-label u-align-center">
    {{ value }}
    </label>

    {% endfor %}

    </div>

    <div class="c-size-picker__calculate-wrap">
    <span class="c-size-picker__calculate u-b5 js-modal-trigger">Calculate Size</span>
    </div>

    </div>