Reading list Switch to dark mode

    Layered navigation for custom collection on a custom page in Magento 2

    Updated 9 February 2024

    Hello Friends!!!

    In this blog, we are going to learn how we can create based on Magento 2 layered navigation for our custom collection on a custom page.
    Sometimes, when we create a custom collection page in our module, we extend the \Magento\Catalog\Block\Product\ListProduct class and apply the filters in collection for our custom collection and it works. But sometimes, we face wrong product count or pagination issues, or layered navigation issues on our custom collection page.

    So, here I will explain in following steps to achieve the correct result for the custom collection page with the toolbar and layered navigation.

    Step 1: Create di.xml file inside the app/code/Webkul/MyCustomCollection/etc/ directory.

    <?xml version="1.0"?>
    <!--
    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_MyCustomCollection
     * @author    Webkul
     * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    -->
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    	<!-- here custom layer class extends the default Magento layer class.
    		 That class instantiates a number of interfaces. -->
    	<preference for="Magento\Catalog\Model\Layer\ContextInterface" type="Magento\Catalog\Model\Layer\Context" />
    	<preference for="Magento\Catalog\Model\Layer\ItemCollectionProviderInterface" type="Magento\Catalog\Model\Layer\Category\ItemCollectionProvider" />
    	<preference for="Magento\Catalog\Model\Layer\StateKeyInterface" type="Magento\Catalog\Model\Layer\Category\StateKey" />
    	<preference for="Magento\Catalog\Model\Layer\CollectionFilterInterface" type="Magento\Catalog\Model\Layer\Category\CollectionFilter" />
    	<preference for="Magento\Catalog\Model\Layer\FilterableAttributeListInterface" type="Magento\Catalog\Model\Layer\Category\FilterableAttributeList" />
    	<preference for="Magento\Catalog\Model\Layer\AvailabilityFlagInterface" type="Magento\Catalog\Model\Layer\Category\AvailabilityFlag" />
        
        <preference for="Magento\Catalog\Model\ResourceModel\Category" type="Webkul\MyCustomCollection\Model\ResourceModel\Category" />
        <preference for="Magento\Catalog\Model\ResourceModel\Layer\Filter\Price" type="Webkul\MyCustomCollection\Model\ResourceModel\Layer\Filter\Price" />
    </config>

    Step 2: Now, create Model files to override the collection data. Here, we will create Layer.php file inside the app/code/Webkul/MyCustomCollection/Model/ directory.

    Searching for an experienced
    Magento 2 Company ?
    Find out More
    <?php
    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_MyCustomCollection
     * @author    Webkul
     * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    namespace Webkul\MyCustomCollection\Model;
    
    class Layer extends \Magento\Catalog\Model\Layer
    {
    	public function getProductCollection()
    	{
    		$collection = parent::getProductCollection();
    		$collection->addAttributeToFilter('deal_status', ['eq' => '1']);
    		$idArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
    		if (!empty($idArray)) {
                $collection->addAttributeToFilter('entity_id', ["in"=>$idArray]);
            }
    		return $collection;
    	}
    }

    Now, we will create Resolver.php file inside the app/code/Webkul/MyCustomCollection/Model/Layer directory.

    <?php
    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_MyCustomCollection
     * @author    Webkul
     * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    namespace Webkul\MyCustomCollection\Model\Layer;
    
    class Resolver extends \Magento\Catalog\Model\Layer\Resolver
    {
    	public function __construct(
    		\Magento\Framework\ObjectManagerInterface $objectManager,
    		\Webkul\MyCustomCollection\Model\Layer $layer,
    		array $layersPool
    	) {
    		$this->layer = $layer;
    		parent::__construct($objectManager, $layersPool);
    	}
    
    	public function create($layerType)
    	{
    		//$this->layer gets set in the constructor, so this create function
    		//doesn't need to do anything.
    	}
    }

    Now, we will create Category.php file inside the app/code/Webkul/MyCustomCollection/Model/ResourceModel/ directory.

    <?php
    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_MyCustomCollection
     * @author    Webkul
     * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    namespace Webkul\MyCustomCollection\Model\ResourceModel;
    
    class Category extends \Magento\Catalog\Model\ResourceModel\Category
    {
        public function getProductCount($category)
        {
            $collection = $category->getProductCollection();
            $collection->addAttributeToFilter('deal_status', ['eq' => '1']);
    		$idArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
    		if (!empty($idArray)) {
                $collection->addAttributeToFilter('entity_id', ["in"=>$idArray]);
            }
            return intval($collection->count());
        }
    }

    Now, we will create Price.php file inside the app/code/Webkul/MyCustomCollection/Model/ResourceModel/Layer/Filter directory.

    <?php
    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_MyCustomCollection
     * @author    Webkul
     * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    namespace Webkul\MyCustomCollection\Model\ResourceModel\Layer\Filter;
    
    class Price extends \Magento\Catalog\Model\ResourceModel\Layer\Filter\Price
    {
        /**
         * @param \Magento\Framework\Model\ResourceModel\Db\Context $context
         * @param \Magento\Framework\Event\ManagerInterface $eventManager
         * @param \Magento\Catalog\Model\Layer\Resolver $layerResolver
         * @param \Magento\Customer\Model\Session $session
         * @param \Magento\Store\Model\StoreManagerInterface $storeManager
         * @param \Webkul\MyCustomCollection\Model\Layer $layer
         * @param null $connectionName
         */
        public function __construct(
            \Magento\Framework\Model\ResourceModel\Db\Context $context,
            \Magento\Framework\Event\ManagerInterface $eventManager,
            \Magento\Catalog\Model\Layer\Resolver $layerResolver,
            \Magento\Customer\Model\Session $session,
            \Magento\Store\Model\StoreManagerInterface $storeManager,
            \Webkul\MyCustomCollection\Model\Layer $layer,
            $connectionName = null
            ) {
                $this->layer        = $layerResolver->get();
                $this->session      = $session;
                $this->storeManager = $storeManager;
                parent::__construct(
                    $context,
                    $eventManager,
                    $layerResolver,
                    $session,
                    $storeManager,
                    $connectionName
                );
        }
        
        public function getCount($range)
        {
            $select = $this->getSelect();
            $priceExpression = $this->_getFullPriceExpression($select);
    
            /**
             * Check and set correct variable values to prevent SQL-injections
             */
            $range = floatval($range);
            if ($range == 0) {
                $range = 1;
            }
            $countExpr = new \Zend_Db_Expr('COUNT(*)');
            $rangeExpr = new \Zend_Db_Expr("FLOOR(({$priceExpression}) / {$range}) + 1");
    
            $select->columns(['range' => $rangeExpr, 'count' => $countExpr]);
            $select->group($rangeExpr)->order(new \Zend_Db_Expr("({$rangeExpr}) ASC"));
            
            return $this->getConnection()->fetchPairs($select);
        }
        
        public function getSelect()
        {
            $collection = $this->layer->getProductCollection();
            $collection->addAttributeToFilter('deal_status', ['eq' => '1']);
    		$idArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
    		if (!empty($idArray)) {
                $collection->addAttributeToFilter('entity_id', ["in"=>$idArray]);
            }
            $collection->addPriceData(
                $this->session->getCustomerGroupId(),
                $this->storeManager->getStore()->getWebsiteId()
            );
            
            $select = clone $collection->getSelect();
            // reset columns, order and limitation conditions
            $select->reset(\Magento\Framework\DB\Select::COLUMNS);
            $select->reset(\Magento\Framework\DB\Select::ORDER);
            $select->reset(\Magento\Framework\DB\Select::LIMIT_COUNT);
            $select->reset(\Magento\Framework\DB\Select::LIMIT_OFFSET);
            
            // remove join with main table
            $fromPart = $select->getPart(\Magento\Framework\DB\Select::FROM);
            if (!isset(
                $fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::INDEX_TABLE_ALIAS]
            ) || !isset(
                $fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::MAIN_TABLE_ALIAS]
                )
            ) {
                return $select;
            }
            
            // processing FROM part
            $priceIndexJoinPart = $fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::INDEX_TABLE_ALIAS];
            $priceIndexJoinConditions = explode('AND', $priceIndexJoinPart['joinCondition']);
            $priceIndexJoinPart['joinType'] = \Magento\Framework\DB\Select::FROM;
            $priceIndexJoinPart['joinCondition'] = null;
            $fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::MAIN_TABLE_ALIAS] = $priceIndexJoinPart;
            unset($fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::INDEX_TABLE_ALIAS]);
            $select->setPart(\Magento\Framework\DB\Select::FROM, $fromPart);
            foreach ($fromPart as $key => $fromJoinItem) {
                $fromPart[$key]['joinCondition'] = $this->_replaceTableAlias($fromJoinItem['joinCondition']);
            }
            $select->setPart(\Magento\Framework\DB\Select::FROM, $fromPart);
            
            // processing WHERE part
            $wherePart = $select->getPart(\Magento\Framework\DB\Select::WHERE);
            foreach ($wherePart as $key => $wherePartItem) {
                $wherePart[$key] = $this->_replaceTableAlias($wherePartItem);
            }
            $select->setPart(\Magento\Framework\DB\Select::WHERE, $wherePart);
            $excludeJoinPart = \Magento\Catalog\Model\ResourceModel\Product\Collection::MAIN_TABLE_ALIAS . '.entity_id';
            foreach ($priceIndexJoinConditions as $condition) {
                if (strpos($condition, $excludeJoinPart) !== false) {
                    continue;
                }
                $select->where($this->_replaceTableAlias($condition));
            }
            $select->where($this->_getPriceExpression($select) . ' IS NOT NULL');
            
            return $select;
        }
    }

    Step 3: Create layout file mycollection_index_index.xml inside the app/code/Webkul/MyCustomCollection/view/frontend/layout/ directory.

    <?xml version="1.0"?>
    <!--
    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_MyMyCustomCollection
     * @author    Webkul
     * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    -->
    <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>
    		<!--adding attribute tag to inherit the default stying for products-->
    		<attribute name="class" value="page-products"/>
    		<referenceContainer name="content">
    			<block class="Webkul\MyCustomCollection\Block\Product\ListProduct" name="mycollection_index_index" as="product_list" template="Magento_Catalog::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\Item\Container" name="category.product.addto" as="addto">
    					<block class="Magento\Catalog\Block\Product\ProductList\Item\AddTo\Compare"
    						name="category.product.addto.compare" as="compare"
    						template="Magento_Catalog::product/list/addto/compare.phtml"/>
    				</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>
    		<referenceContainer name="sidebar.main">
    		<block class="Webkul\MyCustomCollection\Block\Navigation" name="catalog.leftnav" as="navigation" before="-" template="Magento_LayeredNavigation::layer/view.phtml">
    				<block class="Webkul\MyCustomCollection\Block\Navigation\State" name="catalog.navigation.state" as="state" template="Magento_LayeredNavigation::layer/state.phtml" />
    				<block class="Magento\LayeredNavigation\Block\Navigation\FilterRenderer" name="catalog.navigation.renderer" as="renderer" template="Magento_LayeredNavigation::layer/filter.phtml">
    					<arguments>
    						<argument name="product_layer_view_model" xsi:type="object">Magento\LayeredNavigation\ViewModel\Layer\Filter</argument>
    					</arguments>
    				</block>
    			</block>
    		</referenceContainer>
    		<referenceBlock name="mycollection_index_index">
    			<arguments>
    				<argument name="viewModel" xsi:type="object">Magento\Catalog\ViewModel\Product\OptionsData</argument>
    			</arguments>
    		</referenceBlock>
    	</body>
    </page>

    Step 4: Now, we will override the block files to get the required data.
    Here, we will create Navigation.php file inside the app/code/Webkul/MyCustomCollection/Block/ directory.

    <?php
    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_MyCustomCollection
     * @author    Webkul
     * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    namespace Webkul\MyCustomCollection\Block;
    
    class Navigation extends \Magento\LayeredNavigation\Block\Navigation
    {
    	public function __construct(
    		\Magento\Framework\View\Element\Template\Context $context,
    		\Webkul\MyCustomCollection\Model\Layer\Resolver $layerResolver,
    		\Magento\Catalog\Model\Layer\FilterList $filterList,
    		\Magento\Catalog\Model\Layer\AvailabilityFlagInterface $visibilityFlag,
    		array $data = []
    	) {
    		parent::__construct(
    			$context,
    			$layerResolver,
    			$filterList,
    			$visibilityFlag
    		);
    	}
    }
    ?>

    Then, create State.php file inside the app/code/Webkul/MyCustomCollection/Block/Navigation/ directory.

    <?php
    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_MyCustomCollection
     * @author    Webkul
     * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    namespace Webkul\MyCustomCollection\Block\Navigation;
    
    class State extends \Magento\LayeredNavigation\Block\Navigation\State
    {
    	public function __construct(
    		\Magento\Framework\View\Element\Template\Context $context,
    		\Webkul\MyCustomCollection\Model\Layer\Resolver $layerResolver,
    		array $data = []
    	) {
    		parent::__construct(
    			$context,
    			$layerResolver,
    			$data
    		);
    	}
    }

    Create ListProduct.php file inside the app/code/Webkul/MyCustomCollection/Block/Product/ directory.

    <?php
    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_MyCustomCollection
     * @author    Webkul
     * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    namespace Webkul\MyCustomCollection\Block\Product;
    
    class ListProduct extends \Magento\Catalog\Block\Product\ListProduct
    {
    	public function __construct(
    		\Magento\Catalog\Block\Product\Context $context,
    		\Magento\Framework\Data\Helper\PostHelper $postDataHelper,
    		\Webkul\MyCustomCollection\Model\Layer\Resolver $layerResolver,
    		\Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository,
    		\Magento\Framework\Url\Helper\Data $urlHelper,
    		array $data = []
    	) {
    		parent::__construct(
    			$context,
    			$postDataHelper,
    			$layerResolver,
    			$categoryRepository,
    			$urlHelper,
    			$data
    		);
    	}
    }

    Now, check the result on the front end. For reference, please look into the following image:

    192.168.15.170_mage243_pub_mycollection_index_index_

    For a better understanding of layered-navigation related classes, please check “Anatomy of Magento 2: Layered Navigation”

    Download the code.

    Hope this will be helpful. Thanks 🙂

    . . .

    Leave a Comment

    Your email address will not be published. Required fields are marked*


    2 comments

  • Khushboo Sahu (Moderator)
  • Nilesh
  • Back to Top

    Message Sent!

    If you have more details or questions, you can reply to the received confirmation email.

    Back to Home