How to Add Custom Field for Option Values in Advanced Product Options

0
3028
How to add custom fields for products on Magento 2 | Mageworx Blog
Reading Time: 6 minutes

From the previous article, you’ve learned how to create Magento custom option fields. We also have found out how to display the field data on both the product page front-end and the order page in the admin panel.

What’s now?

Let’s learn how to add the same custom GTIN field for the option values and display it on the product page front-end.

Step #1. New Module Creation

Let’s start with the new module creation, which process was covered in detail in this Mageworx blog post.

Thus, without further ado, here is the code that we will need:

1. composer.json

{
    "name": "mageworx/module-optionvaluegtin",
    "description": "N/A",
    "require": {
        "magento/framework"     :     ">=100.1.0 <101",
        "magento/module-catalog":     ">=101.0.0 <104"
    },
    "type": "magento2-module",
    "version": "1.0.0",
    "license": [
        "OSL-3.0",
        "AFL-3.0"
    ],
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "VendorName\\OptionValueGtin\\": ""
        }
    }
}

2. etc/module.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="VendorName_OptionValueGtin" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Catalog"/>
            <module name="MageWorx_OptionBase"/>
        </sequence>
    </module>
</config>

3. registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'VendorName_OptionValueGtin',
    __DIR__
);

Step #2. Adding New Field to Database

It’s time to create the GTIN field and add it to the corresponding table in the database.

As we are adding a field for option values, the `catalog_product_option_type_value` table will be required.

Let’s create the following file:

`app/code/VendorName/OptionValueGtin/Setup/InstallSchema.php`

<?php
namespace VendorName\OptionValueGtin\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table;

class InstallSchema implements InstallSchemaInterface
{
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();

        $setup->getConnection()->addColumn(
            $setup->getTable('catalog_product_option_type_value'),
            'gtin',
            [
                'type'     => Table::TYPE_TEXT,
                'nullable' => true,
                'default'  => null,
                'comment'  => 'Gtin (added by MageWorx Option Value Gtin)',
            ]
        );

        $setup->endSetup();

    }
}

Step #3. Adding Logic to Work with Backend

Use the pool-modifier mechanism to Magento add field to custom option.

Let’s create the following file:

`app/code/VendorName/OptionValueGtin/etc/adminhtml/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">
    <virtualType name="MageWorx\OptionBase\Ui\DataProvider\Product\Form\Modifier\Pool">
        <arguments>
            <argument name="modifiers" xsi:type="array">
                <item name="mageworx-option-value-gtin" xsi:type="array">
                    <item name="class" xsi:type="string">MageWorx\OptionValueGtin\Ui\DataProvider\Product\Form\Modifier\OptionValueGtin</item>
                    <item name="sortOrder" xsi:type="number">72</item>
                </item>
            </argument>
        </arguments>
    </virtualType>
</config>

Here:

We add our modifier to the general pool of the Advanced Product Options extension for Magento 2―

`MageWorx\OptionBase\Ui\DataProvider\Product\Form\Modifier\Pool`.

`VendorName\OptionValueGtin\Ui\DataProvider\Product\Form\Modifier\OptionValueGtin` is the modifier class.

Below:

See the code that allows adding our field to the `app/code/VendorName/OptionValueGtin/Ui/DataProvider/Product/Form/Modifier/OptionValueGtin.php` form:

<?php
namespace VendorName\OptionValueGtin\Ui\DataProvider\Product\Form\Modifier;

use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions;
use Magento\Ui\Component\Form\Element\Input;
use Magento\Ui\Component\Form\Element\DataType\Number;
use Magento\Ui\Component\Form\Field;
use MageWorx\OptionBase\Ui\DataProvider\Product\Form\Modifier\ModifierInterface;

class OptionValueGtin extends AbstractModifier implements ModifierInterface
{
    /**
     * @var array
     */
    protected $meta = [];

    /**
     * {@inheritdoc}
     */
    public function modifyData(array $data)
    {
        return $data;
    }

    /**
     * {@inheritdoc}
     */
    public function modifyMeta(array $meta)
    {
        $this->meta = $meta;

        $this->addFields();

        return $this->meta;
    }

    /**
     * Adds fields to the meta-data
     */
    protected function addFields()
    {
        $groupCustomOptionsName    = CustomOptions::GROUP_CUSTOM_OPTIONS_NAME;
        $optionContainerName       = CustomOptions::CONTAINER_OPTION;

        // Add fields to the values
        $valueFeaturesFields   = $this->getValueFieldsConfig();
        $this->meta[$groupCustomOptionsName]['children']['options']['children']['record']['children']
        [$optionContainerName]['children']['values']['children']['record']['children'] = array_replace_recursive(
            $this->meta[$groupCustomOptionsName]['children']['options']['children']['record']['children']
            [$optionContainerName]['children']['values']['children']['record']['children'],
            $valueFeaturesFields
        );

    }


    /**
     * The custom option fields config
     *
     * @return array
     */
    protected function getValueFieldsConfig()
    {
        $fields['gtin'] = $this->getGtinFieldConfig();

        return $fields;
    }

    /**
     * Get gtin field config
     *
     * @return array
     */
    protected function getGtinFieldConfig()
    {
        return [
            'arguments' => [
                'data' => [
                    'config' => [
                        'label'         => __('GTIN'),
                        'componentType' => Field::NAME,
                        'formElement'   => Input::NAME,
                        'dataType'      => Number::NAME,
                        'dataScope'     => 'gtin',
                        'sortOrder'     => 92
                    ],
                ],
            ],
        ];
    }

    /**
     * Check is current modifier for the product only
     *
     * @return bool
     */
    public function isProductScopeOnly()
    {
        return false;
    }

    /**
     * Get sort order of modifier to load modifiers in the right order
     *
     * @return int
     */
    public function getSortOrder()
    {
        return 32;
    }
}

Now, install the extension and check that all is done correctly, i.e.,

  • php bin/magento module:enable VendorName_OptionValueGtin
  • php bin/magento setup:upgrade
  • php bin/magento cache:flush

As you can see, the newly added field gets displayed now:

How to add custom fields for products on Magento 2 | Mageworx Blog

Step #4. Adding Setting to Disable GTIN Field Display for Advanced Product Options Config

What about mixing up our article a little?

I offer to add some new functionality―the ability to enable/disable the GTIN field display for option values on the product page front-end. 

You will need to create the file:

`app/code/VendorName/OptionValueGtin/etc/adminhtml/system.xml`

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="mageworx" sortOrder="2001">
            <label>MageWorx</label>
        </tab>
        <section id="mageworx_apo" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
            <label><![CDATA[Advanced Product Options]]></label>
            <tab>mageworx</tab>
            <resource>VendorName_OptionValueGtin::config_optionvaluegtin</resource>
            <group id="optionvaluegtin" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1">
                <label><![CDATA[Option Value GTIN]]></label>
                <field id="use_optionvaluegtin" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label><![CDATA[Enable Option's Value 'GTIN']]></label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
            </group>
        </section>
    </system>
</config>

For simplicity:

We will add the new Option Value GTIN tab to the config of our Advanced Product Options extension. You should be able to create a tab in your module too.

The new Helper Class should be created. There, we will obtain data about the setting.

So, let’s create and fill in the following class:

`app/code/VendorName/OptionValueGtin/Helper/Data.php`

<?php
namespace VendorName\OptionValueGtin\Helper;

use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Store\Model\ScopeInterface;
use Magento\Framework\App\Helper\Context;

class Data extends AbstractHelper
{

    const XML_PATH_DEFAULT_OPTION_VALUE_GTIN  = 'mageworx_apo/optionvaluegtin/use_optionvaluegtin';

    /**
     * Additional product attributes for product_attributes table
     *
     * @var array
     */
    protected $additionalProductAttributes;

    /**
     * @param Context $context
     */
    public function __construct(
        Context $context
    ) {
        parent::__construct($context);
    }

    /**
     * Check if option value GTIN enabled
     *
     * @param int|null $storeId
     * @return string
     */
    public function isOptionValueGtinEnabled($storeId = null)
    {
        return $this->scopeConfig->getValue(
            self::XML_PATH_DEFAULT_OPTION_VALUE_GTIN,
            ScopeInterface::SCOPE_STORE,
            $storeId
        );
    }

}

Don’t forget to save the changes and clear the cache.

The setting should get displayed in the admin panel.

How to add custom fields for products on Magento 2 | Mageworx Blog

Step #5. Displaying New Field on Product Page Front-End

Remember what we talked about in the previous article?

We mentioned that our MageWorx_OptionBase module already has the `getExtendedOptionsConfig()` method that collects and displays all our custom attributes on the front-end via blocks.

To see how it gets implemented, open the following class:

`app/code/MageWorx/OptionBase/Block/Product/View/Options.php`

Now:

Create a model with our attribute:

`app/code/VendorName/OptionValueGtin/Model/Attribute/OptionValue/Gtin.php`

<?php

namespace VendorName\OptionValueGtin\Model\Attribute\OptionValue;

use MageWorx\OptionBase\Model\Product\Option\AbstractAttribute;

class Gtin extends AbstractAttribute
{
    /**
     * @return string
     */
    public function getName()
    {
        return 'gtin';
    }

}

Via dependency injection, add the attribute to the general attributes block of the Advanced Product Options extension and create the following file:

`app/code/VendorName/OptionValueGtin/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">
    <!-- Data -->
    <type name="MageWorx\OptionBase\Model\Product\Option\Value\Attributes">
        <arguments>
            <argument name="data" xsi:type="array">
                <item name="gtin" xsi:type="object">VendorName\OptionValueGtin\Model\Attribute\OptionValue\Gtin</item>
            </argument>
        </arguments>
    </type>
</config>

Now:

Create our new block and a template for it:

`app/code/VendorName/OptionValueGtin/Block/ValueGtin.php`

<?php

namespace MageWorx\OptionValueGtin\Block;

use Magento\Framework\Json\EncoderInterface;
use Magento\Framework\View\Element\Template;
use Magento\Framework\View\Element\Template\Context;
use MageWorx\OptionValueGtin\Helper\Data as Helper;

class ValueGtin extends Template
{
    /**
     * @var EncoderInterface
     */
    protected $jsonEncoder;

    /**
     * @var Helper
     */
    protected $helper;

    /**
     * @param Context $context
     * @param EncoderInterface $jsonEncoder
     * @param Helper $helper
     * @param array $data
     */
    public function __construct(
        Context $context,
        EncoderInterface $jsonEncoder,
        Helper $helper,
        array $data = []
    ) {
        parent::__construct(
            $context,
            $data
        );
        $this->jsonEncoder = $jsonEncoder;
        $this->helper = $helper;
    }

    /**
     * @return string
     */
    public function getJsonData()
    {
        $data = [
            'isOptionValueGtinEnabled' => $this->helper->isOptionValueGtinEnabled($this->_storeManager->getStore())
        ];
        return $this->jsonEncoder->encode($data);
    }
}

Here, we’ve obtained data about the setting from the Helper Class.

Next:

Using the `getJsonData()` method, let’s render data to the front-end using the template that we are about to create:

`app/code/VendorName/OptionValueGtin/view/frontend/templates/config.phtml`

<?php
/** @var \VendorName\OptionValueGtin\Block\ValueGtin $block */
?>
<script>
    require([
        'jquery',
        'optionValueGtin',
        'uiRegistry'
    ], function ($, optionValueGtin, registry) {
        var optionBase = registry.get('mageworxOptionBase');
        if (optionBase) {
            optionBase.addUpdater(7, optionValueGtin(<?= /* @noEscape */ $block->getJsonData() ?>));
        } else {
            var updaters = registry.get('mageworxOptionUpdaters');
            if (!updaters) {
                updaters = {};
            }
            updaters[7] = optionValueGtin(<?= /* @noEscape */ $block->getJsonData() ?>);
            registry.set('mageworxOptionUpdaters', updaters);
        }
    });
</script>

We used the JavaScript mixins mechanism to display the values for the GTIN field.

What’s next?

Let’s take a different approach and create a js widget, which will be used to display the new data on the product page.

Define the new js:

`app/code/VendorName/OptionValueGtin/view/frontend/requirejs-config.js`

var config = {
    map: {
        '*': {
            optionValueGtin: 'VendorName_OptionValueGtin/js/option-value-gtin'
        }
    }
};

It’s high time to create the widget itself. It will contain all the logic in the work with the new attribute on the front-end.

In the example file, let’s implement the display logic of GTIN for the select options, and for the radio and check box options separately.

 These are going to be two different logics as the logic in the work and markup of such options differ from one another:

`app/code/VendorName/OptionValueGtin/view/frontend/web/js/option-value-gtin.js`

define([
    'jquery',
    'Magento_Catalog/js/price-utils',
    'underscore',
    'jquery/ui'
], function ($, utils, _) {
    'use strict';

    $.widget('mageworx.optionValueGtin', {
        options: {
            optionConfig: {}
        },

        /**
         *
         * @param optionConfig
         * @param productConfig
         * @param base
         * @param self
         */
        firstRun: function firstRun(optionConfig, productConfig, base, self) {

            if (parseFloat(this.options.isOptionValueGtinEnabled)) {
                var extendedOptionsConfig = typeof base.options.extendedOptionsConfig != 'undefined' ?
                    base.options.extendedOptionsConfig : {};

                for (var option_id in optionConfig) {
                    if (!optionConfig.hasOwnProperty(option_id)) {
                        continue;
                    }
                    var $option = base.getOptionHtmlById(option_id);

                    this._addValueGtin($option, optionConfig, extendedOptionsConfig);
                }
            }
        },

        /**
         * Add description to the values
         * @param $option
         * @param optionConfig
         * @param extendedOptionsConfig
         * @private
         */
        _addValueGtin: function _addValueGtin($option, optionConfig, extendedOptionsConfig) {
            var self = this,
                $options = $option.find('.product-custom-option');

            //selectable options
            $options.filter('select').each(function (index, element) {
                var $element = $(element),
                    optionId = utils.findOptionId($element),
                    value = extendedOptionsConfig[optionId]['values'];

                if ($element.attr('multiple') && !$element.hasClass('mageworx-swatch')) {
                    return;
                }

                if (typeof value == 'undefined' || _.isEmpty(value)) {
                    return;
                }

                var gtinTitle = 'GTIN: ';
                var $gtin = $('<div class="option-value-gtin"></div>', {
                    style: 'display: none'
                });

                var $label = $option.find('.control');


                $element.parent().prepend($gtin);
                $element.on('change', function (e) {
                    var valueId = $element.val();
                    if (!_.isUndefined(value[valueId]) &&
                        !_.isEmpty(value[valueId]['gtin'])
                    ) {
                        if ($label.length > 0) {
                            $label
                                .first()
                                .after($gtin.text(gtinTitle + value[valueId]['gtin']));
                        }
                        $gtin.show();
                    } else {
                        $gtin.hide();
                    }
                });

                if ($element.val()) {
                    $element.trigger('change');
                }
            });

            $options.filter('input[type="radio"], input[type="checkbox"]').each(function (index, element) {
                var $element = $(element),
                    optionId = utils.findOptionId($element),
                    value = extendedOptionsConfig[optionId]['values'];

                if ($element.attr('multiple') && !$element.hasClass('mageworx-swatch')) {
                    return;
                }

                if (typeof value == 'undefined' || _.isEmpty(value)) {
                    return;
                }

                var gtinTitle = 'GTIN: ';
                var $gtin = $('<div class="option-value-gtin-redio-check"></div>');

                var $label = $option.find('.control');


                $element.parent().append($gtin);
                var valueId = $element.val();
                if (!_.isUndefined(value[valueId]) && !_.isEmpty(value[valueId]['gtin'])) {
                    $gtin.text(gtinTitle + value[valueId]['gtin']);
                }

                if ($element.val()) {
                    $element.trigger('change');
                }
            });
        },
    });

    return $.mageworx.optionValueGtin;

});

How about adding some styles?

`app/code/VendorName/OptionValueGtin/view/frontend/web/css/valueGtin.css`

.option-value-gtin, .option-value-gtin-redio-check {
    color: #1da0e0;
    font-weight: 700;
    margin-top: 5px;
}

.option-value-gtin-redio-check {
    display: contents;
}

It only remains to connect our block and the styles.

For that, create the following file:

`app/code/VendorName/OptionValueGtin/view/frontend/layout/catalog_product_view.xml`

<?xml version="1.0"?>
<page layout="1column" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="VendorName_OptionValueGtin::css/valueGtin.css"/>
    </head>
    <body>
        <referenceBlock name="product.info.options.wrapper">
            <container name="vendorname.option.value.gtin.container" after="product.info.options">
                <block class="VendorName\OptionValueGtin\Block\ValueGtin"
                       name="vendorname.option.value.gtin"
                       template="VendorName_OptionValueGtin::config.phtml"/>
            </container>
        </referenceBlock>
    </body>
</page>

We are almost finished.

Before we run the final check, don’t forget to clear the cache and deploy the static content again:

  • php bin/magento cache:flush
  • php bin/magento static-content:deploy

And finally:

Log in to the admin panel.

Create a product with custom options in Magento product.

In our example, I added dropdown, swatch, radio, and check box.

Don’t forget to fill in our new GTIN fields for the corresponding option values.

Save the product.

Time to see how it all looks like on the front-end:

How to add custom fields for products on Magento 2 | Mageworx Blog

What do you think about the result?


Please share your feedback about the article in the comments field below. How easy was it to Magento add field to custom options?

LEAVE A REPLY

Please enter your comment!
Please enter your name here