Reputation: 128
I've been searching for several days but I have no answer yet. Basically, I'm trying to replace woocommerce standart "update cart" button with ajax call, which automatically updates order total price when quantity changed. This is my html
<div class="cart_item">
<div class="product-thumbnail">
<a href="http://example.com"><img width="90" height="90" src="path to thumbnail"/></a>
</div>
<div class="product-name">
<a class="cart-page-product__title" href="http://example.com">Product1 name</a>
</div>
<div class="product-quantity">
<div class="quantity">
<input type="number" step="1" name="cart[some_security_key][qty]" value="1" class="input-text qty text" size="4"/>
</div>
</div>
<div class="product-subtotal"><span class="amount">2 000</span></div>
<div class="product-remove">
<a class="product-remove_link" href="http://example.com/?remove_item">×</a>
</div>
</div>
<div class="cart_item">
<div class="product-thumbnail">
<a href="http://example.com"><img width="90" height="90" src="path to thumbnail"/></a>
</div>
<div class="product-name">
<a class="cart-page-product__title" href="http://example.com">Product2 name</a>
</div>
<div class="product-quantity">
<div class="quantity">
<input type="number" step="1" name="cart[some_security_key][qty]" value="1" class="input-text qty text" size="4"/>
</div>
</div>
<div class="product-subtotal"><span class="amount">2 000</span></div>
<div class="product-remove">
<a class="product-remove_link" href="http://example.com/?remove_item">×</a>
</div>
</div>
In functions.php I have a function for updating totals:
public function update_total_price() {
check_ajax_referer( 'update_total_price', 'security' );
if ( ! defined('WOOCOMMERCE_CART') ) {
define( 'WOOCOMMERCE_CART', true );
}
$cart_updated = false;
$cart_totals = isset( $_POST['cart'] ) ? $_POST['cart'] : '';
if ( sizeof( WC()->cart->get_cart() ) > 0 ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
$_product = $values['data'];
// Skip product if no updated quantity was posted
if ( ! isset( $_POST['quantity'] ) ) {
// if ( ! isset( $cart_totals[ $cart_item_key ]['qty'] ) ) {
continue;
}
// Sanitize
$quantity = apply_filters( 'woocommerce_stock_amount_cart_item', apply_filters( 'woocommerce_stock_amount', preg_replace( "/[^0-9\.]/", '', filter_var($_POST['quantity'], FILTER_SANITIZE_NUMBER_INT)) ), $cart_item_key );
// $quantity = apply_filters( 'woocommerce_stock_amount_cart_item', apply_filters( 'woocommerce_stock_amount', preg_replace( "/[^0-9\.]/", '', $cart_totals[ $cart_item_key ]['qty'] ) ), $cart_item_key );
if ( '' === $quantity || $quantity == $values['quantity'] )
continue;
// Update cart validation
$passed_validation = apply_filters( 'woocommerce_update_cart_validation', true, $cart_item_key, $values, $quantity );
// is_sold_individually
if ( $_product->is_sold_individually() && $quantity > 1 ) {
wc_add_notice( sprintf( __( 'You can only have 1 %s in your cart.', 'woocommerce' ), $_product->get_title() ), 'error' );
$passed_validation = false;
}
if ( $passed_validation ) {
WC()->cart->set_quantity( $cart_item_key, $quantity, false );
}
$cart_updated = true;
}
}
// Trigger action - let 3rd parties update the cart if they need to and update the $cart_updated variable
$cart_updated = apply_filters( 'woocommerce_update_cart_action_cart_updated', $cart_updated );
if ( $cart_updated ) {
// Recalc our totals
WC()->cart->calculate_totals();
woocommerce_cart_totals();
exit;
}
}
And Jquery code is:
jQuery( function( $ ) {
// wc_cart_params is required to continue, ensure the object exists
if ( typeof wc_cart_params === 'undefined' ) {
return false;
}
// Cart price update depends on quantity
//$( document ).on( 'click', '.quantity', function() {
$( document ).on( 'change', '.quantity, input[type=number]', function() {
var qty = $( this ).val();
var currentVal = parseFloat( qty);
$( 'div.cart_totals' ).block({ message: null, overlayCSS: { background: '#fff url(' + wc_cart_params.ajax_loader_url + ') no-repeat center', backgroundSize: '16px 16px', opacity: 0.6 } });
var data = {
action: 'rf_update_total_price',
security: rf_cart_params.rf_update_total_price_nonce,
quantity: currentVal
};
$.post( rf_cart_params.ajax_url, data, function( response ) {
$( 'div.cart_totals' ).replaceWith( response );
$( 'body' ).trigger( 'rf_update_total_price' );
});
return false;
});
});
The above code works great if only one product is in the cart.
But when I add some other product and change quantity of one of them my function use last value of quantity for all of my products.
For example, total price for the second image must be 7000(2000*1+2500*2) but it is 9000(2000*2+2500*2). I am new to ajax and jquery so appreciate any help.
Upvotes: 9
Views: 53806
Reputation: 2412
Since WooCommerce 2.6.0, released June 2016, WooCommerce cart page uses Ajax to update cart totals after clicking on Update cart button. WooCommerce 2.6.0 requires WP 4.4 or newer.
No need to worry about backend and creating your own Ajax call, the one assigned to Update cart button can be used.
To get this functionality out of the box, you can use my free plugin, which also has some handy additional options:
Ajax Cart AutoUpdate for WooCommerce
Or use child theme to do the following. Hide Update cart button and then enqueue script which triggers default update cart event on cart page quantity change (works both for cart and mini-cart widget). Use template redirect hook, dependency with jQuery and make sure that this script loads only on cart page.
To hide button with CSS, use class .button instead of tag, to keep it compatible with all WooCommerce versions:
.button[name='update_cart'] {
display: none!important;
}
Or hide button with PHP head style:
add_action('wp_head', 'hide_update_cart_button', 20);
function hide_update_cart_button() {
echo "<style>.button[name='update_cart']{ display: none!important;}</style>";
}
Enqueue script in dependency with jQuery:
add_action( 'template_redirect', 'auto_update_cart_totals' );
function auto_update_cart_totals() {
if (! is_cart() ) return; // Only if it's cart page.
// Enqueue js file.
add_action( 'wp_enqueue_scripts', 'my_cart_autoupdate' );
}
function my_cart_autoupdate( ) {
// Here goes code to hide CSS button if you decide to use PHP solution for styling.
wp_enqueue_script( 'my-cart-autoupdate', 'path-to-js-file-its-name-and-extension', array('jquery'), '', true);
}
The most important and usually overlooked thing about jQuery code is to set update delay, 1000 in this example, my plugin allows to change this value in settings. If user changes quantity again during this delay, it will be reset to full duration. If it's not implemented and you change quantity from 1 to 10 by clicking on increment button, it will trigger 9 Ajax calls instead of 1. Place this in .js file:
var timeout;
jQuery('div.woocommerce').on('change keyup mouseup', 'input.qty', function(){ // keyup and mouseup for Firefox support
if (timeout != undefined) clearTimeout(timeout); //cancel previously scheduled event
if (jQuery(this).val() == '') return; //qty empty, instead of removing item from cart, do nothing
timeout = setTimeout(function() {
jQuery('[name="update_cart"]').trigger('click');
}, 1000 );
});
Upvotes: 0
Reputation: 4782
Here's a simpler way of achieving this. All credit goes to Reigel Gallarde.
// we are going to hook this on priority 31, so that it would display below add to cart button.
add_action( 'woocommerce_single_product_summary', 'woocommerce_total_product_price', 31 );
function woocommerce_total_product_price() {
global $woocommerce, $product;
// let's setup our divs
echo sprintf('<div id="product_total_price" style="margin-bottom:20px;display:none">%s %s</div>',__('Product Total:','woocommerce'),'<span class="price">'.$product->get_price().'</span>');
echo sprintf('<div id="cart_total_price" style="margin-bottom:20px;display:none">%s %s</div>',__('Cart Total:','woocommerce'),'<span class="price">'.$product->get_price().'</span>');
?>
<script>
jQuery(function($){
var price = <?php echo $product->get_price(); ?>,
current_cart_total = <?php echo $woocommerce->cart->cart_contents_total; ?>,
currency = '<?php echo get_woocommerce_currency_symbol(); ?>';
$('[name=quantity]').change(function(){
if (!(this.value < 1)) {
var product_total = parseFloat(price * this.value),
cart_total = parseFloat(product_total + current_cart_total);
$('#product_total_price .price').html( currency + product_total.toFixed(2));
$('#cart_total_price .price').html( currency + cart_total.toFixed(2));
}
$('#product_total_price,#cart_total_price').toggle(!(this.value <= 1));
});
});
</script>
<?php
}
Upvotes: 5
Reputation: 2617
It's because you are updating all your cart, not just a product.
First you need to send the item cart hash (it's not a security hash, but the product hash with all the product variation) on the javascript script:
var item_hash = $( this ).attr( 'name' ).replace(/cart\[([\w]+)\]\[qty\]/g, "$1");
var data = {
action: 'rf_update_total_price',
security: rf_cart_params.rf_update_total_price_nonce,
quantity: currentVal,
hash : item_hash
};
Then you can edit your function update_total_price
, I've simplified ;)
function update_total_price() {
// Skip product if no updated quantity was posted or no hash on WC_Cart
if( !isset( $_POST['hash'] ) || !isset( $_POST['quantity'] ) ){
exit;
}
$cart_item_key = $_POST['hash'];
if( !isset( WC()->cart->get_cart()[ $cart_item_key ] ) ){
exit;
}
$values = WC()->cart->get_cart()[ $cart_item_key ];
$_product = $values['data'];
// Sanitize
$quantity = apply_filters( 'woocommerce_stock_amount_cart_item', apply_filters( 'woocommerce_stock_amount', preg_replace( "/[^0-9\.]/", '', filter_var($_POST['quantity'], FILTER_SANITIZE_NUMBER_INT)) ), $cart_item_key );
if ( '' === $quantity || $quantity == $values['quantity'] )
exit;
// Update cart validation
$passed_validation = apply_filters( 'woocommerce_update_cart_validation', true, $cart_item_key, $values, $quantity );
// is_sold_individually
if ( $_product->is_sold_individually() && $quantity > 1 ) {
wc_add_notice( sprintf( __( 'You can only have 1 %s in your cart.', 'woocommerce' ), $_product->get_title() ), 'error' );
$passed_validation = false;
}
if ( $passed_validation ) {
WC()->cart->set_quantity( $cart_item_key, $quantity, false );
}
// Recalc our totals
WC()->cart->calculate_totals();
woocommerce_cart_totals();
exit;
}
Upvotes: 5