Solving Magento

Solutions for Magento E-Commerce Platform

by Oleg Ishenko

Magento Bundle Product Type Tutorial

An overview of Magento’s Bundle product type would not be complete without an exercise providing a practical insight to the functionality of bundles. In this tutorial I will guide you through development of a relatively simple module that slightly alters the standard bundle implementation. If you read the first part of the discussion on the Bundle product type, specifically the description of the option and selection settings, you’d recall that some option types can be configured to allow customers to enter their own quantity when selecting an option. This property is called User Defined Quantity and is defined per selection item. Bundle selections store this flag in table catalog_product_bundle_selection in field selection_can_change_qty. By default, this functionality is available for options of type “Drop-down” and “Radio buttons” only. You can’t set this property for options of type “Checkbox” and “Multiselect”. Consider the following example from a test shop which uses sample data available here. Let’s look at a bundle with SKU mycomputer. This how the options management looks like in the back-end:

bundle_options

Figure 1. Options of a bundle.

The first option, a “Radio buttons” sort, has a column called “User Defined Qty” in its selections grid. The second option is of type “Checkbox” has no “User Defined Qty” column. If you open the product’s detail page in the front-end, the configuration form for these two options would appear as follows:

bundle_options_front_end

Figure 2. “Radio buttons” and “checkbox” options in the front-end.

You can see there is a “Qty” input field next to the “Radio buttons” option, while checkboxes selections have pre-entered quantities next to them which correspond to the “Default Qty” setting of the respective selection.

What we are going to do is to extend the Magento Bundle type to allow checkbox options to have user-defined quantities. At the end of this tutorial the front-end form will display editable input fields next to any checkbox selection item that has “User Defined Qty” set to yes. We will have to limit ourselves to bringing this functionality to “Checkbox” options only and leave “Multiselect” out, because creating a sensible user interface for such combination is out of scope of a Bundle product type tutorial.

The first thing to do is as always creating a scaffold structure for the new module. The name of the package is “Solvingmagento” and the extension’s name would be “BundleExtended”. We add the following directories to the local code pool folder:

app/
        code/
                local/
                        Solvingmagento/
                                BundleExtended/
                                        Block/
                                                Adminhtml/
                                                Catalog/
                                        Model/
                                        etc/
Figure 3. Directories of the new module.

As you can see, we are going to add class overrides for front- and back-end blocks and to the models of the Mage_Bundle module. Before we continue, add a module declaration file to the app/etc/modules/ folder:

<?xml version="1.0"?>
<config>
        <modules>
                <Solvingmagento_BundleExtended>
                        <active>true</active>
                        <codePool>local</codePool>
                </Solvingmagento_BundleExtended>
        </modules>
</config>

Listing 1. Module declaration file Solvingmagento_BundleExtended.xml

Step 1. Rewriting the back-end blocks.

We’ve seen in the back-end that the selections grid of a checkbox option lacks a “User Defined Qty” column. To add it, we will have to override two back-end blocks. They are Mage_Bundle_Block_Adminhtml_Catalog_Product_Edit_Tab_Bundle_Option and Mage_Bundle_Block_Adminhtml_Catalog_Product_Edit_Tab_Bundle_Option_Selection. These classes are responsible for outputting form grids for options and selections respectively. The only thing we need to change there is their public construct methods. These methods set template files to the blocks, and it is actually the template files that need to be modified to achieve our goal of enabling user defined quantity settings for the checkbox options. I am not going to change the core template files since this is a bad idea. Instead I will copy them under a new path and modify the copies. These altered template copies will then have to be assigned to the blocks.

Another challenge there is the naming of the override classes. As you can see, the original class names are already quite long. Since our module’s name isn’t short either, adding “Solvingmagento_BundleExtended” to the new classes’ names would only make things more difficult. An alternative would be to break the Magento’s naming convention and devise shorter names for the override classes. However, in this tutorial I am going to stick to the original naming convention no matter how ugly the new names may appear. So, here it goes, the option override block class name is: Solvingmagento_BundleExtended_Block_Adminhtml_Catalog_Product_Edit_Tab_Bundle_Option. I’ve created a tree of subdirectories, Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle and placed a file named Option.php there. Its content is this:

class Solvingmagento_BundleExtended_Block_Adminhtml_Catalog_Product_Edit_Tab_Bundle_Option
        extends Mage_Bundle_Block_Adminhtml_Catalog_Product_Edit_Tab_Bundle_Option
{
        /**
         * Class construct, sets a custom template
         *
         * @return void
         */
        public function __construct()
        {
                $this->setTemplate('bundleextended/product/edit/bundle/option.phtml');
                $this->setCanReadPrice(true);
                $this->setCanEditPrice(true);
        }
}

Listing 3. Back-end option block class override.

The only difference to the original construct method is the path to the template: bundleextended/product/edit/bundle/option.phtml. The same has to be done to override the selection block. The new class is called Solvingmagento_BundleExtended_Block_Adminhtml_Catalog_Product_Edit_Tab_Bundle_Option_Selection and has the following construct method:

class Solvingmagento_BundleExtended_Block_Adminhtml_Catalog_Product_Edit_Tab_Bundle_Option_Selection
        extends Mage_Bundle_Block_Adminhtml_Catalog_Product_Edit_Tab_Bundle_Option_Selection
{
        /**
         * Initialize bundle option selection block
         */
        public function __construct()
        {
                $this->setTemplate('bundleextended/product/edit/bundle/option/selection.phtml');
                $this->setCanReadPrice(true);
                $this->setCanEditPrice(true);
        }
}

Listing 4. Back-end selection block override.

Here too mind the new template path. To enable the overrides, we have to create a config.xml file for our module and enter the override configuration there:

<config>
    <modules>
        <Solvingmagento_BundleExtended>
            <version>0.1.0</version>
        </Solvingmagento_BundleExtended>
    </modules>
    <global>
        <blocks>
            <bundle>
                <rewrite>
                    <adminhtml_catalog_product_edit_tab_bundle_option_selection>
                        Solvingmagento_BundleExtended_Block_Adminhtml_
                        Catalog_Product_Edit_Tab_Bundle_Option_Selection
                    </adminhtml_catalog_product_edit_tab_bundle_option_selection>
                    <adminhtml_catalog_product_edit_tab_bundle_option>
                        Solvingmagento_BundleExtended_Block_Adminhtml_Catalog_
                        Product_Edit_Tab_Bundle_Option
                    </adminhtml_catalog_product_edit_tab_bundle_option>
                </rewrite>
            </bundle>
        </blocks>
    </config>
</config>


Listing 5. Adminhtml block override configuration.

I’ve added line breaks to the node values to fit the listing into the limited page width for display purposes. The actual values have no line breaks.

The back-end block overrides are almost done. What is left to do is to create the new templates. For that I have created a directory tree under /app/design/adminhtml/default/default/template/bundleextended/product/edit/bundle/. It mimics the directory structure of the original back-end bundle template folder. The template directory tree of our new model contains two back-end templates: option.phtml and option/selection.phtml. The contents of these two files is nearly identical to the originals – it was copied there. I’ve done a few alterations that are the whole point of the block override. The option.phtml template contains the following piece of JavaScript:

changeType : function(event) {
    var element = Event.element(event);
    parts = element.id.split('_');
    i = parts[2];
    if (element.value == 'multi' || element.value == 'checkbox') {
        inputs = $A($$('#' + bSelection.idLabel + '_box_' + i
                            + ' tr.selection input.default'));
        inputs.each(
            function(elem){
                //elem.type = "checkbox";
                changeInputType(elem, 'checkbox');
            }
        );
        /**
         * Hide not needed elements (user defined qty select box)
         */
        inputs = $A($$('#' + bSelection.idLabel + '_box_'
                            + i + ' .qty-box'));
        inputs.each(
            function(elem){
                elem.hide();
            }
        );

    } else {
        inputs = $A($$('#' + bSelection.idLabel + '_box_'
                            + i + ' tr.selection input.default'));
        have = false;
        for (j=0; j< inputs.length; j++) {
            //inputs[j].type = "radio";
            changeInputType(inputs[j], 'radio');
            if (inputs[j].checked && have) {
                inputs[j].checked = false;
            } else {
                have = true;
            }
        }

        /**
         * Show user defined select box
         */
        inputs = $A($$('#' + bSelection.idLabel + '_box_' + i
                            + ' .qty-box'));
        inputs.each(
            function(elem){
                elem.show();
            }
        );
    }
},

Listing 6. Hiding and displaying user defined quantity inputs depending on the option type, /app/design/adminhtml/default/default/template/bundle/product/edit/bundle/option.phtml, line 142.

This code is a function that is triggered when a user changes an HTML element responsible for option type in the bundle option edit form. If the new option type value is multi or checkbox the inputs with suffix ”.qty-box” are hidden, otherwise they are displayed. These inputs are the “User Defined Qty” flag setters that we need to enable for the “checkbox” option type. So, we remove the “checkbox” condition:

/*...*/
if (element.value == 'multi') {
inputs = $A($$('#' + bSelection.idLabel + '_box_' + i + ' tr.selection input.default'));
/*...*/

Listing 7. Modified condition in the changeType function.

A similar change must be made in the option/selection.phtml template’s addRow JavaScript function:

if (option_type.value == 'multi' || option_type.value == 'checkbox') {
    /**
     * Hide not needed elements (user defined qty select box)
     */
    inputs = $A($$('#' + this.idLabel + '_box_' + data.parentIndex
                    + ' .qty-box'));
    inputs.each(
        function(elem){
            elem.hide();
        }
    );
}

Listing 8.Excerpt of the original template’s addRow function, line 182.

Here again, we remove the “checkbox” condition so that the desired inputs are shown when a new checkbox type selection is added to the grid:

/*...*/
if (option_type.value == 'multi') {
/*...*/

Listing 9. The modified condition.

Back-end block overrides’ summary

Before, the system would check if an option or selection is a checkbox type and hide the “User Defined Qty” inputs. We’ve created alternative templates whose scripts don’t have these checks. We’ve assigned these new templates to blocks that override the original “option” and “selection” back-end blocks. Now, the bundle edit form in the back-end should show the “User Defined Qty” inputs for checkbox options too. No change in the selection model was necessary – this restriction was enforced by the limited edit elements display only.

checkbox_option_new

Figure 4. “User Defined Qty” elements in the selections grid of a checkbox option.

Note that I have set different values to different selections. Once we are done, we can experiment with them to test if the extension works as expected.

Step 2. Rewriting the front-end

The next step in our tutorial is setting up the front-end to display the modified options form. For that we have to rewrite one block and change its template. The block in question is Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option_Checkbox. In its current form it contains only a construct method which sets its template. Our override class will rewrite the constructor to replace the template with our custom one. We will also add one more function to the block. The new class, Solvingmagento_BundleExtended_Block_Catalog_Product_View_Type_Bundle_Option_Checkbox looks like this:

class Solvingmagento_BundleExtended_Block_Catalog_Product_View_Type_Bundle_Option_Checkbox
        extends Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option_Checkbox
{
        protected function _construct()
        {
                $this->setTemplate('bundleextended/catalog/product/view/type/bundle/option/checkbox.phtml');
        }

        public function getSelectionValues()
        {
                $option    = $this->getOption();
                $selections = $option->getSelections();

                $result = array();

                foreach ($selections as $selection) {
                        $result[$selection->getSelectionId()] = array(
                                'default_qty'  => max($selection->getSelectionQty() * 1, 1),
                                'user_defined' => (bool) $selection->getSelectionCanChangeQty()
                        );
                }

                return $result;
        }
}

Listing 10. Checkbox option front-end block override.

The constructor now assigns a new template file which we will create in a moment. The new method getSelectionValues is similar in purpose to the Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option::_getDefaultValues() function that is used by “Radio buttons” and “Drop-down” options. It takes a list of the option’s selections and generates an array of their “Default Qty” values and “User Defined Qty” flags. This array is used in the customized template. In this template we first get the option object, its selections, and the selections’ defaults:

<!--?php /* @var $this Mage_Bundle_Block_Catalog_Product_View_Type_Bundle_Option_Checkbox */ ?-->
<!--?php $_option = $this--->getOption() ?>
<!--?php $_selections = $_option--->getSelections() ?>
<!--?php $_defaults = $this--->getSelectionValues() ?>

Listing 11. Getting the selections’ default values.

The selections are outputted in two ways. First, there is a case when an option is required and has only one selection. The original template code for this is:

<!--?php if (count($_selections) == 1 && $_option--->getRequired()): ?>
    <!--?php echo $this--->getSelectionQtyTitlePrice($_selections[0]) ?>
    <input type="hidden" name="&quot;bundle_option[<?php" />getId() ?>]"
                    value="<!--?php echo $_selections[0]--->getSelectionId() ?>"/>
<!--?php else:?-->

Listing 12. Displaying a single selection of a required option, /app/design/frontend/base/default/template/bundle/catalog/product/view/type/bundle/option/checkbox.phtml, line 35.

We will add a text input for the customer defined quantity next to it so that it looks like that:

<!--?php if (count($_selections) == 1 && $_option--->getRequired()): ?>
    <!--?php $_selection = $_selections[0];     $default = $_defaults[$_selection--->getSelectionId()]; ?>
<span class="qty-holder" style="float: left; margin-right: 10px; padding-top: 0px;">
<label for="bundle-option-<?php echo $_option->getId() ?>-<?php                             echo $_selection->getSelectionId() ?>-qty-input">
             <!--?php echo $this--->__('Qty:') ?>
        </label>
        <input onkeyup="bundle.changeOptionQty(this, event)" type="text" />                            onblur="bundle.changeOptionQty(this, event)" <!--?php             if (!$default['user_defined']) echo ' disabled="disabled"' ?-->
                            id="bundle-option-<!--?php echo $_option--->getId() ?>-<!--?php                             echo $_selection--->getSelectionId() ?>-qty-input"
                            class="input-text qty<!--?php if (!$_canChangeQty) echo ' qty-disabled' ?-->"
                            type="text" name="bundle_option_qty[<!--?php             echo $_option--->getId().'-'.$_selection->getSelectionId() ?>]"
                            value="<!--?php echo $default['default_qty'] ?-->"/>
        </span>
<!--?php echo $this--->getSelectionQtyTitlePrice($_selections[0]) ?>
<input type="hidden" name="&quot;bundle_option[<?php" />getId() ?>]"
    value="<!--?php echo $_selections[0]--->getSelectionId() ?>"/>
<!--?php else:?-->

Listing 13. Modified checkbox selection’s code

The input field has an onkeyup and an onblur event bound to it. These events update the selection price depending on quantity entered by customer . We will have to update the underlying JavaScript file too so that the value changes from checkbox options are not ignored. This will be a task for this tutorial’s the next step.

We also modify the template code for the case when there is more than one selection. The alteration is pretty much the same so I am not going to post a listing for it. You will be able to download the whole module from the link in the end of the tutorial.

Front-end rewrite summary

In this step we have created a new template for “Checkbox” type options which allows displaying custom quantity inputs next to each selection item. The template is assigned to a block class that overrides the original checkbox option block. The rewrite configuration had to be entered into the module’s config.xml in a similar way we did for the back-end blocks. The new look of the option’s form is this:

checkbox_option_new_frontend

Figure 5. Input elements for user-defined quantity in the front-end.

Step 3. Modifying the Javascript

As you select and unselect other options of a bundle product you’ll notice that the price at the bottom of the product page changes. The change reflects the total sum of the selected components. This functionality is provided by a JavaScript file located at /skin/frontend/base/default/js/bundle.js. This file defines event handlers for the input elements of the bundle options form. Whenever option selection or quantity is updated the script calculates the new price using the quantity of the currently selected items. The new template for the “checkbox” option type contains code that binds new elements to these event handlers. However, it is not enough to make them work yet. We will have to modify the bundle.js file as well. To do that we will create a copy of this file and include it into the bundle product detail page to replace the original script.

What we have to change in the bundle.js is its function changeOptionQty. In its original form it looks like this:

changeOptionQty: function (element, event) {
    var checkQty = true;
    if (typeof(event) != 'undefined') {
        if (event.keyCode == 8 || event.keyCode == 46) {
            checkQty = false;
        }
    }
    if (checkQty && (Number(element.value) == 0 || isNaN(Number(element.value)))) {
        element.value = 1;
    }
    parts = element.id.split('-');
    optionId = parts[2];
    if (!this.config['options'][optionId].isMulti) {
        selectionId = this.config.selected[optionId][0];
        this.config.options[optionId].selections[selectionId].qty = element.value*1;
        this.reloadPrice();
    }
},

Listing 14. The original changeOptionQty function, /skin/frontend/base/default/js/bundle.js, line 193.

This function is the handler for onBlur and on keyUp events that are triggered when a user changes a selection’s quantity. Here, the last condition checks if the option being updated is a “multi” type (which applies for both “Multiselect” and “Checkbox” options), and if no, updates the quantity of the selected option and reloads the price. We want this to work for the “Checkbox” option too, so we extend this condition with one more check:

parts = element.id.split('-');
optionId = parts[2];
if (!this.config['options'][optionId].isMulti) {
    selectionId = this.config.selected[optionId][0];
    this.config.options[optionId].selections[selectionId].qty = element.value*1;
    this.reloadPrice();
} else if (parts[3] != undefined) {
    selectionId = parts[3];
    if (this.config['options'][optionId].selections[selectionId] != undefined
        && this.config['options'][optionId].selections[selectionId].customQty == 1) {
        this.config['options'][optionId].selections[selectionId].qty = element.value*1;
        this.reloadPrice();
    }

}

Listing 15. A modified quantity update conditions tree.

The variable parts is an array created from the element ID property. If you look into the new checkbox option’s template again you’ll see that the element IDs are comprised of four parts separated by hyphens, e.g., “bundle-option-14-29”. The third and the fourth element are option ID and selection ID respectively. Our new condition checks if there is a selection ID and if a selection element with this ID has a customQty property set to 1. This property corresponds to the selection’s property “User Defined Qty”. If the condition is true, we update the selection’s quantity and reload the price. That is it.

The new bundle.js file is placed under the /js/solvingmagento/bundleextended folder. To use it in place of the old script I have created a front-end layout update file with the following content:

<layout version="0.1.0">
        <PRODUCT_TYPE_bundle>
                <reference name="head">
                        <action method="removeItem">
                                <type>skin_js</type>
                                <name>js/bundle.js</name>
                        </action>
                        <action method="addItem">
                                <type>js</type>
                                <name>solvingmagento/bundleextended/bundle.js</name>
                        </action>
                </reference>
        </PRODUCT_TYPE_bundle>
</layout>

Listing 16. Replacing the JavaScript file.

This update removes the old script inclusion from the layout and adds a new “head” item: the customized bundle.js script. This update file has to be placed under /app/design/frontend/default/default/layout/solvingmagento_bundleextended.xml` and registered in the module’s config.xml file:

<config>
        ...
        <frontend>
                <layout>
                        <updates>
                                <solvingmagento_bundleextended>
                                        <file>solvingmagento_bundleextended.xml</file>
                                </solvingmagento_bundleextended>
                        </updates>
                </layout>
        </frontend>
</config>

Listing 17. Registering the front-end layout update.

Summary of the JavaScript customization

In this step we’ve created a modified version of the bundle.js script, which observes quantity changes for “checkbox” selections which have the “User Defined Qty” property enabled. The new script file replaces the original one via a front-end layout update.

Step 4. Rewriting the Bundle product type model

In this last step we are going to rewrite the Bundle product type model so that it can accept the customized form data when a user adds a bundle product to cart. The override class file is at Model/Product/Type.php. To register it we add the following lines to the config.xml:

<config>
        ...
        <global>
                ...
                <models>
                        <bundle>
                                <rewrite>
                                        <product_type>Solvingmagento_BundleExtended_Model_Product_Type</product_type>
                                </rewrite>
                        </bundle>
                </models>
        </global>
        ...
</config>

Listing 18. Registering the model rewrite.

The new class will contain two functions. The first function overrides the original Mage_Bundle_Model_Product_Type::_prepareProduct. The required change is quite small, but to include it I had to copy the entire huge method into our new class. The following listing shows the modified part only:

protected function _prepareProduct(Varien_Object $buyRequest, $product, $processMode)
{
    $result = $this->_abstractPrepareProduct($buyRequest, $product, $processMode);
    /**code omitted for brevity*/

        foreach ($selections as $selection) {
            $opId   = $selection->getOptionId();
            $combId = $opId . '-' . $selection->getSelectionId();
            if ($selection->getSelectionCanChangeQty()
                && (isset($qtys[$opId]) || isset($qtys[$combId]))
            ) {
                if ((float) $qtys[$opId] > 0) {
                    $qty = $qtys[$opId];
                } elseif ((float) $qtys[$combId] > 0) {
                    $qty = $qtys[$combId];
                } else {
                    $qty = 1;
                }
            } else {
                $qty = (float) $selection->getSelectionQty() ? $selection->getSelectionQty() : 1;
            }
            $qty = (float) $qty;
            /**code omitted for brevity*/
}

Listing 19. The modified _prepareProduct function.

In the original _prepareProduct method the first line looks like this:

$result = parent::_prepareProduct($buyRequest, $product, $processMode);

Listing 20. Call to a parent _prepareProduct method in the Bundle product type model, /app/code/core/Mage/Bundle/Model/Product/Type.php, line 529.

The parent of the Mage_Bundle_Model_Product_Type class is Mage_Catalog_Model_Product_Type_Abstract. To access its _prepareProduct method from my new class means to call the “grandparent“‘s function while skipping the “parent” method. Unfortunately, I couldn’t not find an elegant way to do that without modifying the parent class, which is a no-go. Using reflection can’t help either since the method is protected. If you happen to know a way to do it, please do contact me. I stumble upon this problem quite often in Magento. Absent a better idea, I’ve simply copied the Mage_Catalog_Model_Product_Type_Abstract:: _prepareProduct into my new class under name _abstractPrepareProduct and replaced the line in the top with the new call.

The most important modification, however, is in the foreach loop. The original looks like this:

foreach ($selections as $selection) {
if ($selection->getSelectionCanChangeQty() && isset($qtys[$selection->getOptionId()])) {
    $qty = (float)$qtys[$selection->getOptionId()] > 0 ? $qtys[$selection->getOptionId()] : 1;
} else {
    $qty = (float)$selection->getSelectionQty() ? $selection->getSelectionQty() : 1;
}
$qty = (float)$qty;

Listing 21. Reading the selection quantities, /app/code/core/Mage/Bundle/Model/Product/Type.php, line 641.

The add-to-cart form transfers selection quantities in element bundle_option_qty[]. Its array keys are numeric option IDs and values are quantities that customers select or enter. The quantity input fields in the new checkbox template have composite keys consisting of an option and of a selection ID, e.g, bundle_option_qty[14-36]. The $qty variable in Listing 21 is the same as the form element bundle_option_qty – keys are option IDs and values are quantities. Our modification allows the system to recognize composite keys and set the quantities to the selected checkbox items. The line $combId = $opId . '-' . $selection->getSelectionId(); generates a key from the option and selection IDs, e.g. 14-36, which is then used to reference the quantity from the $qty array. Checkbox selection that do not have “User defined qty” enabled get their quantity not from the form data but from the default value stored in their selection_qty property, or get quantity 1 if no such value is available. Thus, an add-to-cart request is processed correctly for the new checkbox options.

Summary of the Bundle product type model rewrite

The task of the last step was to ensure that the new property of the “checkbox” type option is correctly transferred to quote items when a bundle product is added to cart. This is done in the _prepareProduct method of the product type model, which we had to rewrite. With this change the new bundle extension is now complete. Go to the front-end page, select and unselect checkbox option items, enter custom quantity where it is enabled, and see if the configured price is updated correctly. Add a bundle to cart and make sure that your configuration is passed to cart without unexpected changes.

Conclusion

The goal of this extension was to introduce you to the Bundle product type implementation in Magento. You’ve seen what role is played by the module’s back-end and front-end blocks. You now know how is the option selection form in the product detail page generated and what elements does it consist of. You’ve looked at the client-side functionality provided by the bundle.js file and seen how to modify it. Finally, with rewriting of the Bundle product type model you’ve learned how to extend the method responsible for converting bundle product to quote and wishlist items. The entire module can be downloaded here: Solvingmagento_BundleExtended at Github.

Readers who read this post also read these:

  • Magento Grouped Product Type Tutorial
    This post is a follow up to the discussion on the Grouped product type in Magento. In this tutorial I will develop a simple Magento module, which will change some front-end display features of grou...
  • Magento Bundle Product Type (Part 2)
    This is the second part of our discussion on Magento Bundle product type. In the first part we concentrated on the back-end management of bundle products and on the front-end display specifics. In ...
  • Creating a Custom Product Type in Magento
    The standard product types in Magento cover a wide range of possible merchandise: physical and virtual, configurable and downloadable, grouped and bundled. Yet, the e-commerce world is so diverse t...
  • Magento Bundle Product Type (Part 1)
    Bundle is a composite Magento product type with many useful properties, most prominent of which, in my opinion, is its flexibility. Bundles are usually understood as two or more products sold toget...

24 thoughts on “Magento Bundle Product Type Tutorial

  1. Pingback: Magento Bundle Product Type (Part 2) | Solving Magento

  2. Hello,

    Thanks for the great advice! there is only 1 issue and maybe you can help me. If you want to update or change your product (from the checkout/cart) it does not remember the quantity of the products you choose. It show the default quantity.

    Is there a way to fix this?

    thanks!

      • I think it’s not the usual “Magento-Way”, but I found a way to do it.

        I looked at a lot of files and find the
        app/code/core/Mage/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php
        It has a method called _getDefaultValues() and therein is the line:

        $this->getProduct()->getPreconfiguredValues()
            ->getData('bundle_option_qty/' . $_option->getId())
        

        So, I changed the code of the getSelectionValues() method of the Checkbox.php of this tutorial to:

            public function getSelectionValues()
            {     
                $option    = $this->getOption();
                $selections = $option->getSelections();
                
                $result = array();
        
                foreach ($selections as $selection) {
                    $selectionId = $selection->getSelectionId();
        
                    $_defaultQty = 0;
        
                    if($this->getProduct()->getPreconfiguredValues()
                            ->getData('bundle_option_qty/' . $option->getId() . '-' . $selectionId)) {
                        
                        $_defaultQty = (int)$this->getProduct()->getPreconfiguredValues()
                            ->getData('bundle_option_qty/' . $option->getId() . '-' . $selectionId);
        
                    } else {
                        $_defaultQty = $selection->getSelectionQty();
                    }
        
                    $result[$selectionId] = array(
                        'default_qty'  => max($_defaultQty * 1, 1),
                        'user_defined' => (bool) $selection->getSelectionCanChangeQty()
                    );
                }        
                return $result;
            }
        

        I hope this helps someone!

  3. Hi Oleg,

    I must first say thank you for writing up great tutorials for us to learn from. I have added the bundle type quantity input to my checkbox and working. But as i sell in increments i would like to get the total bundle options input quantity so i can use jquery on addtocart.phtml to pull the total bundle options input quantity and update the product input quantity with the value. I have been reading alot of articals and js functions but can’t find a way to pull the bundle option quantity total. My obejective was to use the bundle.js to get the quantity information but would there be another way to pull this information from and is this technique the right way to complete this task. Hope you can point me to the right direction. Many Thanks!

    Regards

    Kevin

  4. Hi Oleg,

    thank you for your great tutorial.
    I used the files you shared on github but when a user changes the quantity of an selection the sum price is allways calculated with the default quantity nad not the new quantity.
    Can you please give me a hint what might be wrong.

    Thanks

    Christoph

    • I investigated my problem a little bit more.
      The total price in the cart is ok.
      Only the total price on the product page is wrong.

      • Hi Oleg,

        thank you for this detailed information and usefull extension of bundle logic.

        @Christoph:
        It works for me in magento 1.7.0.2 now.
        Had the same issue, another extension (sidebar summary for selectd bundle options) adds a second “changeOptionQty” function for bundles with javascript in a phtml file, so 2 definitions exists.

        Using an alert or console.log you can check if the correct bundle.js from this extension is used or not.

        In IE you can search within the developer tools (F12) ->Script for the string “changeOptionQty” if you called the bundle configuratiion.

        if the function is defined more than one time this could cause the error as the parts[3] logic is not included.

        Kozure

        • @Christoph:
          You are right … it is still not working. Going back from cart with edit calculates the price with the default values.

          My solution was for the error, that the total price was not calculated by quantity change with checkboxes.

          @Oleg: Maybe you can help us out … Thanks 😉

  5. hello there.
    I am beginner with Magento. I didn’t understand what should I do. May I ask you can you please send me all of the files plus the folders to me? Thank you so much.

  6. Thanks for the tute Oleg. Arrived after googling for a way to add Qty to checkbox options.

    There’s a few gotchas in the example code given in the listings though.

    Listing 5: config.xml
    You’ve got two closing tags, the first should be , not

    Listing 13: the qty input
    All the php is commented out

    I also believe replacing the core bundle.js file with a modified version is unnecessary and the wrong way to go about achieving the desired result.

    The best way is to use prototype’s wrap function.

    So instead of removing bundle.js from the header, you just add a new bundleextended.js file and override the desired function.

    bundleextended.xml layout would be thus:

    js
    Solvingmagento/bundleextended.js


    The name param is added to ensure this file is added AFTER bundle.js – else you get object undefined trouble.

    bundleextended.js

    // wrap bundleextended version of
    Product.Bundle.prototype.changeOptionQty =
    Product.Bundle.prototype.changeOptionQty.wrap(function(originalMethod, element, event){
    var checkQty = true;
    if (typeof(event) != 'undefined') {
    if (event.keyCode == 8 || event.keyCode == 46) {
    checkQty = false;
    }
    }
    if (checkQty && (Number(element.value) == 0 || isNaN(Number(element.value)))) {
    element.value = 1;
    }
    parts = element.id.split('-');
    optionId = parts[2];
    if (!this.config['options'][optionId].isMulti) {
    selectionId = this.config.selected[optionId][0];
    this.config.options[optionId].selections[selectionId].qty = element.value*1;
    this.reloadPrice();
    } else if (parts[3] != undefined) {
    selectionId = parts[3];
    if (this.config['options'][optionId].selections[selectionId] != undefined
    && this.config['options'][optionId].selections[selectionId].customQty == 1) {
    this.config['options'][optionId].selections[selectionId].qty = element.value*1;
    this.reloadPrice();
    }

    }

    //return originalMethod();
    });

    Thanks also to Densen’s fix for editing cart item.

  7. erro add to card

    array (size=12)
    ‘uenc’ => string ‘aHR0cDovL2xvY2FsaG9zdC9tYWtlcnNtZS9ldmVudHMtbmV3cy5odG1s’ (length=56)
    ‘product’ => string ‘1013’ (length=4)
    ‘form_key’ => string ‘e8G8iMO7NtTZa5Ai’ (length=16)
    ‘related_product’ => string ” (length=0)
    ‘bundle_option’ =>
    array (size=4)
    4 => string ‘5’ (length=1)
    3 => string ‘4’ (length=1)
    2 =>
    array (size=1)
    0 => string ‘1’ (length=1)
    5 => string ’12’ (length=2)
    ‘bundle_option_qty’ =>
    array (size=9)
    4 => string ‘1’ (length=1)
    3 => string ‘1’ (length=1)
    ‘5-6’ => string ‘5’ (length=1)
    ‘5-7’ => string ‘1’ (length=1)
    ‘5-8’ => string ‘1’ (length=1)
    ‘5-9’ => string ‘4’ (length=1)
    ‘5-10’ => string ‘1’ (length=1)
    ‘5-11’ => string ‘2’ (length=1)
    ‘5-12’ => string ‘7’ (length=1)
    ‘id_events’ => string ” (length=0)
    ‘qty’ => string ‘1’ (length=1)
    ‘review_title’ => string ” (length=0)
    ‘review_content’ => string ” (length=0)
    ‘display_name’ => string ” (length=0)
    ’email’ => string ” (length=0)

  8. I am having problem in step 2 showing in frontend in checkbox.phtml i wrote the same code given here but it doesn’t display each bundle item quantity box.

  9. Can you suggest changes to be made in the code if i want to use drop down on frontend side instead of textbox and its maximum value should be restricted from admin default qty.

  10. Hi Oleg,

    Thanks for this great blog. It’s really a quality writing.

    You mentioned that calling grand parent _prepareProduct () issue. Though I haven’t ran your code with the suggestion I am going to give but probably you can use the Abstract grand parent class name to call the grand parent method like this: Mage_Catalog_Model_Product_Type_Abstract::_prepareProduct().
    This is the way to call any grand parent’s method in a inheritance chain.

  11. Hi,

    I found bundle extended module in github. Its not working with Magento 1.9.2. Giving me cart adding error. Please help how can I resolve. Following is my addtocart array for bundle option qty.

    Array
    (
    [uenc] => aHR0cDovLzEyNy4wLjAuMS9tYWdlbnRvL2FjY2Vzc29yaWVzL2pld2VscnkvamV3ZWxsZXJ5LWJ1bmRsZS5odG1s
    [product] => 906
    [form_key] => RukBkRb0eJEh1oRt
    [related_product] =>
    [bundle_option_qty] => Array
    (
    [25-116] => 1
    [26-120] => 2
    [26-121] => 3
    )

    [bundle_option] => Array
    (
    [25] => Array
    (
    [0] => 116
    [1] => 117
    )

    [26] => Array
    (
    [0] => 120
    [1] => 121
    )

    )

    [qty] => 1
    )

  12. Thanks a lot. It really helped me to add custom quantity to the bundle options with multiple checkboxes !

    Mage_Bundle_Model_Product_Type::_prepareProduct , this method does the major job by adding custom quantity to option selections.

Leave a Reply

Your email address will not be published. Required fields are marked *

Theme: Esquire by Matthew Buchanan.