Building the Testimonials Grid: Columns, Filters, Mass Actions
Building the Testimonials Grid: Columns, Filters, Mass Actions
~9 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
With the fundamentals from block 2 in place, the testimonial grid comes together quickly - controller, layout, listing.xml, and DataProvider follow the exact pattern from chapters 4 and 5, here fully built out for the real project.
DataProvider
<?php
declare(strict_types=1);
namespace Mironsoft\Testimonial\Ui\DataProvider\Listing;
use Magento\Ui\DataProvider\AbstractDataProvider;
use Mironsoft\Testimonial\Model\ResourceModel\Testimonial\CollectionFactory;
/**
* Supplies testimonial rows to the admin grid.
*/
class TestimonialDataProvider extends AbstractDataProvider
{
/**
* @param string $name Component name, injected by the UI Component framework.
* @param string $primaryFieldName Primary key column of the underlying collection.
* @param string $requestFieldName Request parameter name carrying the current ID.
* @param CollectionFactory $collectionFactory Factory building the testimonial collection.
* @param array<string, mixed> $meta Additional UI Component meta configuration.
* @param array<string, mixed> $data Additional UI Component data configuration.
*/
public function __construct(
string $name,
string $primaryFieldName,
string $requestFieldName,
CollectionFactory $collectionFactory,
array $meta = [],
array $data = [],
) {
parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
$this->collection = $collectionFactory->create();
}
}listing.xml with all column types from block 2
<?xml version="1.0"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_components.xsd">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="provider" xsi:type="string">mironsoft_testimonial_listing.mironsoft_testimonial_listing_data_source</item>
</item>
<item name="spinner" xsi:type="string">testimonial_columns</item>
<item name="deps" xsi:type="string">mironsoft_testimonial_listing.mironsoft_testimonial_listing_data_source</item>
</argument>
<dataSource name="mironsoft_testimonial_listing_data_source">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="class" xsi:type="string">Mironsoft\Testimonial\Ui\DataProvider\Listing\TestimonialDataProvider</argument>
<argument name="name" xsi:type="string">mironsoft_testimonial_listing_data_source</argument>
<argument name="primaryFieldName" xsi:type="string">testimonial_id</argument>
<argument name="requestFieldName" xsi:type="string">id</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">
<filters name="listing_filters"/>
<massaction name="listing_massaction"/>
<paging name="listing_paging"/>
</listingToolbar>
<columns name="testimonial_columns">
<column name="testimonial_id">
<settings>
<filter>text</filter>
<label translate="true">ID</label>
<sorting>desc</sorting>
</settings>
</column>
<column name="image" component="Magento_Ui/js/grid/columns/thumbnail">
<settings>
<altField>customer_name</altField>
<has_preview>1</has_preview>
<label translate="true">Photo</label>
<sortable>false</sortable>
</settings>
</column>
<column name="customer_name">
<settings>
<filter>text</filter>
<label translate="true">Customer</label>
</settings>
</column>
<column name="company">
<settings>
<filter>text</filter>
<label translate="true">Company</label>
</settings>
</column>
<column name="rating">
<settings>
<filter>textRange</filter>
<dataType>text</dataType>
<label translate="true">Rating</label>
</settings>
</column>
<column name="is_active">
<settings>
<options class="Mironsoft\Testimonial\Model\Source\IsActive"/>
<filter>select</filter>
<dataType>select</dataType>
<label translate="true">Status</label>
</settings>
</column>
<actionsColumn name="actions" class="Magento\Ui\Component\Listing\Columns\Actions">
<settings>
<indexField>testimonial_id</indexField>
<resizeEnabled>false</resizeEnabled>
<sortable>false</sortable>
</settings>
</actionsColumn>
</columns>
</listing>The column selection deliberately combines everything from chapter 6: image as a thumbnail, rating with a textRange filter (filtering for a rating between 3 and 5, for example), is_active as a select using the same IsActive source class as the practice module.
Mass actions: delete and status
The massaction declaration and the MassDelete/MassStatus controllers match chapter 8 one to one - Filter::getCollection() again handles the entire selection logic; only the namespace and table name change to Testimonial.
Achtung: During mass delete, the onDelete="CASCADE" rule from chapter 14 now kicks in: every $testimonial->delete() automatically removes the matching rows in mironsoft_testimonial_store too. Without that cascade, the MassDelete controller would have to remove the store relations manually beforehand.
The IsActive source class
The same small class from chapter 6 is repeated one to one for the testimonial module (own namespace Mironsoft\Testimonial\Model\Source) - the grid filter and the form select (chapter 16) later both draw from the same source.