Add Product to Shopping Cart using Ajax

In this blog post, i will show how to add product to shopping cart using ajax, we will use jQuery library for ajax operations

By default, in magento the add to cart process is a simple form submit, so the page get reloaded. It would be much faster if we didn’t have to reload the entire page.
Attached is the source code for this blog’s module.

Module update for 1.9 magento version Download here
Modules for older version also on git
Ok lets start step by step.

Step1: Include jQuery on Product Page

First download jQuery and then place it inside /js/jquery folder, so path would be /js/jquery/jquery.js. Next create a javascript file called noconflict.js in the jquery folder (/js/jquery/noconflict.js) jQuery Folder Structure. Write this code inside noconflict.js file

jQuery.noConflict();

Next open that catalog.xml layout file in your theme folder [app/design/frontend/base/default/layout/catalog.xml in default magento theme] and place this code inside tag <catalog_product_view>

<reference name="head">
        	<action method="addItem"><type>js</type><name>jquery/jquery-1.6.4.min.js</name></action>
        	<action method="addItem"><type>js</type><name>jquery/noconflict.js</name></action>
        </reference>

Now open any product page in your magento, and through firebug or chrome inspector, see if these two jquery files are included in your page.

Step2: Product Page

Next we need to make changes in product page, so that instead of form submit an ajax request is fired. To do this open the catalog/product/view.phtml file in your theme in default magento theme, the path is app\design\frontend\base\default\template\catalog\product\view.phtml
In this file you will find the javascript code as

 productAddToCartForm.submit = function(button, url) {
            if (this.validator.validate()) {
                var form = this.form;
                var oldUrl = form.action;

                if (url) {
                   form.action = url;
                }
                var e = null;
                try {
                    this.form.submit();
                } catch (e) {
                }
                this.form.action = oldUrl;
                if (e) {
                    throw e;
                }

                if (button && button != 'undefined') {
                    button.disabled = true;
                }
            }
        }.bind(productAddToCartForm);

change this code to

 productAddToCartForm.submit = function(button, url) {
            if (this.validator.validate()) {
                var form = this.form;
                var oldUrl = form.action;

                if (url) {
                   form.action = url;
                }
                var e = null;
//Start of our new ajax code
                if(!url){
                    url = jQuery('#product_addtocart_form').attr('action');
                }
                var data = jQuery('#product_addtocart_form').serialize();
                data += '&isAjax=1';	
                jQuery('#ajax_loader').show();
                try {
                    jQuery.ajax({
                    	  url: url,
                    	  dataType: 'json',
                    	  type : 'post',
                    	  data: data,
                    	  success: function(data){
                  	  		    jQuery('#ajax_loader').hide();
                  	  		    alert(data.status + ": " + data.message);
                  	  	  }
                    });
                } catch (e) {
                }
//End of our new ajax code
                this.form.action = oldUrl;
                if (e) {
                    throw e;
                }
            }
        }.bind(productAddToCartForm);

Next to do a little bit of styling go to phtml file catalog/product/view/addtocart.phtml
and then find this code there

<button type="button" title="<?php echo $buttonTitle ?>" class="button btn-cart" onclick="productAddToCartForm.submit(this)"><span><span><?php echo $buttonTitle ?></span></span></button>

change this to

<button type="button" title="<?php echo $buttonTitle ?>" class="button btn-cart" onclick="productAddToCartForm.submit(this)"><span><span><?php echo $buttonTitle ?></span></span></button>
<span id='ajax_loader' style='display:none'><img src='<?php echo $this->getSkinUrl('images/opc-ajax-loader.gif')?>'/></span>

Now open your product page again and when you press the add to cart button, you should see a loading image + ajax request being sent.

Product Page Ajax Loading

Project Page Ajax Loading


Firebug View of Ajax Request

Firebug View of Ajax Request

Step3: Add to cart Controller

Next, we need to change the code at CartController.php in the addAction. Right now, we will directly change the core file, but later will show you how do this using magento best practices.
Open the class Mage_Checkout_CartController located at app\code\core\Mage\Checkout\controllers\CartController.php and find the addAction() function. In the addAction() you have the code,

$params = $this->getRequest()->getParams();

Just after this line place this code

if($params['isAjax'] == 1){
			$response = array();
			try {
				if (isset($params['qty'])) {
					$filter = new Zend_Filter_LocalizedToNormalized(
					array('locale' => Mage::app()->getLocale()->getLocaleCode())
					);
					$params['qty'] = $filter->filter($params['qty']);
				}

				$product = $this->_initProduct();
				$related = $this->getRequest()->getParam('related_product');

				/**
				 * Check product availability
				 */
				if (!$product) {
					$response['status'] = 'ERROR';
					$response['message'] = $this->__('Unable to find Product ID');
				}

				$cart->addProduct($product, $params);
				if (!empty($related)) {
					$cart->addProductsByIds(explode(',', $related));
				}

				$cart->save();

				$this->_getSession()->setCartWasUpdated(true);

				/**
				 * @todo remove wishlist observer processAddToCart
				 */
				Mage::dispatchEvent('checkout_cart_add_product_complete',
				array('product' => $product, 'request' => $this->getRequest(), 'response' => $this->getResponse())
				);

				if (!$this->_getSession()->getNoCartRedirect(true)) {
					if (!$cart->getQuote()->getHasError()){
						$message = $this->__('%s was added to your shopping cart.', Mage::helper('core')->htmlEscape($product->getName()));
						$response['status'] = 'SUCCESS';
						$response['message'] = $message;
					}
				}
			} catch (Mage_Core_Exception $e) {
				$msg = "";
				if ($this->_getSession()->getUseNotice(true)) {
					$msg = $e->getMessage();
				} else {
					$messages = array_unique(explode("\n", $e->getMessage()));
					foreach ($messages as $message) {
						$msg .= $message.'<br/>';
					}
				}

				$response['status'] = 'ERROR';
				$response['message'] = $msg;
			} catch (Exception $e) {
				$response['status'] = 'ERROR';
				$response['message'] = $this->__('Cannot add the item to shopping cart.');
				Mage::logException($e);
			}
			$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($response));
			return;
		}

Save the file and go back to the product page. Now add to cart using ajax should be functional. After clicking add to cart, you see a alert box with success message.

Step4: Update the My Cart Box + Top Links

Now, our ajax add to cart is working. But for the frontend user, the process is not very smooth. He just see a loading image and then an alert box. A better UI, would be if we are able to update the My Cart Box shown on right + the My Cart link shown at header. So lets see how to do that.

Magento Header Top Links
Magento Left Side Bar My Cart Box

For this first we need to change our javascript code we added in the view.phtml file. Change the jQuery.ajax function to

jQuery.ajax({
                    	  url: url,
                    	  dataType: 'json',
                    	  type : 'post',
                    	  data: data,
                    	  success: function(data){
                  	  		    jQuery('#ajax_loader').hide();
                  	  		    //alert(data.status + ": " + data.message);
                  	  		    if(jQuery('.block-cart')){
                  	  		    	jQuery('.block-cart').replaceWith(data.sidebar);
                  	  		    }
	                  	  		if(jQuery('.header .links')){
	              	  		    	jQuery('.header .links').replaceWith(data.toplink);
	              	  		    }
                  	  	  }
                    });

and in our CartController.php addAction() method, put this new code instead of the old code

if($params['isAjax'] == 1){
			$response = array();
			try {
				if (isset($params['qty'])) {
					$filter = new Zend_Filter_LocalizedToNormalized(
					array('locale' => Mage::app()->getLocale()->getLocaleCode())
					);
					$params['qty'] = $filter->filter($params['qty']);
				}

				$product = $this->_initProduct();
				$related = $this->getRequest()->getParam('related_product');

				/**
				 * Check product availability
				 */
				if (!$product) {
					$response['status'] = 'ERROR';
					$response['message'] = $this->__('Unable to find Product ID');
				}

				$cart->addProduct($product, $params);
				if (!empty($related)) {
					$cart->addProductsByIds(explode(',', $related));
				}

				$cart->save();

				$this->_getSession()->setCartWasUpdated(true);

				/**
				 * @todo remove wishlist observer processAddToCart
				 */
				Mage::dispatchEvent('checkout_cart_add_product_complete',
				array('product' => $product, 'request' => $this->getRequest(), 'response' => $this->getResponse())
				);

				if (!$this->_getSession()->getNoCartRedirect(true)) {
					if (!$cart->getQuote()->getHasError()){
						$message = $this->__('%s was added to your shopping cart.', Mage::helper('core')->htmlEscape($product->getName()));
						$response['status'] = 'SUCCESS';
						$response['message'] = $message;
//New Code Here
						$this->loadLayout();
						$toplink = $this->getLayout()->getBlock('top.links')->toHtml();
						$sidebar = $this->getLayout()->getBlock('cart_sidebar')->toHtml();
						$response['toplink'] = $toplink;
						$response['sidebar'] = $sidebar;
					}
				}
			} catch (Mage_Core_Exception $e) {
				$msg = "";
				if ($this->_getSession()->getUseNotice(true)) {
					$msg = $e->getMessage();
				} else {
					$messages = array_unique(explode("\n", $e->getMessage()));
					foreach ($messages as $message) {
						$msg .= $message.'<br/>';
					}
				}

				$response['status'] = 'ERROR';
				$response['message'] = $msg;
			} catch (Exception $e) {
				$response['status'] = 'ERROR';
				$response['message'] = $this->__('Cannot add the item to shopping cart.');
				Mage::logException($e);
			}
			$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($response));
			return;
		}

Now, when you click on AddtoCart button, instead of alert, the MyCart Box + Top Links will get updated. So now the user experience is much better.

Proper Way to do Step3

Now going back to step3, as per magento best practices we should not change any core files. So, what we will do is, create a new module called Excellence_Ajax using module creator and then in the IndexController.php add an action addAction() and extend of controller with Mage_Checkout_CartController. So our class declaration becomes

class Excellence_Ajax_IndexController extends Mage_Checkout_CartController

Now we will change our javascript code at view.phtml so, that our new controller is called instead of magento’s default cart controller. So there is the new javascript code.

if(!url){
       url = jQuery('#product_addtocart_form').attr('action');
}
url = url.replace("checkout/cart","ajax/index"); // New Code

So we are just replacing checkout/cart with our controller path.
Next in the addAction we will write the exact same code, except in the else condition we will call the parent::addAction() function.

public function addAction()
	{
		$cart   = $this->_getCart();
		$params = $this->getRequest()->getParams();
		if($params['isAjax'] == 1){
                   ....same code as above
                }else{
                   return parent::addAction();
                }
         }

Bug Reported
1. The cart side bar that shows up, after adding product using ajax. The ‘x’ or remove button doesn’t work. [This bug has been fixed and module files have been updated.]
The above code has been tested for all product types in Magneto 1.6 version