PROBLEM
Working on a module to add a product custom option input type (Derivated from file type).
In the back-end, under catalog/product/custom options, the new option is present in the “input type” list but when selecting it, the associated custom option type fields are not displayed (e.g.: price, price type, sky, compatible file extension, etc.).
app/code/A/Custoptiontype/etc/module.xml
<?xml version="1.0" encoding="UTF-8"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="A_Custoptiontype" setup_version="1.0.0"> <sequence> <module name="Magento_Catalog"/> </sequence> </module> </config>
app/code/A/Custoptiontype/etc/di.xml
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\Option" type="A\Custoptiontype\Block\Adminhtml\Product\Edit\Tab\Options\Option"/> <preference for="Magento\Catalog\Model\Product\Option" type="A\Custoptiontype\Model\Catalog\Product\Option"/> </config>
app/code/A/Custoptiontype/etc/product_options.xml
<?xml version="1.0" encoding="UTF-8"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_options.xsd"> <option name="xfile" label="X File" renderer="A\Custoptiontype\Block\Adminhtml\Product\Edit\Tab\Options\Type\Xfile"> <inputType name="xfile" label="X File" /> </option> </config>
app/code/A/Custoptiontype/Block/Adminhtml/Product/Edit/Tab/Options/Option.php
<?php namespace A\Custoptiontype\Block\Adminhtml\Product\Edit\Tab\Options; class Option extends \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\Option { protected $_template = 'A_Custoptiontype::catalog/product/edit/options/option.phtml'; /** * Class constructor */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Config\Model\Config\Source\Yesno $configYesNo, \Magento\Catalog\Model\Config\Source\Product\Options\Type $optionType, \Magento\Catalog\Model\Product $product, \Magento\Framework\Registry $registry, \Magento\Catalog\Model\ProductOptions\ConfigInterface $productOptionConfig, array $data = [] ) { parent::__construct( $context, $configYesNo, $optionType, $product, $registry, $productOptionConfig, $data ); } /** * Retrieve html templates for different types of product custom options * * @return string */ public function getTemplatesHtml() { $canEditPrice = $this->getCanEditPrice(); $canReadPrice = $this->getCanReadPrice(); $this->getChildBlock('xfile_option_type') ->setCanReadPrice($canReadPrice) ->setCanEditPrice($canEditPrice); $templates = parent::getTemplatesHtml() . "\n" . $this->getChildHtml('xfile_option_type'); return $templates; } }
app/code/A/Custoptiontype/Block/Adminhtml/Product/Edit/Tab/Options/Type/Xfile.php
<?php namespace A\Custoptiontype\Block\Adminhtml\Product\Edit\Tab\Options\Type; class Xfile extends \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\Type\AbstractType { /** * @var string */ protected $_template = 'A_Custoptiontype::catalog/product/edit/options/type/xfile.phtml'; public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Catalog\Model\Config\Source\Product\Options\Price $optionPrice, array $data = [] ) { $this->_optionPrice = $optionPrice; parent::__construct($context, $optionPrice, $data); } }
app/code/A/Custoptiontype/view/adminhtml/templates/catalog/product/edit/options/option.phtml
<?php /** * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ // @codingStandardsIgnoreFile ?> <?php /** @var $block \A\Custoptiontype\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\Option */ ?> <?php echo $block->getTemplatesHtml() ?> <script id="custom-option-base-template" type="text/x-magento-template"> <div class="fieldset-wrapper admin__collapsible-block-wrapper opened" id="option_<%- data.id %>"> <div class="fieldset-wrapper-title"> <strong class="admin__collapsible-title" data-toggle="collapse" data-target="#<%- data.id %>-content"> <span id="option_<%- data.id %>_header_title"><%- data.title %></span> </strong> <div class="actions"> <button type="button" title="<?php /* @escapeNotVerified */ echo __('Delete Custom Option'); ?>" class="action-delete" id="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>_delete"> <span><?php /* @escapeNotVerified */ echo __('Delete Custom Option'); ?></span> </button> </div> <div id="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>_move" data-role="draggable-handle" class="draggable-handle" title="<?php /* @escapeNotVerified */ echo __('Sort Custom Options'); ?>"></div> </div> <div class="fieldset-wrapper-content in collapse" id="<%- data.id %>-content"> <fieldset class="fieldset"> <fieldset class="fieldset-alt" id="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>"> <input id="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>_is_delete" name="<?php /* @escapeNotVerified */ echo $block->getFieldName() ?>[<%- data.id %>][is_delete]" type="hidden" value=""/> <input id="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>_previous_type" name="<?php /* @escapeNotVerified */ echo $block->getFieldName() ?>[<%- data.id %>][previous_type]" type="hidden" value="<%- data.type %>"/> <input id="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>_previous_group" name="<?php /* @escapeNotVerified */ echo $block->getFieldName() ?>[<%- data.id %>][previous_group]" type="hidden" value=""/> <input id="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>_id" name="<?php /* @escapeNotVerified */ echo $block->getFieldName() ?>[<%- data.id %>][id]" type="hidden" value="<%- data.id %>"/> <input id="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>_option_id" name="<?php /* @escapeNotVerified */ echo $block->getFieldName() ?>[<%- data.id %>][option_id]" type="hidden" value="<%- data.option_id %>"/> <input name="<?php /* @escapeNotVerified */ echo $block->getFieldName() ?>[<%- data.id %>][sort_order]" type="hidden" value="<%- data.sort_order %>"/> <div class="field field-option-title required"> <label class="label" for="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>_title"> <?php /* @escapeNotVerified */ echo __('Option Title') ?> </label> <div class="control"> <input id="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>_title" name="<?php /* @escapeNotVerified */ echo $block->getFieldName() ?>[<%- data.id %>]" class="required-entry input-text" type="text" value="<%- data.title %>" data-store-label="<%- data.title %>" <% if (typeof data.scopeTitleDisabled != 'undefined' && data.scopeTitleDisabled != null) { %> disabled="disabled" <% } %> > <%- data.checkboxScopeTitle %> </div> </div> <div class="field field-option-input-type required"> <label class="label" for="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>_title"> <?php /* @escapeNotVerified */ echo __('Input Type') ?> </label> <div class="control opt-type"> <?php echo $block->getTypeSelectHtml() ?> </div> </div> <div class="field field-option-req"> <div class="control"> <input id="<?php /* @escapeNotVerified */ echo $block->getFieldId() ?>_<%- data.id %>_required" class="is-required" type="checkbox" checked="checked"/> <label for="field-option-req"> <?php /* @escapeNotVerified */ echo __('Required')?> </label> <span style="display:none"><?php echo $block->getRequireSelectHtml() ?></span> </div> </div> </fieldset> </fieldset> </div> </div> </script> <div id="import-container" style="display: none;"></div> <?php if (!$block->isReadonly()): ?> <div><input type="hidden" name="affect_product_custom_options" value="1"/></div> <?php endif; ?> <script> require([ "jquery", "Magento_Catalog/js/custom-options" ], function(jQuery){ jQuery(function ($) { var fieldSet = $('[data-block=product-custom-options]'); fieldSet.customOptions(<?php /* @escapeNotVerified */ echo $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode( [ 'fieldId' => $block->getFieldId(), 'productGridUrl' => $block->getProductGridUrl(), 'formKey' => $block->getFormKey(), 'customOptionsUrl' => $block->getCustomOptionsUrl(), 'isReadonly' => $block->isReadonly(), 'itemCount' => $block->getItemCount(), 'currentProductId' => $block->getCurrentProductId(), ] )?>); //adding data to templates <?php /** @var $_value \Magento\Framework\DataObject */ ?> <?php foreach ($block->getOptionValues() as $_value): ?> fieldSet.customOptions('addOption', <?php /* @escapeNotVerified */ echo $_value->toJson() ?>); <?php endforeach; ?> }); }); </script>Lorem ipsum dolor sit amet...
app/code/A/Custoptiontype/view/adminhtml/templates/catalog/product/edit/options/type/xfile.phtml
<?php /** * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ // @codingStandardsIgnoreFile ?> <?php /** @var $block \A\Custoptiontype\Block\Adminhtml\Product\Edit\Tab\Options\Type\Xfile */ ?> <h1>TOTOTOTOTO</h1> <script id="custom-option-xfile-type-template" type="text/x-magento-template"> <div id="product_option_<%- data.option_id %>_type_<%- data.group %>" class="fieldset"> <table class="data-table"> <thead> <tr> <?php if ($block->getCanReadPrice() !== false) : ?> <th><?php /* @escapeNotVerified */ echo __('Price'); ?></th> <th><?php /* @escapeNotVerified */ echo __('Price Type'); ?></th> <?php endif; ?> <th><?php /* @escapeNotVerified */ echo __('SKU'); ?></th> <th><?php /* @escapeNotVerified */ echo __('Compatible File Extensions'); ?></th> <th><?php /* @escapeNotVerified */ echo __('Maximum Image Size'); ?></th> </tr> </thead> <tr> <?php if ($block->getCanReadPrice() !== false) : ?> <td class="opt-price"> <input name="product[options][<%- data.option_id %>][price]" data-store-label="<%- data.price %>" class="input-text validate-zero-or-greater" type="text" value="<%- data.price %>" <?php if ($block->getCanEditPrice() === false) : ?> disabled="disabled" <?php endif; ?>> </td> <td class="opt-price-type"><?php echo $block->getPriceTypeSelectHtml('data-attr="price-type"') ?><%- data.checkboxScopePrice %></td> <?php else : ?> <input name="product[options][<%- data.option_id %>][price]" type="hidden"> <input id="product_option_<%- data.option_id %>_price_type" name="product[options][<%- data.option_id %>][price_type]" type="hidden"> <?php endif; ?> <td> <input name="product[options][<%- data.option_id %>][sku]" class="input-text" type="text" value="<%- data.sku %>"> </td> <td> <input name="product[options][<%- data.option_id %>][file_extension]" class="input-text" type="text" value="<%- data.file_extension %>"> </td> <td class="col-file"><?php /* @escapeNotVerified */ echo __('%1 <span>x</span> %2 <span>px.</span>', '<input class="input-text" type="text" name="product[options][<%- data.option_id %>][image_size_x]" value="<%- data.image_size_x %>">', '<input class="input-text" type="text" name="product[options][<%- data.option_id %>][image_size_y]" value="<%- data.image_size_y %>">') ?> <div class="note"><?php /* @escapeNotVerified */ echo __('Please leave blank if it is not an image.') ?></div> </td> </tr> </table> </div> </script>
SOLUTIONS
Rename namepsace from Custoptiontype to CustomOptions
- Step 1: Add folder Ui
Create the file CustomOption.php in A\CustomOption\Ui\DataProvider\Catalog\Product\Form\Modifier
Code CustomOption.php
<?php namespace A\CustomOptions\Ui\DataProvider\Catalog\Product\Form\Modifier; use Magento\Catalog\Model\Locator\LocatorInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Catalog\Model\ProductOptions\ConfigInterface; use Magento\Catalog\Model\Config\Source\Product\Options\Price as ProductOptionsPrice; use Magento\Framework\UrlInterface; use Magento\Framework\Stdlib\ArrayManager; use Magento\Ui\Component\Modal; use Magento\Ui\Component\Container; use Magento\Ui\Component\DynamicRows; use Magento\Ui\Component\Form\Fieldset; use Magento\Ui\Component\Form\Field; use Magento\Ui\Component\Form\Element\Input; use Magento\Ui\Component\Form\Element\Select; use Magento\Ui\Component\Form\Element\Checkbox; use Magento\Ui\Component\Form\Element\ActionDelete; use Magento\Ui\Component\Form\Element\DataType\Text; use Magento\Ui\Component\Form\Element\DataType\Number; use Magento\Framework\Locale\CurrencyInterface; class CustomOptions extends \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions { const FIELD_TESTOPTION_NAME = 'testoption'; protected function getStaticTypeContainerConfig($sortOrder) { return [ 'arguments' => [ 'data' => [ 'config' => [ 'componentType' => Container::NAME, 'formElement' => Container::NAME, 'component' => 'Magento_Ui/js/form/components/group', 'breakLine' => false, 'showLabel' => false, 'additionalClasses' => 'admin__field-group-columns admin__control-group-equal', 'sortOrder' => $sortOrder, ], ], ], 'children' => [ static::FIELD_PRICE_NAME => $this->getPriceFieldConfig(10), static::FIELD_PRICE_TYPE_NAME => $this->getPriceTypeFieldConfig(20), static::FIELD_SKU_NAME => $this->getSkuFieldConfig(30), static::FIELD_TESTOPTION_NAME => $this->getTestoptionFieldConfig(30), static::FIELD_MAX_CHARACTERS_NAME => $this->getMaxCharactersFieldConfig(40), static::FIELD_FILE_EXTENSION_NAME => $this->getFileExtensionFieldConfig(50), static::FIELD_IMAGE_SIZE_X_NAME => $this->getImageSizeXFieldConfig(60), static::FIELD_IMAGE_SIZE_Y_NAME => $this->getImageSizeYFieldConfig(70) ] ]; } /** * Get config for grid for "select" types * * @param int $sortOrder * @return array */ protected function getSelectTypeGridConfig($sortOrder) { return [ 'arguments' => [ 'data' => [ 'config' => [ 'addButtonLabel' => __('Add Value'), 'componentType' => DynamicRows::NAME, 'component' => 'Magento_Ui/js/dynamic-rows/dynamic-rows', 'additionalClasses' => 'admin__field-wide', 'deleteProperty' => static::FIELD_IS_DELETE, 'deleteValue' => '1', 'renderDefaultRecord' => false, 'sortOrder' => $sortOrder, ], ], ], 'children' => [ 'record' => [ 'arguments' => [ 'data' => [ 'config' => [ 'componentType' => Container::NAME, 'component' => 'Magento_Ui/js/dynamic-rows/record', 'positionProvider' => static::FIELD_SORT_ORDER_NAME, 'isTemplate' => true, 'is_collection' => true, ], ], ], 'children' => [ static::FIELD_TITLE_NAME => $this->getTitleFieldConfig(10), static::FIELD_PRICE_NAME => $this->getPriceFieldConfig(20), static::FIELD_PRICE_TYPE_NAME => $this->getPriceTypeFieldConfig(30, ['fit' => true]), static::FIELD_SKU_NAME => $this->getSkuFieldConfig(40), static::FIELD_SORT_ORDER_NAME => $this->getPositionFieldConfig(50), static::FIELD_IS_DELETE => $this->getIsDeleteFieldConfig(60) ] ] ] ]; } protected function getTypeFieldConfig($sortOrder) { return [ 'arguments' => [ 'data' => [ 'config' => [ 'label' => __('Option Type'), 'componentType' => Field::NAME, 'formElement' => Select::NAME, 'component' => 'Magento_Catalog/js/custom-options-type', 'elementTmpl' => 'ui/grid/filters/elements/ui-select', 'selectType' => 'optgroup', 'dataScope' => static::FIELD_TYPE_NAME, 'dataType' => Text::NAME, 'sortOrder' => $sortOrder, 'options' => $this->getProductOptionTypes(), 'disableLabel' => true, 'multiple' => false, 'selectedPlaceholders' => [ 'defaultPlaceholder' => __('-- Please select --'), ], 'validation' => [ 'required-entry' => true ], 'groupsConfig' => [ 'text' => [ 'values' => ['field', 'area'], 'indexes' => [ static::CONTAINER_TYPE_STATIC_NAME, static::FIELD_PRICE_NAME, static::FIELD_PRICE_TYPE_NAME, static::FIELD_SKU_NAME, static::FIELD_MAX_CHARACTERS_NAME ] ], 'Testvendor' => [ 'values' => ['testoption'], 'indexes' => [ static::CONTAINER_TYPE_STATIC_NAME, #static::FIELD_SKU_NAME, static::FIELD_TESTOPTION_NAME, ] ], 'file' => [ 'values' => ['file'], 'indexes' => [ static::CONTAINER_TYPE_STATIC_NAME, static::FIELD_PRICE_NAME, static::FIELD_PRICE_TYPE_NAME, static::FIELD_SKU_NAME, static::FIELD_FILE_EXTENSION_NAME, static::FIELD_IMAGE_SIZE_X_NAME, static::FIELD_IMAGE_SIZE_Y_NAME ] ], 'select' => [ 'values' => ['drop_down', 'radio', 'checkbox', 'multiple'], 'indexes' => [ static::GRID_TYPE_SELECT_NAME ] ], 'data' => [ 'values' => ['date', 'date_time', 'time'], 'indexes' => [ static::CONTAINER_TYPE_STATIC_NAME, static::FIELD_PRICE_NAME, static::FIELD_PRICE_TYPE_NAME, static::FIELD_SKU_NAME ] ] ], ], ], ], ]; } protected function getTestoptionFieldConfig($sortOrder) { return [ 'arguments' => [ 'data' => [ 'config' => [ 'label' => __('Test Option'), 'componentType' => Field::NAME, 'formElement' => Input::NAME, 'dataScope' => static::FIELD_SKU_NAME, 'dataType' => Text::NAME, 'sortOrder' => $sortOrder, ], ], ], ]; } }
- Step 2: Edit: product_options.xml
app/code/A/ CustomOptions /etc/product_options.xml
<?xml version="1.0" encoding="UTF-8"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_options.xsd"> <option name="xfile" label="X File" renderer="A\CustomOptions\Block\Adminhtml\Product\Edit\Tab\Options\Type\Xfile"> <inputType name="testoption" label="Test Option" /> </option> </config>
After that, you run the following command in the bin director:
./magento setup:upgrade ./magento setup:di:compile ./magento cache:flush ./magento cache:clean
We have shown you 2 simple steps to fix the error: Custom Option Type Fields Are Not Displayed in Magento 2. If you have any problems when following this instruction, be free to leave a comment below.
More From Magento Fix It Series
[Fix It Series] Front Controller Reached 100 router Match Iterations
[ratings]