Packaged, bundled or addon products can be a great way to create special product offers or include required fees or costs with a purchase. This is especially useful when a customer is adding a subscription to a service that requires a once off setup fee to also be purchased at the same time. Or if a free product is being given to customers, but only if certain products are purchased at the same time. By utilising the following script, these scenarios are completely possible, allowing you to bundle any number of parent products to any number of dependent products with or without a matching quantity.
For any products that you plan to use with this script, ensure that they all have unique product codes associated with them. For bundled products, this script leans towards them being added at the same time, through the same add-to-cart link or form. For adding multiple products in one click here for more information.
Paste the following code right before the closing </head>
tag of your cart template.
<style type="text/css" media="screen"> input.fc_readonly { color:#888; border:1px solid #CCC; background:#F1F1F1; } tr.fc_dependent td { background:#F1f1f1 !important; } span.fc_dependent_text { color:#AAA; font-style:italic; } </style> <script type="text/javascript"> jQuery(document).ready(function($){ // Check that we're not editing a sub, this string needs to *exactly match* what is shown in the alert when editing a sub. if (fc_json["messages"]["warnings"] != "You are currently modifying a subscription.") { // {"parent":["parentCode1","parentCode2"], "dependent":["dependantCode1","dependantCode2"], "quantity-match":true} var productBundles = [ {"parent":["seat"], "dependent":["base"], "quantity-match":true}, {"parent":["tee","tee2"], "dependent":["freetee","freeteefee"], "quantity-match":true} ]; for (var c = 0; c < productBundles.length; c++) { var parentFound = false; var parentName = []; var parentQuantity = 0; for (var pc = 0; pc < productBundles[c]["parent"].length; pc++) { for (var p = 0; p < fc_json.products.length; p++) { // setup parent elements if (productBundles[c]["parent"][pc] == fc_json.products[p].code) { parentFound = true; parentName.push(fc_json.products[p].name); var parentChildren = jQuery("input[value='"+fc_json.products[p].id+"']").parent("td").next("td.fc_cart_item_quantity"); parentQuantity += parseInt(jQuery(parentChildren).children("input").val()); jQuery(parentChildren).children("input").addClass("parent-"+c) if(productBundles[c]["quantity-match"]) { jQuery(parentChildren).children("input").unbind().change(function() { var id = jQuery(this).attr('class').match(/parent-(\d+)/)[1] jQuery("input.child-"+id).val(totalQuantity(".parent-"+id)); fc_TestCheckout(); }).keyup(function(event){ var id = jQuery(this).attr('class').match(/parent-(\d+)/)[1] jQuery("input.child-"+id).val(totalQuantity(".parent-"+id)); fc_TestCheckout(event); }); } jQuery(parentChildren).children("span").children("a").attr("onclick","").click(function() { var id = jQuery(this).parent("span").siblings("input.fc_cart_item_quantity").attr('class').match(/parent-(\d+)/)[1]; jQuery(this).parent("span").siblings("input.fc_cart_item_quantity").val(0); if (jQuery("input.parent-"+id).length == 1 || totalQuantity(".parent-"+id) == 0) { // Only remove the children product if this is the only parent jQuery("input.child-"+id).val(0).attr("readonly", true).addClass("fc_readonly"); } else { jQuery("input.child-"+id).val(totalQuantity(".parent-"+id)); } fc_TestCheckout(); }); p = fc_json.products.length; // Finishes the loop } } } // Find dependent fields var dependentMissing = false; for (var d = 0; d < productBundles[c]["dependent"].length; d++) { var dependentFound = false; for (var p = 0; p < fc_json.products.length; p++) { if (productBundles[c]["dependent"][d] == fc_json.products[p].code) { var dependentFound = true; jQuery("input[value="+fc_json.products[p].id+"]").parent("td").next("td") var dependentChildren = jQuery("input[value="+fc_json.products[p].id+"]").parent("td").next("td.fc_cart_item_quantity"); jQuery(dependentChildren).children("input").addClass("child-"+c); jQuery(dependentChildren).children("span").hide(); jQuery(dependentChildren).parent("tr").addClass("fc_dependent"); jQuery("input[value="+fc_json.products[p].id+"]").siblings(".fc_cart_item_options").before("<span class=\"fc_dependent_text\"> - added with '" + parentName.join("', '") + "'</span>"); if (productBundles[c]["quantity-match"]) { jQuery(dependentChildren).children("input").attr("readonly", true).addClass("fc_readonly"); if (jQuery(dependentChildren).children("input").val() != parentQuantity) { jQuery(dependentChildren).children("input").val(parentQuantity); showError("The product '"+fc_json.products[p].name+"' must have a matching quantity to '" + parentName.join("' and '") + "'. The quantity has been updated, please update the cart to save the new quantity."); fc_PreventCheckout(); } } p = fc_json.products.length; // Finishes the loop } } if (dependentFound === false) { dependentMissing = true; } } if ((parentFound === false && dependentMissing === false) || (parentFound === true && dependentMissing === true)) { if (parentFound === false && dependentMissing === false) { showError("Parent product missing. Please re-add the product with the code(s) '" + productBundles[c]["parent"].join("' or '") + "'."); } else if (parentFound === true && dependentMissing === true) { showError("A required addon product for '" + parentName.join("' and '") + "' is missing. Please re-add the product."); } jQuery("input.parent-"+c+", input.child-"+c).val(0).attr("readonly", true).addClass("fc_readonly"); fc_PreventCheckout(); } } } }); function showError(text) { if ($('#fc_error_container ul li').length) { jQuery("#fc_error_container ul").append("<li>"+text+"</li>"); } else { jQuery("table#fc_cart_table").before("<div id=\"fc_message_container\"><div id=\"fc_error_container\" class=\"fc_message fc_error\"><ul><li>"+text+"</li></ul></div></div>"); } } function totalQuantity(ident) { var result = 0; jQuery(ident).each(function() { result += parseInt(jQuery(this).val()); }); return result; } </script>
Now that you have the script in place, you need to let it know what products it needs to look out for. Look for the productBundles
in the script, and edit/add to the object. Adding another bundle is quite simple, simply follow this structure:
{"parent":["parentCode1","parentCode2"], "dependent":["dependantCode1","dependantCode2"], "quantity-match":true}
parent
parent:[“code1”,“code2”,“code3”]
dependent
dependent:[“code1”,“code2”,“code3”]
quantity-match
quantity-match:false
true
or false
Note: If you are defining multiple bundles, they must be comma separated (each one must end in a comma apart from the last)
The script works by completing the following set of actions on cart load:
quantity-match
is set to truequantity-match
quantity-match
is set to true, compares its quantity to the parent(s) quantity