3

Goal : Create a /test/index/index route that will load a product listing page with a custom collection.

Expectation : The custom collection printed in listing and the filters must be properly defined from the collection we asked.

Issue : I can get the custom collection in the listing, but the layer navigation is not updated.

Technical requirements :

magento version : 2.4.3

"smile/elasticsuite": "~2.10.13"

Controller code :

/**
 * Execute action
 *
 * @return \Magento\Framework\View\Result\Page
 */
public function execute()
{
    // Load the page with the filtered product collection
    $resultPage = $this->pageFactory->create();
    $resultPage->getConfig()->getTitle()->set((__('Search result')));
    return $resultPage;
}

Associated layout

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <attribute name="class" value="spa-cat" />

        <referenceContainer name="sidebar.main">
            <block class="Magento\LayeredNavigation\Block\Navigation\Category" name="catalog.leftnav" template="Smile_ElasticsuiteCatalog::layer/view.phtml">
                <block class="Magento\LayeredNavigation\Block\Navigation\State" name="catalog.navigation.state" as="state" />
                <block class="Smile\ElasticsuiteCatalog\Block\Navigation\FilterRenderer" name="catalog.navigation.renderer" as="renderer">
                    <block class="Smile\ElasticsuiteCatalog\Block\Navigation\Renderer\Attribute" name="catalogsearch.navigation.renderer.attribute" template="Smile_ElasticsuiteCatalog::layer/filter/attribute.phtml" after="-" />
                    <block class="Smile\ElasticsuiteCatalog\Block\Navigation\Renderer\Category" name="catalogsearch.navigation.renderer.category" template="Smile_ElasticsuiteCatalog::layer/filter/default.phtml" />
                    <block class="Smile\ElasticsuiteCatalog\Block\Navigation\Renderer\Slider" name="catalog.navigation.renderer.slider" template="Smile_ElasticsuiteCatalog::layer/filter/slider.phtml" />
                    <block class="Smile\ElasticsuiteCatalog\Block\Navigation\Renderer\PriceSlider" name="catalog.navigation.renderer.price.slider" template="Smile_ElasticsuiteCatalog::layer/filter/slider.phtml" />
                </block>
            </block>
        </referenceContainer>

        <referenceContainer name="content">
            <block class="Sa\CustomCollection\Block\Collection" name="testplate_index_index" template="Sa_CustomCollection::product_list.phtml">
                <container name="category.product.list.additional" as="additional" />
                <block class="Magento\Framework\View\Element\RendererList" name="category.product.type.details.renderers" as="details.renderers">
                    <block class="Magento\Framework\View\Element\Template" name="category.product.type.details.renderers.default" as="default" />
                </block>
                <block class="Magento\Catalog\Block\Product\ProductList\Toolbar" name="product_list_toolbar" template="Magento_Catalog::product/list/toolbar.phtml">
                    <block class="Magento\Theme\Block\Html\Pager" name="product_list_toolbar_pager" />
                </block>
                <action method="setToolbarBlockName">
                    <argument name="name" xsi:type="string">product_list_toolbar</argument>
                </action>
            </block>
        </referenceContainer>

    </body>
</page>

Relevant part of the product_list template

<?php
/**
 * @var Sa\CustomCollection\Block\Collection $block
 * @var Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\Collection $_productCollection
 */


$_productCollection = $block->getFilteredProductCollection();
<?php if (!$_productCollection->count()) { ?>
    <div class="message info empty">
        <div><?= $escaper->escapeHtml(__('We can\'t find products matching the selection.')); ?></div>
    </div>
<?php } else { ?>
    <?= $block->getToolbarHtml(); ?>
    <?= $block->getAdditionalHtml(); ?>

    <div class="products wrapper grid products-grid">
        <ol class="spa-product-list">
            <?php foreach ($_productCollection as $_product) {

Collection Block that actually filter the collection (i've applied a filter on a local field, feel free to update with any other fields)

<?php

namespace Sa\CustomCollection\Block;
use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Block\Product\ListProduct;
use Magento\Catalog\Helper\Output as OutputHelper;
use Magento\Catalog\Model\Layer\Resolver;
use Magento\Framework\Data\Helper\PostHelper;
use Magento\Framework\Url\Helper\Data;
use Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\CollectionFactory as ElasticCollectionFactory;

class Collection extends ListProduct
{
    /**
     * @var ElasticCollectionFactory
     */
    protected $elasticCollectionFactory;

    public function __construct(\Magento\Catalog\Block\Product\Context $context, PostHelper $postDataHelper,
                                Resolver $layerResolver, CategoryRepositoryInterface $categoryRepository,
                                ElasticCollectionFactory $elasticCollectionFactory,
                                Data $urlHelper, array $data = [], ?OutputHelper $outputHelper = null)
    {
        $this->elasticCollectionFactory = $elasticCollectionFactory;
        parent::__construct($context, $postDataHelper, $layerResolver, $categoryRepository, $urlHelper, $data, $outputHelper);
    }


    /**
     * Retrieve the filtered product collection
     *
     * @return \Magento\Catalog\Model\ResourceModel\Product\Collection
     */
    public function getFilteredProductCollection()
    {


        // Get the ElasticSuite product collection
        $productCollection = $this->elasticCollectionFactory->create()
            ->addFieldToSelect('name')
            ->addFieldToSelect('sku')
            ->addFieldToSelect('small_image')
            ->addFieldToSelect('price')
            ->addFieldToSelect('tws_piece_libelle')
            ->addFieldToSelect('tws_piece_marque_veh')
            ->addFieldToSelect('tws_piece_modele_veh')
            ->addFieldToSelect('tws_piece_annee_veh')
            ->addFieldToFilter('tws_piece_libelle', ['eq' =>'TRAPPE A CARBURANT'])
            ->setCurPage(1)
            ->setPageSize(12);

        $dir = "ASC";
        $attributeToSort = 'position';

        foreach ($this->getRequest()->getParams() as $name => $value){
            if($name != "product_list_order" && $name != "product_list_dir"){
                $productCollection->addFieldToFilter($name,['eq' => $value]);
            }else{
                if($name == "product_list_dir"){
                    $dir = "DESC";
                }
                if($name == "product_list_order"){
                    $attributeToSort = $value;
                }
            }
        }
        $productCollection->addAttributeToSort($attributeToSort,$dir);

        $this->getToolbarBlock()->setCollection($productCollection);
        $this->getLayer()->prepareProductCollection($productCollection);

        return $productCollection;
    }
}

I'm now currently working with a Navigation block which I think is responsible of my issue (i'm not using it so far cause i'm failing).

src/app/code/Sa/CustomCollection/Block/Navigation.php

<?php

namespace Sa\CustomCollection\Block;

class Navigation extends \Smile\ElasticsuiteCatalog\Block\Navigation
{

    /**
     * @var \Magento\Framework\ObjectManagerInterface
     */
    private $objectManager;

    /**
     * @var \Magento\Framework\Module\Manager
     */
    private $moduleManager;

    /**
     * @var string[]
     */
    private $inlineLayouts = ['1column'];

    /**
     * @var string|NULL
     */
    private $pageLayout;

    /**
     * @var \Magento\Catalog\Helper\Data
     */
    protected $helperData;

    /**
     * @var Collection
     */
    protected $customCollection;

    /**
     * Navigation constructor.
     * @param \Magento\Framework\View\Element\Template\Context $context
     * @param \Magento\Catalog\Model\Layer\Resolver $layerResolver
     * @param \Magento\Catalog\Model\Layer\FilterList $filterList
     * @param \Magento\Catalog\Model\Layer\AvailabilityFlagInterface $visibilityFlag
     * @param \Magento\Framework\ObjectManagerInterface $objectManager
     * @param \Magento\Framework\Module\Manager $moduleManager
     * @param \Magento\Catalog\Helper\Data $helperData
     * @param array $data
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function __construct(
        \Magento\Framework\View\Element\Template\Context $context,
        \Magento\Catalog\Model\Layer\Resolver $layerResolver,
        \Magento\Catalog\Model\Layer\FilterList $filterList,
        \Magento\Catalog\Model\Layer\AvailabilityFlagInterface $visibilityFlag,
        \Magento\Framework\ObjectManagerInterface $objectManager,
        \Magento\Framework\Module\Manager $moduleManager,
        \Magento\Catalog\Helper\Data $helperData,
        Collection $customCollection,
        array $data
    )
    {
        parent::__construct($context, $layerResolver, $filterList, $visibilityFlag, $objectManager, $moduleManager, $data);
        $this->pageLayout         = $context->getPageConfig()->getPageLayout() ?: $this->getLayout()->getUpdate()->getPageLayout();
        $this->objectManager      = $objectManager;
        $this->moduleManager      = $moduleManager;
        $this->helperData = $helperData;

        $this->customCollection = $customCollection;
        $customCollectionFiltered = $customCollection->getFilteredProductCollection();

        //$before = $this->_catalogLayer->getProductCollection();
        //var_dump("BEFORE: ".count($before));
        $this->_catalogLayer->prepareProductCollection($customCollectionFiltered);
        //$after = $this->_catalogLayer->getProductCollection();
        //var_dump("AFTER:" . count($after));
        //$this->getLayer()->prepareProductCollection($customCollectionFiltered);
    }

    /**
     * Check if we can show this block.
     * According to @return bool
     * @see \Magento\LayeredNavigationStaging\Block\Navigation::canShowBlock
     * We should not show the block if staging is enabled and if we are currently previewing the results.
     *
     */
    public function canShowBlock()
    {
        $canShowBlock = parent::canShowBlock();

        if ($this->moduleManager->isEnabled('Magento_Staging')) {
            try {
                $versionManager = $this->objectManager->get('\Magento\Staging\Model\VersionManager');

                $canShowBlock = $canShowBlock && !$versionManager->isPreviewVersion();
            } catch (\Exception $exception) {
                ;
            }
        }

        if ($this->getLayer() instanceof \Magento\Catalog\Model\Layer\Category &&
            $this->getLayer()->getCurrentCategory()->getDisplayMode() === \Magento\Catalog\Model\Category::DM_PAGE) {
            $canShowBlock = false;
        }

        return $canShowBlock;
    }

    /**
     * Return index of the facets that are expanded for the current page :
     *
     *  - nth first facets (depending of config)
     *  - facets with at least one selected filter
     *
     * @return string
     */
    public function getActiveFilters()
    {
        $activeFilters = [];
        var_dump("SURCHARGE");
        if (!$this->isInline()) {
            $requestParams = array_keys($this->getRequest()->getParams());
            $displayedFilters = $this->getDisplayedFilters();
            $expandedFacets = $this->_scopeConfig->getValue(self::DEFAULT_EXPANDED_FACETS_COUNT_CONFIG_XML_PATH);
            $activeFilters = [];
            if ($expandedFacets > 0) {
                $activeFilters = range(0, min(count($displayedFilters), $expandedFacets) - 1);
            }

            foreach ($displayedFilters as $index => $filter) {
                if (in_array($filter->getRequestVar(), $requestParams)) {
                    $activeFilters[] = $index;
                }
            }
        }

        return json_encode($activeFilters);
    }

    /**
     * Returns facet that are displayed.
     *
     * @return array
     */
    public function getDisplayedFilters()
    {
        $displayedFilters = array_filter(
            $this->getFilters(),
            function ($filter) {
                return $filter->getItemsCount() > 0;
            }
        );

        return array_values($displayedFilters);
    }

    /**
     * Indicates if the block is displayed inline or not.
     *
     * @return boolean
     */
    public function isInline()
    {
        return in_array($this->pageLayout, $this->inlineLayouts);
    }

    /**
     * @return string
     */
    public function getCurrentCategory()
    {
        return is_null($this->helperData->getCategory()) ? '' : $this->helperData->getCategory()->getName();
    }
}

As you can see, the products printed are good. But the counting of categories (and all other filterable attributes) is false and not updated to use the custom collection we are providing).

product listing

That being said the filtering process is working properly, cause if i go from this page result to apply a filter then the product listing is properly filtered

product list after filter

But as you can see even after that, the filters counting and data printed are not coherent as i should only have 24 result max in counting of each filterable attribute.

Obviously if i was passing the attribute i filtered in the collection block, the result of both printed collection and layer navigation would be fine; but this is not something i want to do as i need to filter by so many parameter that it triggers an html error due to url being too long. So it has to be with a custom collection block.

I'm also expecting not to have the attribute filtered in collection block appearing as an active filter it has to be hidden from customer side. Otherwise i will have a thousand filter printed may be. Thats not what we want.

Basically, the behaviour should be the same as a category listing. When you filter on a category...the category is not printed as an active filter but still it's there. So i'm looking to replicate that behaviour. It looks simple, but it really isn't.

If in my layout I replace with this line

<block class="Sa\CustomCollection\Block\Navigation" name="catalog.leftnav" template="Smile_ElasticsuiteCatalog::layer/view.phtml">

Then this is the error i'm getting

Error: Cannot instantiate interface Magento\Catalog\Model\Layer\FilterableAttributeListInterface in /var/www/magento/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php:50 Stack trace: #0 /var/www/magento/vendor/magento/framework/ObjectManager/ObjectManager.php(70): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->create() #1 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(170): Magento\Framework\ObjectManager\ObjectManager->get() #2 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(276): Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgument() #3 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(239): Magento\Framework\ObjectManager\Factory\AbstractFactory->getResolvedArgument() #4 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php(34): Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgumentsInRuntime() #5 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php(59): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->_resolveArguments() #6 /var/www/magento/vendor/magento/framework/ObjectManager/ObjectManager.php(70): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->create() #7 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(170): Magento\Framework\ObjectManager\ObjectManager->get() #8 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(276): Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgument() #9 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(239): Magento\Framework\ObjectManager\Factory\AbstractFactory->getResolvedArgument() #10 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php(34): Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgumentsInRuntime() #11 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php(59): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->_resolveArguments() #12 /var/www/magento/vendor/magento/framework/ObjectManager/ObjectManager.php(56): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->create() #13 /var/www/magento/vendor/magento/framework/View/Element/BlockFactory.php(46): Magento\Framework\ObjectManager\ObjectManager->create() #14 /var/www/magento/vendor/magento/framework/View/Layout/Generator/Block.php(272): Magento\Framework\View\Element\BlockFactory->createBlock() #15 /var/www/magento/vendor/magento/framework/View/Layout/Generator/Block.php(252): Magento\Framework\View\Layout\Generator\Block->getBlockInstance() #16 /var/www/magento/vendor/magento/framework/View/Layout/Generator/Block.php(229): Magento\Framework\View\Layout\Generator\Block->createBlock() #17 /var/www/magento/vendor/magento/framework/View/Layout/Generator/Block.php(134): Magento\Framework\View\Layout\Generator\Block->generateBlock() #18 /var/www/magento/vendor/magento/framework/View/Layout/GeneratorPool.php(93): Magento\Framework\View\Layout\Generator\Block->process() #19 /var/www/magento/vendor/magento/framework/View/Layout.php(365): Magento\Framework\View\Layout\GeneratorPool->process() #20 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Framework\View\Layout->generateElements() #21 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Framework\View\Layout\Interceptor->___callParent() #22 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(153): Magento\Framework\View\Layout\Interceptor->Magento\Framework\Interception{closure}() #23 /var/www/magento/generated/code/Magento/Framework/View/Layout/Interceptor.php(68): Magento\Framework\View\Layout\Interceptor->___callPlugins() #24 /var/www/magento/vendor/magento/framework/View/Layout/Builder.php(129): Magento\Framework\View\Layout\Interceptor->generateElements() #25 /var/www/magento/vendor/magento/framework/View/Page/Builder.php(65): Magento\Framework\View\Layout\Builder->generateLayoutBlocks() #26 /var/www/magento/vendor/magento/framework/View/Layout/Builder.php(65): Magento\Framework\View\Page\Builder->generateLayoutBlocks() #27 /var/www/magento/vendor/magento/framework/View/Page/Config.php(224): Magento\Framework\View\Layout\Builder->build() #28 /var/www/magento/vendor/magento/framework/View/Page/Config.php(247): Magento\Framework\View\Page\Config->build() #29 /var/www/magento/app/code/Sa/CustomCollection/Controller/Index/Index.php(58): Magento\Framework\View\Page\Config->getTitle() #30 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(58): Sa\CustomCollection\Controller\Index\Index->execute() #31 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(138): Sa\CustomCollection\Controller\Index\Index\Interceptor->___callParent() #32 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(153): Sa\CustomCollection\Controller\Index\Index\Interceptor->Magento\Framework\Interception{closure}() #33 /var/www/magento/generated/code/Sa/CustomCollection/Controller/Index/Index/Interceptor.php(23): Sa\CustomCollection\Controller\Index\Index\Interceptor->___callPlugins() #34 /var/www/magento/vendor/magento/framework/App/Action/Action.php(111): Sa\CustomCollection\Controller\Index\Index\Interceptor->execute() #35 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Framework\App\Action\Action->dispatch() #36 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(138): Sa\CustomCollection\Controller\Index\Index\Interceptor->___callParent() #37 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(153): Sa\CustomCollection\Controller\Index\Index\Interceptor->Magento\Framework\Interception{closure}() #38 /var/www/magento/generated/code/Sa/CustomCollection/Controller/Index/Index/Interceptor.php(32): Sa\CustomCollection\Controller\Index\Index\Interceptor->___callPlugins() #39 /var/www/magento/vendor/magento/framework/App/FrontController.php(245): Sa\CustomCollection\Controller\Index\Index\Interceptor->dispatch() #40 /var/www/magento/vendor/magento/framework/App/FrontController.php(212): Magento\Framework\App\FrontController->getActionResponse() #41 /var/www/magento/vendor/magento/framework/App/FrontController.php(147): Magento\Framework\App\FrontController->processRequest() #42 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Framework\App\FrontController->dispatch() #43 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Framework\App\FrontController\Interceptor->___callParent() #44 /var/www/magento/vendor/magento/module-store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception{closure}() #45 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(135): Magento\Store\App\FrontController\Plugin\RequestPreprocessor->aroundDispatch() #46 /var/www/magento/vendor/magento/module-page-cache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception{closure}() #47 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(135): Magento\PageCache\Model\App\FrontController\BuiltinPlugin->aroundDispatch() #48 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(153): Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception{closure}() #49 /var/www/magento/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\Framework\App\FrontController\Interceptor->___callPlugins() #50 /var/www/magento/vendor/magento/framework/App/Http.php(116): Magento\Framework\App\FrontController\Interceptor->dispatch() #51 /var/www/magento/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\Framework\App\Http->launch() #52 /var/www/magento/vendor/magento/framework/App/Bootstrap.php(264): Magento\Framework\App\Http\Interceptor->launch() #53 /var/www/magento/pub/index.php(29): Magento\Framework\App\Bootstrap->run() #54 {main}

Navigation is defined as follow in di.xml too

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">

    <virtualType name="Magento\LayeredNavigation\Block\Navigation\Category" type="Sa\CustomCollection\Block\Navigation">
        <arguments>
            <argument name="filterList" xsi:type="object">categoryFilterList</argument>
        </arguments>
    </virtualType>

    <virtualType name="Magento\LayeredNavigation\Block\Navigation\Search" type="Sa\CustomCollection\Block\Navigation">
        <arguments>
            <argument name="filterList" xsi:type="object">searchFilterList</argument>
        </arguments>
    </virtualType>

    <virtualType name="Magento\LayeredNavigationStaging\Block\Navigation\Category" type="Sa\CustomCollection\Block\Navigation">
        <arguments>
            <argument name="filterList" xsi:type="object">categoryFilterList</argument>
        </arguments>
    </virtualType>

    <virtualType name="Magento\LayeredNavigationStaging\Block\Navigation\Search" type="Sa\CustomCollection\Block\Navigation">
        <arguments>
            <argument name="filterList" xsi:type="object">searchFilterList</argument>
        </arguments>
    </virtualType>
</config>

6
  • Can you provide the whole Module source code here?
    – Msquare
    Commented Sep 11, 2023 at 11:10
  • everything relevant s already there the global design of the page is irrelevant too, the problematic would be the same under luma.
    – Claims
    Commented Sep 11, 2023 at 12:50
  • I faced a similar issue recently and did a solution to get filters for a web api. I can post a solution but it would just return the filters in an array. You would still have to find a way to render said filters. Do you want me to post that as answer? Commented Sep 18, 2023 at 7:30
  • @VivekKumar Thanks but i don't think it would be related, the issue there is really to make the filter adapt to the collection. Which seems easy when i say it...it should be...but nobody seems to have the solution...even smile support doesn't want to answer without taking the credit card out...what a world xD
    – Claims
    Commented Sep 18, 2023 at 9:09
  • Yeah, but in my solution you can send the parameters that need to affect the product collection such as category etc and it would return relevant filter with respect to the product collection. I thought that was your question. Just I would have to do additional R&D to see how to set those filters on a page. Commented Sep 18, 2023 at 12:50

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.