Plus, Minus, automatic cart update on Magento 2
vendor\magento\module-checkout\view\frontend\web\template\minicart\item\default.html
Copy to default.html
app\design\yourvendor\yourtheme\Magento_Checkout\web\template\minicart\item\default.html
add + and – button as span tag in line number 74 and 84.
Or insert red line code
[code language=”html”]
<!–
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
–>
<li class="item product product-item" data-role="product-item">
<div class="product">
<div class="product-item-photo">
<!– ko if: product_has_url –>
<a data-bind="attr: {href: product_url, title: product_name}" tabindex="-1" class="product-item-photo">
<!– ko foreach: $parent.getRegion(‘itemImage’) –>
<!– ko template: {name: getTemplate(), data: item.product_image} –><!– /ko –>
<!– /ko –>
</a>
<!– /ko –>
<!– ko ifnot: product_has_url –>
<span class="product-item-photo">
<!– ko foreach: $parent.getRegion(‘itemImage’) –>
<!– ko template: {name: getTemplate(), data: item.product_image} –><!– /ko –>
<!– /ko –>
</span>
<!– /ko –>
<div class="product actions">
<!– ko if: is_visible_in_site_visibility –>
<div class="primary">
<a data-bind="attr: {href: configure_url, title: $t(‘Edit item’)}" class="action edit">
<span data-bind="i18n: ‘Edit’"></span>
</a>
</div>
<!– /ko –>
<div class="secondary">
<a href="#" data-bind="attr: {‘data-cart-item’: item_id, title: $t(‘Remove item’)}"
class="action delete">
<span data-bind="i18n: ‘Remove’"></span>
</a>
</div>
</div>
</div>
<div class="product-item-details">
<strong class="product-item-name">
<!– ko if: product_has_url –>
<a data-bind="attr: {href: product_url}, html: product_name"></a>
<!– /ko –>
<!– ko ifnot: product_has_url –>
<!– ko text: product_name –><!– /ko –>
<!– /ko –>
</strong>
<!– ko if: options.length –>
<div class="product options" data-mage-init='{"collapsible":{"openedState": "active", "saveState": false}}’>
<span data-role="title" class="toggle"><!– ko i18n: ‘See Details’ –><!– /ko –></span>
<div data-role="content" class="content">
<strong class="subtitle"><!– ko i18n: ‘Options Details’ –><!– /ko –></strong>
<dl class="product options list">
<!– ko foreach: { data: options, as: ‘option’ } –>
<dt class="label"><!– ko text: option.label –><!– /ko –></dt>
<dd class="values">
<!– ko if: Array.isArray(option.value) –>
<span data-bind="html: option.value.join(‘<br>’)"></span>
<!– /ko –>
<!– ko if: (!Array.isArray(option.value) && [‘file’, ‘html’].includes(option.option_type)) –>
<span data-bind="html: option.value"></span>
<!– /ko –>
<!– ko if: (!Array.isArray(option.value) && ![‘file’, ‘html’].includes(option.option_type)) –>
<span data-bind="text: option.value"></span>
<!– /ko –>
</dd>
<!– /ko –>
</dl>
</div>
</div>
<!– /ko –>
<div class="product-item-pricing">
<!– ko if: canApplyMsrp –>
<div class="details-map">
<span class="label" data-bind="i18n: ‘Price’"></span>
<span class="value" data-bind="i18n: ‘See price before order confirmation.’"></span>
</div>
<!– /ko –>
<!– ko ifnot: canApplyMsrp –>
<!– ko foreach: $parent.getRegion(‘priceSidebar’) –>
<!– ko template: {name: getTemplate(), data: item.product_price, as: ‘price’} –><!– /ko –>
<!– /ko –>
<!– /ko –>
<div class="details-qty qty">
<label class="label" data-bind="i18n: ‘Qty’, attr: {
for: ‘cart-item-‘+item_id+’-qty’}"></label>
<span data-bind="attr: { id: item_id }" class="minus" >-</span>
<input data-bind="attr: {
id: ‘cart-item-‘+item_id+’-qty’,
‘data-cart-item’: item_id,
‘data-item-qty’: qty,
‘data-cart-item-id’: product_sku
}, value: qty"
type="number"
size="4"
class="item-qty cart-item-qty">
<span data-bind="attr: { id: item_id }" class="plus" >+</span>
<button data-bind="attr: {
id: ‘update-cart-item-‘+item_id,
‘data-cart-item’: item_id,
title: $t(‘Update’)
}"
class="update-cart-item"
style="display: none">
<span data-bind="i18n: ‘Update’"></span>
</button>
</div>
</div>
</div>
</div>
<div class="message notice" if="$data.message">
<div data-bind="text: $data.message"></div>
</div>
</li>
[/code]
override sidebar.js from
vendor\magento\module-checkout\view\frontend\web\js\sidebar.js
to
app\design\yourvendor\yourtheme\Magento_Checkout\web\js\sidebar.js
[code language=”javascript”]
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
define([ ‘jquery’, ‘Magento_Customer/js/model/authentication-popup’, ‘Magento_Customer/js/customer-data’, ‘Magento_Ui/js/modal/alert’, ‘Magento_Ui/js/modal/confirm’, ‘underscore’, ‘jquery/ui’, ‘mage/decorate’, ‘mage/collapsible’, ‘mage/cookies’ ], function ($, authenticationPopup, customerData, alert, confirm, _) { ‘use strict’;
$.widget(‘mage.sidebar’, {
options: {
isRecursive: true,
minicart: {
maxItemsVisible: 3
}
},
scrollHeight: 0,
shoppingCartUrl: window.checkout.shoppingCartUrl,
/**
* Create sidebar.
* @private
*/
_create: function () {
this._initContent();
},
/**
* Update sidebar block.
*/
update: function () {
$(this.options.targetElement).trigger(‘contentUpdated’);
this._calcHeight();
this._isOverflowed();
},
/**
* @private
*/
_initContent: function () {
var self = this,
events = {};
this.element.decorate(‘list’, this.options.isRecursive);
/**
* @param {jQuery.Event} event
*/
events[‘click ‘ + this.options.button.close] = function (event) {
event.stopPropagation();
$(self.options.targetElement).dropdownDialog(‘close’);
};
events[‘click ‘ + this.options.button.checkout] = $.proxy(function () {
var cart = customerData.get(‘cart’),
customer = customerData.get(‘customer’),
element = $(this.options.button.checkout);
if (!customer().firstname && cart().isGuestCheckoutAllowed === false) {
// set URL for redirect on successful login/registration. It’s postprocessed on backend.
$.cookie(‘login_redirect’, this.options.url.checkout);
if (this.options.url.isRedirectRequired) {
element.prop(‘disabled’, true);
location.href = this.options.url.loginUrl;
} else {
authenticationPopup.showModal();
}
return false;
}
element.prop(‘disabled’, true);
location.href = this.options.url.checkout;
}, this);
/**
* @param {jQuery.Event} event
*/
events[‘click ‘ + this.options.button.remove] = function (event) {
event.stopPropagation();
confirm({
content: self.options.confirmMessage,
actions: {
/** @inheritdoc */
confirm: function () {
self._removeItem($(event.currentTarget));
},
/** @inheritdoc */
always: function (e) {
e.stopImmediatePropagation();
}
}
});
};
/**
* @param {jQuery.Event} event
*/
events[‘click ‘ + ‘.minicart-items-wrapper .plus’] = function (event) {//Custom event
var plusId=event.target.id;
var qtyBoxId=’#cart-item-‘+plusId+’-qty’;
var qtyVal = parseInt($(event.target).parent().find(this.options.item.qty).val());
$(qtyBoxId).val(qtyVal + 1);
event.stopPropagation();
self._updateItemQty($(event.target).parent().find(this.options.item.button));
};
/**
* @param {jQuery.Event} event
*/
events[‘click ‘ + ‘.minicart-items-wrapper .minus’] = function (event) {//Custom event
var minusId=event.target.id;
var qtyBoxId=’#cart-item-‘+minusId+’-qty’;
var qtyVal = parseInt($(event.target).parent().find(this.options.item.qty).val());
if(qtyVal > 1){
$(qtyBoxId).val(qtyVal – 1);
event.stopPropagation();
self._updateItemQty($(event.target).parent().find(this.options.item.button));
}
};
/**
* @param {jQuery.Event} event
*/
events[‘keyup ‘ + this.options.item.qty] = function (event) {//Hide event for not displaying update button
//self._showItemButton($(event.target));
};
/**
* @param {jQuery.Event} event
*/
events[‘change ‘ + this.options.item.qty] = function (event) {//Hide event for not displaying update button
//self._showItemButton($(event.target));
};
/**
* @param {jQuery.Event} event
*/
events[‘click ‘ + this.options.item.button] = function (event) {
event.stopPropagation();
self._updateItemQty($(event.currentTarget));
};
/**
* @param {jQuery.Event} event
*/
events[‘focusout ‘ + this.options.item.qty] = function (event) {
self._validateQty($(event.currentTarget));
};
this._on(this.element, events);
this._calcHeight();
this._isOverflowed();
},
/**
* Add ‘overflowed’ class to minicart items wrapper element
*
* @private
*/
_isOverflowed: function () {
var list = $(this.options.minicart.list),
cssOverflowClass = ‘overflowed’;
if (this.scrollHeight > list.innerHeight()) {
list.parent().addClass(cssOverflowClass);
} else {
list.parent().removeClass(cssOverflowClass);
}
},
/**
* @param {HTMLElement} elem
* @private
*/
_showItemButton: function (elem) {
var itemId = elem.data(‘cart-item’),
itemQty = elem.data(‘item-qty’);
if (this._isValidQty(itemQty, elem.val())) {
$(‘#update-cart-item-‘ + itemId).show(‘fade’, 300);
} else if (elem.val() == 0) { //eslint-disable-line eqeqeq
this._hideItemButton(elem);
} else {
this._hideItemButton(elem);
}
},
/**
* @param {*} origin – origin qty. ‘data-item-qty’ attribute.
* @param {*} changed – new qty.
* @returns {Boolean}
* @private
*/
_isValidQty: function (origin, changed) {
return origin != changed && //eslint-disable-line eqeqeq
changed.length > 0 &&
changed – 0 == changed && //eslint-disable-line eqeqeq
changed – 0 > 0;
},
/**
* @param {Object} elem
* @private
*/
_validateQty: function (elem) {
var itemQty = elem.data(‘item-qty’);
if (!this._isValidQty(itemQty, elem.val())) {
elem.val(itemQty);
}
},
/**
* @param {HTMLElement} elem
* @private
*/
_hideItemButton: function (elem) {
var itemId = elem.data(‘cart-item’);
$(‘#update-cart-item-‘ + itemId).hide(‘fade’, 300);
},
/**
* @param {HTMLElement} elem
* @private
*/
_updateItemQty: function (elem) {
var itemId = elem.data(‘cart-item’);
this._ajax(this.options.url.update, {
‘item_id’: itemId,
‘item_qty’: $(‘#cart-item-‘ + itemId + ‘-qty’).val()
}, elem, this._updateItemQtyAfter);
},
/**
* Update content after update qty
*
* @param {HTMLElement} elem
*/
_updateItemQtyAfter: function (elem) {
var productData = this._getProductById(Number(elem.data(‘cart-item’)));
if (!_.isUndefined(productData)) {
$(document).trigger(‘ajax:updateCartItemQty’);
if (window.location.href === this.shoppingCartUrl) {
window.location.reload(false);
}
}
this._hideItemButton(elem);
},
/**
* @param {HTMLElement} elem
* @private
*/
_removeItem: function (elem) {
var itemId = elem.data(‘cart-item’);
this._ajax(this.options.url.remove, {
‘item_id’: itemId
}, elem, this._removeItemAfter);
},
/**
* Update content after item remove
*
* @param {Object} elem
* @private
*/
_removeItemAfter: function (elem) {
var productData = this._getProductById(Number(elem.data(‘cart-item’)));
if (!_.isUndefined(productData)) {
$(document).trigger(‘ajax:removeFromCart’, {
productIds: [productData[‘product_id’]]
});
}
},
/**
* Retrieves product data by Id.
*
* @param {Number} productId – product Id
* @returns {Object|undefined}
* @private
*/
_getProductById: function (productId) {
return _.find(customerData.get(‘cart’)().items, function (item) {
return productId === Number(item[‘item_id’]);
});
},
/**
* @param {String} url – ajax url
* @param {Object} data – post data for ajax call
* @param {Object} elem – element that initiated the event
* @param {Function} callback – callback method to execute after AJAX success
*/
_ajax: function (url, data, elem, callback) {
$.extend(data, {
‘form_key’: $.mage.cookies.get(‘form_key’)
});
$.ajax({
url: url,
data: data,
type: ‘post’,
dataType: ‘json’,
context: this,
/** @inheritdoc */
beforeSend: function () {
elem.attr(‘disabled’, ‘disabled’);
},
/** @inheritdoc */
complete: function () {
elem.attr(‘disabled’, null);
}
})
.done(function (response) {
var msg;
if (response.success) {
callback.call(this, elem, response);
} else {
msg = response[‘error_message’];
if (msg) {
alert({
content: msg
});
}
}
})
.fail(function (error) {
console.log(JSON.stringify(error));
});
},
/**
* Calculate height of minicart list
*
* @private
*/
_calcHeight: function () {
var self = this,
height = 0,
counter = this.options.minicart.maxItemsVisible,
target = $(this.options.minicart.list),
outerHeight;
self.scrollHeight = 0;
target.children().each(function () {
if ($(this).find(‘.options’).length > 0) {
$(this).collapsible();
}
outerHeight = $(this).outerHeight();
if (counter– > 0) {
height += outerHeight;
}
self.scrollHeight += outerHeight;
});
target.parent().height(height);
}
});
return $.mage.sidebar;
});
[/code]
We can learn how to take joy in the things we create whether they take the form of a fleeting experience or an heirloom that will last for generations
Here is one of the few effective keys to the design problem: the ability of the designer to recognize as many of the constraints as possible