Search code examples
javascriptshopifyvariant

Dynamically pulling Variant id via Javascript using Shopify


I've been working on a common request with Shopify stores to "Add to cart button" for a store using the "Debut" theme. The goal is to have the cart update automatically when the "add to cart button" is clicked without jumping to the cart page keeping the customer on the page for ease of convenience and such.

Now usually this isn't much of a problem as there is a lot of source material on the subject through out the net. I can typically "google my way to freedom"

Here are some links I've referenced to get to this point

https://ecommerce.shopify.com/c/ecommerce-design/t/adding-an-add-to-cart-button-to-collection-pages-in-debut-theme-433551

https://help.shopify.com/themes/customization/products/add-to-cart/stay-on-product-page-when-items-added-to-cart

The add to cart button works fine when the following code is added to the product-card-grid.liquid

  <form method="post" action="/cart/add">
   <input type="hidden" name="id" value="{{ product.variants.first.id }}" />
   <input min="1" type="number" id="quantity" name="quantity" value="1"/>
   <input type="submit" value="Add to cart" class="btn" />
  </form> 

THE PROBLEM:

The issue I'm running into though is that my collection page uses a sidebar filter. And the code works everywhere but on the collection pages. Through some digging and a little inspection I discovered that it's probably because the products in the sidebar/collection page are populated from some code in theme.js file.

Now this is where I'm definitely just flying blind, Javascript is something I'd like to get more familiar with, but as with everything it takes time.

So... What I've noticed by perusing the theme.js file is that html can be added to js if formatted correctly. And I was able to insert it and get the " add to cart" button to show up in my sidebar/collection page. A small victory! But doesn't quite work.

Collection Page Screenshot

When the "add to cart" button is clicked it I get the following error

"array contains unpermitted members: id"

Array contains... Error pic

Here's a bit of the code from the theme.js file that I'm hoping can be modified

    function runFilter() {
              typeFilter(typebxs);
              vendorFilter(vendorbxs);
              tagsFilter(tagbxs);
              optionsFilter(optionsbxs);
              colorFilter(colorbxs);
              sizeFilter(sizebxs);
              var has_price_filter = document.getElementById("sidebar-product-price").innerHTML;
              if ( has_price_filter == "true" ) { 
                priceFilter(pricebxs); 
              } else { 
                passedpricefiltertest = "true"; 
              }
              if ( passedtagfiltertest == "true"
                  && passedtypefiltertest == "true"
                  && passedpricefiltertest == "true"
                  && passedvendorfiltertest == "true"
                  && passedoptionsfiltertest == "true"
                  && passedcolorfiltertest == "true"
                  && passedsizefiltertest == "true" ) { 
                var money_sign = document.getElementById("money-sign").innerHTML; money_sign = money_sign.replace("0.00", "");
                if ( varaints_min_price != varaints_max_price ) { var price_string = money_sign + parseFloat((varaints_min_price)).toFixed(2) + "+" } else { var price_string = money_sign + parseFloat((varaints_min_price)).toFixed(2); }
                var product_url = collection_url +"products/"+ val.handle;
                var product_images_ratio = document.getElementById("product_images_ratio").innerHTML;
                var product_image_url =  val.images[0].src.replace(".jpg", "_600x600.jpg");
                product_image_url =  product_image_url.replace(".png", "_600x600.png");
                if (val.variants[0].available == true) { var sold_out = ''; } else { var sold_out = ' product-price--sold-out grid-view-item--sold-out'; }
                var string1 = '<div class="grid__item grid__item--' + sectionid + ' ' + grid_item_width + '"><div class="grid-view-item' + sold_out + '" data-section-id="section.id" data-section-type="collection-template"><div class="product-container"><div class="collection-product-overlay"><a class="grid-view-item__link" href="' + product_url + '">';
                if (product_images_ratio == "none") { var string2 = '<div style="display:none;" id="product-image-align">false</div><div style="display:none;" id="product-image-ratio">' + product_images_ratio + '</div><img class="grid-view-item__image" src="' + product_image_url + '" alt="">'; } else { var string2 = '<div class="image-bar__item box ratio-container lazyload product-grid-image"data-bgset="' + product_image_url + '" data-sizes="auto" data-parent-fit="contain" style="  background-size: contain;    background-color: transparent; background-attached:fixed; background-position: center; background-image: url("' + product_image_url + '");"></div><div style="display:none;" id="product-image-align">true</div><div style="display:none;" id="product-image-ratio">' + product_images_ratio + '</div>'; }
                if (show_vendor == "true") { var string3 = '<div style="" class="h4 grid-view-item__title collection-title_wrapper">' + val.title + '</div><div class="grid-view-item__vendor collection-title_wrapper">' + val.vendor + '</div>'; } else { var string3 = '<div style="" class="h4 grid-view-item__title collection-title_wrapper">' + val.title + '</div>'; }
                if ( val.variants[0].compare_at_price > val.variants[0].price ) { var string4 = '<div class="grid-view-item__meta collection-title_wrapper"><span class="visually-hidden">Regular price</span><s class="product-price__price" style="">' + money_sign + val.compare_at_price + '</s><span class="product-price__price product-price__sale" style="">' + price_string + '<span class="product-price__sale-label">Sale</span></span>'; } else { var string4 = '<div class="grid-view-item__meta collection-title_wrapper"><span class="product-price__price" style="">' + price_string + '</span>' + '<form  class="addtocart"method="post" action="/cart/add"><input type="hidden" name="id"  value="quantity" /><input min="1" type="number" id="{{ product.variants.first.id }}" name="quantity" value="1"/><input type="submit" value="Add to cart" class="btn" /></form>' ; }    
                if ( val.variants[0].available == true ) { var string5 = '</div></a></div></div></div></div>'; } else { var string5 = '<span class="product-price__sold-out" style="">Sold out</span></div></a></div></div></div>'; }    
                var string = string1 + string2 + string3 + string4 + string5;

                filteredProducts.push(val.title+val.id);
                productGrid[val.title+val.id] = [string];
                if (total_json_pages == counter2) {
                  if (counter3 == collectionLength) { 
                    circle.animate(1,{
                      duration: (100)
                    });


                                            debouncedPagintor();


                  }
                }
                if ( product_images_ratio != "none" ) {
                  var width = $( ".product-grid-image" ).width();
                  width = parseInt(width) * parseFloat(product_images_ratio) ;
                  $( ".product-grid-image" ).css( "height", width );
                }
              } else { 
                if (total_json_pages == counter2) { 
                  if (counter3 == collectionLength) { 


                                            debouncedPagintor();



                  }
                } 
              }
            }

            runFilter();

          }
        }


        $('.filter-update ~ .lbl, .filter-update ~ .cbx').unbind("click");
        $('.filter-update ~ .lbl, .filter-update ~ .cbx').on( "click", function() {
          debouncedfilterIt();
        });
      }

      var debouncedfilterIt = function() {
        filterIt();
      }
      var debouncedfilterIt = $.debounce( 250, false, debouncedfilterIt );
      var needsFilter = document.getElementById('sb-master-input-format-1');           

      function grabCollection() {  
   var show_vendor = document.getElementById("show_vendor").innerHTML;
   var grid_item_width = document.getElementById("grid_item_width").innerHTML;
   var sectionid = sectionId;
   var money_sign = document.getElementById("money-sign").innerHTML;
   money_sign = money_sign.replace("0.00", "");
   var filteredProducts = [];
   var jsonDataString = "";
   var jsonData = {};



   if (masterString[jsonsrc + "1"] == undefined) {
     if ( total_json_pages > 2 ) { 
       $( "#progress" ).show(); 
       circle.animate(1,{
duration: ((total_json_pages)*200)

});

You can see where I've inserted the html to load the button on the collection page. The only modification I've made is to insert the bit of html that's wrapped in the "form" tag.

I believe the bit in question would most likely found in the particularly long line of code

from that file

if ( val.variants[0].compare_at_price > val.variants[0].price ) { var string4 = '<div class="grid-view-item__meta collection-title_wrapper"><span class="visually-hidden">Regular price</span><s class="product-price__price" style="">' + money_sign + val.compare_at_price + '</s><span class="product-price__price product-price__sale" style="">' + price_string + '<span class="product-price__sale-label">Sale</span></span>'; } else { var string4 = '<div class="grid-view-item__meta collection-title_wrapper"><span class="product-price__price" style="">' + price_string + '</span>' + '<form  class="addtocart"method="post" action="/cart/add"><input type="hidden" name="id"  value="quantity" /><input min="1" type="number" id="{{ product.variants.first.id }}" name="quantity" value="1"/><input type="submit" value="Add to cart" class="btn" /></form>' ; }

This is my first time posting. I apologize if I missed an easier way to format this for the this forum.

I'm hoping this is enough though. Am I almost there? Is this a simple fix? Not enough info?

Thanks for your time! I hope this is thorough enough and not too convoluted.

Sincerely

Jeff J

EDIT:

I'm almost there! (Thanks Darshit Patel!) changing "{{ product.variants.first.id }}" to "'+ val.variants[0].id +'" in the theme.js got rid of the "array contains unpermitted members: id" error and updates the cart but it takes me to the cart page and doesn't seem interact with the ajaxify-cart.liquid

here's the entire ajaxify-cart.liquid code

<script>

/**
 * Module to ajaxify all add to cart forms on the page.
 *
 * Copyright (c) 2015 Caroline Schnapp (11heavens.com)
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 */
Shopify.AjaxifyCart = (function($) {

  // Some configuration options.
  // I have separated what you will never need to change from what
  // you might change.

  var _config = {

    // What you might want to change
    addToCartBtnLabel:             'Add to cart',
    addedToCartBtnLabel:           'Thank you!',
    addingToCartBtnLabel:          'Adding...',
    soldOutBtnLabel:               'Sold Out',
    howLongTillBtnReturnsToNormal: 1000, // in milliseconds.
    cartCountSelector:             '#CartCount, .cart-count, #cart-count a:first, #gocart p a, #cart .checkout em, .item-count',
    cartTotalSelector:             '#cart-price',
    // 'aboveForm' for top of add to cart form, 
    // 'belowForm' for below the add to cart form, and 
    // 'nextButton' for next to add to cart button.
    feedbackPosition:              'nextButton',

    // What you will never need to change
    addToCartBtnSelector:          '[type="submit"]',
    addToCartFormSelector:         'form[action="/cart/add"]',
    shopifyAjaxAddURL:             '/cart/add.js',
    shopifyAjaxCartURL:            '/cart.js'
  };

  // We need some feedback when adding an item to the cart.
  // Here it is.  
  var _showFeedback = function(success, html, $addToCartForm) {
    $('.ajaxified-cart-feedback').remove();
    var feedback = '<p class="ajaxified-cart-feedback ' + success + '">' + html + '</p>';
    switch (_config.feedbackPosition) {
      case 'aboveForm':
        $addToCartForm.before(feedback);
        break;
      case 'belowForm':
        $addToCartForm.after(feedback);
        break;
      case 'nextButton':
      default:
        $addToCartForm.find(_config.addToCartBtnSelector).after(feedback);
        break;   
    }
    // If you use animate.css
    // $('.ajaxified-cart-feedback').addClass('animated bounceInDown');
    $('.ajaxified-cart-feedback').slideDown();
  };
  var _setText = function($button, label) {
    if ($button.children().length) {
      $button.children().each(function() {
        if ($.trim($(this).text()) !== '') {
          $(this).text(label);
        }
      });
    }
    else {
      $button.val(label).text(label);
    }
  };
  var _init = function() {   
    $(document).ready(function() { 
      $(_config.addToCartFormSelector).submit(function(e) {
        e.preventDefault();
        var $addToCartForm = $(this);
        var $addToCartBtn = $addToCartForm.find(_config.addToCartBtnSelector);
        _setText($addToCartBtn, _config.addingToCartBtnLabel);
        $addToCartBtn.addClass('disabled').prop('disabled', true);
        // Add to cart.
        $.ajax({
          url: _config.shopifyAjaxAddURL,
          dataType: 'json',
          type: 'post',
          data: $addToCartForm.serialize(),
          success: function(itemData) {
            // Re-enable add to cart button.
            $addToCartBtn.addClass('inverted');
            _setText($addToCartBtn, _config.addedToCartBtnLabel);
            _showFeedback('success','<i class="fa fa-check"></i> Added to cart! <a href="/cart">View cart</a> or <a href="/collections/all">continue shopping</a>.',$addToCartForm);
            window.setTimeout(function(){
              $addToCartBtn.prop('disabled', false).removeClass('disabled').removeClass('inverted');
              _setText($addToCartBtn,_config.addToCartBtnLabel);
            }, _config.howLongTillBtnReturnsToNormal);
            // Update cart count and show cart link.
            $.getJSON(_config.shopifyAjaxCartURL, function(cart) {
              if (_config.cartCountSelector && $(_config.cartCountSelector).size()) {
                var value = $(_config.cartCountSelector).html() || '0';
                $(_config.cartCountSelector).html(value.replace(/[0-9]+/,cart.item_count)).removeClass('hidden-count');
              }
              if (_config.cartTotalSelector && $(_config.cartTotalSelector).size()) {
                if (typeof Currency !== 'undefined' && typeof Currency.moneyFormats !== 'undefined') {
                  var newCurrency = '';
                  if ($('[name="currencies"]').size()) {
                    newCurrency = $('[name="currencies"]').val();
                  }
                  else if ($('#currencies span.selected').size()) {
                    newCurrency = $('#currencies span.selected').attr('data-currency');
                  }
                  if (newCurrency) {
                    $(_config.cartTotalSelector).html('<span class=money>' + Shopify.formatMoney(Currency.convert(cart.total_price, "{{ shop.currency }}", newCurrency), Currency.money_format[newCurrency]) + '</span>');
                  } 
                  else {
                    $(_config.cartTotalSelector).html(Shopify.formatMoney(cart.total_price, "{{ shop.money_format | remove: "'" | remove: '"' }}"));
                  }
                }
                else {
                  $(_config.cartTotalSelector).html(Shopify.formatMoney(cart.total_price, "{{ shop.money_format | remove: "'" | remove: '"' }}"));
                }
              };
            });        
          }, 
          error: function(XMLHttpRequest) {
            var response = eval('(' + XMLHttpRequest.responseText + ')');
            response = response.description;
            if (response.slice(0,4) === 'All ') {
              _showFeedback('error', response.replace('All 1 ', 'All '), $addToCartForm);
              $addToCartBtn.prop('disabled', false);
              _setText($addToCartBtn, _config.soldOutBtnLabel);
              $addToCartBtn.prop('disabled',true);
            }
            else {
              _showFeedback('error', '<i class="fa fa-warning"></i> ' + response, $addToCartForm);
              $addToCartBtn.prop('disabled', false).removeClass('disabled');
              _setText($addToCartBtn, _config.addToCartBtnLabel);
            }
          }
        });   
        return false;    
      });
    });
  };
  return {
    init: function(params) {
        // Configuration
        params = params || {};
        // Merging with defaults.
        $.extend(_config, params);
        // Action
        $(function() {
          _init();
        });
    },    
    getConfig: function() {
      return _config;
    }
  }  
})(jQuery);

Shopify.AjaxifyCart.init();

</script>

{% comment %}
  If you want to animate your feedback message.
{% endcomment %}

{% comment %}
{{ '//cdnjs.cloudflare.com/ajax/libs/animate.css/3.1.0/animate.min.css' | stylesheet_tag }}
{% endcomment %}

{{ '//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.0.3/css/font-awesome.css' | stylesheet_tag }}

<style>
.ajaxified-cart-feedback {
  display: block;
  line-height: 36px;
  font-size: 90%;
  vertical-align: middle;
}
.ajaxified-cart-feedback.success { 
  color: #3D9970;
}
.ajaxified-cart-feedback.error { 
  color: #FF4136; 
} 
.ajaxified-cart-feedback a {
  border-bottom: 1px solid;
}
</style>

I'm wondering if there's a simple solution for this. I'm at a loss. I would make this a new question, but it really seems like a "two parter" at this point, as it relates to all the info I've shared.

Again, thanks for your time

Cheers!


Solution

  • This line of code 'id="{{ product.variants.first.id }}"' from theme.js is the exact issue.When you add this line in any .js file it will not render in liquid but it will render it as a String. Theme.js is simple .js file not a liquid so this liquid line will be render as String. You need to change that line and your problem will be resolved. You just need to replace "{{ product.variants.first.id }}" with "val.variants[0].id" in whole theme.js.