Magento 1.8 AJAX Add to Cart – How to Implement with Prototype JS

ajaxce-1.8.1.0magento-1.8prototype-js

I am developing a site on Magento 1.8.1.0 and using following plugin for add to cart with ajax.

http://www.magentocommerce.com/magento-connect/ajax-add-to-cart-1.html

It is working fine on both category listing page and product view page but i need quantity field on category listing page and for that i am copying the form tag with quantity field from product view page

C:\wamp\www\mage\app\design\frontend\bluefin\default\template\catalog\product\view.phtml
to
C:\wamp\www\mage\app\design\frontend\bluefin\default\template\catalog\product\list.phtml

On the product view page ajax is working on form id and form code looks like this

<form action="http://localhost/mage/index.php/checkout/cart/add/uenc/aHR0cDovL2xvY2FsaG9zdC9tYWdlL2luZGV4LnBocC9mdXJuaXR1cmUvbGl2aW5nLXJvb20vb3R0b21hbi5odG1sP19fX1NJRD1V/product/51/form_key/6sRQPimEU620349z/" method="post" id="product_addtocart_form">

<input name="form_key" type="hidden" value="6sRQPimEU620349z">
<div class="no-display">
    <input type="hidden" name="product" value="51">
    <input type="hidden" name="related_product" id="related-products-field" value="">
</div>

<div class="product-shop">

    <div class="add-to-box">
        <div class="add-to-cart">
            <label for="qty">Qty:</label>
            <input type="text" name="qty" id="qty" maxlength="12" value="1" title="Qty" class="input-text qty">
            <button type="button" title="Add to Cart" class="button btn-cart" onclick="productAddToCartForm.submit(this)"><span><span>Add to Cart</span></span></button>
        </div>
    </div>

</div>

Its javascript code looks like this

<script type="text/javascript">
//<![CDATA[
    var productAddToCartForm = new VarienForm('product_addtocart_form');
    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);

    productAddToCartForm.submitLight = function(button, url){
        if(this.validator) {
            var nv = Validation.methods;
            delete Validation.methods['required-entry'];
            delete Validation.methods['validate-one-required'];
            delete Validation.methods['validate-one-required-by-name'];
            // Remove custom datetime validators
            for (var methodName in Validation.methods) {
                if (methodName.match(/^validate-datetime-.*/i)) {
                    delete Validation.methods[methodName];
                }
            }

            if (this.validator.validate()) {
                if (url) {
                    this.form.action = url;
                }
                this.form.submit();
            }
            Object.extend(Validation.methods, nv);
        }
    }.bind(productAddToCartForm);
//]]>
</script>

As we can't have multiple element of same id in an html page that's why i am generating form id through php loop and not using inline property onclick of button. My form ids are product_addtocart_form_0, product_addtocart_form_1, product_addtocart_form_2 and so on. And my script is

<script type="text/javascript">
//<![CDATA[
    // console.log(<?php //echo $counter; ?>);
    window.onload = function () {
        var productAddToCartForm = new Array();

        for (var i = 0; i < <?php echo $counter; ?>; i++) {
            productAddToCartForm[i] = new VarienForm('product_addtocart_form_'+i);
            productAddToCartForm[i].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[i]);

            productAddToCartForm[i].submitLight = function(button, url){
                if(this.validator) {
                    var nv = Validation.methods;
                    delete Validation.methods['required-entry'];
                    delete Validation.methods['validate-one-required'];
                    delete Validation.methods['validate-one-required-by-name'];
                    // Remove custom datetime validators
                    for (var methodName in Validation.methods) {
                        if (methodName.match(/^validate-datetime-.*/i)) {
                            delete Validation.methods[methodName];
                        }
                    }

                    if (this.validator.validate()) {
                        if (url) {
                            this.form.action = url;
                        }
                        this.form.submit();
                    }
                    Object.extend(Validation.methods, nv);
                }
            }.bind(productAddToCartForm[i]);
        };

        var j = 0;
        $$('.btn-cart').each( function(item) {
            console.log(productAddToCartForm);
          item.observe('click', function(event) {
            productAddToCartForm[j].submit($(item));
          });
          j++;
        });
    }
//]]>
</script>

But when i click add to cart an error appears
Uncaught TypeError: Cannot read property 'submit' of undefined

If anyone can help me then it would be great.

Best Answer

I tried some thing and it is working fine for me, i am sharing code hope this might be useful for someone else or one can suggest me the better approach.

In mage\app\design\frontend\your-design-package\default\template\catalog\product\list.phtml

Replace the following code

<?php if($_product->isSaleable()): ?>
      <button type="button" title="<?php echo $this->__('Add to Cart') ?>" class="button btn-cart" onclick="setLocation('<?php echo $this->getAddToCartUrl($_product) ?>')"><span><span><?php echo $this->__('Add to Cart') ?></span></span></button>
<?php else: ?>

with

<?php if($_product->isSaleable()): ?>

<form class="product_addtocart_form" action="<?php echo $this->getSubmitUrl($_product) ?>" method="post" id="product_addtocart_form_<?php echo $counter; ?>"<?php if($_product->getOptions()): ?> enctype="multipart/form-data"<?php endif; ?>>
    <?php echo $this->getBlockHtml('formkey') ?>
    <div class="no-display">
        <input type="hidden" name="product" value="<?php echo $_product->getId() ?>" />
        <input type="hidden" name="related_product" id="related-products-field" value="" />
    </div>

    <?php $buttonTitle = $this->__('Add to Cart'); ?>
    <div class="add-to-cart">
        <?php if(!$_product->isGrouped() && !$_product->isconfigurable()): ?>
        <label for="qty"><?php echo $this->__('Qty:') ?></label>
        <input type="text" name="qty" id="qty_<?php echo $counter; ?>" maxlength="12" value="<?php echo '1'; //echo $this->getProductDefaultQty() * 1 ?>" title="<?php echo $this->__('Qty') ?>" class="input-text qty" />
        <?php endif; ?>
        <button type="button" title="<?php echo $buttonTitle ?>" class="button btn-cart" data-count="<?php echo $counter; ?>" data-ptype="<?php echo $_product->getTypeID(); ?>"><span><span><?php echo $buttonTitle ?></span></span></button>
        <?php echo $this->getChildHtml('', true, true) ?>
    </div>
</form>

<?php else: ?>

Remember i am generating form id and quantity field id with loop and as there are two display modes in magento category listing page List Mode and Grid Mode so you have to replace the above code in both areas.

id="product_addtocart_form_<?php echo $counter; ?>"

Also for button i have used two HTML5 data attributes data-count and data-ptype. In data-count i am just storing the value of $counter variable and in the data-ptype i am storing product type (i.e simple, grouped etc)

data-count="<?php echo $counter; ?>" data-ptype="<?php echo $_product->getTypeID(); ?>"

finally the javascript code

<script type="text/javascript">
//<![CDATA[
    // console.log(<?php //echo $counter; ?>);
    document.observe("dom:loaded", function() {
        var productAddToCartForm = new Array();
        for (var i = 0; i < <?php echo $counter; ?>; i++) {
            productAddToCartForm[i] = new VarienForm('product_addtocart_form_'+i);
            productAddToCartForm[i].submit = ajx_form_submit.bind(productAddToCartForm[i]);
            productAddToCartForm[i].submitLight = ajx_form_submitLight.bind(productAddToCartForm[i]);
        };

        $$('.btn-cart').each( function(item) {
            // console.log($(item).up('form').action);
            item.observe('click', function(event) {
                if (item.dataset.ptype == 'simple') {
                    ajaxcart.ajaxCartSubmit(productAddToCartForm[item.dataset.count]);
                } else if (item.dataset.ptype == 'grouped') {
                    formurl = $(item).up('form').action;
                    ajaxcart.ajaxCartSubmit(formurl);
                } else if (item.dataset.ptype == 'configurable') {
                    formurl = $(item).up('form').action + '?options=cart&ajax=true';
                    ajaxcart.getConfigurableOptions(formurl);
                }
                // productAddToCartForm[item.dataset.count].submit(item);
            });
        });
    });

    var ajx_form_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;
            }
        }
    };

    var ajx_form_submitLight = function(button, url){
        if(this.validator) {
            var nv = Validation.methods;
            delete Validation.methods['required-entry'];
            delete Validation.methods['validate-one-required'];
            delete Validation.methods['validate-one-required-by-name'];
            // Remove custom datetime validators
            for (var methodName in Validation.methods) {
                if (methodName.match(/^validate-datetime-.*/i)) {
                    delete Validation.methods[methodName];
                }
            }

            if (this.validator.validate()) {
                if (url) {
                    this.form.action = url;
                }
                this.form.submit();
            }
            Object.extend(Validation.methods, nv);
        }
    };

    $$('.product_addtocart_form input.qty').each(function(el) {
        el.observe('blur', function(e){
            if( el.value <= 0 || isNaN(el.value) === true) {
                el.value = 1;
            }
        });
    });
//]]>
</script>