Magento 2 - Admin grid from file collection
-
04-10-2020 - |
سؤال
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.
المحلول
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.