Skip to content

Magento 2.4.6: Creat admin product edit form button with modal and ajax submission

Magento 2.4.6-p6

I am trying to create a button on Product edit form which can translate one store product attribute data and store into another store attribute data.

Vendor/ModuleName/view/adminhtml/ui_component/product_form.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <settings>
        <buttons>
            <button name="Translate" class="VendorModuleNameBlockAdminhtmlProductEditButtonTranslateFrom"/>
        </buttons>
    </settings>
</form>

Vendor/ModuleName/Block/Adminhtml/Product/Edit/Button/TranslateFrom.php

    <?php

namespace VendorModuleNameBlockAdminhtmlProductEditButton;

use MagentoCatalogBlockAdminhtmlProductEditButtonGeneric;

class TranslateFrom extends Generic
{
    /**
     * {@inheritdoc}
     */
    public function getButtonData(): array
    {
        return [
            'label' => __('Translate From'),
            'class' => 'action-secondary',
            'data_attribute' => [
                'mage-init' => [
                    'Magento_Ui/js/form/button-adapter' => [
                        'actions' => [
                            [
                                'targetName' => 'product_form.product_form.translate_product_from',
                                'actionName' => 'toggleModal'
                            ],
                            [
                                'targetName' => 'product_form.product_form.translate_product_from.translate_from_store_form',
                                'actionName' => 'render'
                            ]
                        ]
                    ]
                ]
            ],
            'on_click' => '',
            'sort_order' => 20
        ];
    }
}

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="MagentoCatalogUiDataProviderProductFormModifierPool" type="MagentoUiDataProviderModifierPool">
        <arguments>
            <argument name="modifiers" xsi:type="array">
                <item name="translateFromModal" xsi:type="array">
                    <item name="class" xsi:type="string">VendorModuleNameUiDataProviderProductFormModifierTranslateFromModal</item>
                    <item name="sortOrder" xsi:type="number">170</item>
                </item>
            </argument>
        </arguments>
    </virtualType>
</config>

‘TranslateFromModal.php’

    <?php

namespace VendorModuleNameUiDataProviderProductFormModifier;

use MagentoCatalogUiDataProviderProductFormModifierAbstractModifier;
use MagentoCatalogModelLocatorLocatorInterface;
use MagentoFrameworkAuthorizationInterface;
use MagentoFrameworkExceptionNoSuchEntityException;
use MagentoFrameworkRegistry;
use MagentoFrameworkUrlInterface;
use MagentoStoreModelStoreManagerInterface;
use MagentoUiComponentContainer;
use MagentoUiComponentFormField;
use MagentoUiComponentFormFieldset;
use MagentoUiComponentModal;

class TranslateFromModal extends AbstractModifier
{
    const GROUP_TRANSLATE_FROM = 'translate_product_from';

    /**
     * @var UrlInterface
     */
    protected $urlBuilder;

    /**
     * @var Registry
     */
    protected $registry;

    /**
     * @var LocatorInterface
     */
    protected $locator;

    /**
     * @var AuthorizationInterface
     */
    protected $authorization;

    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @param UrlInterface $urlBuilder
     * @param Registry $registry
     * @param AuthorizationInterface $authorization
     * @param LocatorInterface $locator
     * @param StoreManagerInterface $storeManager
     */
    public function __construct(
        UrlInterface $urlBuilder,
        Registry $registry,
        AuthorizationInterface $authorization,
        LocatorInterface $locator,
        StoreManagerInterface $storeManager
    ) {
        $this->urlBuilder = $urlBuilder;
        $this->registry = $registry;
        $this->authorization = $authorization;
        $this->locator = $locator;
        $this->storeManager = $storeManager;
    }

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

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

    /**
     * Add Translate From modal to the meta.
     *
     * @param array $meta
     * @return array
     */
    protected function addTranslateFromModal(array $meta)
    {
        $meta[static::GROUP_TRANSLATE_FROM] = [
            'arguments' => [
                'data' => [
                    'config' => [
                        'isTemplate' => false,
                        'componentType' => Modal::NAME,
                        'dataScope' => '',
                        'provider' => 'product_form.product_form_data_source',
                        'options' => [
                            'title' => __('Translate From'),
                            'buttons' => [
                                [
                                    'text' => __('Cancel'),
                                    'actions' => [
                                        [
                                            'targetName' => '${ $.name }',
                                            '__disableTmpl' => ['targetName' => false],
                                            'actionName' => 'closeModal'
                                        ]
                                    ]
                                ],
                                [
                                    'text' => __('Translate'),
                                    'class' => 'action-primary',
                                    'actions' => [
                                        [
                                            'targetName' => '${ $.name }.translate_from_store_form',
                                            '__disableTmpl' => ['targetName' => false],
                                            'actionName' => 'save'
                                        ],
                                        [
                                            'closeModal'
                                        ]
                                    ]
                                ]
                            ],
                        ],
                    ],
                ],
            ],
            'children' => [
                'translate_from_store_form' => [
                    'arguments' => [
                        'data' => [
                            'config' => [
                                'componentType' => Container::NAME,
                                'label' => __('Translate From Form'),
                                'dataScope' => '',
                                'autoRender' => false,
                                'ns' => 'translate_from_store_form',
                                'externalProvider' => 'translate_from_store_form.translate_from_store_form_data_source',
                                'toolbarContainer' => '${ $.parentName }',
                                '__disableTmpl' => ['toolbarContainer' => false],
                                'formSubmitType' => 'ajax',
                                'saveUrl' => $this->urlBuilder->getUrl('translate/product/translateFrom'),
                                'productId' => $this->locator->getProduct()->getId(),
                                'currentStoreId' => $this->locator->getStore()->getId(),
                                'productType' => $this->locator->getProduct()->getTypeId(),
                            ],
                        ],
                    ],
                    'children' => [
                        'select_store' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'componentType' => Fieldset::NAME,
                                        'label' => __('Select Store'),
                                        'dataScope' => '',
                                    ]
                                ]
                            ],
                            'children' => [
                                'current_store' => [
                                    'arguments' => [
                                        'data' => [
                                            'config' => [
                                                'componentType' => Field::NAME,
                                                'formElement' => 'input',
                                                'dataScope' => 'current_store',
                                                'dataType' => 'text',
                                                'label' => __('Current Store'),
                                                'value' => $this->getCurrentStoreName(),
                                                'disabled' => true
                                            ]
                                        ]
                                    ]
                                ],
                                'store_id' => [
                                    'arguments' => [
                                        'data' => [
                                            'config' => [
                                                'componentType' => Field::NAME,
                                                'label' => __('Store'),
                                                'formElement' => 'select',
                                                'dataScope' => 'store_id',
                                                'dataType' => 'int',
                                                'options' => $this->getStoreOptions(),
                                                'validation' => [
                                                    'required-entry' => true
                                                ],
                                            ]
                                        ]
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ];

        return $meta;
    }

    /**
     * Retrieve the current store name.
     *
     * @return string
     * @throws NoSuchEntityException
     */
    protected function getCurrentStoreName(): string
    {
        $storeId = $this->locator->getStore()->getId();
        $store = $this->storeManager->getStore($storeId);
        return $store->getName();
    }

    /**
     * Retrieve the store options for the select field, excluding the current store.
     *
     * @return array
     */
    protected function getStoreOptions(): array
    {
        $stores = $this->storeManager->getStores();
        $currentStoreId = $this->locator->getStore()->getId();
        $options = [];

        foreach ($stores as $store) {
            if ($store->getId() != $currentStoreId) {
                $options[] = ['value' => $store->getId(), 'label' => $store->getName()];
            }
        }

        return $options;
    }
}

‘Vendor/ModuleName/Controller/Adminhtml/Product/TranslateFrom.php’

<?php

namespace VendorModuleNameControllerAdminhtmlProduct;

use MagentoBackendAppAction;
use MagentoBackendAppActionContext;
use MagentoFrameworkControllerResultJsonFactory;

class TranslateFrom extends Action
{
    /**
     * @var JsonFactory
     */
    protected $resultJsonFactory;

    /**
     * @param Context $context
     * @param JsonFactory $resultJsonFactory
     */
    public function __construct(
        Context $context,
        JsonFactory $resultJsonFactory
    ) {
        parent::__construct($context);
        $this->resultJsonFactory = $resultJsonFactory;
    }

    /**
     * Execute method
     *
     * @return MagentoFrameworkControllerResultJson
     */
    public function execute()
    {
        $resultJson = $this->resultJsonFactory->create();

        // Retrieve the parameters
        $storeId = $this->getRequest()->getParam('store_id');
        $productId = $this->getRequest()->getParam('product_id');

        // Your translation logic here
        $response = [
            'success' => true,
            'message' => __('Translation successful for store ID %1 and product ID %2', $storeId, $productId)
        ];

        return $resultJson->setData($response);
    }

    /**
     * Check if the user has the permission to access this controller
     *
     * @return bool
     */
    protected function _isAllowed()
    {
        return $this->_authorization->isAllowed('Paket24_TranslateProduct::translate');
    }
}

Error:

registry.js:57 Uncaught TypeError: Cannot read properties of undefined (reading 'apply')
    at registry.js:57:35
    at Registry._resolveRequest (registry.js:416:30)
    at Registry._addRequest (registry.js:383:22)
    at Registry.get (registry.js:227:18)
    at async (registry.js:56:22)
    at UiClass.triggerAction (modal-component.js:305:24)
    at modal-component.js:338:25
    at Array.forEach (<anonymous>)
    at $.<computed>.<computed>.<anonymous> (modal-component.js:336:25)
    at executeBound (underscore.js:986:71)

Any help would be appreciated. Thanks 🙂

For reference Images:
Button
Modal