Local Storage seems to be disabled in your browser.
For the best experience on our site, be sure to turn on Local Storage in your browser.
Customization for searching empty field in Magento 2 Admin Grid

As illustrated in the concept image, we got a request from our client that he wants to use the built-in Admin Grid filter feature to search entries that has empty field. To be more specific in his situation, the "Delivery Date" field is an optional input during the checkout process and the client is looking for a way to only list orders that has this field empty. After some investigation, we discovered that a small customization is required achieve the goal. Let's see how to do this.
Analysis
GUI Limitation
First, you cannot input an empty string and apply the filter because the filter component treats empty input as no filtering on that field, so we must input something. Finally, after talking with the client, we decide to use ""
and ''
to represent empty string.
Database
Then let's think the database part. Depending on the database schema and model implementation, the delivery_date
field may be NULL
or an empty string. So we need to modify the final SQL so that it can match both cases.
Strategy
\Magento\Framework\Api\Filter
Now we need to identify how the filter is generated.
(Magento 2.4.7-p3) Input.php#L84
As you can see in the above source file, the text input always has a LIKE
condition type, and the value becomes %value%
.
So our customization is to modify this filter's value
and condition type
.
BTW, there's bug on Line 77 (Magento 2.4.7-p3).
Where to Intercept ?
Another important thing is that we need to make this customization specific to the target dataSource. In other words, this customization should not affect other Admin Grids.
Finally, we decide to "after plugin" the DataProvider
's getSearchCriteria
method as the request name
is also set within this method.
Customization
Code was tested on Magento 2.4.x
Identify the correct DataProvider
({{something}}
is placeholder)
Open the model listing component
, usually, it is view/adminhtml/ui_component/{{model}}_listing.xml
.
Note down the dataProvider
element's name and class:
<dataProvider name="{{model}}_listing_data_source" class="{{DataProvider}}">
<settings>
...
</settings>
</dataProvider>
Typically, {{DataProvider}}
class is Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider
, but it may be different in your case. It just needs to be class that implements Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface
, so identify the correct class.
The {{DataProvider}}
can also be a virtualType
. If so, you need to plugin the real one.
Build the Plugin with di.xml
configuration
Below is partial di.xml configuration. Note we set the requestName
argument to limit the plugin only working for the dataSource with that name.
<!-- etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
...
<type name="{{DataProvider}}">
<plugin name="wubinworks_tutorial_plugin_dataprovider_emptyfield" type="Wubinworks\Tutorial\Plugin\DataProvider\EmptyField" />
</type>
<type name="Wubinworks\Tutorial\Plugin\DataProvider\EmptyField">
<arguments>
<argument name="requestName" xsi:type="string">{{model}}_listing_data_source</argument>
</arguments>
</type>
...
</config>
Base abstract class for generic procedures:
<?php
/**
* Copyright © Wubinworks. All rights reserved.
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL-3.0)
* that is available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
*
* @author Wubinworks Dev Team
* @copyright Wubinworks
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL-3.0)
* @link https://www.wubinworks.com/contact Contact us
*/
declare(strict_types=1);
namespace Wubinworks\Tutorial\Plugin\DataProvider;
use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface;
use Magento\Framework\Api\Search\FilterGroup;
use Magento\Framework\Api\Search\SearchCriteria;
use Magento\Framework\Api\Search\SearchCriteriaBuilder;
use Magento\Framework\Api\FilterBuilder;
/**
* Abstract search criteria modifier
*/
abstract class AbstractModifier
{
public const FIELDS = [];
public const VALUES = [];
public const CONDITION_TYPES = [];
/**
* @var string
*/
protected $requestName;
/**
* @var FilterBuilder
*/
protected $filterBuilder;
/**
* Constructor
*
* @param FilterBuilder $filterBuilder
* @param ?string $requestName
*/
public function __construct(
FilterBuilder $filterBuilder,
?string $requestName = null
) {
$this->filterBuilder = $filterBuilder;
$this->requestName = $requestName;
}
/**
* Plugin entry. Modify search criteria
*
* @param DataProviderInterface $subject
* @param SearchCriteria $result
* @return SearchCriteria
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function afterGetSearchCriteria(
DataProviderInterface $subject,
SearchCriteria $result
): SearchCriteria {
if (!$this->matchRequestName($result)) {
return $result;
}
foreach ($result->getFilterGroups() as $filterGroup) {
if ($this->matchFilterGroup($filterGroup)) {
$this->modifyFilterGroup($filterGroup);
}
}
return $result;
}
/**
* Match request name so it only affects specific DataProvider
*
* @param SearchCriteria $searchCriteria
* @return bool
*/
protected function matchRequestName(SearchCriteria $searchCriteria): bool
{
return $searchCriteria->getRequestName() === $this->requestName;
}
/**
* Match filter group. Override if needed
*
* @param FilterGroup $filterGroup
* @return bool
*/
protected function matchFilterGroup(FilterGroup $filterGroup): bool
{
$filters = $filterGroup->getFilters();
if (count($filters) !== 1) {
return false;
}
$filter = reset($filters);
if ((empty(static::FIELDS) || in_array($filter->getField(), static::FIELDS))
&& (empty(static::VALUES) || in_array($filter->getValue(), static::VALUES))
&& (empty(static::CONDITION_TYPES) || in_array($filter->getConditionType(), static::CONDITION_TYPES))) {
return true;
}
return false;
}
/**
* Modify filter group. Your implementation
*
* @param FilterGroup $filterGroup
* @return void
*/
abstract protected function modifyFilterGroup(FilterGroup $filterGroup): void;
}
Plugin that modifies FilterGroup
:
<?php
/**
* Copyright © Wubinworks. All rights reserved.
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL-3.0)
* that is available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
*
* @author Wubinworks Dev Team
* @copyright Wubinworks
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL-3.0)
* @link https://www.wubinworks.com/contact Contact us
*/
declare(strict_types=1);
namespace Wubinworks\Tutorial\Plugin\DataProvider;
use Magento\Framework\Api\Search\FilterGroup;
use Wubinworks\Tutorial\Plugin\DataProvider\AbstractSearchCriteriaModifier;
/**
* "" and '' can search NULL or empty string
*/
class EmptyField extends AbstractSearchCriteriaModifier
{
public const FIELDS = [
'delivery_date'
];
public const VALUES = [
'%""%',
"%''%"
];
public const CONDITION_TYPES = [
'like'
];
/**
* @inheritdoc
*/
protected function modifyFilterGroup(FilterGroup $filterGroup): void
{
$filters = $filterGroup->getFilters();
$filter = reset($filters);
$filters = [];
// 'IS NULL'
$filters[] = $this->filterBuilder->setField($filter->getField())
->setValue(true)
->setConditionType('null')
->create();
// Empty string
$filters[] = $this->filterBuilder->setField($filter->getField())
->setValue('')
->setConditionType('eq')
->create();
// One filter becomes two filters now
$filterGroup->setFilters($filters);
}
}
Final Thoughts
By extending the AbstractSearchCriteriaModifier
class, you can even modify the filter to:
- Exact match:
eq
- Regular expression match:
regexp
- 'FIND_IN_SET':
finset
Check here to get more options and hints.
In next tutorial, we will demonstrate how to use GROUP BY
and HAVING
clauses in Magento 2 Admin Grid. They are very useful when you want to make a report for model tables.
。⋆*ᴴᴬᴾᴾᵞ ᴺᴱᵂ ᵞᴱᴬᴿ*⋆。