0

I am trying to get file upload working in a ui-component admin form. The file input field is coming from a htmlContent block. This is all sitting in a <form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> element.

Now the issue is that the form element is not actually being rendered as html, as such I cannot add/apply enctype="multipart/form-data" to this form and $_FILES remains empty upon submitting.

Attempts to change the uiComponent to include form element and/or enctype definition failed as it doesn't allow usage of such elements/attributes in the ui-component file directly (collapsible.xhtml). How to move forward with this?

Code example:

Form definition

<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        [...]
        <item name="template" xsi:type="string">templates/form/collapsible</item>
        <item name="enctype" xsi:type="string">multipart/form-data</item>
    </argument>
    <settings>
        [...]
    </settings>
    <dataSource name="XXX">
        [...]
    </dataSource>
    [...]
    <fieldset name="XXX" sortOrder="20">
        <settings>
            <collapsible>false</collapsible>
            <opened>true</opened>
            <label translate="true">Products</label>
        </settings>
        <htmlContent name="custom_field">
            <block name="html_content_input_field" class="XXXX">
                <arguments>
                    <argument name="template" xsi:type="string">
                        XXX.phtml
                    </argument>
                </arguments>
            </block>
        </htmlContent>
    </fieldset>
</form>

XXX.phtml - Simplified as the file is nested in an array-like structure set up with Knockout

<div>
    <input type="file" name="nameforfile" data-form-part="the-correct-name"/>
</div>

All textual data is send - even the fake file name of the file but not the file itself. I don't need instant upload and/or visible files - Solely the data of the file actually being send to the server.

Raw POST data looks like:

------WebKitFormBoundaryvmSygS0ldVua30Aw
Content-Disposition: form-data; name="author_id"

1 [...] etc

1 Answers1

2

If you need a file uploader in a UI component, you can use their default component, which provides a way to upload a file with data and save it in your controller. When you create a form using UI component, it does not create form elements; that's why you cannot get file data into the controller. If you want file data, your input must be inside a form tag, and the enctype attribute value must be multipart/form-data, which is not available in a UI component.

To implement a file uploader with UI components, consider using a combination of UI components and a traditional form for handling file uploads. This way, you can achieve the desired functionality while still working within the constraints of UI components.

File Upload into UI component: https://magento.stackexchange.com/a/186781/82670

And if you don't have an option for it, you need to add an AJAX call, as described in the above answer link. In UI components, a similar approach is used—when you upload anything, it triggers an AJAX controller that stores the file in a temporary location. You can follow a similar approach here. Below is a small example of how to retrieve file data in a UI form

Form UI Component

<fieldset name="test_uploader">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="label" xsi:type="string" translate="true">Test Image Uploader</item>
        </item>
    </argument>
    <container name="test_uploader_container" >
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="sortOrder" xsi:type="number">1</item>
            </item>
        </argument>
        <htmlContent name="html_content">
            <argument name="block" xsi:type="object">VendorName\ModuleName\Block\Adminhtml\CustomData</argument>
        </htmlContent>
    </container>
</fieldset>

app/code/VendorName/ModuleName/Block/Adminhtml

CustomData.php

<?php

namespace VendorName\ModuleName\Block\Adminhtml;

use Magento\Framework\View\Asset\Repository;

class CustomData extends \Magento\Backend\Block\Widget\Form\Generic { protected $_systemStore;

protected $_countryFactory;

protected $_assetRepo;

public function __construct(
    \Magento\Backend\Block\Template\Context $context,
    \Magento\Framework\Registry $registry,
    \Magento\Framework\Data\FormFactory $formFactory,
    \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig,
    \Magento\Framework\View\Asset\Repository $assetRepo,
    \Magento\Directory\Model\Config\Source\Country $countryFactory,
    \Magento\Store\Model\System\Store $systemStore,
    array $data = []
) {
    $this-&gt;_systemStore = $systemStore;
    $this-&gt;_assetRepo = $assetRepo;
    $this-&gt;_countryFactory = $countryFactory;
    $this-&gt;_wysiwygConfig = $wysiwygConfig;
    parent::__construct($context, $registry, $formFactory, $data);
}

/**
 * Prepare form.
 *
 * @return $this
 */
protected function _prepareForm()
{
    // $model = $this-&gt;_coreRegistry-&gt;registry('row_data');

    $form = $this-&gt;_formFactory-&gt;create(
        ['data' =&gt; [
                        'id' =&gt; 'custom_form',
                        'enctype' =&gt; 'multipart/form-data',
                        'action' =&gt; $this-&gt;getData('action'),
                        'method' =&gt; 'post',
                        'data-form-part' =&gt; &quot;custom_form&quot;,
                    ]
        ]
    );

    $form-&gt;setHtmlIdPrefix('storelocation_');

       $fieldset = $form-&gt;addFieldset(
            'base_fieldset',
            ['legend' =&gt; __('General Information'), 'class' =&gt; 'fieldset-wide']
        );

   $importdata_script =  $fieldset-&gt;addField(
        'importdata',
        'file',
            array(
                    'label'     =&gt; 'Upload File',
                    'name'      =&gt; 'importdata',
                    'data-form-part' =&gt; &quot;custom_form&quot;,
                 )
    );

    $importdata_script-&gt;setAfterElementHtml(&quot;

    &lt;span id='sample-file-span' &gt;

        &lt;script type=\&quot;text/javascript\&quot;&gt;

        document.getElementById('storelocation_importdata').onchange = function () {

            var fileInputData =  document.getElementById('storelocation_importdata').files[0];
            console.log(fileInputData);

        };

        &lt;/script&gt;&quot;
    );


    // $form-&gt;setValues($model-&gt;getData());
    $form-&gt;setUseContainer(true);
    $this-&gt;setForm($form);

    return parent::_prepareForm();
}

}

You can display file data in the console when selected using file input and implement AJAX to save it to a temporary path, similar to what the UI component does for file uploading.

OUTPUT enter image description here

Msquare
  • 9,063
  • 7
  • 25
  • 63
  • 1
    Thank you for your complete response. Sorry I'm not working this fulltime, thus responding slow. So it is a hard constraint in UI-Components. Bad design imo, but okay. It's sad as the images should be attached to an array-like entity which may not exist in DB yet, why I tried so hard to have it in one POST. Many thanks again! – vandijkstef Dec 20 '23 at 13:13