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
4
  • just follow basic Magento code standard for file upload. developer.adobe.com/commerce/frontend-core/ui-components/…
    – Suraj Tupe
    Commented Nov 28, 2023 at 18:48
  • It should come from a html file, not a ui-component as it's nested in a htmlContent Commented Dec 1, 2023 at 13:55
  • Can you please share your code so that we can better understand the problem?
    – Msquare
    Commented Dec 5, 2023 at 15:35
  • @msquare Edited/added minimum code example Commented Dec 13, 2023 at 9:16

1 Answer 1

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->_systemStore = $systemStore;
        $this->_assetRepo = $assetRepo;
        $this->_countryFactory = $countryFactory;
        $this->_wysiwygConfig = $wysiwygConfig;
        parent::__construct($context, $registry, $formFactory, $data);
    }

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

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

        $form->setHtmlIdPrefix('storelocation_');

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

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

        $importdata_script->setAfterElementHtml("

        <span id='sample-file-span' >

            <script type=\"text/javascript\">

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

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

            };

            </script>"
        );


        // $form->setValues($model->getData());
        $form->setUseContainer(true);
        $this->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

1
  • 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! Commented Dec 20, 2023 at 13:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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