Multiple configurable products with options on one page in Magento

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 (catalogproductviewtypeoptionsconfigurable.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;
}
 
}
}
}