How to add a custom filter into the storefront

How to add a custom filter into the storefront

·

3 min read

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.