For each selection in a bundle option, I need to add a dynamic row with fields image and mapping id. Currently I've managed to add them with a plugin with afterModifyMeta on class Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleCustomOptions
, but I'm currently struggling making the dynamic rows UI component working for these fields.
The fields are showing, but the fields does not show any addButton, as this is set to false on the knockout element (even though I've specified this to true in the dynamic-rows component).
For setting the data on the fields, I've created a custom table bundle_selection_image
:
<?php
declare(strict_types=1);
namespace Vendor\Bundle\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
{
/**
* @throws \Zend_Db_Exception
*/
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context): void
{
$installer = $setup;
$installer->startSetup();
$tableName = $installer->getTable('bundle_selection_image');
if (!$installer->tableExists($tableName)) {
$table = $installer->getConnection()->newTable($tableName)
->addColumn(
'id',
Table::TYPE_INTEGER,
null,
['identity' => true, 'nullable' => false, 'primary' => true],
'ID'
)
->addColumn(
'selection_id',
Table::TYPE_INTEGER,
null,
['unsigned' => true, 'nullable' => false],
'Selection ID'
)
->addColumn(
'image_path',
Table::TYPE_TEXT,
255,
['nullable' => true],
'Image Path'
)
->addColumn(
'mapping_id',
Table::TYPE_TEXT,
255,
['nullable' => true],
'Mapping ID'
)
->addForeignKey(
$installer->getFkName($tableName, 'selection_id', 'catalog_product_bundle_selection', 'selection_id'),
'selection_id',
$installer->getTable('catalog_product_bundle_selection'),
'selection_id',
Table::ACTION_CASCADE
)
->setComment('Custom Bundle Selection Image Table');
$installer->getConnection()->createTable($table);
}
$installer->endSetup();
}
}
Where I add values to the field through a plugin for class Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\Composite
with afterModifyData.
Here is the afterModifyData composite plugin:
public function afterModifyData(
Composite $subject,
array $data
): array {
$modelId = array_keys($data)[0];
if (!isset($data[$modelId]['product']) ||
!isset($data[$modelId][BundlePanel::CODE_BUNDLE_OPTIONS][BundlePanel::CODE_BUNDLE_OPTIONS])) {
return $data;
}
$preparedOptions = [];
foreach ($this->optionRepository->getList($data[$modelId]['product']['sku']) as $option) {
$extensionAttributes = $option->getExtensionAttributes();
$superAttribute = $extensionAttributes?->getBundleSuperAttribute();
$selections = [];
foreach ($option->getProductLinks() as $productLink) {
$bundleImageItems = $this->bundleImageRepository->getItems();
if (count($bundleImageItems) > 0) {
$count = 0;
foreach ($bundleImageItems as $bundleImageItem) {
if ($bundleImageItem->getSelectionId() === (int)$productLink->getId()) {
$imagePath = $bundleImageItem->getImagePath();
if ($imagePath !== null && file_exists($imagePath)) {
$fileName = basename($imagePath);
$fileSize = filesize($imagePath);
$mimeType = mime_content_type($imagePath);
$url = $this->getImageUrl($fileName);
$selections[$productLink->getId()]['dynamic_image_mapping_row'][$count] = [
'image' => [
0 => [
'file' => $fileName,
'path' => $imagePath,
'size' => $fileSize,
'type' => $mimeType,
'url' => $url
]
],
'mapping_id' => $bundleImageItem->getMappingId() ?? ''
];
}
$count++;
}
}
}
if (!isset($selections[$productLink->getId()]['dynamic_image_mapping_row'])) {
$selections[$productLink->getId()]['dynamic_image_mapping_row'][0] = [
'image' => [],
'mapping_id' => ''
];
}
}
$preparedOptions[$option->getOptionId()] = [
'position' => $option->getPosition(),
'option_id' => $option->getOptionId(),
'title' => $option->getTitle(),
'default_title' => $option->getDefaultTitle(),
'type' => $option->getType(),
'required' => ($option->getRequired()) ? '1' : '0',
'bundle_super_attribute' => $superAttribute,
'bundle_selections' => $selections
];
}
foreach ($data[$modelId][BundlePanel::CODE_BUNDLE_OPTIONS][BundlePanel::CODE_BUNDLE_OPTIONS] as &$bundleOption) {
$optionId = $bundleOption['option_id'];
if (isset($preparedOptions[$optionId])) {
$bundleOption = array_merge($bundleOption, array_diff_key($preparedOptions[$optionId], ['bundle_selections' => []]));
if (isset($preparedOptions[$optionId]['bundle_selections'])) {
foreach ($bundleOption['bundle_selections'] as $index => &$selection) {
$selectionId = $selection['selection_id'];
if (isset($preparedOptions[$optionId]['bundle_selections'][$selectionId])) {
$selection = array_merge(
$selection,
$preparedOptions[$optionId]['bundle_selections'][$selectionId]
);
}
}
}
}
}
return $data;
}
And finally, here is the BundleCustomOptionsPlugin I have for BundleCustomOptions:
<?php
declare(strict_types = 1);
namespace Kje\Bundle\Plugin;
use Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleCustomOptions;
use Magento\Framework\Stdlib\ArrayManager;
use Magento\Ui\Component\Form;
use Magento\Ui\Component\Container;
use Magento\Ui\Component\DynamicRows;
class BundleCustomOptionsPlugin
{
private const FIELD_BUNDLE_SUPER_ATTRIBUTE = 'bundle_super_attribute';
private const FIELD_BUNDLE_DYNAMIC_IMAGE_MAPPING_ROW = 'dynamic_image_mapping_row';
public function __construct(
private readonly ArrayManager $arrayManager,
) {
}
public function afterModifyMeta(
BundleCustomOptions $subject,
array $result
): array {
$superAttributeField = [
self::FIELD_BUNDLE_SUPER_ATTRIBUTE => $this->getOptionSuperAttributeFieldConfig()
];
$dynamicRows = [
self::FIELD_BUNDLE_DYNAMIC_IMAGE_MAPPING_ROW => $this->getOptionSelectionDynamicRowFieldConfig()
];
$optionInfoPath = $this->arrayManager->findPath('option_info', $result);
if ($optionInfoPath !== null) {
$result = $this->arrayManager->merge($optionInfoPath . '/children', $result, $superAttributeField);
}
$pathForDynamicRows = $this->arrayManager->findPath('product_bundle_container', $result);
if ($pathForDynamicRows !== null) {
$result = $this->arrayManager->merge($pathForDynamicRows . '/children/bundle_selections/children/record/children', $result, $dynamicRows);
}
return $result;
}
private function getOptionSuperAttributeFieldConfig(): array
{
return [
'arguments' => [
'data' => [
'config' => [
'dataType' => Form\Element\DataType\Text::NAME,
'formElement' => Form\Element\Input::NAME,
'componentType' => Form\Field::NAME,
'label' => __('Bundle Super Attribute'),
'showLabel' => true,
'breakLine' => true,
'sortOrder' => 40,
'disabled' => true
],
],
]
];
}
private function getOptionSelectionDynamicRowFieldConfig(): array
{
return [
'arguments' => [
'data' => [
'config' => [
'component' => 'Magento_Ui/js/dynamic-rows/dynamic-rows',
'componentType' => DynamicRows::NAME,
'label' => __('Image & mapping'),
'sortOrder' => 1,
'addButton' => true,
'deleteProperty' => true,
'dndConfig' => [
'enabled' => false
]
],
],
],
'children' => [
'record' => [
'arguments' => [
'data' => [
'config' => [
'componentType' => Container::NAME,
'isTemplate' => true,
'component' => 'Magento_Ui/js/dynamic-rows/record',
'is_collection' => true
],
],
],
'children' => [
'image' => $this->getOptionSelectionImageFieldConfig(),
'mapping_id' => $this->getOptionSelectionMappingFieldConfig()
],
],
],
];
}
private function getOptionSelectionImageFieldConfig(): array
{
return [
'arguments' => [
'data' => [
'config' => [
'dataType' => Form\Element\DataType\Media\Image::NAME,
'formElement' => 'fileUploader',
'visible' => true,
'required' => false,
'label' => __('Image'),
'notice' => null,
'default' => null,
'size' => null,
'componentType' => Form\Field::NAME,
'elementTmpl' => 'ui/form/element/uploader/uploader',
'previewTmpl' => 'Magento_Catalog/image-preview',
'uploaderConfig' => [
'url' => 'bundle_upload/upload/index'
],
'sortOrder' => 1
],
],
],
];
}
private function getOptionSelectionMappingFieldConfig(): array
{
return [
'arguments' => [
'data' => [
'config' => [
'dataType' => Form\Element\DataType\Text::NAME,
'formElement' => Form\Element\Input::NAME,
'componentType' => Form\Field::NAME,
'label' => __('Mapping ID'),
'sortOrder' => 2,
],
],
],
];
}
}
Here is the current result of bundle option selections:
Here is the knockout $data object:
Here is the rendered html for button:
<tfoot data-bind="visible: element.addButton || (!!element.getRecordCount() && pages() > 1)" style="display: none;">
When checking element, I see this:
What am I missing here for this to properly work?