Multiple configurable products with options on one page in Magento

Featured Image

Displaying configurable products with options on one page can be problem because Javascript in Magento is setup to work only with one configurable product.
You can write your own Javascript or U can reuse Magento code.

To accomplish that we need to modify product Javascript (js/varien/product.js) and configurable (catalog\product\view\type\options\configurable.phtml) select element template.

In configurable.phtml call our new JS class (we will create that class later in article) and add product id to configurable select element class.

configurable.phtml:

<?php
$_product    = $this->getProduct();
$_attributes = Mage::helper('core')->decorateArray($this->getAllowAttributes());
?>
<?php if ($_product->isSaleable() && count($_attributes)):?>
    <dl>
    <?php foreach($_attributes as $_attribute): ?>
        <dt><label class="required"><em>*</em><?php echo $_attribute->getLabel() ?></label></dt>
        <dd<?php if ($_attribute->decoratedIsLast){?> class="last"<?php }?>>
            <div class="input-box">
                <select name="super_attribute[<?php echo $_attribute->getAttributeId() ?>]" id="attribute<?php echo $_attribute->getAttributeId() ?>" class="required-entry super-attribute-select_<?php echo $_product->getId()?>  ">
                    <option><?php echo $this->__('Choose an Option...') ?></option>
                  </select>
              </div>
        </dd>
    <?php endforeach; ?>
    </dl>
    <script type="text/javascript">
        var spConfig_<?php echo $_product->getId()?> = new Inchoo_Product.Config(<?php echo $this->getJsonConfig() ?>);
    </script>
<?php endif;?>
 

Then create Javascript file and Javascript class.
Copy javascript code from js/varien/product.js for configurable product (use own namespace to avoid JS collision with Magento).

if(typeof Inchoo_Product =='undefined') {
    var Inchoo_Product  = {};
}
/**************************** CONFIGURABLE PRODUCT **************************/
Inchoo_Product.Config = Class.create();
Inchoo_Product.Config.prototype = {

Because we change selector name we have to modify our JS code to work with new selector.

var settingsClassToSelect = '.super-attribute-select_'+this.config.productId;
this.settings   = $$(settingsClassToSelect);

Magento code is stripping “attribute” string from class of configurable select element but we also have to strip product id (we added product id to class name in configurable.phtml).

        // fill state
        this.settings.each(function(element){
            var attributeId = element.id.replace(/[a-z]*/, '');
            attributeId = attributeId.replace(/_.*/, '');
            ........
        fillSelect: function(element){
        var attributeId = element.id.replace(/[a-z]*/, '');
        attributeId = attributeId.replace(/_.*/, '');

Now we can have multiple configurable products with options on same page.

Update, as requested complete JS file

product.js:

if(typeof Inchoo_Product =='undefined') {
    var Inchoo_Product  = {};
}
/**************************** CONFIGURABLE PRODUCT **************************/
Inchoo_Product.Config = Class.create();
Inchoo_Product.Config.prototype = {
    initialize: function(config){
        this.config     = config;
        this.taxConfig  = this.config.taxConfig;
        var settingsClassToSelect = '.super-attribute-select_'+this.config.productId;
        this.settings   = $$(settingsClassToSelect);
        this.state      = new Hash();
        this.priceTemplate = new Template(this.config.template);
        this.prices     = config.prices;
        this.settings.each(function(element){
            Event.observe(element, 'change', this.configure.bind(this))
        }.bind(this));
        // fill state
        this.settings.each(function(element){
            var attributeId = element.id.replace(/[a-z]*/, '');
            attributeId = attributeId.replace(/_.*/, '');
            if(attributeId && this.config.attributes[attributeId]) {
                element.config = this.config.attributes[attributeId];
                element.attributeId = attributeId;
                this.state[attributeId] = false;
            }
        }.bind(this))
        // Init settings dropdown
        var childSettings = [];
        for(var i=this.settings.length-1;i>=0;i--){
            var prevSetting = this.settings[i-1] ? this.settings[i-1] : false;
            var nextSetting = this.settings[i+1] ? this.settings[i+1] : false;
            if(i==0){
                this.fillSelect(this.settings[i])
            }
            else {
                this.settings[i].disabled=true;
            }
            $(this.settings[i]).childSettings = childSettings.clone();
            $(this.settings[i]).prevSetting   = prevSetting;
            $(this.settings[i]).nextSetting   = nextSetting;
            childSettings.push(this.settings[i]);
        }
        // Set default values - from config and overwrite them by url values
        if (config.defaultValues) {
            this.values = config.defaultValues;
        }
        var separatorIndex = window.location.href.indexOf('#');
        if (separatorIndex != -1) {
            var paramsStr = window.location.href.substr(separatorIndex+1);
            var urlValues = paramsStr.toQueryParams();
            if (!this.values) {
                this.values = {};
            }
            for (var i in urlValues) {
                this.values[i] = urlValues[i];
            }
        }
        this.configureForValues();
        document.observe("dom:loaded", this.configureForValues.bind(this));
    },
    configureForValues: function () {
        if (this.values) {
            this.settings.each(function(element){
                var attributeId = element.attributeId;
                element.value = (typeof(this.values[attributeId]) == 'undefined')? '' : this.values[attributeId];
                this.configureElement(element);
            }.bind(this));
        }
    },
    configure: function(event){
        var element = Event.element(event);
        this.configureElement(element);
    },
    configureElement : function(element) {
        this.reloadOptionLabels(element);
        if(element.value){
            this.state[element.config.id] = element.value;
            if(element.nextSetting){
                element.nextSetting.disabled = false;
                this.fillSelect(element.nextSetting);
                this.resetChildren(element.nextSetting);
            }
        }
        else {
            this.resetChildren(element);
        }
        //this.reloadPrice();
//      Calculator.updatePrice();
    },
    reloadOptionLabels: function(element){
        var selectedPrice;
        if(element.options[element.selectedIndex].config){
            selectedPrice = parseFloat(element.options[element.selectedIndex].config.price)
        }
        else{
            selectedPrice = 0;
        }
        for(var i=0;i<element.options.length;i++){
            if(element.options[i].config){
                element.options[i].text = this.getOptionLabel(element.options[i].config, element.options[i].config.price-selectedPrice);
            }
        }
    },
    resetChildren : function(element){
        if(element.childSettings) {
            for(var i=0;i<element.childSettings.length;i++){
                element.childSettings[i].selectedIndex = 0;
                element.childSettings[i].disabled = true;
                if(element.config){
                    this.state[element.config.id] = false;
                }
            }
        }
    },
    fillSelect: function(element){
        var attributeId = element.id.replace(/[a-z]*/, '');
        attributeId = attributeId.replace(/_.*/, '');
        var options = this.getAttributeOptions(attributeId);
        this.clearSelect(element);
        element.options[0] = new Option(this.config.chooseText, '');
        var prevConfig = false;
        if(element.prevSetting){
            prevConfig = element.prevSetting.options[element.prevSetting.selectedIndex];
        }
        if(options) {
            var index = 1;
            for(var i=0;i<options.length;i++){
                var allowedProducts = [];
                if(prevConfig) {
                    for(var j=0;j<options[i].products.length;j++){
                        if(prevConfig.config.allowedProducts
                            && prevConfig.config.allowedProducts.indexOf(options[i].products[j])>-1){
                            allowedProducts.push(options[i].products[j]);
                        }
                    }
                } else {
                    allowedProducts = options[i].products.clone();
                }
                if(allowedProducts.size()>0){
                    options[i].allowedProducts = allowedProducts;
                    element.options[index] = new Option(this.getOptionLabel(options[i], options[i].price), options[i].id);
                    element.options[index].config = options[i];
                    index++;
                }
            }
        }
    },
    getOptionLabel: function(option, price){
        var price = parseFloat(price);
        if (this.taxConfig.includeTax) {
            var tax = price / (100 + this.taxConfig.defaultTax) * this.taxConfig.defaultTax;
            var excl = price - tax;
            var incl = excl*(1+(this.taxConfig.currentTax/100));
        } else {
            var tax = price * (this.taxConfig.currentTax / 100);
            var excl = price;
            var incl = excl + tax;
        }
        if (this.taxConfig.showIncludeTax || this.taxConfig.showBothPrices) {
            price = incl;
        } else {
            price = excl;
        }
        var str = option.label;
        if(price){
            if (this.taxConfig.showBothPrices) {
                str+= ' ' + this.formatPrice(excl, true) + ' (' + this.formatPrice(price, true) + ' ' + this.taxConfig.inclTaxTitle + ')';
            } else {
                str+= ' ' + this.formatPrice(price, true);
            }
        }
        return str;
    },
    formatPrice: function(price, showSign){
        var str = '';
        price = parseFloat(price);
        if(showSign){
            if(price<0){
                str+= '-';
                price = -price;
            }
            else{
                str+= '+';
            }
        }
        var roundedPrice = (Math.round(price*100)/100).toString();
        if (this.prices && this.prices[roundedPrice]) {
            str+= this.prices[roundedPrice];
        }
        else {
            str+= this.priceTemplate.evaluate({price:price.toFixed(2)});
        }
        return str;
    },
    clearSelect: function(element){
        for(var i=element.options.length-1;i>=0;i--){
            element.remove(i);
        }
    },
    getAttributeOptions: function(attributeId){
        if(this.config.attributes[attributeId]){
            return this.config.attributes[attributeId].options;
        }
    },
    reloadPrice: function(){
        var price    = 0;
        var oldPrice = 0;
        for(var i=this.settings.length-1;i>=0;i--){
            var selected = this.settings[i].options[this.settings[i].selectedIndex];
            if(selected.config){
                price    += parseFloat(selected.config.price);
                oldPrice += parseFloat(selected.config.oldPrice);
            }
        }
        optionsPrice.changePrice('config', {'price': price, 'oldPrice': oldPrice});
        optionsPrice.reload();
        return price;
        if($('product-price-'+this.config.productId)){
            $('product-price-'+this.config.productId).innerHTML = price;
        }
        this.reloadOldPrice();
    },
    reloadOldPrice: function(){
        if ($('old-price-'+this.config.productId)) {
            var price = parseFloat(this.config.oldPrice);
            for(var i=this.settings.length-1;i>=0;i--){
                var selected = this.settings[i].options[this.settings[i].selectedIndex];
                if(selected.config){
                    price+= parseFloat(selected.config.price);
                }
            }
            if (price < 0)
                price = 0;
            price = this.formatPrice(price);
            if($('old-price-'+this.config.productId)){
                $('old-price-'+this.config.productId).innerHTML = price;
            }
        }
    }
}
34
Top

Enjoyed this post?

Subscribe to our RSS Feed, Follow us on Twitter and spread it to your friends!

Author

Goran Sambolic

Ex. Inchooer

Goran is a PHP developer who is encountered during the work with different number of PHP technologies, Joomla, OpenX, Symfony, Zend, Magento, PRADO. He worked in Inchoo from 2011 to 2012.

Other posts from this author

Discussion 34 Comments

Add Comment
  1. This sounds like something I might need. Not sure I quiet get what you mean though.

    If I have a configurable product in two different colors, will this make me able to show them both but to point to one single url?

    Please explain further! :)

  2. Goran Sambolic

    This tutorial is for more than one different configurable product on same page.
    You have one configurable product with different color (multiple simple products). So U can use your configurable product url for your simple products with different colors.

  3. Jon

    How do you get multiple configurable products onto one page in the first place? I’m looking to create a grouped product which allows multiple configurable products to be associated to it. Any ideas how I can acheive this?

  4. Goran Sambolic

    U can use grouped product with configurable but it is out of scope of this tutorial. This tutorial is more suited for situations in which you use configurable product not as part of grouped product. For example for widget where you display all configurable product with options in some category.

  5. jun

    Hi, i found your tutorial and i’m really want it but i’m a bit of confuse on how to do it. can you show me what would be the final code in configurable.php and in the product.js?

  6. Goran Sambolic

    Hello, @jun complete source code for product.js and configurable.js is posted.

  7. Pau

    Hi Goran,

    I’ve been trying to do this but I’ve run into a few problems. What I did was:

    1. Add to the catalog_product_layer on the catalog.xml file the block related to configurable product (so I can call the configurable block from the list.phtml file).

    2. The problem is that on the configurable block the first line:
    $this->getProduct();

    doesn’t get any product, and then it just can’t get anything.

    How have you solved this?

    Thanks!

  8. Pau

    Hi again!

    I’ve just used the:

    And then I could see the configurable options. But only the first configurable option is working. I think we have to change id’s and other things…

  9. Pau

    Sorry:

    Mage::unregister(‘product’)
    Mage::register(‘product’, $_product)

  10. Pau

    OK, I have to merge all my answers…..

    The thing is that the register/unregister seems to not work well: it always gets the same id. How did you solve this?

    Thanks!

  11. Pau

    Ok, I thinks it’s time to talk about viagra!!

    We finally got it!! We were calling the same block again and again, so it was always the same. So we decided to dynamically create the block directly from the template:
    $this->getLayout()->createBlock(‘catalog/product_view_type_configurable’, ”, array(‘template’=> ‘catalog/product/view/type/options/configurable.phtml’))->toHtml(); ?>

  12. Goran Sambolic

    Glad you solve it:)

  13. Lu Duc Tuan

    Thank Goran a lot. That’s what i looking for.

  14. Hi,
    Can you please tell me that how can i load configurable option via ajax call and load them into dom elements ?

  15. Hi ,
    Please put demo of yours tutorial.
    I like used configurable product in site http://www.pruneshop.com/carteras/cartera-de-cuero-p008463qm.html . I want to do similar in my site .Please answer – is your code do similar ?
    Thanks

  16. Pau

    Hi Rusian,

    You can take a look at this (note that I could write this thanks to Goran!):
    http://www.catgento.com/adding-configurable-product-options-to-category-list-in-magento/

  17. Hi ,
    I did steps like you explain. Code working – I can add in cart choose option , but can’t change image in list when made option ( maybe some I made wrong). Can see here http://www.modatadnes.com/shop/woman.html , but only my product 1856 is now configurable. Can you give me advice how can change image when change option configurable product in catalog page.
    Thanks again Pau and Goran

  18. K J Ramana Rao

    Where can i specify the second configurable product id in the code.

    Please help me on this.

  19. Timo Peschka

    Hey!

    Thank you for your great solution!

    But i have one problem: How do you handle the price reload?
    On the product page is missing var optionsPrice = new Product.OptionsPrice(….
    => exception, no price update after attribute selection possible

    Did you have any idea?

  20. dhananjay

    Hi,

    how can I reload price on option change?

  21. Faroque

    Can’t reload the price.

  22. Faroque

    Finally I can reload the price :)

  23. BONI

    Hello,
    I develop a module to compose a grouped product with configurable product.

    With your solution a retrieve well production option, I already manage the recursive item for configurable product in grouped product.

    How can i do to add all configurable product to cart after selection? in the same form…

  24. Atul Bhalerao

    Hi
    i m new to magento can you please help
    i want the configurable drop down in crosssell option (checkout option)
    please help….

  25. I am setting up an apparel store for a client, they have a selection of items and each item has a color selection, however, not all of the items come in the same colors. Originally I set up each item with its own color attribute with the colors specific to that item, however, when you do a seach, you then end up with multiple “Color” sections listed on the left that you can use to narrow your search. Is there any way to set one “Color” attribute with all of the options, and then specify which options a specific product has access to?

  26. Hi, can somepne please tell me how they got the different getJsonConfig() for the products.

    Ive used a config product page, and brough more config products in….but i cant get anything from the second config product’s getJsonConfig() method when called.

    Ive tried bypassing this….but just doesnt seem right…having to copy a quite bit of code into an extension to get this to work correctly.

    The main config product for the page is fine….just the included products that have issues here.

    Ive tried calling and using config-product-model object, tried instantiating the product-config block itself (but cant get it populated with all the data it needs i think to product a jsonconfig string.)

    How can i easily generate this string, or get the getJsonConfig function itself working?

    thx

  27. BEEware

    @Faroque: How did you achieve the price reload? Can you post your solution?

  28. Hi,
    I’m new magento developer and implement this code but fetchning some proble Attribute name is display but attribute value not display in dropdown.
    please help me.

  29. Jed

    This is an extremely helpful tutorial, but there is still work to do if you want to use VarienForm validation, which refers to an attribute id. If you’re just using the default attributeN format for the select box, and you have multiple products with attributeN, VarienForm validation will target the first select box with that id. Better to append the product id to the select box directly rather in Inchoo_Product. That way, both validation and options fill work properly.

  30. akshay

    Hi,
    very help full post.
    But can you explain how to define option prices for multiple products.

  31. Hi,

    Basically I’ve got your tutorial working in my site. But instead of assigning the correct simple product related to the attributes/options its assigning the attributes to the configurable product, which only has the standard/lowest price. Any thing you know to change it from assigning attributes to assigning the correct simple product like in the product

    Below is an example of a product added in the product page and one in the list. The top one is from the list, which wont add the new prices and the bottom is from the product which would add a different price depending on the options selected

    http://www.hillsidegroup.co.uk/lordwholesale-configurable-options.jpg.

    Any help would be greatly appreciated, thank you.

  32. hemisha

    hi

    i really liked your document and its really helpful but can you pls explain same method for bundle product too. so it will be very helpful for me.

    Thanks

  33. amitabh

    Hi,

    Is we can merge two configurable product JSON into one ?

  34. Dev

    How can i skip the product view page for configurable products when add to cart?. Please help me…

Add Your Comment

Please wrap all source codes with [code][/code] tags.
Top