During the time developing a store, maybe you get a requirement from your client to make a custom filter then it makes you stuck to find the way to do it, so you can refer to my tutorial here as a source to finish your task.
Now I will give you an example with implement a search box in the product listing. I pushed the code here on my Github.
In the backend
I create a subscriber to listening to the ProductListingCriteriaEvent
to add my Criteria.
You have to use ProductListingCollectFilterEvent
If your feature needs to add the Filter Collectio, take a look at it in the core here
class ProductListingSubscriber implements EventSubscriberInterface
{
private ProductSearchBuilderInterface $searchBuilder;
public function __construct(ProductSearchBuilderInterface $searchBuilder) {
$this->searchBuilder = $searchBuilder;
}
public static function getSubscribedEvents(): array
{
return [
ProductListingCriteriaEvent::class => 'loadProductListingCriteria',
];
}
public function loadProductListingCriteria(ProductListingCriteriaEvent $event): void
{
// If the request is empty search param, we don't have to run it
$request = $event->getRequest();
if (!$request->get('search')) {
return;
}
// I reused the search function in the core to add the criteria of search by keyword
$this->searchBuilder->build($request, $event->getCriteria(), $event->getSalesChannelContext());
}
}
In the frontend
I created my new search box in src/Resources/views/storefront/component/search.html.twig
{% block mac_product_listing_search %}
<div data-mac-listing-search="true"
class="mac-listing-search">
{% block layout_header_search_input_group %}
<div class="input-group">
{% block layout_header_search_input %}
<input type="search"
name="search"
class="form-control mac-listing-search-input"
autocomplete="off"
autocapitalize="off"
placeholder="{{ "macListingSearch.searchPlaceholder"|trans|striptags }}"
aria-label="{{ "macListingSearch.searchPlaceholder"|trans|striptags }}"
value="{{ app.request.get('search') }}"
>
{% endblock %}
{% block layout_header_search_icon %}
<div class="input-group-append">
<span class="btn mac-listing-search-icon">
{% sw_icon 'search' %}
</span>
</div>
{% endblock %}
</div>
{% endblock %}
</div>
{% endblock %}
And then overwrite the component/product/listing.html.twig
to include the search box
{% sw_extends '@Storefront/storefront/component/product/listing.html.twig' %}
{% block element_product_listing_sorting %}
<div class="cms-element-product-listing-action-right">
{% sw_include '@Storefront/storefront/component/search.html.twig' %}
{{ parent() }}
</div>
{% endblock %}
{% block element_product_listing_col_empty_alert %}
<div class="cms-element-product-listing-alert">
{% sw_include '@Storefront/storefront/component/search.html.twig' %}
</div>
{{ parent() }}
{% endblock %}
After that, I create a script for search box
export default class MacListingSearchPlugin extends FilterBasePlugin {
static options = deepmerge(FilterBasePlugin.options, {
keywords: null, // To stored the keywords
searchListingInputFieldSelector: 'input[type=search]', // Get the search field
searchListingDelay: 250, // Add delay to make sure only load 1 time when the user typing
searchListingMinChars: 3, // Min chars to get results
});
init() {
this._inputField = DomAccess.querySelector(this.el, this.options.searchListingInputFieldSelector);
this._registerEvents();
}
/**
* @private
*/
_registerEvents() {
this._inputField.addEventListener(
'input',
Debouncer.debounce(this._handleInputEvent.bind(this), this.options.searchListingDelay),
{
capture: true,
passive: true,
},
);
}
/**
* Fire the XHR request if the user inputs a search term
* @private
*/
_handleInputEvent() {
const value = this._inputField.value.trim();
// stop search if minimum input value length has not been reached
if (value.length === 0 || value.length >= this.options.searchListingMinChars) {
this.options.keywords = value;
this.listing.changeListing();
}
}
/**
* @public
*/
reset() {
// Do not remove it
}
/**
* @public
*/
resetAll() {
// Do not remove it
}
/**
* @return {Object}
* @public
*/
getValues() {
if (this.options.keywords === null) {
return { search: '' };
}
return {
search: this.options.keywords,
};
}
/**
* @return {Array}
* @public
*/
getLabels() {
return [];
}
afterContentChange() {
this.listing.deregisterFilter(this);
}
}
After all, don’t forget to add your script to main.js
import MacListingSearchPlugin from "./plugin/listing/listing-search.plugin";
const PluginManager = window.PluginManager;
PluginManager.register('MacListingSearchPlugin', MacListingSearchPlugin, '[data-mac-listing-search]');
That’s it, if you have any questions, please leave me a comment :D.