Question

I am actually trying to create a really simple grid (for now) listing files from server.
The collection work well, I take as example Magento_Backup module but for the grid the module is using the old way with class and not the UI components.

My collection class \Foo\Bar\Model\Log\Collection is extending \Magento\Framework\Data\Collection\Filesystem to construct file collection.

I defined a new type in my di.xml

<type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
    <arguments>
        <argument name="collections" xsi:type="array">
            <item name="foo_bar_log_listing_data_source" xsi:type="string">Foo\Bar\Model\Log\Collection</item>
        </argument>
    </arguments>
</type>

Here is my ui_component named foo_bar_log_listing.xml

<?xml version="1.0"?>

<listing 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="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">foo_bar_log_listing.foo_bar_log_listing_data_source</item>
            <item name="deps" xsi:type="string">foo_bar_log_listing.foo_bar_log_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">foo_bar_log_columns</item>
    </argument>
    <dataSource name="foo_bar_log_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
            <argument name="name" xsi:type="string">foo_bar_log_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">code</argument>
            <argument name="requestFieldName" xsi:type="string">code</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                    <item name="storageConfig" xsi:type="array">
                        <item name="indexField" xsi:type="string">code</item>
                    </item>
                </item>
            </argument>
        </argument>
    </dataSource>
    <listingToolbar name="listing_top">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="sticky" xsi:type="boolean">true</item>
            </item>
        </argument>
        <bookmark name="bookmarks"/>
        <columnsControls name="columns_controls"/>
        <filterSearch name="fulltext"/>
        <filters name="listing_filters" />
        <paging name="listing_paging"/>
    </listingToolbar>
    <columns name="foo_bar_log_columns">
        <column name="code">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Code</item>
                </item>
            </argument>
        </column>
        <column name="file">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">File</item>
                </item>
            </argument>
        </column>
    </columns>
</listing>

Here is my collection class

<?php

namespace Foo\Bar\Model\Log;

use Magento\Framework\Data\Collection\EntityFactoryInterface;

class Collection extends FilesystemCollection
{

    /**
     * Filenames regex filter
     *
     * @var string
     */
    protected $_allowedFilesMask = '/^[a-z0-9\.\-\_]+[bar]\.log/i';
    /**
     * @var \Foo\Bar\Helper\Log $logHelper
     */
    private $logHelper;
    /**
     * @var \Foo\Bar\Helper\Adminhtml\Log $adminLogHelper
     */
    private $adminLogHelper;

    public function __construct(
        EntityFactoryInterface $entityFactory,
        \Foo\Bar\Helper\Log $logHelper,
        \Foo\Bar\Helper\Adminhtml\Log $adminLogHelper
    ) {
        $this->logHelper      = $logHelper;
        $this->adminLogHelper = $adminLogHelper;
        parent::__construct($entityFactory);

        /** @var string $logDirectory */
        $logDirectory = $this->logHelper->getLogDirectoryPath();

        $this->setCollectRecursively(true);
        $this->addTargetDir($logDirectory);
    }

    /**
     * @param string $filename
     *
     * @return array
     */
    protected function _generateRow($filename)
    {
        /** @var array $row */
        $row = parent::_generateRow($filename);
        $row = $this->adminLogHelper->generateRow($row);

        return $row;
    }
}

I understand that the dataSource is refering my \Foo\Bar\Log\Collection class with his arguments and for basics entities it is a VirtualType of \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult

So my questions are
- Is it possible to create a file listing grid with UI Component as basic entities ?
- Which classes do I have to use in di.xml / foo_bar_log_listing.xml classes ?

Thank you.

Was it helpful?

Solution

Here is what you need.
All files path are relative to the [Namespace]/[Module] folder.
First, the layout handle for your action: view/adminhtml/layout/foo_bar_index.xml (make sure the name matches the route)

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="styles"/>
    <body>
        <referenceContainer name="content">
            <uiComponent name="foo_bar_listing"/>
        </referenceContainer>
    </body>
</page>

Now the ui component view/adminhtml/ui_component/foo_bar_listing.xml

<?xml version="1.0" encoding="UTF-8"?>
<listing 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="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">foo_bar_listing.foo_bar_listing_data_source</item>
            <item name="deps" xsi:type="string">foo_bar_listing.foo_bar_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">foo_bar_columns</item>
        <item name="buttons" xsi:type="array"><!-- define buttons you need above the list. you may skip this -->
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Foo Bar</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/new</item>
            </item>
        </item>
    </argument>
    <dataSource name="foo_bar_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">[Namespace]\[Module]\Ui\DataProvider\FooBarDataProvider</argument>
            <argument name="name" xsi:type="string">foo_bar_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">filename_id</argument>
            <argument name="requestFieldName" xsi:type="string">filename_id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
            </item>
        </argument>
    </dataSource>
    <listingToolbar name="listing_top">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="sticky" xsi:type="boolean">true</item>
            </item>
        </argument>
        <bookmark name="bookmarks"/>
        <columnsControls name="columns_controls"/>
        <filterSearch name="fulltext"/><!-- skip this if you don't want full text search-->
        <filters name="listing_filters">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="displayArea" xsi:type="string">dataGridFilters</item>
                    <item name="dataScope" xsi:type="string">filters</item>
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">foo_bar_listing.foo_bar_listing.listing_top.bookmarks</item>
                        <item name="namespace" xsi:type="string">current.filters</item>
                    </item>
                    <item name="childDefaults" xsi:type="array">
                        <item name="provider" xsi:type="string">foo_bar_listing.foo_bar_listing.listing_top.listing_filters</item>
                        <item name="imports" xsi:type="array">
                            <item name="visible" xsi:type="string">foo_bar_listing.foo_bar_listing.listing_top.bookmarks:current.columns.${ $.index }.visible</item>
                        </item>
                    </item>
                </item>
            </argument>
            <filterInput name="filename_id">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="dataScope" xsi:type="string">filename_id</item>
                        <item name="label" xsi:type="string" translate="true">Module Name</item>
                    </item>
                </argument>
            </filterInput><!-- additional filters go here -->
        </filters>
        <paging name="listing_paging"/>
    </listingToolbar>
    <columns name="foo_bar_columns">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="childDefaults" xsi:type="array">
                    <item name="fieldAction" xsi:type="array">
                        <item name="provider" xsi:type="string">foo_bar_listing.foo_bar_listing.foo_bar_columns.actions</item>
                        <item name="target" xsi:type="string">applyAction</item>
                        <item name="params" xsi:type="array">
                            <item name="0" xsi:type="string">edit</item>
                            <item name="1" xsi:type="string">${ $.$data.rowIndex }</item>
                        </item>
                    </item>
                    <item name="controlVisibility" xsi:type="boolean">true</item>
                    <item name="appendTo" xsi:type="string">foo_bar_listing.foo_bar_listing.listing_top.columns_controls</item>
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">foo_bar_listing.foo_bar_listing.listing_top.bookmarks</item>
                        <item name="root" xsi:type="string">columns.${ $.index }</item>
                        <item name="namespace" xsi:type="string">current.${ $.storageConfig.root}</item>
                    </item>
                </item>
            </item>
        </argument>
        <column name="filename_id"><!-- feel free to change the column name -->
            <argument name="data" xsi:type="array">
                <item name="js_config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/column</item>
                </item>
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="align" xsi:type="string">left</item>
                    <item name="label" xsi:type="string" translate="true">File name</item>
                    <item name="sortOrder" xsi:type="number">10</item>
                </item>
            </argument>
        </column> <!-- additional columns here -->
        <actionsColumn name="actions" class="[Namespace]\[Module]\Ui\Component\Listing\Columns\FooBarActions"><!-- Skip this if your module does not have any actions -->
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="sortOrder" xsi:type="number">200</item>
                    <item name="dataType" xsi:type="string">actions</item>
                    <item name="align" xsi:type="string">left</item>
                    <item name="label" xsi:type="string" translate="true">Action</item>
                    <item name="data_type" xsi:type="string">actions</item>
                    <item name="filterable" xsi:type="boolean">false</item>
                    <item name="sortable" xsi:type="boolean">false</item>
                </item>
            </argument>
        </actionsColumn>
    </columns>
</listing>

(please don't ask me to explain what every tag does. I barely know what a few of them mean).

Now the controller action for your grid page.

Controllers/Adminhtml/FooBar/Index.php (make sure the filename matches your route).

<?php
namespace [Namespace]\[Module]\Controller\Adminhtml\FooBar;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;

class Index extends Action
{
    /**
     * page factory reference
     *
     * @var PageFactory
     */
    protected $resultPageFactory;

    /**
     * @param Context $context
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        PageFactory $resultPageFactory
    )
    {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context);
    }
    /**
     * run index action
     *
     * @return \Magento\Framework\View\Result\Page
     */
    public function execute()
    {
        $pageResult = $this->resultPageFactory->create();
        $pageResult->getConfig()->getTitle()->set(__('Your Page title'));
        $pageResult->getConfig()->getTitle()->prepend(__('FooBar List'));
        $this->_setActiveMenu('Your menu id here') //can skip this
            ->_addBreadcrumb(
                __('Your Module Name'),
                __('Your Module Name')
            )->_addBreadcrumb(
                __('FooBar List'),
                __('FooBar List')
            );
        return $pageResult;
    }
}

the data provider for the grid: Ui/DataProvider/FooBarDataProvider.php

<?php
namespace [Namespace]\[Module]\Ui\DataProvider;

use Magento\Ui\DataProvider\AbstractDataProvider;
use [Namespace]\[Module]\Model\FooBar\CollectionFactory;

/**
 * Class ProductDataProvider
 */
class FooBarDataProvider extends AbstractDataProvider
{
    /**
     * foobar collection
     *
     * @var \[Namespace]\[Module]\Model\FooBar\Collection
     */
    protected $collection;

    /**
     * @var \Magento\Ui\DataProvider\AddFieldToCollectionInterface[]
     */
    protected $addFieldStrategies;

    /**
     * @var \Magento\Ui\DataProvider\AddFilterToCollectionInterface[]
     */
    protected $addFilterStrategies;

    /**
     * Construct
     *
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param CollectionFactory $collectionFactory
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $collectionFactory,
        array $meta = [],
        array $data = []
    ) {
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
        $this->collection = $collectionFactory->create();
    }

    /**
     * Get collection
     *
     * @return \[Namespace]\[Module]\Model\FooBar\Collection
     */
    public function getCollection()
    {
        return $this->collection;

    }

    /**
     * Get data
     *
     * @return array
     */
    public function getData()
    {
        if (!$this->getCollection()->isLoaded()) {
            $this->getCollection()->load();
        }
        return $this->getCollection()->toArray();
    }
}

the class for adding actions to each row, only if you need it.

Ui/Component/Listing/Columns/FooBarActions.php

<?php
namespace [Namespace]\[Module]\Ui\Component\Listing\Columns;

use Magento\Framework\Filesystem;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;
use Magento\Framework\UrlInterface;
use Magento\Framework\App\Filesystem\DirectoryList;

class FooBarActions extends Column
{
    /**
     * @var UrlInterface
     */
    protected $urlBuilder;

    /**
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory
     * @param UrlInterface $urlBuilder
     * @param array $components
     * @param array $data
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        UrlInterface $urlBuilder,
        array $components = [],
        array $data = []
    ) {
        $this->urlBuilder = $urlBuilder;
        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     * @return array
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            //this is just a demo, it might not work on your instance.
            foreach ($dataSource['data']['items'] as &$item) {
                $item[$this->getData('name')]['edit'] = [
                    'href' => $this->urlBuilder->getUrl(
                        '[module]/controller/edit',
                        ['id' => $item['id']]
                    ),
                    'label' => __('Edit'),
                    'hidden' => false,
                ];

            }
        }
        return $dataSource;
    }
}

your collection class looks fine, just add this method in it.

/**
 * @param $field
 * @param $direction
 * @return \Magento\Framework\Data\Collection
 */
public function addOrder($field, $direction)
{
    return $this->setOrder($field, $direction);
}

That's all I have for now, it this does not solve your problem it should at least push your forward.
I left some comments in the code, read them and take the necessary actions.

Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top