What is AJAX?

Ajax is a client-side script that communicates to and from a server/database without the need for the page to reload.

Ajax is fun and a great asset to a developer because it allows us to:
Update a web page without reloading the page
Send data to the server in the background

I will explain how to add AJAX add to cart on your Woocommerce site.

Below is a gif of what ajax add to cart does.

Enqueuing the Javascript

The first step is to add a javascript script to your site, to do this add the following to your functions.php

function bodycommerce_ajax_add_to_cart_script() {
wp_enqueue_script( 'bodycommerce-add-to-cart-ajax', get_stylesheet_directory() . '/js/add-to-cart-ajax.js', array('jquery'), '', true );
}
add_action( 'wp_enqueue_scripts', 'bodycommerce_ajax_add_to_cart_script',99 );

After this create a new folder in your child theme called “js” and in that add a new javascript script called “add-to-cart-ajax.js”.

Javascript side of the AJAX add to cart

The main asset to ajax is that you dont need to reload the page to send data to the database, so the first step in the js creation is to prevent the page to reload.

jQuery( function( $ ) {
 
$( document ).on( 'click', '.single_add_to_cart_button', function(e) {
e.preventDefault();
 
});
});

The above code prevents the button to send the information to the database and reload the page.

So now I have disabled the ability to send information to the database, how do we add a product to the cart? Well, we use jQuery to gather information from the site.

The information we need are as follows:
Product ID
Quantity
Variation ID (if variable product)
Variation Item (if variable product)

So how do we get this? Well with a variable product it is quite straightforward as there are hidden inputs with all this information for example

<input name="variation_id" class="variation_id" value="0" type="hidden">

would give the value for the Variation ID

So to get all the information we need for a variable product we use:

$variation_form = $( this ).closest( '.variations_form' );
var product_id = $variation_form.find( 'input[name=product_id]' ).val();
var quantity = $variation_form.find( 'input[name=quantity]' ).val();
var var_id = $variation_form.find( 'input[name=variation_id]' ).val();

Variation Item is a bit more complicated but will get there in the bulk code.

To get the information for a single product is not that easy as there are no hidden inputs to help us out 🙁 So i have created a hack that works just fine. What we are doing is creating the hidden inputs and using jquery to fill the values with the product ID and quantity as that all we need for a single product.

To do this create a new folder in your child theme called “woocommerce” in that folder copy the “content-single-product.php” file from the woocommerce plugin and paste it in this folder. This will now overwrite the default Woocommerce product page without causing issues when Woocommerce updates. Add the following html for the two hidden inputs.

<!--?php $id = $product--->get_id(); ?&gt;
<input type="hidden" value="<?php echo $id ?>" class="product_id">
<input type="hidden" value="1" class="product_quantity">

The above code will automatically add the product ID in as the value, but for the quantity we need to be a bit more clever as the customer might change the quantity, so how do we find this out? Well… jQuery to the rescue yet again.

$(".input-text.qty.text").bind('keyup mouseup', function () {
    var value = $(this).val();
$(".product_quantity").val(value)
});

The above code looks for if the user changes the quantity and then updates the hidden quantity input we have created with the amount they choose.

Now we have our hidden values, how do we get this in and send it to the database?

I am not going to run through exactly what every line of code means on the full code snipet but the key part where it gathers the information – this is:

var data = {
action: 'bodycommerce_ajax_add_to_cart_woo',
product_id: product_id,
quantity: quantity,
variation_id: var_id,
variation: item
};

As you can see it is getting the varibles we created earlier and adds them into the variable called “data”. The key part here to understand is the action ”bodycommerce_ajax_add_to_cart_woo’ – this basically calls a php function which I will go into more detail below.

The full javascript code you need to add is:

jQuery( function( $ ) {

	$(".single_add_to_cart_button").addClass("ajax_add_to_cart");

	$( ".post-type-archive-product" ).on( "click", ".quantity input", function() {
		return false;
		});

	$( ".archive" ).on( "change input", ".quantity .qty", function() {
			var add_to_cart_button = $( this ).parents( ".product" ).find( ".add_to_cart_button" );
			// For AJAX add-to-cart actions
			add_to_cart_button.data( "quantity", $( this ).val() );
			// For non-AJAX add-to-cart actions
			add_to_cart_button.attr( "href", "?add-to-cart=" + add_to_cart_button.attr( "data-product_id" ) + "&quantity=" + $( this ).val() );
		});

		$(".input-text.qty.text").bind('keyup mouseup', function () {
			var value = $(this).val();
	    $(".product_quantity").val(value)
		});

		if ( typeof wc_add_to_cart_params === 'undefined' )
			return false;

		$( document ).on( 'click', '.ajax_add_to_cart', function(e) {
			e.preventDefault();
			var $thisbutton = $(this);			
			var $variation_form = $( this ).closest( '.variations_form' );
			var var_id = $variation_form.find( 'input[name=variation_id]' ).val();
			$( '.ajaxerrors' ).remove();
			var item = {},
				check = true;
				variations = $variation_form.find( 'select[name^=attribute]' );
				if ( !variations.length) {
					variations = $variation_form.find( '[name^=attribute]:checked' );
				}
				if ( !variations.length) {
	    			variations = $variation_form.find( 'input[name^=attribute]' );
				}
			variations.each( function() {
				var $this = $( this ),
					attributeName = $this.attr( 'name' ),
					attributevalue = $this.val(),
					index,
					attributeTaxName;
					$this.removeClass( 'error' );
				if ( attributevalue.length === 0 ) {
					index = attributeName.lastIndexOf( '_' );
					attributeTaxName = attributeName.substring( index + 1 );
					$this
						.addClass( 'required error' )
						.before( '<div class="ajaxerrors"><p>Please select ' + attributeTaxName + '</p></div>' )
					check = false;
				} else {
					item[attributeName] = attributevalue;
				}
			} );
			if ( !check ) {
				return false;
			}

			if ( $thisbutton.is( '.ajax_add_to_cart' ) ) {
				$thisbutton.removeClass( 'added' );
				$thisbutton.addClass( 'loading' );
				if ($( this ).parents(".variations_form")[0]){
				var product_id = $variation_form.find('input[name=product_id]').val();
				var quantity = $variation_form.find( 'input[name=quantity]' ).val();
                                var data = {
                                action: 'bodycommerce_ajax_add_to_cart_woo',
                                product_id: product_id,
                                quantity: quantity,
                                variation_id: var_id,
                                variation: item
                                };
				    }
				else {
				var product_id = $(this).parent().find(".product_id").val();
				var quantity = $(this).parent().find(".qty").val();
				var data = {
				action: 'bodycommerce_ajax_add_to_cart_woo_single',
				product_id: product_id,
				quantity: quantity
				};
				}

				$( 'body' ).trigger( 'adding_to_cart', [ $thisbutton, data ] );
				$.post( wc_add_to_cart_params.ajax_url, data, function( response ) {
					if ( ! response )
						return;
					var this_page = window.location.toString();
					this_page = this_page.replace( 'add-to-cart', 'added-to-cart' );
					if ( response.error && response.product_url ) {
						window.location = response.product_url;
						return;
					}
					if ( wc_add_to_cart_params.cart_redirect_after_add === 'yes' ) {
						window.location = wc_add_to_cart_params.cart_url;
						return;
					} else {
						$thisbutton.removeClass( 'loading' );
						var fragments = response.fragments;
						var cart_hash = response.cart_hash;
						if ( fragments ) {
							$.each( fragments, function( key ) {
								$( key ).addClass( 'updating' );
							});
						}
		$( '.shop_table.cart, .updating, .cart_totals' ).fadeTo( '400', '0.6' ).block({
							message: null,
							overlayCSS: {
								opacity: 0.6
							}
						});
						$thisbutton.addClass( 'added' );
						if ( fragments ) {
							$.each( fragments, function( key, value ) {
								$( key ).replaceWith( value );
							});
						}
						$( '.widget_shopping_cart, .updating' ).stop( true ).css( 'opacity', '1' ).unblock();
						$( '.shop_table.cart' ).load( this_page + ' .shop_table.cart:eq(0) &gt; *', function() {
						$( '.shop_table.cart' ).stop( true ).css( 'opacity', '1' ).unblock();
						$( document.body ).trigger( 'cart_page_refreshed' );
						});
						$( '.cart_totals' ).load( this_page + ' .cart_totals:eq(0) &gt; *', function() {
						$( '.cart_totals' ).stop( true ).css( 'opacity', '1' ).unblock();
					});
					}
				});
				return false;
			} else {
				return true;
			}
		});
	});

As you can see above we have two seperate functions being called depening on if it is a single or a variable product – so we need to add two functions.

PHP side of AJAX add to cart

Go back to your functions.php file and add the following (which will update the cart with the information recieved by the jQuery file)

// VARIATION CALLBACK
	add_action( 'wp_ajax_bodycommerce_ajax_add_to_cart_woo', 'bodycommerce_ajax_add_to_cart_woo_callback' );
	add_action( 'wp_ajax_nopriv_bodycommerce_ajax_add_to_cart_woo', 'bodycommerce_ajax_add_to_cart_woo_callback' );

	function bodycommerce_ajax_add_to_cart_woo_callback() {

		ob_start();

		$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
    $quantity = empty( $_POST['quantity'] ) ? 1 : apply_filters( 'woocommerce_stock_amount', $_POST['quantity'] );
    // $product_quantity = $_POST['product_quantity'];
		$variation_id = $_POST['variation_id'];
		$variation  = $_POST['variation'];


      error_log("Variation Product", 0);
      $passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity, $variation_id, $variation  );

      if ( $passed_validation && WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variation  ) ) {
        do_action( 'woocommerce_ajax_added_to_cart', $product_id );
        if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
          wc_add_to_cart_message( $product_id );
        }

        // Return fragments
        WC_AJAX::get_refreshed_fragments();
      }  else  {
        // $this->json_headers(); // REMOVED AS WAS THROWING AN ERROR

        // If there was an error adding to the cart, redirect to the product page to show any errors
        $data = array(
          'error' => true,
          'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id  )
          );
        echo json_encode( $data );
      }

    		die();
	}

add_action( 'wp_ajax_bodycommerce_ajax_add_to_cart_woo_single', 'bodycommerce_ajax_add_to_cart_woo_single_callback' );
add_action( 'wp_ajax_nopriv_bodycommerce_ajax_add_to_cart_woo_single', 'bodycommerce_ajax_add_to_cart_woo_single_callback' );
function bodycommerce_ajax_add_to_cart_woo_single_callback() {
  ob_start();
  $product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
  $quantity = empty( $_POST['quantity'] ) ? 1 : apply_filters( 'woocommerce_stock_amount', $_POST['quantity'] );
error_log("Simple Product", 0);
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );

if ( $passed_validation && WC()->cart->add_to_cart( $product_id, $quantity  ) ) {
do_action( 'woocommerce_ajax_added_to_cart', $product_id );
if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
  wc_add_to_cart_message( $product_id );
}

// Return fragments
WC_AJAX::get_refreshed_fragments();
}  else  {
$this->json_headers();

// If there was an error adding to the cart, redirect to the product page to show any errors
$data = array(
  'error' => true,
  'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
  );
echo json_encode( $data );
}

    		die();
	}

Wrapping it up

If you have added it all correctly it will work well for you and your site will now have ajax add to cart.

If you not that code-savy or prefer using a plugin for this we have created a plugin called Divi BodyCommerce which does all this for you as well as many other changes such as customising your email templates and using a mini cart with your Divi website.

Read More About BodyCommerce

We specilise in the Divi theme, however this code will work on all themes

Please give us a shout if you have any questions.