2

I am using Magento 2 REST API for the mobile application. I am facing some complexity so I need your help.

I have DynamicRows component with two fields in UI Form, the name of the field is localization.

<container name="localization" sortOrder="9" >
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="component" xsi:type="string">Magento_Ui/js/dynamic-rows/dynamic-rows</item>
            <item name="template" xsi:type="string">ui/dynamic-rows/templates/default</item>
            <item name="componentType" xsi:type="string">dynamicRows</item>
            <item name="label" xsi:type="string" translate="true">Data</item>
            <item name="recordTemplate" xsi:type="string">record</item>
            <item name="addButtonLabel" xsi:type="string">Add Parameter</item>
            <item name="deleteProperty" xsi:type="boolean">false</item>
            <item name="enabled" xsi:type="boolean">false</item>
        </item>
    </argument>

    <container name="record">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="label" xsi:type="string" translate="true" />
                <item name="component" xsi:type="string" translate="true">Magento_Ui/js/dynamic-rows/record</item>
                <item name="isTemplate" xsi:type="boolean">true</item>
                <item name="is_collection" xsi:type="boolean">true</item>
                <item name="showFallbackReset" xsi:type="boolean">false</item>
            </item>
        </argument>

        <field name="option_label">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Parameter</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="dataScope" xsi:type="string">option_label</item>
                    <item name="showFallbackReset" xsi:type="boolean">false</item>
                    <item name="validation" xsi:type="array">
                        <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                    <item name="sortOrder" xsi:type="string">10</item>
                </item>
            </argument>
        </field>

        <field name="option_value">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Value</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="dataScope" xsi:type="string">option_value</item>
                    <item name="showFallbackReset" xsi:type="boolean">false</item>
                    <item name="additionalClasses" xsi:type="array">
                        <item name="cirklestudio_color_picker" xsi:type="boolean">true</item>
                    </item>
                    <item name="validation" xsi:type="array">
                        <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                    <item name="sortOrder" xsi:type="string">20</item>
                </item>
            </argument>
        </field>

        <actionDelete>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="componentType" xsi:type="string">actionDelete</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="fit" xsi:type="boolean">false</item>
                    <item name="label" xsi:type="string" />
                    <item name="additionalClasses" xsi:type="string">data-grid-actions-cell</item>
                    <item name="template" xsi:type="string">Magento_Backend/dynamic-rows/cells/action-delete</item>
                </item>
            </argument>
        </actionDelete>
        
    </container>

</container>

The output of the DynamicRows is below

Array
(
    [0] => Array
        (
            [row_id] => 
            [option_label] => tmp_dir
            [option_value] => temp
            [position] => 1
            [record_id] => 0
            [initialize] => true
        )

    [1] => Array
        (
            [record_id] => 1
            [row_id] => 
            [option_label] => main_dir
            [option_value] => APD
            [position] => 2
            [initialize] => true
        )
)

I have declared a method getLocalization() in my Interface

/**
 * Return localized parameters for the application
 *
 * @return mixed[]
 */
public function getLocalization();

Then I have implemented the method

/**
 * @inheritdoc
 */
public function getLocalization()
{
    // Unserialize array
    $data = $this->_getData('localization') ? unserialize($this->_getData('localization')) : "";


    $output = [];
    foreach ($data as $param) {
        $output[$param['option_label']] =  $param['option_value'];
    }

    return $output;
}

In the above method, I have refined the DynamicRows array output as per the requirements.

Array
(
    [tmp_dir] => temp
    [main_dir] => APD
)

When I hit the REST API endpoint, I get the following response.

<localization>
    <item>temp</item>
    <item>APD</item>
</localization>

Whereas I expect the following response

<localization>
    <tmp_dir>temp</tmp_dir>
    <main_dir>APD</main_dir>
</localization>

I have searched around different articles but couldn't find the best answer to suit my requirements.

Question: How I will get my expected result keeping this thing in mind that DynamicRows data will not be always the same, might be another record has more than two fields with different option_label and option_value?

2
  • is it possible to put more code? more of the uicomponents would be helpful.. Oct 3 '20 at 8:13
  • In Ui Component there is only a single DynamicRow field/container, I have updated my question.
    – Adnan
    Oct 3 '20 at 10:42
4

I have managed to come up to the following:

enter image description here

I think you will struggle to get more from it. Basically, the item keys do come from Magento core parsing method so that your result comes to you as an xml.

I can detail my steps but it does amount to 2 more interfaces and a bit of parsing.. Do let me know if this output is something you could compromise with.

Joseph's answer is simpler than mine (more elegant). But below is the core from my solution. I also put a link to the bitbucket repository for you to try out by yourself. https://bitbucket.org/magstaging/apiparser/src/master/

    $output = [];
            foreach ($data as $param) {
                $output[$param['option_label']] =  $param['option_value'];
            }
    
            return $this->parsedDataInterfaceFactory->create([
                'itemsData' => $output
            ]);


class ParsedResult implements ParsedDataInterface
{
    /**
     * @var \Mbs\Calculator\Api\Data\ParsedDataItemInterface[]
     */
    private $items = [];

    /**
     * OperationResult constructor.
     * @param array $itemsData
     * @param \Mbs\Calculator\Api\Data\ParsedDataItemInterfaceFactory $dataItemInterfaceFactory
     */
    public function __construct(
        $itemsData,
        \Mbs\Calculator\Api\Data\ParsedDataItemInterfaceFactory $dataItemInterfaceFactory
    ) {
        foreach ($itemsData as $itemLabel => $itemValue) {
            $this->items[] = $dataItemInterfaceFactory->create([
                'label' => $itemLabel,
                'value' => $itemValue
            ]);
        }
    }

    /**
     * @return \Mbs\Calculator\Api\Data\ParsedDataItemInterface[]
     */
    public function getLocalization()
    {
        return $this->items;
    }
}

class Item implements ParsedDataItemInterface
{
    /**
     * @var string
     */
    private $label;

    /**
     * @var string
     */
    private $value;

    /**
     * @inheritdoc
     */
    public function __construct(
        string $label,
        string $value
    ) {
        $this->label = $label;
        $this->value = $value;
    }

    /**
     * @inheritdoc
     */
    public function getLabel()
    {
        return $this->label;
    }

    /**
     * @inheritdoc
     */
    public function getValue()
    {
        return $this->value;
    }
}
6
  • Thank you very much for your time! I think I can manage such output. I am waiting for your detailed answer.
    – Adnan
    Oct 3 '20 at 22:04
  • Wow, amazing logic, thanks a ton mate. You said that Joseph answer is more elegant, is this possible to elaborate his answer? Once again, thank you very much for your precious time.
    – Adnan
    Oct 4 '20 at 11:24
  • 1
    if you use josepth's code and directly use the interface \Mbs\Calculator\Api\Data\ParsedDataItemInterface, you'll get the same result as mine.. But be careful, the interfaces annotations need updating and that is a big part of this work.. can you accept btw, aby little point counts Oct 4 '20 at 11:29
  • Thanks once again @Herve, I really appreciate your time and efforts.
    – Adnan
    Oct 4 '20 at 11:36
  • 1
    I am sure you understand we all like good questions.. this was really interesting even though I had a few sweats for it. thank you indeed Oct 4 '20 at 12:03
2

To get the desired result, you need to create more classes.

In this case, getLocalization should return an instance of a class, like:

/**
 * @return \MyCompany\MyModule\Model\Localization[]
 **/
public function getLocalization(): array
{
    // Unserialize array
    $data = $this->_getData('localization') ? unserialize($this->_getData('localization')) : "";

    return array_map(function($option) {
        return $this->localizationFactory->create([
            'label' => $param['option_label'],
            'value' => $param['option_value']
        ]);
    });
}

Where $this->localizationFactory is: \MyCompany\MyModule\Model\LocalizationFactory.

LocalizationFactory should have two methods:

  • getLabel
  • getValue

This might seem verbose, and it is. However, this route gives you 100% control over the output. Testability is a bonus.

1
  • Do you mean mapper classes? Like I have to map my output to another class? The factory class is usually used to fetch data from the database/table. How I will be able to map such a class over my output? I shall be very thankful if you can further elaborate on the class.
    – Adnan
    Oct 3 '20 at 22:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.