5

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.

4
  • Can you show your module. – Sohel Rana Dec 14 '16 at 9:28
  • I just edited my question to add the Collection class ? Which part of the module do you need ? – Matthéo Geoffray Dec 14 '16 at 10:46
  • @MatthéoGeoffray Could you help me with stackoverflow.com/q/43344902/1616003 – Mukesh Apr 11 '17 at 12:02
  • @Muk In your case the var/log path had to be returned by $logDirectory = $this->logHelper->getLogDirectoryPath(); or any other function / class in the Collection class – Matthéo Geoffray Apr 12 '17 at 13:04
6
+50

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.

8
  • forgot to mention that the full text search does not work properly for me. – Marius Dec 14 '16 at 12:44
  • Huge thank you the only thing missing to my initial development was the correct DataProvider I was still using the entity default one. I validate your answer ;) – Matthéo Geoffray Dec 14 '16 at 13:00
  • For the bounty I have to wait 20 hours so I validate it later – Matthéo Geoffray Dec 14 '16 at 13:01
  • NO problem. Just one more thing. I forgot to tell you that you don't need to declare your collection in di.xml anymore. – Marius Dec 14 '16 at 13:01
  • Yes I already deleted it because of the new FooBarDataProvider directly declared in the ui component file. Thank you. – Matthéo Geoffray Dec 14 '16 at 15:25

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.