Magento and jQuery: Ajax Add to Cart Button

Introduction

This post describes adding an Ajax add-to-cart feature on my Magento’s product view page. This was an interesting exercise, and I learned a lot from a number of different sources. I combined their collected wisdom, and applied it as described here. Some of that wisdom was my own. You’ll need to bring in jQuery and jQuery UI as described in my previous post, Magento and jQuery.

Goal

Provide add to cart button that will give us the familiar ajax modal feel while it’s working, and then let us know it’s added the item. Then, present the user with the option of staying on the current page, or proceeding to the checkout page.

Prep Work I: Reflect the Correct number of Items in “My Cart”

In order for our work to correctly update the “My Cart” link in the header, I needed to add identification information to that section. You can see in the highlighted lines of code from my template/page/html/header.phtml. I applied a style to the whole thing in case the designers wanted to improve its appearance. And then I added div around only the number itself:

template/page/html/header.phtml

<div class="up-top-cart-status">
    <a href="<?php echo $this->getUrl('checkout/cart'); ?>"><img src="<?php echo $this->getSkinUrl(); ?>images/cart-icon.png" alt="Cart" />My Order
        <?php
    $cart = Mage::getModel('checkout/cart')->getQuote()->getData();
    $cart_qty = (!empty($cart['items_qty'])) ? $cart['items_qty'] : 0;
    printf('(<div style="display:inline" id="up-top-number-in-cart">%d</div> Items)', $cart_qty);
          ?>
     </a>
</div>

Prep Work II: Build the php Code for the Ajax Call

This code was based on work found at iFuel Interactive’s Adding to the Cart with a jQuery Ajax Call in Magento. I’ve modified the code to use php’s json_encode() function. I believe that, as it’s php, we should work in php’s milieu instead of handcrafting the JSON ourselves.

Of special note, I am calling getSummaryCount after I’ve saved the cart. This call did not yield the correct number of items in the cart until after I’d saved it. This way, I’m able to put the current number of items into the JSON returned back to the caller.

/mxwest_atc.php

<?php
include_once 'app/Mage.php';
Mage::app();
try{
    $result = array();
    // usage /mxwest_atc.php?product_id=838qty=1

    if(!isset($_GET['sku'])) {
        $sku = '';
    }
    else {
        $sku = $_GET['sku'];
    }

    if(!isset($_GET['product_id'])) {
        $product_id = '';
    }
    else {
        $product_id = $_GET['product_id'];
    }
    if(!isset($_GET['qty'])) {
        $qty = '1';
    }
    else { 
        $qty = $_GET['qty'];
    }

    if ($sku != ""){
        $product_id = Mage::getModel('catalog/product')->getIdBySku("$sku");
        if ($product_id == '') {
            $session->addError("<strong>Product Not Added</strong>The SKU you entered ($sku) was not found.");
        }
    }

    $request = Mage::app()->getRequest();
    $product = Mage::getModel('catalog/product')->load($product_id);
    $session = Mage::getSingleton('core/session', array('name'=>'frontend'));
    $cart = Mage::helper('checkout/cart')->getCart();

    $cart->addProduct($product, $qty);

    $session->setLastAddedProductId($product->getId());
    $session->setCartWasUpdated(true);

    $cart->save();

    $items_in_cart = Mage::helper('checkout/cart')->getSummaryCount();

    $result['result']="success";
    $result['message']="Added!";
    $result['items_in_cart'] = "$items_in_cart";

    echo json_encode($result);
}
catch (Exception $e) {
    $result['result'] = 'error';
    $result['message'] =  $e->getMessage();
    echo json_encode($result);
}
?>

Add to Cart: Pulling it All Together

template/catalog/product/view/addtocart.phtml

Here, we are setting up the input fields and naming the various input elements correctly. In my case, “minimum quantity” also means “quantity multiplier.” That is, I must order a multiple of minimum quantity. If minimum quantity is 4, I must order 4, 8, 12 or so on. My code will round the order up. If you tried to order 3, I’d round you up to 4. Your code might not need this. If that’s the case, just don’t use the qtyM variables, and don’t call upFixQtyMult.

template/catalog/product/view/addtocart.phtml, Part 1 – php

Of note here is that I am uniquely identifying the qty field; this isn’t necessary when I’m using this code for only 1 item. But if this code is used multiple times on a page, I’ll need unique identifiers. “A-ha!” I here you say “- the SKU isn’t unique!” Calm down. It’s not finished yet.

The UI experience will be encapsulated in the div id dialog. The subsequent jQuery UI code will use that div to keep the user up to date on what’s happening. See line 17, highlighted.

<?php
    $_product = $this->getProduct();
    $_myProductSku = $_product->getSku();
    $_mxwMinQty = $this->getMinimalQty($_product);
?>
<?php $buttonTitle = $this->__('Add to Cart'); ?>
<?php if($_product->isSaleable()): ?>
    <div class="add-to-cart">
        <?php if(!$_product->isGrouped()): ?>
        <label for="qty"><?php echo $this->__('Qty:') ?></label>
        <input type="text" size="4" name="qty" id="qty<?=$_myProductSku?>" maxlength="4" value="<?=$_mxwMinQty?>" title="<?php echo $this->__('Qty') ?>" class="input-text qty" />
        <input type="hidden" id="qtyM<?=$_myProductSku?>" value="<?=$_mxwMinQty?>" />
        <?php endif; ?>
        <br /><a href="#"><img src="<?php echo $this->getSkinUrl(); ?>images/buy-now-btn.png" alt="Buy Now" onclick="addItemToCart('<?=$_myProductSku?>');" alt='<?=$buttonTitle ?>' /></a>
        <?php echo $this->getChildHtml('', true, true) ?>
    </div>
<div class="ui-widget" id="dialog" title="Adding to Cart"></div>

template/catalog/product/view/addtocart.phtml, Part 2 – the Javascript

jQuery here is represented by $jQ. For more details on how we arrived at that nomenclature, please see my earlier post Magento and jQuery, which explains how I brought jQuery in in its noConflict() mode. That post also describes the reasoning behind the “ui-xxx” styling – that’s what jQuery UI’s ThemeRoller produces. Again, please consult the previous post for those details.

This code is pretty straightforward. Call the mxwest_atc.php script. It will return a JSON encoded response in (duh) “response.” The fancy bit of updating the header for number of items in cart is highlighted at lines 20 and 21. (You did remember to do the work described in “Prep Work I”, above, right?). I used the variable “o” as shorthand for object. Although I generally prefer more descriptive variable names, I figured the 2 lines here were comprehensible enough that I could shorthand it.

<script type="text/javascript">
function
addItemToCart(sku) {
    $jQ("#dialog").dialog('destroy');
    $jQ("#dialog").html("<center>Adding ...<p><img src='<?=$this->getSkinUrl()?>/images/up-ajax-loader.gif'></p></center>");
    $jQ("#dialog").dialog({show: 'scale', hide: 'scale', modal: 'true'});

    var qty_id = "#qty"+sku.toString();
    var qty = $jQ(qty_id).val();

    var qtyM_id = "#qtyM"+sku.toString();
    var qtyM = $jQ(qtyM_id).val();

    qty = upFixQtyMult(parseInt(qty),parseInt(qtyM));

    var cart_parms = "sku=" + sku + "&qty=" + qty;
    var myUrl = "<?=Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB);?>/mxwest_atc.php?"+cart_parms;
    var jqhxr = $jQ.ajax({ url: myUrl })
        .success( function(response){
            o = $jQ.parseJSON(response);    
            $jQ("#up-top-number-in-cart").html(o.items_in_cart);
            $jQ("#dialog").html("<span class=\"ui-icon ui-icon-info\"></span>Thanks! " + sku + " was added to your cart." );
            $jQ("#dialog").addClass("ui-state-highlight ui-corner-all");
            $jQ("#dialog").dialog({hide: 'scale',
                buttons: {
                    "Keep Shopping": function() { $jQ(this).dialog("close"); },
                    "Checkout": function() { $jQ(this).dialog("close"); window.location = "<?=Mage::getUrl('checkout/cart', array('_secure' => true));?>"; }
                }
            });
        })
        .error( function() {
            $jQ("#dialog").html("<span class=\"ui-icon ui-icon-alert\"></span>Sorry, something bad happened. " + sku + " not added.");
            $jQ("#dialog").addClass("ui-state-error ui-corner-all");
            $jQ("#dialog").dialog({ buttons: { "Ok": function() { $jQ(this).dialog("close"); } } });
        });
}
function
upFixQtyMult(qty,qtyM) {
    var m = +qtyM;
    var q = m;
    if(isNaN(qty)) return +m;
    q = +qty;
    if( q <= m ) return m;
    return +((m<=1)?q:(q+(m-(q%m))));
}
</script>

For completeness sake … let’s close that php if!

<?php endif; ?>

End Note

Hope this helps! Magento is incredibly feature-rich, but I also find it incredibly dense to penetrate. I think that much of the User Experience still needs to abstracted out of php and back into jQuery UI (or similar) to improve the User Experience. There are many parts of the code that make me scratch my head. A head full of headaches at times. Hopefully, though, I helped shed a little light on using this cantankerous rascal in a more effective way. Let me know what you think!

Facebook comments: