Dedicated to the Hydrogen framework, headless commerce, and building custom storefronts using the Storefront API.
I am developing an app to create product bundles. Firstly, I help customers to create a new variant with the quantity equals to 1 after selection. After that, I am using Cart ajax API to add this new variant to Cart. However, when I add the new variant with Fetch API, it's always showing sold out
{status: 422, message: "Cart Error", description: "The product xxxx is already sold out."}
Here is the code to add the new variant to Cart (I copied from doc):
addNewVariantToCart(variant_id) {
let formData = {
'items': [{
'id': parseInt(variant_id),
'quantity': 1
}]
};
fetch('/cart/add.js', {
method: 'POST',
headers: {
// 'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => response.json())
.then(data => {
console.log('Success adding variant to cart:', data);
// console.log('Your bundle has been added to cart! Check your cart now.')
})
.catch((error) => {
console.error('Error:', error);
});
}
When I run these code in Chrome console by substituting the new variant id, it works without any problem. What's wrong here?
Solved! Go to the solution
This is an accepted solution.
I found the solution in another thread. Just set 'inventory_policy' to 'continue' and it'll be fine.
Hi,
I have the same error message when trying to add to cart a product out of stock; I tried everything to get a pop message that this product is out of stock instead of this alert but it didn't work!!
This is below my Ajaxify.js.liquid:
/*============================================================================
(c) Copyright 2015 Shopify Inc. Author: Carson Shold (@cshold). All Rights Reserved.
Plugin Documentation - https://shopify.github.io/Timber/#ajax-cart
Ajaxify the add to cart experience and flip the button for inline confirmation,
show the cart in a modal, or a 3D drawer.
This file includes:
- Basic Shopify Ajax API calls
- Ajaxify plugin
This requires:
- jQuery 1.8+
- handlebars.min.js (for cart template)
- modernizer.min.js
- snippet/ajax-cart-template.liquid
JQUERY API (c) Copyright 2009-2015 Shopify Inc. Author: Caroline Schnapp. All Rights Reserved.
Includes slight modifications to addItemFromForm.
==============================================================================*/
if ((typeof Shopify) === 'undefined') { Shopify = {}; }
/*============================================================================
API Helper Functions
==============================================================================*/
function attributeToString(attribute) {
if ((typeof attribute) !== 'string') {
attribute += '';
if (attribute === 'undefined') {
attribute = '';
}
}
return jQuery.trim(attribute);
}
/*============================================================================
API Functions
- Shopify.format money is defined in option_selection.js.
If that file is not included, it is redefined here.
==============================================================================*/
if ( !Shopify.formatMoney ) {
Shopify.formatMoney = function(cents, format) {
var value = '',
placeholderRegex = /\{\{\s*(\w+)\s*\}\}/,
formatString = (format || this.money_format);
if (typeof cents == 'string') {
cents = cents.replace('.','');
}
function defaultOption(opt, def) {
return (typeof opt == 'undefined' ? def : opt);
}
function formatWithDelimiters(number, precision, thousands, decimal) {
precision = defaultOption(precision, 2);
thousands = defaultOption(thousands, ',');
decimal = defaultOption(decimal, '.');
if (isNaN(number) || number == null) {
return 0;
}
number = (number/100.0).toFixed(precision);
var parts = number.split('.'),
dollars = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + thousands),
cents = parts[1] ? (decimal + parts[1]) : '';
return dollars + cents;
}
switch(formatString.match(placeholderRegex)[1]) {
case 'amount':
value = formatWithDelimiters(cents, 2);
break;
case 'amount_no_decimals':
value = formatWithDelimiters(cents, 0);
break;
case 'amount_with_comma_separator':
value = formatWithDelimiters(cents, 2, '.', ',');
break;
case 'amount_no_decimals_with_comma_separator':
value = formatWithDelimiters(cents, 0, '.', ',');
break;
}
return formatString.replace(placeholderRegex, value);
};
}
Shopify.onProduct = function(product) {
// alert('Received everything we ever wanted to know about ' + product.title);
};
Shopify.onCartUpdate = function(cart) {
// alert('There are now ' + cart.item_count + ' items in the cart.');
};
Shopify.updateCartNote = function(note, callback) {
var params = {
type: 'POST',
url: '/cart/update.js',
data: 'note=' + attributeToString(note),
dataType: 'json',
success: function(cart) {
if ((typeof callback) === 'function') {
callback(cart);
}
else {
Shopify.onCartUpdate(cart);
}
},
error: function(XMLHttpRequest, textStatus) {
Shopify.onError(XMLHttpRequest, textStatus);
}
};
jQuery.ajax(params);
};
Shopify.onError = function(XMLHttpRequest, textStatus) {
var data = eval('(' + XMLHttpRequest.responseText + ')');
if (!!data.message) {
alert(data.message + '(' + data.status + '): ' + data.description);
} else {
alert('Error : ' + Shopify.fullMessagesFromErrors(data).join('; ') + '.');
}
};
/*============================================================================
POST to cart/add.js returns the JSON of the line item associated with the added item
==============================================================================*/
Shopify.addItem = function(variant_id, quantity, callback) {
var quantity = quantity || 1;
var params = {
type: 'POST',
url: '/cart/add.js',
data: 'quantity=' + quantity + '&id=' + variant_id,
dataType: 'json',
success: function(line_item) {
if ((typeof callback) === 'function') {
callback(line_item);
}
else {
Shopify.onItemAdded(line_item);
}
},
error: function(XMLHttpRequest, textStatus) {
Shopify.onError(XMLHttpRequest, textStatus);
}
};
jQuery.ajax(params);
};
/*============================================================================
POST to cart/add.js returns the JSON of the line item
- Allow use of form element instead of id
- Allow custom error callback
==============================================================================*/
Shopify.addItemFromForm = function(form, callback, errorCallback) {
var params = {
type: 'POST',
url: '/cart/add.js',
data: jQuery(form).serialize(),
dataType: 'json',
success: function(line_item) {
if ((typeof callback) === 'function') {
callback(line_item, form);
}
else {
Shopify.onItemAdded(line_item, form);
}
},
error: function(XMLHttpRequest, textStatus) {
if ((typeof errorCallback) === 'function') {
errorCallback(XMLHttpRequest, textStatus);
}
else {
Shopify.onError(XMLHttpRequest, textStatus);
}
}
};
jQuery.ajax(params);
};
// Get from cart.js returns the cart in JSON
Shopify.getCart = function(callback) {
jQuery.getJSON('/cart.js', function (cart, textStatus) {
if ((typeof callback) === 'function') {
callback(cart);
}
else {
Shopify.onCartUpdate(cart);
}
});
};
// GET products/<product-handle>.js returns the product in JSON
Shopify.getProduct = function(handle, callback) {
jQuery.getJSON('/products/' + handle + '.js', function (product, textStatus) {
if ((typeof callback) === 'function') {
callback(product);
}
else {
Shopify.onProduct(product);
}
});
};
// POST to cart/change.js returns the cart in JSON
Shopify.changeItem = function(line, quantity, callback) {
var params = {
type: 'POST',
url: '/cart/change.js',
data: 'quantity=' + quantity + '&line=' + line,
dataType: 'json',
success: function(cart) {
if ((typeof callback) === 'function') {
callback(cart);
}
else {
Shopify.onCartUpdate(cart);
}
},
error: function(XMLHttpRequest, textStatus) {
Shopify.onError(XMLHttpRequest, textStatus);
}
};
jQuery.ajax(params);
};
/*============================================================================
Ajaxify Shopify Add To Cart
==============================================================================*/
var ajaxifyShopify = (function(module, $) {
'use strict';
// Public functions
var init;
// Private general variables
var settings, isUpdating, cartInit, $drawerHeight, $cssTransforms, $cssTransforms3d, $nojQueryLoad, $w, $body, $html;
// Private plugin variables
var $formContainer, $btnClass, $wrapperClass, $addToCart, $flipClose, $flipCart, $flipContainer, $cartCountSelector, $cartCostSelector, $toggleCartButton, $modal, $cartContainer, $drawerCaret, $modalContainer, $modalOverlay, $closeCart, $drawerContainer, $prependDrawerTo, $callbackData={};
// Private functions
var updateCountPrice, flipSetup, revertFlipButton, modalSetup, showModal, sizeModal, hideModal, drawerSetup, showDrawer, hideDrawer, sizeDrawer, loadCartImages, formOverride, itemAddedCallback, itemErrorCallback, cartUpdateCallback, setToggleButtons, flipCartUpdateCallback, buildCart, cartTemplate, adjustCart, adjustCartCallback, createQtySelectors, qtySelectors, scrollTop, toggleCallback, validateQty;
/*============================================================================
Initialise the plugin and define global options
==============================================================================*/
init = function (options) {
// Default settings
settings = {
method: 'drawer', // Method options are drawer, modal, and flip. Default is drawer.
formSelector: 'form[action^="/cart/add"]',
addToCartSelector: 'input[type="submit"]',
cartCountSelector: null,
cartCostSelector: null,
toggleCartButton: null,
btnClass: null,
wrapperClass: null,
useCartTemplate: false,
moneyFormat: '{% raw %}${{amount}}{% endraw %}',
disableAjaxCart: false,
enableQtySelectors: true,
prependDrawerTo: 'body',
onToggleCallback: null
};
// Override defaults with arguments
$.extend(settings, options);
// Make sure method is lower case
settings.method = settings.method.toLowerCase();
// Select DOM elements
$formContainer = $(settings.formSelector);
$btnClass = settings.btnClass;
$wrapperClass = settings.wrapperClass;
$addToCart = $formContainer.find(settings.addToCartSelector);
$flipContainer = null;
$flipClose = null;
$cartCountSelector = $(settings.cartCountSelector);
$cartCostSelector = $(settings.cartCostSelector);
$toggleCartButton = $(settings.toggleCartButton);
$modal = null;
$prependDrawerTo = $(settings.prependDrawerTo);
// CSS Checks
$cssTransforms = Modernizr.csstransforms;
$cssTransforms3d = Modernizr.csstransforms3d;
// General Selectors
$w = $(window);
$body = $('body');
$html = $('html');
// Track cart activity status
isUpdating = false;
// Check if we can use .load
$nojQueryLoad = $html.hasClass('lt-ie9');
if ($nojQueryLoad) {
settings.useCartTemplate = false;
}
// Setup ajax quantity selectors on the any template if enableQtySelectors is true
if (settings.enableQtySelectors) {
qtySelectors();
}
// Enable the ajax cart
if (!settings.disableAjaxCart) {
// Handle each case add to cart method
switch (settings.method) {
case 'flip':
flipSetup();
break;
case 'modal':
modalSetup();
break;
case 'drawer':
drawerSetup();
break;
}
// Escape key closes cart
$(document).keyup( function (evt) {
if (evt.keyCode == 27) {
switch (settings.method) {
case 'flip':
case 'drawer':
hideDrawer();
break;
case 'modal':
hideModal();
break;
}
}
});
if ( $addToCart.length ) {
// Take over the add to cart form submit
formOverride();
}
}
// Run this function in case we're using the quantity selector outside of the cart
adjustCart();
};
updateCountPrice = function (cart) {
if ($cartCountSelector) {
$cartCountSelector.html(cart.item_count).removeClass('hidden-count');
if (cart.item_count === 0) {
$cartCountSelector.addClass('hidden-count');
}
}
if ($cartCostSelector) {
$cartCostSelector.html(Shopify.formatMoney(cart.total_price, settings.moneyFormat));
}
};
flipSetup = function () {
// Build and append the drawer in the DOM
drawerSetup();
// Stop if there is no add to cart button
if ( !$addToCart.length ) {
return
}
// Wrap the add to cart button in a div
$addToCart.addClass('flip-front').wrap('<div class="flip"></div>');
// Write a (hidden) Checkout button, a loader, and the extra view cart button
var checkoutBtn = $('<a href="/cart" class="flip-back" style="background-color: #C00; color: #fff;" id="flip-checkout">' + {{ 'cart.general.checkout' | t | json }} + '</a>').addClass($btnClass),
flipLoader = $('<span class="ajaxifyCart-loader"></span>'),
flipExtra = $('<div class="flip-extra">' + {{ 'cart.general.or' | t | json }} + ' <a href="#" class="flip-cart">' + {{ 'cart.general.view_cart' | t | json }} + ' (<span></span>)</a></div>');
// Append checkout button and loader
checkoutBtn.insertAfter($addToCart);
flipLoader.insertAfter(checkoutBtn);
// Setup new selectors
$flipContainer = $('.flip');
if (!$cssTransforms3d) {
$flipContainer.addClass('no-transforms')
}
// Setup extra selectors once appended
flipExtra.insertAfter($flipContainer);
$flipCart = $('.flip-cart');
$flipCart.on('click', function(e) {
e.preventDefault();
showDrawer(true);
});
// Reset the button if a user changes a variation
$('input[type="checkbox"], input[type="radio"], select', $formContainer).on('click', function() {
revertFlipButton();
})
};
revertFlipButton = function () {
$flipContainer.removeClass('is-flipped');
};
modalSetup = function () {
// Create modal DOM elements with handlebars.js template
var source = $("#modalTemplate").html(),
template = Handlebars.compile(source);
// Append modal and overlay to body
$body.append(template).append('<div id="ajaxifyCart-overlay"></div>');
// Modal selectors
$modalContainer = $('#ajaxifyModal');
$modalOverlay = $('#ajaxifyCart-overlay');
$cartContainer = $('#ajaxifyCart');
// Close modal when clicking the overlay
$modalOverlay.on('click', hideModal);
// Create a close modal button
$modalContainer.prepend('<button class="ajaxifyCart--close" title="' + {{ 'cart.general.close_cart' | t | json }} + '">' + {{ 'cart.general.close_cart' | t | json }} + '</button>');
$closeCart = $('.ajaxifyCart--close');
$closeCart.on('click', hideModal);
// Add a class if CSS translate isn't available
if (!$cssTransforms) {
$modalContainer.addClass('no-transforms')
}
// Update modal position on screen changes
$(window).on({
orientationchange: function(e) {
if ($modalContainer.hasClass('is-visible')) {
sizeModal('resize');
}
}, resize: function(e) {
// IE8 fires this when overflow on body is changed. Ignore IE8.
if (!$nojQueryLoad && $modalContainer.hasClass('is-visible')) {
sizeModal('resize');
}
}
});
// Toggle modal with cart button
setToggleButtons();
};
showModal = function (toggle) {
$body.addClass('ajaxify-modal--visible');
// Build the cart if it isn't already there
if ( !cartInit && toggle ) {
Shopify.getCart(cartUpdateCallback);
} else {
sizeModal();
}
};
sizeModal = function(isResizing) {
if (!isResizing) {
$modalContainer.css('opacity', 0);
}
// Position modal by negative margin
$modalContainer.css({
'margin-left': - ($modalContainer.outerWidth() / 2),
'opacity': 1
});
$modalContainer.addClass('is-visible');
scrollTop();
toggleCallback({
'is_visible': true
});
};
hideModal = function (e) {
$body.removeClass('ajaxify-modal--visible');
if (e) {
e.preventDefault();
}
if ($modalContainer) {
$modalContainer.removeClass('is-visible');
$body.removeClass('ajaxify-lock');
}
toggleCallback({
'is_visible': false
});
};
drawerSetup = function () {
// Create drawer DOM elements with handlebars.js template
var source = $("#drawerTemplate").html(),
template = Handlebars.compile(source),
data = {
wrapperClass: $wrapperClass
};
// Append drawer (defaults to body)
$prependDrawerTo.prepend(template(data));
// Drawer selectors
$drawerContainer = $('#ajaxifyDrawer');
$cartContainer = $('#ajaxifyCart');
$drawerCaret = $('.ajaxifyDrawer-caret > span');
// Toggle drawer with cart button
setToggleButtons();
// Position caret and size drawer on resize if drawer is visible
var timeout;
$(window).resize(function() {
clearTimeout(timeout);
timeout = setTimeout(function(){
if ($drawerContainer.hasClass('is-visible')) {
positionCaret();
sizeDrawer();
}
}, 500);
});
// Position the caret the first time
positionCaret();
// Position the caret
function positionCaret() {
if ($toggleCartButton.offset()) {
// Get the position of the toggle button to align the carat with
var togglePos = $toggleCartButton.offset(),
toggleWidth = $toggleCartButton.outerWidth(),
toggleMiddle = togglePos.left + toggleWidth/2;
$drawerCaret.css('left', toggleMiddle + 'px');
}
}
};
showDrawer = function (toggle) {
// If we're toggling with the flip method, use a special callback
if (settings.method == 'flip') {
Shopify.getCart(flipCartUpdateCallback);
}
// opening the drawer for the first time
else if ( !cartInit && toggle) {
Shopify.getCart(cartUpdateCallback);
}
// simple toggle? just size it
else if ( cartInit && toggle ) {
sizeDrawer();
}
// Show the drawer
$drawerContainer.addClass('is-visible');
scrollTop();
toggleCallback({
'is_visible': true
});
};
hideDrawer = function () {
$drawerContainer.removeAttr('style').removeClass('is-visible');
scrollTop();
toggleCallback({
'is_visible': false
});
};
sizeDrawer = function ($empty) {
if ($empty) {
$drawerContainer.css('height', '0px');
} else {
$drawerHeight = $cartContainer.outerHeight();
$('.cart-row img').css('width', 'auto'); // fix Chrome image size bug
$drawerContainer.css('height', $drawerHeight + 'px');
}
};
loadCartImages = function () {
// Size cart once all images are loaded
var cartImages = $('img', $cartContainer),
count = cartImages.length,
index = 0;
cartImages.on('load', function() {
index++;
if (index==count) {
switch (settings.method) {
case 'modal':
sizeModal();
break;
case 'flip':
case 'drawer':
sizeDrawer();
break;
}
}
});
};
formOverride = function () {
$formContainer.submit(function(e) {
e.preventDefault();
// Add class to be styled if desired
$addToCart.removeClass('is-added').addClass('is-adding');
// Remove any previous quantity errors
$('.qty-error').remove();
Shopify.addItemFromForm(e.target, itemAddedCallback, itemErrorCallback);
// Set the flip button to a loading state
switch (settings.method) {
case 'flip':
$flipContainer.addClass('flip--is-loading');
break;
}
});
};
itemAddedCallback = function (product) {
$addToCart.removeClass('is-adding').addClass('is-added');
// Slight delay of flip to mimic a longer load
switch (settings.method) {
case 'flip':
setTimeout(function () {
$flipContainer.removeClass('flip--is-loading').addClass('is-flipped');
}, 600);
break;
}
Shopify.getCart(cartUpdateCallback);
};
itemErrorCallback = function (XMLHttpRequest, textStatus) {
switch (settings.method) {
case 'flip':
$flipContainer.removeClass('flip--is-loading');
break;
}
var data = eval('(' + XMLHttpRequest.responseText + ')');
if (!!data.message) {
if (data.status == 422) {
$formContainer.after('<div class="errors qty-error">'+ data.description +'</div>')
}
}
};
cartUpdateCallback = function (cart) {
// Update quantity and price
updateCountPrice(cart);
switch (settings.method) {
case 'flip':
$('.flip-cart span').html(cart.item_count);
break;
case 'modal':
buildCart(cart);
break;
case 'drawer':
buildCart(cart);
if ( !$drawerContainer.hasClass('is-visible') ) {
showDrawer();
} else {
scrollTop();
}
break;
}
};
setToggleButtons = function () {
// Reselect the element in case it just loaded
$toggleCartButton = $(settings.toggleCartButton);
if ($toggleCartButton) {
// Turn it off by default, in case it's initialized twice
$toggleCartButton.off('click');
// Toggle the cart, based on the method
$toggleCartButton.on('click', function(e) {
e.preventDefault();
switch (settings.method) {
case 'modal':
if ( $modalContainer.hasClass('is-visible') ) {
hideModal();
} else {
showModal(true);
}
break;
case 'drawer':
case 'flip':
if ( $drawerContainer.hasClass('is-visible') ) {
hideDrawer();
} else {
showDrawer(true);
}
break;
}
});
}
};
flipCartUpdateCallback = function (cart) {
buildCart(cart);
};
buildCart = function (cart) {
// Empty cart if using default layout or not using the .load method to get /cart
if (!settings.useCartTemplate || cart.item_count === 0) {
$cartContainer.empty();
}
// Show empty cart
if (cart.item_count === 0) {
$cartContainer.append('<h2>' + {{ 'cart.general.empty' | t | json }} + '</h2><span>' + {{ 'cart.general.continue_browsing_html' | t | json }} + '</span>');
switch (settings.method) {
case 'modal':
sizeModal('resize');
break;
case 'flip':
case 'drawer':
sizeDrawer();
if (!$drawerContainer.hasClass('is-visible') && cartInit) {
sizeDrawer(true);
}
break;
}
return;
}
// Use the /cart template, or Handlebars.js layout based on theme settings
if (settings.useCartTemplate) {
cartTemplate(cart);
return;
}
// Handlebars.js cart layout
var items = [],
item = {},
data = {};
var source = $("#cartTemplate").html(),
template = Handlebars.compile(source);
// Add each item to our handlebars.js data
$.each(cart.items, function(index, cartItem) {
var itemAdd = cartItem.quantity + 1,
itemMinus = cartItem.quantity - 1,
itemQty = cartItem.quantity + ' x';
/* Hack to get product image thumbnail
* - Remove file extension, add _small, and re-add extension
* - Create server relative link
*/
var prodImg = cartItem.image.replace(/(\.[^.]*)$/, "_small$1").replace('http:', '');
var prodName = cartItem.title.replace(/(\-[^-]*)$/, "");
var prodVariation = cartItem.title.replace(/^[^\-]*/, "").replace(/-/, "");
// Create item's data object and add to 'items' array
item = {
id: cartItem.variant_id,
line: index + 1, // Shopify uses a 1+ index in the API
url: cartItem.url,
img: prodImg,
name: prodName,
variation: prodVariation,
itemAdd: itemAdd,
itemMinus: itemMinus,
itemQty: itemQty,
price: Shopify.formatMoney(cartItem.price, settings.moneyFormat)
};
items.push(item);
});
// Gather all cart data and add to DOM
data = {
items: items,
totalPrice: Shopify.formatMoney(cart.total_price, settings.moneyFormat),
btnClass: $btnClass
}
$cartContainer.append(template(data));
// With new elements we need to relink the adjust cart functions
adjustCart();
// Setup close modal button and size drawer
switch (settings.method) {
case 'modal':
loadCartImages();
break;
case 'flip':
case 'drawer':
if (cart.item_count > 0) {
loadCartImages();
} else {
sizeDrawer(true);
}
break;
default:
break;
}
// Mark the cart as built
cartInit = true;
};
cartTemplate = function (cart) {
$cartContainer.load('/cart form[action="/cart"]', function() {
// With new elements we need to relink the adjust cart functions
adjustCart();
// Size drawer at this point
switch (settings.method) {
case 'modal':
loadCartImages();
break;
case 'flip':
case 'drawer':
if (cart.item_count > 0) {
loadCartImages();
} else {
sizeDrawer(true);
}
break;
default:
break;
}
// Mark the cart as built
cartInit = true;
});
}
adjustCart = function () {
// This function runs on load, and when the cart is reprinted
// Create ajax friendly quantity fields and remove links in the ajax cart
if (settings.useCartTemplate) {
createQtySelectors();
}
// Prevent cart from being submitted while quantities are changing
$body.on('submit', 'form.cart-form', function(evt) {
if (isUpdating) {
evt.preventDefault();
}
});
// Update quantify selectors
var qtyAdjust = $('.ajaxifyCart--qty span');
// Add or remove from the quantity
qtyAdjust.off('click');
qtyAdjust.on('click', function() {
var el = $(this),
line = el.data('line'),
qtySelector = el.siblings('.ajaxifyCart--num'),
qty = parseInt( qtySelector.val() );
qty = validateQty(qty);
// Add or subtract from the current quantity
if (el.hasClass('ajaxifyCart--add')) {
qty = qty + 1;
} else {
qty = qty <= 0 ? 0 : qty - 1;
}
// If it has a data-line, update the cart.
// Otherwise, just update the input's number
if (line) {
updateQuantity(line, qty);
} else {
qtySelector.val(qty);
}
});
// Update quantity based on input on change
var qtyInput = $('.ajaxifyCart--num');
qtyInput.off('change');
qtyInput.on('change', function() {
var el = $(this),
line = el.data('line'),
qty = el.val();
// Make sure we have a valid integer
if( (parseFloat(qty) == parseInt(qty)) && !isNaN(qty) ) {
// We have a number!
} else {
// Not a number. Default to 1.
el.val(1);
return;
}
// If it has a data-line, update the cart
if (line) {
updateQuantity(line, qty);
}
});
// Highlight the text when focused
qtyInput.off('focus');
qtyInput.on('focus', function() {
var el = $(this);
setTimeout(function() {
el.select();
}, 50);
});
// Completely remove product
$('.ajaxifyCart--remove').on('click', function(e) {
var el = $(this),
line = el.data('line') || null,
qty = 0;
// Without a data-line, let the default link action take over
if (!line) {
return;
}
e.preventDefault();
updateQuantity(line, qty);
});
function updateQuantity(line, qty) {
isUpdating = true;
// Add activity classes when changing cart quantities
if (!settings.useCartTemplate) {
var row = $('.ajaxifyCart--row[data-line="' + line + '"]').addClass('ajaxifyCart--is-loading');
} else {
var row = $('.cart-row[data-line="' + line + '"]').addClass('ajaxifyCart--is-loading');
}
if ( qty === 0 ) {
row.addClass('is-removed');
}
// Slight delay to make sure removed animation is done
setTimeout(function() {
Shopify.changeItem(line, qty, adjustCartCallback);
}, 250);
}
// Save note anytime it's changed
var noteArea = $('textarea[name="note"]');
noteArea.off('change');
noteArea.on('change', function() {
var newNote = $(this).val();
// Simply updating the cart note in case they don't click update/checkout
Shopify.updateCartNote(newNote, function(cart) {});
});
if (typeof GoogleWalletButton === "function") GoogleWalletButton();
if (typeof AmazonPaymentsPayButton === "function") AmazonPaymentsPayButton();
};
adjustCartCallback = function (cart) {
isUpdating = false;
// Update quantity and price
updateCountPrice(cart);
// Hide the modal or drawer if we're at 0 items
if ( cart.item_count === 0 ) {
// Handle each add to cart method
switch (settings.method) {
case 'modal':
break;
case 'flip':
case 'drawer':
hideDrawer();
break;
}
}
// Reprint cart on short timeout so you don't see the content being removed
setTimeout(function() {
Shopify.getCart(buildCart);
}, 150)
};
createQtySelectors = function() {
// If there is a normal quantity number field in the ajax cart, replace it with our version
if ($('input[type="number"]', $cartContainer).length) {
$('input[type="number"]', $cartContainer).each(function() {
var el = $(this),
currentQty = parseInt(el.val());
var itemAdd = currentQty + 1,
itemMinus = currentQty - 1,
itemQty = currentQty + ' x';
var source = $("#ajaxifyQty").html(),
template = Handlebars.compile(source),
data = {
line: el.attr('data-line'),
itemQty: itemQty,
itemAdd: itemAdd,
itemMinus: itemMinus
};
// Append new quantity selector then remove original
el.after(template(data)).remove();
});
}
// If there is a regular link to remove an item, add attributes needed to ajaxify it
if ($('a[href^="/cart/change"]', $cartContainer).length) {
$('a[href^="/cart/change"]', $cartContainer).each(function() {
var el = $(this).addClass('ajaxifyCart--remove');
});
}
};
qtySelectors = function() {
// Change number inputs to JS ones, similar to ajax cart but without API integration.
// Make sure to add the existing name and id to the new input element
var numInputs = $('input[type="number"]');
// Qty selector has a minimum of 1 on the product page
// and 0 in the cart (determined on qty click)
var qtyMin = 0;
if (numInputs.length) {
numInputs.each(function() {
var el = $(this),
currentQty = parseInt(el.val()),
inputName = el.attr('name'),
inputId = el.attr('id');
var itemAdd = currentQty + 1,
itemMinus = currentQty - 1,
itemQty = currentQty;
var source = $("#jsQty").html(),
template = Handlebars.compile(source),
data = {
id: el.data('id'),
itemQty: itemQty,
itemAdd: itemAdd,
itemMinus: itemMinus,
inputName: inputName,
inputId: inputId
};
// Append new quantity selector then remove original
el.after(template(data)).remove();
});
// Setup listeners to add/subtract from the input
$('.js--qty-adjuster').on('click', function() {
var el = $(this),
id = el.data('id'),
qtySelector = el.siblings('.js--num'),
qty = parseInt( qtySelector.val() );
var qty = validateQty(qty);
qtyMin = $body.hasClass('template-product') ? 1 : qtyMin;
// Add or subtract from the current quantity
if (el.hasClass('js--add')) {
qty = qty + 1;
} else {
qty = qty <= qtyMin ? qtyMin : qty - 1;
}
// Update the input's number
qtySelector.val(qty);
});
}
};
scrollTop = function () {
if ($body.scrollTop() > 0 || $html.scrollTop() > 0) {
$('html, body').animate({
scrollTop: 0
}, 250, 'swing');
}
};
toggleCallback = function (data) {
// Run the callback if it's a function
if (typeof settings.onToggleCallback == 'function') {
settings.onToggleCallback.call(this, data);
}
};
validateQty = function (qty) {
if((parseFloat(qty) == parseInt(qty)) && !isNaN(qty)) {
// We have a valid number!
return qty;
} else {
// Not a number. Default to 1.
return 1;
}
};
module = {
init: init
};
return module;
}(ajaxifyShopify || {}, jQuery));
This is an accepted solution.
I found the solution in another thread. Just set 'inventory_policy' to 'continue' and it'll be fine.