20

I want to make an "Add To Cart" button on a product page that would work with AJAX. How can I do it? When I add to cart on a product page - it refreshes the page, how can I make it work by AJAX?

The "Add to cart" button on "Quick View" on archive works by ajax - and it's great, but how can I do the same on product page?

I want to click on "Take me Home" on the product page which would then add the product with the selected attributes to my cart by ajax and will open that cart (like when you hover onto the bag image on menu) and shakes the bag image.

Tom
  • 305
  • 1
  • 3
  • 6

12 Answers12

28

Just add the following attributes to the Add to Cart button to enable the Ajax button.

<a href="<?php echo $product->add_to_cart_url() ?>" value="<?php echo esc_attr( $product->get_id() ); ?>" class="ajax_add_to_cart add_to_cart_button" data-product_id="<?php echo get_the_ID(); ?>" data-product_sku="<?php echo esc_attr($sku) ?>" aria-label="Add “<?php the_title_attribute() ?>” to your cart"> 
Add to Cart 
</a>

The ajax_add_to_cart add_to_cart_button classes, and the data-product_id="<?php echo get_the_ID(); ?>" data-product_sku="<?php echo esc_attr($sku) ?>" attributes are required.

No need to apply any action or filter.

Eh Jewel
  • 619
  • 8
  • 14
  • 1
    To use with a variation product, use the item's variation ID and sku. – Jeff Wilkerson Jun 21 '20 at 22:27
  • 1
    Specify a data-quantity attribute to pass in quantity – Jeff Wilkerson Jun 21 '20 at 22:33
  • 1
    Best answer, I like the way you just use the WooCommerce way of doing it. – Loosie94 Jun 25 '21 at 08:51
  • use $product->get_sku() if you don't have $sku available – Richard Sep 15 '21 at 13:47
  • 1
    It seems data-product_sku attribute is not mandatory. – Mahesh Samudra Oct 27 '21 at 17:02
  • @JeffWilkerson please can you expand on the use of this code with a variation product, this would be an ideal fix for me but unfortunately the code above is not passing variations to the cart – van Mar 09 '22 at 14:29
  • For Variable products I pass the "data-product_id" (variation sku), "value" (variation sku) and "data-quantity" via jQuery when user makes a selection/change. example: `code` var varid = $( '.variation_id' ).attr('value'); $('#ajaxadd').removeClass('disabled').attr( 'data-product_id', varid ).attr( 'value', varid ); `code` This code runs on variation input select with a needed timeout (woocommerce .variation_id element value changes later). – Petros Mar 29 '22 at 09:04
  • For the "data-quantity" of a variable product I use: `code` $('.qty').on('input change', function() { $('#ajaxadd').attr( 'data-quantity', $( this ).val() ); }); `code` – Petros Mar 29 '22 at 09:15
18

We can use ajax from archive page. it's easy -

Remove old button which submiting form:

remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_add_to_cart', 30 );

Add ajax-link from archive page to single product page:

add_action( 'woocommerce_single_product_summary', 'woocommerce_template_loop_add_to_cart', 30 );

P.S. JS Callback. For example you can show popup with links "Back to shop" and "Cart" or "Checkout"

$('body').on('added_to_cart',function(){
    // Callback -> product added
    //$('.popup').show();
});
andrewsi
  • 10,807
  • 132
  • 35
  • 51
anatoliy_kot
  • 181
  • 1
  • 5
  • (1) The JS callback does not fire for me even though the AJAX add-to-cart behavior works. (2) When I use the WooCommerce Products Add-On plugin to add a global form to all products, it breaks your solution. The single page Add to Cart button reverts to HTTP, and the global form does not display. – Adam Friedman Aug 15 '16 at 16:25
  • A big problem with this solution is that the loop button returns a read more link if the product ist not available. If you click on such a product there is a read more button again ... some kind of a bug. – GDY Aug 26 '16 at 13:08
  • It removes quantity box for me. – FatalError Sep 06 '17 at 10:24
  • 7
    Not to mention this is completely useless with variable products – Z. Zlatev Aug 12 '18 at 20:33
9

Please start by reading this page:

http://codex.wordpress.org/Plugin_API/Action_Reference/wp_ajax_(action)

First you need to add some code to your functions.php for example:

add_action( 'wp_ajax_add_foobar', 'prefix_ajax_add_foobar' );
add_action( 'wp_ajax_nopriv_add_foobar', 'prefix_ajax_add_foobar' );

function prefix_ajax_add_foobar() {
   $product_id  = intval( $_POST['product_id'] );
// add code the add the product to your cart
die();
}

Then you have to add some javascript code that triggers the add to cart and makes a call to the function:

  jQuery( ".add-to-cart" ).each(function() 
{


    var product_id = jQuery(this).attr('rel');
    var el = jQuery(this);

    el.click(function() {

            var data = {
                action: 'add_foobar',
                product_id: product_id
            };

            jQuery.post('/wp-admin/admin-ajax.php' , data, function(response) {
                if(response != 0) {
                    // do something
                } else {
                    // do something else
                }

            });


        return false;

    });

});

This is just an example of how it can be done. Although its very basic. This javascript checks for links with the classname .add-to-cart and checks the rel attribute for the corresponding product. It then sends the product id to the php class. There you need to add code to add the corresponding product to the cart.

I suggest you search some more about the topic to make it suit your needs. Good luck.

Menno Evertzen
  • 101
  • 1
  • 4
  • Thanks, can you be more specific please? Because I have no idea what I need to add... Can you please help me with that? I didn't change anything - so it works with the classic code of woocommerce, can you give me a code which will fix my problem? Thanks, Tom. – Tom Sep 26 '14 at 14:11
5

Woocommerce has come along way. I think the solution is quite easy now. In case I am missing something, all that is required it checking the "Enable AJAX add to cart buttons on archives" and using the woocommerce_template_loop_add_to_cart() function.

The checkbox option is under Woocommerce > Settings > Products > General. The checkbox option is under Woocommerce > Settings > Products > General.

Then simply use woocommerce_template_loop_add_to_cart() wherever you wish to output the button.

If you are using a custom loop like I was, you need to be sure to make the product global in order for the woocommerce_template_loop_add_to_cart() to work.

Below is a small example of using the function:

add_shortcode( 'buy_again' , 'msp_buy_again_shortcode' );
function msp_buy_again_shortcode(){
    $order_items = msp_get_customer_unique_order_items( get_current_user_id() );
    echo '<div class="owl-carousel owl-theme">';
    foreach( $order_items as $id ){
        $product = wc_get_product( $id );
        global $product;

        if( ! empty( $product ) ){
            ?>
            <div class="card buy-again-product">
                <a class="link-normal" href="<?php echo $product->get_permalink(); ?>">
                    <?php echo $product->get_image( 'woocommerce_thumbnail', array( 'class' => 'card-img-top' ) ) ?>
                    <div class="card-body">
                        <?php echo wc_get_rating_html( $product->get_average_rating(), $product->get_review_count() ) ?>
                        <h5><?php echo $product->get_name(); ?></h5>
                        <p><?php echo $product->get_price_html() ?></p>
                        <?php woocommerce_template_loop_add_to_cart(); ?>
                    </div>
                </a>
            </div>
            <?php
        }
    }
    // loop and display buy again.
    // try to use the official woocommerce loop.

    echo '</div>';

}
gregbast1994
  • 432
  • 6
  • 16
2

Copy this code into your file. For example: my-theme-wc-single-ajax-add-cart.js.

function myThemeWc_SingleProductAddToCart(thisObj) {
    if (typeof($) === 'undefined') {
        var $ = jQuery.noConflict();
    }

    var thisForm = thisObj.closest('form');
    var button = thisForm.find('.button');
    var formUrl = thisForm.attr('action');
    var formMethod = thisForm.attr('method');
    if (typeof(formMethod) === 'undefined' || formMethod == '') {
        formMethod = 'POST';
    }
    var formData = new FormData(thisForm[0]);
    formData.append(button.attr('name'), button.val());

    button.removeClass('added');
    button.addClass('loading');

    myThemeWc_SingleProductCartAjaxTask = $.ajax({
        url: formUrl,
        method: formMethod,
        data: formData,
        cache: false,
        contentType: false,
        processData: false
    })
    .done(function(data, textStatus, jqXHR) {
        $(document.body).trigger('wc_fragment_refresh');

        $.when(myThemeWc_SingleProductCartAjaxTask)
        .then(myThemeWc_SingleProductUpdateCartWidget)
        .done(function() {
            button.removeClass('loading');
            button.addClass('added');
            setTimeout(function() {
                button.removeClass('added');
            }, 2000);
        });
    })
    .fail(function(jqXHR, textStatus, errorThrown) {
        button.removeClass('loading');
    })
    .always(function(jqXHR, textStatus, errorThrown) {
        $('.cart').off('submit');
        myThemeWc_SingleProductListenAddToCart();
    });
}// myThemeWc_SingleProductAddToCart


function myThemeWc_SingleProductListenAddToCart() {
    if (typeof($) === 'undefined') {
        var $ = jQuery.noConflict();
    }

    $('.cart').on('submit', function(e) {
        e.preventDefault();
        myThemeWc_SingleProductAddToCart($(this));
    });
}// myThemeWc_SingleProductListenAddToCart


/**
 * Update WooCommerce cart widget by called the trigger and listen to the event.
 * 
 * @returns {undefined}
 */
function myThemeWc_SingleProductUpdateCartWidget() {
    if (typeof($) === 'undefined') {
        var $ = jQuery.noConflict();
    }

    var deferred = $.Deferred();

    $(document.body).on('wc_fragments_refreshed', function() {
        deferred.resolve();
    });

    return deferred.promise();
}// myThemeWc_SingleProductUpdateCartWidget


var myThemeWc_SingleProductCartAjaxTask;


// on page load --------------------------------------------
jQuery(function($) {
    $(document.body).on('wc_fragments_refreshed', function() {
        console.log('woocommerce event fired: wc_fragments_refreshed');
    });

    myThemeWc_SingleProductListenAddToCart();
});

You may need to replace function, variable prefix myThemeWc_ to what you want.

This code use the original WooCommerce single product page add to cart button but stop its functional and then use ajax instead by remain all the values in the form.

Then enqueue this js file.

add_action('wp_enqueue_scripts', 'mythemewc_enqueue_scripts');
function mythemewc_enqueue_scripts() {
    if (class_exists('\\WooCommerce') && is_product()) {
        wp_enqueue_script('mythemewc-single-product', trailingslashit(get_stylesheet_directory_uri()) . 'assets/js/my-theme-wc-single-ajax-add-cart.js', ['jquery'], false, true);
    }
}

You may have to code your css button to make it show the loading, added icon.
Here is css.

.woocommerce #respond input#submit.added::after, 
.woocommerce a.btn.added::after, 
.woocommerce button.btn.added::after, 
.woocommerce input.btn.added::after,
.woocommerce .single_add_to_cart_button.added::after {
    font-family: WooCommerce;
    content: '\e017';
    margin-left: .53em;
    vertical-align: bottom;
}
.woocommerce #respond input#submit.loading, 
.woocommerce a.btn.loading, 
.woocommerce button.btn.loading, 
.woocommerce input.btn.loading,
.woocommerce .single_add_to_cart_button.loading {
    opacity: .25;
    padding-right: 2.618em;
    position: relative;
}
.woocommerce #respond input#submit.loading::after, 
.woocommerce a.btn.loading::after, 
.woocommerce button.btn.loading::after, 
.woocommerce input.btn.loading::after,
.woocommerce .single_add_to_cart_button.loading::after {
    font-family: WooCommerce;
    content: '\e01c';
    vertical-align: top;
    font-weight: 400;
    position: absolute;
    right: 1em;
    -webkit-animation: spin 2s linear infinite;
    animation: spin 2s linear infinite;
}
vee
  • 4,506
  • 5
  • 44
  • 81
2

You can replicate the behavour of the archives button in your single products

add_action('wp_ajax_woocommerce_ajax_add_to_cart', 'woocommerce_ajax_add_to_cart');
add_action('wp_ajax_nopriv_woocommerce_ajax_add_to_cart', 'woocommerce_ajax_add_to_cart');      function woocommerce_ajax_add_to_cart() {
            $product_id = apply_filters('woocommerce_add_to_cart_product_id', absint($_POST['product_id']));
            $quantity = empty($_POST['quantity']) ? 1 : wc_stock_amount($_POST['quantity']);
            $variation_id = absint($_POST['variation_id']);
            $passed_validation = apply_filters('woocommerce_add_to_cart_validation', true, $product_id, $quantity);
            $product_status = get_post_status($product_id);

            if ($passed_validation && WC()->cart->add_to_cart($product_id, $quantity, $variation_id) && 'publish' === $product_status) {

                do_action('woocommerce_ajax_added_to_cart', $product_id);

                if ('yes' === get_option('woocommerce_cart_redirect_after_add')) {
                    wc_add_to_cart_message(array($product_id => $quantity), true);
                }

                WC_AJAX :: get_refreshed_fragments();
            } else {

                $data = array(
                    'error' => true,
                    'product_url' => apply_filters('woocommerce_cart_redirect_after_error', get_permalink($product_id), $product_id));

                echo wp_send_json($data);
            }

            wp_die();
        }
add_action('wp_ajax_woocommerce_ajax_add_to_cart', 'woocommerce_ajax_add_to_cart');
add_action('wp_ajax_nopriv_woocommerce_ajax_add_to_cart', 'woocommerce_ajax_add_to_cart');

function woocommerce_ajax_add_to_cart() {

            $product_id = apply_filters('woocommerce_add_to_cart_product_id', absint($_POST['product_id']));
            $quantity = empty($_POST['quantity']) ? 1 : wc_stock_amount($_POST['quantity']);
            $variation_id = absint($_POST['variation_id']);
            $passed_validation = apply_filters('woocommerce_add_to_cart_validation', true, $product_id, $quantity);
            $product_status = get_post_status($product_id);

            if ($passed_validation && WC()->cart->add_to_cart($product_id, $quantity, $variation_id) && 'publish' === $product_status) {

                do_action('woocommerce_ajax_added_to_cart', $product_id);

                if ('yes' === get_option('woocommerce_cart_redirect_after_add')) {
                    wc_add_to_cart_message(array($product_id => $quantity), true);
                }

                WC_AJAX :: get_refreshed_fragments();
            } else {

                $data = array(
                    'error' => true,
                    'product_url' => apply_filters('woocommerce_cart_redirect_after_error', get_permalink($product_id), $product_id));

                echo wp_send_json($data);
            }

            wp_die();
        }

you can see the full tutorial here

https://quadmenu.com/add-to-cart-with-woocommerce-and-ajax-step-by-step/

Kanewilliam
  • 199
  • 2
  • 18
1

info: Tested with WooCommerce 2.4.10.

Hmm, well I did it in another way, used the woocommerce loop from add to cart (woocommerce/templates/loop/add-to-cart.php)

  global $product;

echo apply_filters( 'woocommerce_loop_add_to_cart_link',
    sprintf( '<a href="%s" rel="nofollow" data-product_id="%s" data-product_sku="%s" data-quantity="%s" class="button %s product_type_%s">%s</a>',
        esc_url( $product->add_to_cart_url() ),
        esc_attr( $product->id ),
        esc_attr( $product->get_sku() ),
        esc_attr( isset( $quantity ) ? $quantity : 1 ),
        $product->is_purchasable() && $product->is_in_stock() ? 'add_to_cart_button' : '',
        esc_attr( $product->product_type ),
        esc_html( $product->add_to_cart_text() )
    ),
$product );

BUT the problem was that it was adding just 1 quantity, in fact, you can see in the code that is listed quantity : 1, so I had problems, until I bumped into this guys who saved me

ps. leaving the 1st part where it adds just 1 product for people who don't need more than 1 product in the basket, but I added a solution for those who need more than just 1 product added to the cart.

Koorosh
  • 302
  • 1
  • 5
  • 15
RwkY
  • 80
  • 7
1

I used this plugin for the backend part https://wordpress.org/plugins/woo-ajax-add-to-cart/

I'm not using the product page, so I modified the script to work with any button:

(function($) {
    $(document).on('click', '.btn', function(e) {
        var $thisbutton = $(this);

        try {
            var href = $thisbutton.prop('href').split('?')[1];

            if (href.indexOf('add-to-cart') === -1) return;
        } catch (err) {
            return;
        }

        e.preventDefault();

        var product_id = href.split('=')[1];

        var data = {
            product_id: product_id
        };

        $(document.body).trigger('adding_to_cart', [$thisbutton, data]);

        $.ajax({
            type: 'post',
            url: wc_add_to_cart_params.wc_ajax_url.replace(
                '%%endpoint%%',
                'add_to_cart'
            ),
            data: data,
            beforeSend: function(response) {
                $thisbutton.removeClass('added').addClass('loading');
            },
            complete: function(response) {
                $thisbutton.addClass('added').removeClass('loading');
            },
            success: function(response) {
                if (response.error & response.product_url) {
                    window.location = response.product_url;
                    return;
                } else {
                    $(document.body).trigger('added_to_cart', [
                        response.fragments,
                        response.cart_hash
                    ]);

                    $('a[data-notification-link="cart-overview"]').click();
                }
            }
        });

        return false;
    });
})(jQuery);
  • Hi @Alecu, the script you provided is working but it has an error on the console log. It says "$thisbutton is not defined" –  Oct 26 '21 at 15:12
0

add-to-cart.js

jQuery( document ).on( 'click', '.product_type_simple', function() { 

var post_id = jQuery(this).data('product_id');//store product id in post id variable
var qty = jQuery(this).data('quantity');//store quantity in qty variable

jQuery.ajax({
    url : addtocart.ajax_url, //ajax object of localization
    type : 'post', //post method to access data
    data : 
    {
        action : 'prefix_ajax_add_foobar', //action on prefix_ajax_add_foobar function
        post_id : post_id,
        quantity: qty
    },

    success : function(response){

            jQuery('.site-header .quantity').html(response.qty);//get quantity
            jQuery('.site-header .total').html(response.total);//get total

            //loaderContainer.remove();
            alert("Product Added successfully..");        
    }

});

return false;
});
cske
  • 2,233
  • 4
  • 26
  • 24
0

To make ajax woocomerce work on another page you will need. goes in functions.php

paste this:

remove_action ('woocommerce_single_product_summary', 'woocommerce_template_single_add_to_cart', 30);

add_action ('woocommerce_single_product_summary', 'woocommerce_template_loop_add_to_cart', 30);

in footer.php in the end paste this:

<script>
$ ('body'). on ('add_to_cart', function () {
     // Callback -> product added
     // $ ('. popup'). show ();
});
</script>
0

For me working exelent. I think this is the simplest solution with Ajax.

<a href="<?php echo $product->add_to_cart_url() ?>" value="<?php echo esc_attr( $product->get_id() ); ?>" class="ajax_add_to_cart add_to_cart_button add-cart" data-product_id="<?php echo get_the_ID(); ?>" data-product_sku="<?php echo esc_attr($sku) ?>"> + Add to cart</a>
Adrian
  • 379
  • 4
  • 10
0

I used Eh Jewel's answer but then added some custom JS for once the product was added to the cart. This completes the final AJAX process of being able to update the page however you want.

For completeness, here is the HTML code I used (same as Eh Jewel's):

<a href="<?php echo $product->add_to_cart_url() ?>" value="<?php echo esc_attr( $product->get_id() ); ?>" class="ajax_add_to_cart add_to_cart_button" data-product_id="<?php echo get_the_ID(); ?>" data-product_sku="<?php echo esc_attr($sku) ?>" aria-label="Add “<?php the_title_attribute() ?>” to your cart"> Add to Cart </a>

Then for the custom JS bit:

$(window).on('load', function() {
  $('body').on( 'added_to_cart', function( added_to_cart, cart, cart_hash, button ){
    // Put the functionality here. Debugging shows you what you have to work with:
    console.log('added_to_cart object');
    console.log(added_to_cart);
    console.log('cart object');
    console.log(cart);
    console.log('cart_hash object');
    console.log(cart_hash);
    console.log('button object');
    console.log(button);
  });
});
Brian KD
  • 71
  • 1
  • 4