24

I recently implemented the FileUploader Ui Component in my form on Magento 2.1.7.

The code for it is here (app/code/Vendor/Blog/view/adminhtml/ui_component/vendor_blog_form.xml):

<field name="featured_images">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="label" translate="true" xsi:type="string">Hervorgehobene Bilder:</item>
                    <item name="formElement" xsi:type="string">fileUploader</item>
                    <item name="componentType" xsi:type="string">fileUploader</item>
                    <item name="previewTmpl" xsi:type="string">Magento_Catalog/image-preview</item>
                    <item name="elementTmpl" xsi:type="string">ui/form/element/uploader/uploader</item>
                    <item name="allowedExtensions" xsi:type="string">jpg jpeg gif png</item>
                    <item name="notice" xsi:type="string" translate="true">Erlaubte Dateitypen: png, gif, jpg, jpeg.</item>
                    <item name="maxFileSize" xsi:type="number">2097152</item>
                    <item name="source" xsi:type="string">blog</item>
                    <item name="sortOrder" xsi:type="number">10</item>
                    <item name="dataScope" xsi:type="string">featured_images</item>
                    <item name="validation" xsi:type="array">
                        <item name="required-entry" xsi:type="boolean">false</item>
                    </item>
                    <item name="uploaderConfig" xsi:type="array">
                        <item name="url" xsi:type="url" path="vendor_blog/blog/upload"/>
                    </item>
                </item>
            </argument>
        </field>

My Controller for it is this (app/code/Vendor/Blog/Controller/Adminhtml/Blog/Upload.php):

<?php

namespace Vendor\Blog\Controller\Adminhtml\Blog;

use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Backend\App\Action;  

class Upload extends \Vendor\Blog\Controller\Adminhtml\Blog
{

    protected $_fileUploaderFactory;
    protected $_directory_list;
    protected $_logger;

    public function __construct(
        Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\MediaStorage\Model\File\UploaderFactory $fileUploaderFactory,
        \Magento\Framework\App\Filesystem\DirectoryList $directory_list,
        \Psr\Log\LoggerInterface $logger
    ) {
        $this->_fileUploaderFactory = $fileUploaderFactory;
        $this->_directory_list = $directory_list;
        $this->_logger = $logger;
        parent::__construct($context, $coreRegistry);
    }

    public function execute(){
        $uploader = $this->_fileUploaderFactory->create(['fileId' => 'featured_images']);
        $uploader->setAllowedExtensions(['jpg', 'jpeg', 'gif', 'png']);
        $uploader->setAllowRenameFiles(false);
        $uploader->setFilesDispersion(false);
        $path = $this->_filesystem->getDirectoryRead(DirectoryList::MEDIA)->getAbsolutePath('blog');
        //$path = $this->_directory_list->getPath('media') . '/blog';
        $this->_logger->debug('Uploader.php: '.$path);
        $uploader->save($path);
    }
}

However, when I upload an image and inspect the call in Chrome's console, I get an Error 500 with Exception: $_FILES array is empty.

I am struggling for two days but I can't get it to work right. When I uncomment the alternate $path variable line, the Upload succeeds but I do not get a preview.

I read that it might be the enctype of the form that's causing the issue, but I didn't find any information on how to check this for a UI Component form.

If you need the whole exception code please let me know.

I appreciate every help possible. Thank you!

Prince Patel
  • 22,708
  • 10
  • 97
  • 119
hallleron
  • 355
  • 1
  • 3
  • 18
  • why dont yo try another way of upload? for eg. https://webkul.com/blog/upload-an-image-from-customer-edit-form-magento-2-0/ – Pallavi Sinha Jul 27 '17 at 04:52
  • Sadly I am limited to only use the pure UI Components with XML declaration for this extension. But it would be a nice alternative though. – hallleron Jul 27 '17 at 12:24

2 Answers2

46

I follow this steps to add UI fileuploader component in admin form

I use UI fileuploader component to upload an icon for my FAQ extension. You can take reference from here: https://github.com/mageprince/magento2-FAQ

1) Add field in admin_form.xml(Admin Form)

<field name="icon">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="dataType" xsi:type="string">string</item>
            <item name="source" xsi:type="string">FaqGroup</item>
            <item name="label" xsi:type="string" translate="true">Group Image</item>
            <item name="visible" xsi:type="boolean">true</item>
            <item name="formElement" xsi:type="string">fileUploader</item>
            <item name="elementTmpl" xsi:type="string">ui/form/element/uploader/uploader</item>
            <item name="previewTmpl" xsi:type="string">Vendor_Module/image-preview</item>
            <item name="required" xsi:type="boolean">false</item>
            <item name="sortOrder" xsi:type="number">40</item>
            <item name="uploaderConfig" xsi:type="array">
                <item name="url" xsi:type="url" path="your_router/faqgroup/upload"/>
            </item>
        </item>
    </argument>
</field>

2) Now we need to create controller which we define in uploaderConfig in admin form: <item name="url" xsi:type="url" path="vendor_module/faqgroup/upload"/>

app/code/Vendor/Module/Controller/Adminhtml/FaqGroup/Upload.php

<?php

namespace Vendor\Module\Controller\Adminhtml\FaqGroup;

use Magento\Framework\Controller\ResultFactory;

class Upload extends \Magento\Backend\App\Action { public $imageUploader;

public function __construct(
    \Magento\Backend\App\Action\Context $context,
    \Vendor\Module\Model\ImageUploader $imageUploader
) {
    parent::__construct($context);
    $this-&gt;imageUploader = $imageUploader;
}

public function _isAllowed()
{
    return $this-&gt;_authorization-&gt;isAllowed('Vendor_Module::Faq');
}

public function execute()
{
    try {
        $result = $this-&gt;imageUploader-&gt;saveFileToTmpDir('icon');
        $result['cookie'] = [
            'name' =&gt; $this-&gt;_getSession()-&gt;getName(),
            'value' =&gt; $this-&gt;_getSession()-&gt;getSessionId(),
            'lifetime' =&gt; $this-&gt;_getSession()-&gt;getCookieLifetime(),
            'path' =&gt; $this-&gt;_getSession()-&gt;getCookiePath(),
            'domain' =&gt; $this-&gt;_getSession()-&gt;getCookieDomain(),
        ];
    } catch (\Exception $e) {
        $result = ['error' =&gt; $e-&gt;getMessage(), 'errorcode' =&gt; $e-&gt;getCode()];
    }
    return $this-&gt;resultFactory-&gt;create(ResultFactory::TYPE_JSON)-&gt;setData($result);
}

}

3) Create ImageUploader.php

app/code/Vendor/Module/Model/ImageUploader.php

<?php

namespace Prince\Faq\Model;

class ImageUploader { private $coreFileStorageDatabase; private $mediaDirectory; private $uploaderFactory; private $storeManager; private $logger; public $baseTmpPath; public $basePath; public $allowedExtensions;

public function __construct(
    \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase,
    \Magento\Framework\Filesystem $filesystem,
    \Magento\MediaStorage\Model\File\UploaderFactory $uploaderFactory,
    \Magento\Store\Model\StoreManagerInterface $storeManager,
    \Psr\Log\LoggerInterface $logger
) {
    $this-&gt;coreFileStorageDatabase = $coreFileStorageDatabase;
    $this-&gt;mediaDirectory = $filesystem-&gt;getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA);
    $this-&gt;uploaderFactory = $uploaderFactory;
    $this-&gt;storeManager = $storeManager;
    $this-&gt;logger = $logger;
    $this-&gt;baseTmpPath = &quot;faq/tmp/icon&quot;;
    $this-&gt;basePath = &quot;faq/icon&quot;;
    $this-&gt;allowedExtensions= ['jpg', 'jpeg', 'gif', 'png'];
}

public function setBaseTmpPath($baseTmpPath)
{
    $this-&gt;baseTmpPath = $baseTmpPath;
}

public function setBasePath($basePath)
{
    $this-&gt;basePath = $basePath;
}

public function setAllowedExtensions($allowedExtensions)
{
    $this-&gt;allowedExtensions = $allowedExtensions;
}

public function getBaseTmpPath()
{
    return $this-&gt;baseTmpPath;
}

public function getBasePath()
{
    return $this-&gt;basePath;
}

public function getAllowedExtensions()
{
    return $this-&gt;allowedExtensions;
}

public function getFilePath($path, $imageName)
{
    return rtrim($path, '/') . '/' . ltrim($imageName, '/');
}

public function moveFileFromTmp($imageName)
{
    $baseTmpPath = $this-&gt;getBaseTmpPath();
    $basePath = $this-&gt;getBasePath();
    $baseImagePath = $this-&gt;getFilePath($basePath, $imageName);
    $baseTmpImagePath = $this-&gt;getFilePath($baseTmpPath, $imageName);
    try {
        $this-&gt;coreFileStorageDatabase-&gt;copyFile(
            $baseTmpImagePath,
            $baseImagePath
        );
        $this-&gt;mediaDirectory-&gt;renameFile(
            $baseTmpImagePath,
            $baseImagePath
        );
    } catch (\Exception $e) {
        throw new \Magento\Framework\Exception\LocalizedException(
            __('Something went wrong while saving the file(s).')
        );
    }
    return $imageName;
}

public function saveFileToTmpDir($fileId)
{
    $baseTmpPath = $this-&gt;getBaseTmpPath();
    $uploader = $this-&gt;uploaderFactory-&gt;create(['fileId' =&gt; $fileId]);
    $uploader-&gt;setAllowedExtensions($this-&gt;getAllowedExtensions());
    $uploader-&gt;setAllowRenameFiles(true);
    $result = $uploader-&gt;save($this-&gt;mediaDirectory-&gt;getAbsolutePath($baseTmpPath));
    if (!$result) {
        throw new \Magento\Framework\Exception\LocalizedException(
            __('File can not be saved to the destination folder.')
        );
    }

    $result['tmp_name'] = str_replace('\\', '/', $result['tmp_name']);
    $result['path'] = str_replace('\\', '/', $result['path']);
    $result['url'] = $this-&gt;storeManager
            -&gt;getStore()
            -&gt;getBaseUrl(
                \Magento\Framework\UrlInterface::URL_TYPE_MEDIA
            ) . $this-&gt;getFilePath($baseTmpPath, $result['file']);
    $result['name'] = $result['file'];
    if (isset($result['file'])) {
        try {
            $relativePath = rtrim($baseTmpPath, '/') . '/' . ltrim($result['file'], '/');
            $this-&gt;coreFileStorageDatabase-&gt;saveFile($relativePath);
        } catch (\Exception $e) {
            $this-&gt;logger-&gt;critical($e);
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Something went wrong while saving the file(s).')
            );
        }
    }
    return $result;
}

}

4) Create image-preview.html

app/code/Vendor/Module/view/adminhtml/web/template/image-preview.html

<div class="file-uploader-summary">
    <div class="file-uploader-preview">
        <a attr="href: $parent.getFilePreview($file)" target="_blank">
            <img
                class="preview-image"
                tabindex="0"
                event="load: $parent.onPreviewLoad.bind($parent)"
                attr="
                    src: $parent.getFilePreview($file),
                    alt: $file.name">
        </a>
    &lt;div class=&quot;actions&quot;&gt;
        &lt;button
            type=&quot;button&quot;
            class=&quot;action-remove&quot;
            data-role=&quot;delete-button&quot;
            attr=&quot;title: $t('Delete image')&quot;
            click=&quot;$parent.removeFile.bind($parent, $file)&quot;&gt;
            &lt;span translate=&quot;'Delete image'&quot;/&gt;
        &lt;/button&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;file-uploader-filename&quot; text=&quot;$file.name&quot;/&gt;
&lt;div class=&quot;file-uploader-meta&quot;&gt;
    &lt;text args=&quot;$file.previewWidth&quot;/&gt;x&lt;text args=&quot;$file.previewHeight&quot;/&gt;
&lt;/div&gt;

</div>

5) Now add arguments for ImageUploader.php in di.xml

app/code/Vendor/Module/etc/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">
    <type name="Vendor\Module\Model\ImageUploader">
        <arguments>
            <!-- Temporary file stored in pub/media/faq/tmp/icon -->
            <argument name="baseTmpPath" xsi:type="string">faq/tmp/icon</argument>
            <argument name="basePath" xsi:type="string">faq/icon</argument>
            <argument name="allowedExtensions" xsi:type="array">
                <item name="jpg" xsi:type="string">jpg</item>
                <item name="jpeg" xsi:type="string">jpeg</item>
                <item name="gif" xsi:type="string">gif</item>
                <item name="png" xsi:type="string">png</item>
            </argument>
        </arguments>
    </type>
</config>

Check this file for load uploaded image on edit form: DataProvider.php

OUTPUT:

enter image description here

To save image in database

app/code/Vendor/Module/Controller/Adminhtml/Save.php

<?php

namespace Vendor\Module\Controller\Adminhtml;

use Magento\Framework\Exception\LocalizedException;

class Save extends \Magento\Backend\App\Action { protected $dataPersistor;

public function __construct(
    \Magento\Backend\App\Action\Context $context,
    \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor
) {
    $this-&gt;dataPersistor = $dataPersistor;
    parent::__construct($context);
}

public function execute()
{
    ...
    ...
    $data = $this-&gt;_filterFoodData($data);
    $model-&gt;setData($data);
    $model-&gt;save();
    ...
    ...     
}

public function _filterFoodData(array $rawData)
{
    //Replace icon with fileuploader field name
    $data = $rawData;
    if (isset($data['icon'][0]['name'])) {
        $data['icon'] = $data['icon'][0]['name'];
    } else {
        $data['icon'] = null;
    }
    return $data;
}

}

To show uploaded image in form edit page:

app/code/Vendor/Module/Model/DataProvider.php

<?php

namespace Vendor\Module\Model;

use Magento\Store\Model\StoreManagerInterface;

class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider { ... ...

public function getData()
{
    ...
    ...
    $items = $this-&gt;collection-&gt;getItems();

    //Replace icon with fileuploader field name
    foreach ($items as $model) {
        $this-&gt;loadedData[$model-&gt;getId()] = $model-&gt;getData();
        if ($model-&gt;getIcon()) {
            $m['icon'][0]['name'] = $model-&gt;getIcon();
            $m['icon'][0]['url'] = $this-&gt;getMediaUrl().$model-&gt;getIcon();
            $fullData = $this-&gt;loadedData;
            $this-&gt;loadedData[$model-&gt;getId()] = array_merge($fullData[$model-&gt;getId()], $m);
        }
    }
    ...
    ...

    return $this-&gt;loadedData;
}

public function getMediaUrl()
{
    $mediaUrl = $this-&gt;storeManager-&gt;getStore()
        -&gt;getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA).'faq/tmp/icon/';
    return $mediaUrl;
}

}

Edited Date:- 2nd August, 2021

If you got Error like "Uncaught TypeError: value.map is not a function at UiClass.setInitialValue (file-uploader.js:80)"

Or You can't see Value in Inputs field at edit page

then first of all check your Array which is Returns from DataProviders...

for Show Image at edit Page you need to return array in below sample format. Array which is return from DataProviders

I was returned wrong array using Above DataProvider Source. So I have done some changes in public function getData() function and Its works for me... see below changes

public function getData()
{
    ...
    ...
    $items = $this->collection->getItems();
//Replace icon with fileuploader field name
foreach ($items as $model) {
    $this-&gt;loadedData[$model-&gt;getId()]['post'] = $model-&gt;getData(); 
    //Note that: I used ['post] in above line because I was already defined  *&lt;fieldset name=&quot;post&quot;&gt;* at Form UI Component's xml File... you have to use as your respective... even you can see above Screenshot of Array I return Data in Post Array Object, that why I use ['post']

    if ($model-&gt;getIcon()) {
        $m[0]['name'] = $model-&gt;getIcon();
        $m[0]['url'] = $this-&gt;getMediaUrl().$model-&gt;getIcon();
        $this-&gt;loadedData[$model-&gt;getId()]['post']['featured_image'] = $m;
    }
}
...
...

return $this-&gt;loadedData;

}

you can Debug your Array at DataProvider like this

echo '<script> console.log('. json_encode($this->loadedData) .') </script>';
Harsh Patel
  • 219
  • 1
  • 10
Prince Patel
  • 22,708
  • 10
  • 97
  • 119
  • Thank you so much for this, really appreciate it. However when I implement your solution and upload an Image, my returned response in Chrome's console is: {"error":"$_FILES array is empty","errorcode":0}. The file has not been uploaded (I checked chmod) and of course the preview cannot show up. – hallleron Jul 27 '17 at 15:10
  • 1
    Actually I got it working finally with the help of your code. So thank you very much! You are my hero! :-) – hallleron Jul 28 '17 at 10:18
  • You're most welcome :) – Prince Patel Jul 28 '17 at 11:08
  • @PrincePatel this is just great, but assuming i have an edit form, how can I make the image-preview template read from the getData from the data provider – Yehia A.Salam Sep 30 '17 at 11:59
  • @PrincePatel found answer to that here https://magento.stackexchange.com/questions/138642/magento-2-1-image-not-uploading-in-edit-form – Yehia A.Salam Sep 30 '17 at 16:00
  • @YehiaA.Salam check this file: https://github.com/mageprince/magento2-FAQ/blob/master/Prince/Faq/Model/FaqGroup/DataProvider.php – Prince Patel Sep 30 '17 at 16:47
  • can I add this as attribute for product in catalog ? – ephemeral Jan 19 '18 at 12:10
  • It is working for a single image field, But if I have multiple image fields which is need to upload a image and also need to show preview for each images. Can you provide me a solution – Jaisa Jan 30 '18 at 05:10
  • @sri Refer this module I use two file uploader field in ui form: https://github.com/mageprince/magento2-productlabel – Prince Patel Jan 30 '18 at 05:14
  • @PrincePatel In above code Inside Imageuploader.php, In constructor You have set the extensions, But my scenario, I have set allowed extensions in system configuration, I got the system config values in helper file, Now How do I set my system config value in constructor itself – Jaisa Feb 01 '18 at 05:37
  • In constructor , I had set like $this->allowedExtensions = $this->helper->getImageFileTypes(); but it shows an 500 error – Jaisa Feb 01 '18 at 05:37
  • @sri This issue is not related to this question. Please ask other question about this issue to make clean communication. – Prince Patel Feb 01 '18 at 05:42
  • @PrincePatel, thank you for the response, I have solved the issue – Jaisa Feb 01 '18 at 06:47
  • Now I have posted my issue by refering your code. Please have a glance https://magento.stackexchange.com/questions/211831/admingrid-ui-component-form-in-magento-2 – Jaisa Feb 01 '18 at 09:28
  • As far as I can tell, this code saves to a temporary directory, and doesn't ever move it from there. Am I missing something? – Nathan Merrill Feb 09 '18 at 17:07
  • Can we display the loading icon when when the file is being uploaded ? – WISAM HAKIM Apr 15 '19 at 18:13
  • hi @PrincePatel , can use it for file csv ? – Brian N Apr 25 '19 at 05:09
  • 1
    @PrincePatel I'm working in Magento 2.3 and calling "ImageUploader" from di.xml and sending baseTmpPath, basePath and allowedExtensions params from the same. Now my model "ImageUploader" throws an error "Exception #0 (BadMethodCallException): Missing required argument $baseTmpPath". Can you please help me how to manage from di.xml instead of statically set in "ImageUploader" model's constructor function? – Dhara Bhatti May 20 '19 at 13:30
  • I got {"error":"The file was not uploaded.","errorcode":666} any idea about this error – Soundararajan m Jun 01 '20 at 09:08
  • @PrincePatel, how to use it for product file attachment? do we need to create any attribute, can you please suggest on it – Manjunath Jan 13 '21 at 08:20
  • Worth noting that in order to display the image after saving within the admin (the edit view) or to be able to show the image on the frontend via something like $model->getIcon() you need to add the image column to your database via InstallSchema or UpgradeSchema. – jaybong Sep 13 '22 at 21:01
6

Supplement for Magento 2.2 UI component

Compare with Magento 2.1, in Magento 2.2, the UI component had some optional differences like below. We could make use of the official Magento_Catalog/image-preview as preview templete, and the rest of codes like controller could refer to the accepted answer.

<field name="image" formElement="fileUploader">
    <settings>
        <notice translate="true">Allowed file types: jpg, jpeg, gif, png.</notice>
        <label translate="true">Image</label>
        <componentType>fileUploader</componentType>
    </settings>
    <formElements>
        <fileUploader>
            <settings>
                <allowedExtensions>jpg jpeg gif png</allowedExtensions>
                <maxFileSize>10240000</maxFileSize>
                <placeholderType>image</placeholderType>
                <previewTmpl>Magento_Catalog/image-preview</previewTmpl>
                <uploaderConfig>
                    <param xsi:type="string" name="url">path/to/controller</param>
                </uploaderConfig>
            </settings>
        </fileUploader>
    </formElements>
</field>
Key Shang
  • 3,415
  • 32
  • 58
  • 2
    I get the error TypeError: value.map is not a function. How can I fix it – Nero Phung Jan 06 '18 at 04:11
  • @NeroPhung Hi, please try this solution https://magento.stackexchange.com/q/138642/44237 – Key Shang Jan 06 '18 at 08:17
  • I have done this issue by myself within following that post. Thanks for the support! – Nero Phung Jan 06 '18 at 08:28
  • @KeyShang , In your code , How and where Do I put validation for image uploder field – Jaisa Feb 02 '18 at 04:25
  • @Sri I see your question, I will answer it in your question https://magento.stackexchange.com/questions/211957/ui-component-form-validation-for-file-uploader-field , give me some time. – Key Shang Feb 02 '18 at 04:28
  • @Sri I had a lunch just now and you delete your question, the maxFileSize and allowedExtensions you could see my answer, other validation you could see the top answer and put the validation codes in the upload controller execute method. – Key Shang Feb 02 '18 at 06:12
  • Thank you soo much @KeyShang. I have solved my issue after added the below code true – Jaisa Feb 02 '18 at 08:38