Add new input type in product custom options

Updated . Posted . Visible to the public.

custom module productcoption
config.xml

<config>
    <modules>
        <Somemod_ProductCoption>
            <version>0.0.1</version>
        </Somemod_ProductCoption>
    </modules>
    <global> 
        <resources>
            <productcoption_setup>
                <setup>
                    <module>Somemod_ProductCoption</module>                    
                </setup>                
            </productcoption_setup>            
        </resources>             
        <blocks>
            <productcoption>
                <class>Somemod_ProductCoption_Block</class>
            </productcoption>
            <adminhtml>
                <rewrite>
                    <catalog_product_edit_tab_options_option>Somemod_ProductCoption_Block_Adminhtml_ProductEditTabOptions_Option</catalog_product_edit_tab_options_option>
                    <sales_items_abstract>Somemod_ProductCoption_Block_Adminhtml_SalesItems_Abstract</sales_items_abstract>
                </rewrite>
            </adminhtml>
        </blocks> 
        <helpers>
            <productcoption>
                <class>Somemod_ProductCoption_Helper</class>
            </productcoption>
        </helpers>
        <models>
            <productcoption>
                <class>Somemod_ProductCoption_Model</class>
                <resourceModel>productcoption_resource</resourceModel>                
            </productcoption> 
            <productcoption_resource>
                <class>Somemod_ProductCoption_Model_Resource</class>
            </productcoption_resource>
            <catalog> 
                <rewrite>
                    <product_option>Somemod_ProductCoption_Model_Option</product_option>
                    <product_option_type_file>Somemod_ProductCoption_Model_Option_Type_File</product_option_type_file>
                    <product_option_type_text>Somemod_ProductCoption_Model_Option_Type_Text</product_option_type_text>
                    <product_type_simple>Somemod_ProductCoption_Model_ProductType_Simple</product_type_simple>
                    <product_type_virtual>Somemod_ProductCoption_Model_ProductType_Virtual</product_type_virtual>
                </rewrite>
            </catalog>
        </models>
        <catalog>
            <product>
                <options>
                    <custom>
                        <groups>
                            <productcoption translate="label" module="productcoption">
                                <label>ProductCoption Type</label>
                                <render>productcoption/adminhtml_productEditTabOptions_type</render>
                                <types>
                                    <source translate="label" module="productcoption">
                                        <label>Custom Source</label>
                                    </source>
                                    <country translate="label" module="productcoption">
                                        <label>Country</label>
                                    </country>
                                    <region translate="label" module="productcoption">
                                        <label>Region</label>
                                    </region>
                                </types>
                            </productcoption>
                        </groups>
                    </custom>
                </options>
            </product>
        </catalog>
    </global>
    <frontend>
        <routers>
            <productcoption>
                <use>standard</use>
                <args>
                    <module>Somemod_ProductCoption</module>
                    <frontName>productcoption</frontName>
                </args>
            </productcoption>
            <sales>
                <args>
                    <modules>
                        <Somemod_productcoption before="Mage_Sales">Somemod_ProductCoption_Sales</Somemod_productcoption>
                    </modules>
                </args>
            </sales>
        </routers>
        <layout>
            <updates>
                <emgs>
                    <file>productcoption.xml</file>
                </emgs>
            </updates>
  		</layout>
    </frontend>
    <adminhtml>
        <translate>
            <modules>
                <Somemod_ProductCoption>
                    <files>
                        <default>Somemod_ProductCoption.csv</default>
                    </files>
                </Somemod_ProductCoption>
            </modules>
        </translate>
    </adminhtml>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <Somemod_ProductCoption before="Mage_Adminhtml">Somemod_ProductCoption_Adminhtml</Somemod_ProductCoption>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>   
</config>   
class Scicom_ProductCoption_Block_Adminhtml_ProductEditTabOptions_Type extends Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Type_Abstract
{
    /**
     * Render in admin > product's Custom Options tab
     */
    public function __construct()
    {
        parent::__construct();
        $this->setTemplate('productcoption/productEditOptions/type.phtml');
    }
}

The phtml is the same as app\design\adminhtml\default\default\template\catalog\product\edit\options\type\date.phtml

<script type="text/javascript">

OptionTemplateProductcoption = '<table class="border" cellpadding="0" cellspacing="0">'+
        '<tr class="headings">'+
            <?php if ($this->getCanReadPrice() !== false) : ?>
            '<th class="type-price"><?php echo Mage::helper('core')->jsQuoteEscape(Mage::helper('catalog')->__('Price')) ?></th>' +
            '<th class="type-type"><?php echo Mage::helper('core')->jsQuoteEscape(Mage::helper('catalog')->__('Price Type')) ?></th>' +
            <?php endif; ?>
            '<th class="last"><?php echo Mage::helper('core')->jsQuoteEscape(Mage::helper('catalog')->__('SKU')) ?></th>'+
        '</tr>'+
        '<tr>'+
            <?php if ($this->getCanReadPrice() !== false) : ?>
            '<td><input type="text" class="input-text validate-number product-option-price" id="product_option_{{option_id}}_price" name="product[options][{{option_id}}][price]" value="{{price}}"<?php if ($this->getCanEditPrice() === false) : ?> disabled="disabled"<?php endif; ?>></td>' +
            '<td><?php echo $this->getPriceTypeSelectHtml() ?>{{checkboxScopePrice}}</td>' +
            <?php else : ?>
            '<input type="hidden" id="product_option_{{option_id}}_price" name="product[options][{{option_id}}][price]">' +
            '<input type="hidden" name="product[options][{{option_id}}][price_type]" id="product_option_{{option_id}}_price_type">' +
            <?php endif; ?>
            '<td class="last"><input type="text" class="input-text type-sku" name="product[options][{{option_id}}][sku]" value="{{sku}}"></td>'+
        '</tr>'+
    '</table>';

if ($('option_panel_type_productcoption')) {
    $('option_panel_type_productcoption').remove();
}

</script>

productcoption.xml insert html in frontend product view:

<layout version="0.1.0">
    <catalog_product_view>
        <reference name="product.info.options">
            <action method="addOptionRenderer"><type>source</type><block>productcoption/productViewOptions_type_source</block><template>productcoption/productViewOptions/type/source.phtml</template></action>
        </reference>
    </catalog_product_view>
</layout>

app\design\adminhtml\default\default\productoption\productEditOptions\option.phtml

Search for case 'field':, for the 2 locations found, add like the following:

        switch(element.getValue()){
            case 'field':
            case 'area':
                template = OptionTemplateText;
                group = 'text';
                break;
            // add to the end of switch but before default
            case 'source':
            case 'country':
            case 'region':
                template = OptionTemplateProductcoption;
                group = 'productcoption';
                break;     

Block Override:

/**
 * Overwrite 
 * - reset template for the added custom source
 * - add renderer to the Custom Options tab in admin product page
 */
class Scicom_ProductCoption_Block_Adminhtml_ProductEditTabOptions_Option extends Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Option
{
    public function __construct()
    {
        parent::__construct();
        //$this->setTemplate('catalog/product/edit/options/option.phtml'); //original
        $this->setTemplate('productcoption/productEditOptions/option.phtml');
        $this->setCanReadPrice(true);
        $this->setCanEditPrice(true);
    }

    public function getTemplatesHtml()
    {
        $this->getChild('productcoption_option_type')
            ->setCanReadPrice($this->getCanReadPrice())
            ->setCanEditPrice($this->getCanEditPrice());
        $templates = parent::getTemplatesHtml(). "\n" . $this->getChildHtml('productcoption_option_type');
        return $templates;   
    }
}

Model Override:

class Some_Module_Model_Catalog_Product_Option extends Mage_Catalog_Model_Product_Option
{
    /**
     * Get group name of option by given option type
     *
     * @param string $type
     * @return array
     */
    public function getGroupByType($type = null)
    {
        if (is_null($type)) {
            $type = $this->getType();
        }
        
        if ($group = parent::getGroupByType($type)) {
            return $group;
        }
        
        $optionGroupsToTypes = array(
            'source' => 'source',
            'country'=> 'source',
        );

        return isset($optionGroupsToTypes[$type])?$optionGroupsToTypes[$type]:'';
    }
    
    /**
     * Group model factory
     *
     * @param string $type Option type
     * @return Mage_Catalog_Model_Product_Option_Group_Abstract
     */
    public function groupFactory($type)
    {
        $group = $this->getGroupByType($type);
        if ($group == 'source') {
            return Mage::getModel('somemodule/catalog_product_option_type_' . $group);
        }
        if (!empty($group)) {
            return Mage::getModel('catalog/product_option_type_' . $group);
        }
        Mage::throwException(Mage::helper('catalog')->__('Wrong option type to get group instance.'));
    }
    
    /**
     * Get the source model
     * 
     * @return class          
     */         
    public function getSource()
    {
        $title = $this->getType() == 'country' ? 'country' : lcfirst(str_replace(' ', '', $this->getTitle()));
        return Mage::getSingleton('somemodule/source_productOption_'.$title);
    }
}

The above model is mainly for saving the custom options to the product in admin.

The group factory model:

class Some_Module_Model_Catalog_Product_Option_Type_Source extends Mage_Catalog_Model_Product_Option_Type_Default
{
    /**
     * Validate user input for option
     *
     * @throws Mage_Core_Exception
     * @param array $values All product option values, i.e. array (option_id => mixed, option_id => mixed...)
     * @return Mage_Catalog_Model_Product_Option_Type_Default
     */
    public function validateUserValue($values)
    {
        parent::validateUserValue($values);

        $option = $this->getOption();
        $value = $this->getUserValue();

        if (empty($value) && $option->getIsRequire() && !$this->getSkipCheckRequiredOption()) {
            $this->setIsValid(false);
            Mage::throwException(Mage::helper('catalog')->__('Please specify the product required option(s).'));
        }
        return $this;
    }

    /**
     * Prepare option value for cart
     *
     * @throws Mage_Core_Exception
     * @return mixed Prepared option value
     */
    public function prepareForCart()
    {
        if ($this->getIsValid() && $this->getUserValue()) {
            return $this->getUserValue();
        } else {
            return null;
        }
    }

    /**
     * Return formatted option value for quote option
     *
     * @param string $optionValue Prepared for cart option value
     * @return string
     */
    public function getFormattedOptionValue($optionValue)
    {
        if ($this->_formattedOptionValue === null) {
            $this->_formattedOptionValue = Mage::helper('core')->escapeHtml(
                $this->getEditableOptionValue($optionValue)
            );
        }
        return $this->_formattedOptionValue;
    }

    /**
     * Return printable option value
     *
     * @param string $optionValue Prepared for cart option value
     * @return string
     */
    public function getPrintableOptionValue($optionValue)
    {
        return $this->getFormattedOptionValue($optionValue);
    }

    /**
     * Return currently unavailable product configuration message
     *
     * @return string
     */
    protected function _getWrongConfigurationMessage()
    {
        return Mage::helper('catalog')->__('Some of the selected item options are not currently available.');
    }

    /**
     * Return formatted option value ready to edit, ready to parse
     *
     * @param string $optionValue Prepared for cart option value
     * @return string
     */
    public function getEditableOptionValue($optionValue)
    {
        $option = $this->getOption();
        $result = '';
        
        if ($source = $this->getOption()->getSource()) {
            $result = $source->getOptionText($optionValue);
        } else {
            if ($this->getListener()) {
                $this->getListener()
                        ->setHasError(true)
                        ->setMessage(
                            $this->_getWrongConfigurationMessage()
                        );
            }
            $result = $optionValue;
        }
        
        return $result;
    }

    /**
     * Parse user input value and return cart prepared value, i.e. "one, two" => "1,2"
     *
     * @param string $optionValue
     * @param array $productOptionValues Values for product option
     * @return string|null
     */
    public function parseOptionValue($optionValue, $productOptionValues)
    {
        $_values = array();
        if (array_key_exists($optionValue, $productOptionValues)) {
            return $productOptionValues[$optionValue];
        }       
        return null;
    }
}

Block:

class Some_Module_Block_Catalog_Product_View_Options_Type_Source extends Mage_Catalog_Block_Product_View_Options_Abstract
{
    public function getSource()
    {
        if (!$this->getData('source')) {
            $this->setData('source', $this->getOption()->getSource());
        }
        return $this->getData('source');
    }
    
    public function getOptions()
    {
        if (!$this->getData('options')) {
            $this->setData('options', $this->getSource()->toOptionArray($this->__('-- Please Select --')));
        }
        return $this->getData('options');
    }
    
    public function toOptionAttributeHtml($option)
    {
        $html = '';
        unset($option['label']);
        foreach ($option as $k=>$v) {           
            $html .= $k.'="'.$v.'" ';
        }
        return $html; 
    }
    
    public function getNoteHtml()
    {
        $html = $this->getSource()->getNoteHtml();
        if (!$this->getOptions() && $this->getOption()->getMaxCharacters()) {
            $html .= '<br />'.Mage::helper('catalog')->__('Maximum number of characters:').'<strong>'.$this->getOption()->getMaxCharacters().'</strong>';
        }
        return $html;
    }
    
    public function getDefaultValue()
    {
        return $this->getProduct()->getPreconfiguredValues()->getData('options/' . $this->getOption()->getId());
    }
}

somemodule/catalog/product/view/options/type/source.phtml:

<?php $_option = $this->getOption(); ?>
<dt><label<?php if ($_option->getIsRequire()) echo ' class="required"' ?>><?php if ($_option->getIsRequire()) echo '<em>*</em>' ?><?php echo  $this->htmlEscape($_option->getTitle(), array('br')) ?></label>
    <?php echo $this->getFormatedPrice() ?></dt>
<dd<?php if ($_option->decoratedIsLast){?> class="last"<?php }?>>
    <div class="input-box">       
       <?php if($options = $this->getOptions()): ?>  
            <?php $default = $this->escapeHtml($this->getDefaultValue()) ?>
            <select title="" class="<?php echo $_option->getIsRequire() ? ' required-entry' : '' ?> product-custom-option" id="select_<?php echo $_option->getId() ?>" name="options[<?php echo $_option->getId() ?>]">
                <?php foreach($options as $option): ?>
                    <option <?php echo $this->toOptionAttributeHtml($option) ?> <?php if($option['value']==$default): ?>selected="selected"<?php endif; ?>><?php echo $option['label'] ?></option>
                <?php endforeach; ?>
            </select>           
        <?php else: ?>
            <input type="text" onchange="opConfig.reloadPrice()" id="options_<?php echo $_option->getId() ?>_text" class="input-text<?php echo $_option->getIsRequire() ? ' required-entry' : '' ?> <?php echo $_option->getMaxCharacters() ? ' validate-length maximum-length-'.$_option->getMaxCharacters() : '' ?> product-custom-option" name="options[<?php echo $_option->getId() ?>]" value="<?php echo $this->escapeHtml($this->getDefaultValue()) ?>" />
        <?php endif; ?>
        <?php if($note = $this->getNoteHtml()): ?>
            <p class="note"><?php echo $note ?></p>
        <?php endif; ?>
    </div>
</dd>

Finally, add some source models, eg:

class Some_Module_Model_Source_ProductOption_Country extends Some_Module_Model_Source_Country
{   
    public function toOptionArray($iso3=true, $emptyLabel='')
    {
        if (!$emptyLabel) {
            $emptyLabel = Mage::helper('somemodule')->__('-- Please Select --');
        }
        return parent::toOptionArray($iso3, $emptyLabel);
    }
    
     public function getOptionText($value)
     {
          $options = $this->toOptionArray();
          foreach ($options as $item) {
             if ($item['value'] == $value) {
                 return $item['label'];
             }
          }
         return $value;
     }
     
     public function getNoteHtml()
     {
        return '';
     }
}

In adminhtml layout template for backend reordering:

<!-- see Mage_Adminhtml_Block_Catalog_Product_Composite_Fieldset_Options -->
<ADMINHTML_CATALOG_PRODUCT_COMPOSITE_CONFIGURE>
	<reference name="product.composite.fieldset.options">
		<action method="addOptionRenderer"><type>source</type><block>emgs/catalog_product_view_options_type_source</block><template>emgs/catalog/product/composite/fieldset/options/type/source.phtml</template></action>
	</reference>
</ADMINHTML_CATALOG_PRODUCT_COMPOSITE_CONFIGURE>

Copy the .phtml template from the frontend to adminhtml folder.

To Do
Add pricing for the option, overwrite Mage_Catalog_Block_Product_View_Options::getJsonConfig()

kiatng
Last edit
kiatng
Attachments
Posted by kiatng to OpenMage (2016-02-11 09:08)