4

I want to custom sort by option filter attribute add in tool bar section. Currently Magento 2 provide Position , Product Name , Price And Sorting option to low to high. But i need to change all sorting option Like Price High to Low , Price Low to High.

3 Answers 3

5

I created these custom sort by options:

  • price high - low --> (including all forms of discounts)
  • price low - high --> (including all forms of discounts)
  • name a-z
  • name z-a
  • newest
  • oldest

Magento 2.4+ (when ElasticSearch is used)

Read full instructions below at 2.4- section but implement changes from this section if you use ElasticSearch (M2.4+)

Main difference when using ElasticSearch instead of MySql is that using a plugin to control Toolbar.php setCollection method doesn't seem to work (probably because of ElasticSearch).

But using preference in di.xml instead works:

<preference for="Magento\Catalog\Block\Product\ProductList\Toolbar" type="Vendor\Module\Block\Product\ProductList\Toolbar" />

namespace Vendor\Module\Block\Product\ProductList;

class Toolbar extends \Magento\Catalog\Block\Product\ProductList\Toolbar
{
    public function setCollection($collection)
    {
        $this->_collection = $collection;

        $this->_collection->setCurPage($this->getCurrentPage());

        $limit = (int)$this->getLimit();
        if ($limit) {
            $this->_collection->setPageSize($limit);
        }

        $currentOrder = $this->getCurrentOrder();
        if ($currentOrder) {
            if ($currentOrder == 'name_az') {
                $this->_collection->setOrder('name', 'asc');
            } elseif ($currentOrder == 'name_za') {
                $this->_collection->setOrder('name', 'desc');
            } elseif ($currentOrder == 'price_asc') {
                $this->_collection->setOrder('price', 'asc');
            } elseif ($currentOrder == 'price_desc') {
                $this->_collection->setOrder('price', 'desc');
            } elseif ($currentOrder == 'newest') { // created_at must have "used_for_sort_by = 1" in catalog_eav_attribute table
                $this->_collection->setOrder('created_at', 'desc');
            } elseif ($currentOrder == 'oldest') {
                $this->_collection->setOrder('created_at', 'asc');
            } else {
                $this->_collection->setOrder($currentOrder, $this->getCurrentDirection());
            }
        }
        return $this;
    }
}

Note the comment next to newest order - if you use newset/oldest sorting by created_at attribute you need to change the value of the field used_for_sort_by to 1 in catalog_eav_attribute table for this attribute.

Also in Magento\Catalog\Model\Config make sure to put unset($options['created_at']); since the actual options used are newest/oldest.

Afterwards, just reindex and clear cache.

Magento 2.4- (when Mysql search engine is used)

by creating a plugin to bypass the default functionality of the product sorter which can be found here:

Magento\Catalog\Block\Product\ProductList\Toolbar

and

Magento\Catalog\Model\Config

You start by creating your own module and registering it.

Then create:

{Vendor}/{ModuleName}/etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Model\Config">
        <plugin name="Vendor_ModuleName::addCustomSortOptions" type="Vendor\ModuleName\Plugin\Model\Config" />
    </type>
    <type name="Magento\Catalog\Block\Product\ProductList\Toolbar">
        <plugin name="Vendor_ModuleName::implementCustomSortOptions" type="Vendor\ModuleName\Plugin\Product\ProductList\Toolbar" />
    </type>
</config>

Create

{Vendor}/{ModuleName}/Plugin/Model/Config.php

<?php
namespace Vendor\ModuleName\Plugin\Model;

class Config
{
    /**
     * Adding custom options and changing labels
     *
     * @param \Magento\Catalog\Model\Config $catalogConfig
     * @param [] $options
     * @return []
     */
    public function afterGetAttributeUsedForSortByArray(\Magento\Catalog\Model\Config $catalogConfig, $options)
    {
        //Remove default sorting options
        unset($options['position']);
        unset($options['name']);
        unset($options['price']);
    
        //Change label of default sorting options if needed
        //$options['position'] = __('Relevance');
    
        //New sorting options
        $options['price_desc'] = __('Price High - Low');
        $options['price_asc'] = __('Price Low - High');
        $options['newest'] = __('Newest');
        $options['oldest'] = __('Oldest');
        $options['name_az'] = __('Product Name A - Z');
        $options['name_za'] = __('Product Name Z - A');
    
        return $options;
    }
}

Create

{vendor}/{modulename}/Plugin/Product/ProductList/Toolbar.php

<?php
namespace Vendor\ModuleName\Plugin\Product\ProductList;

class Toolbar
{
    /**
    * Plugin
    *
    * @param \Magento\Catalog\Block\Product\ProductList\Toolbar $subject
    * @param \Closure $proceed
    * @param \Magento\Framework\Data\Collection $collection
    * @return \Magento\Catalog\Block\Product\ProductList\Toolbar
    */
    public function aroundSetCollection(
        \Magento\Catalog\Block\Product\ProductList\Toolbar $toolbar,
        \Closure $proceed,
        $collection
    ) {
        $this->_collection = $collection;
        $currentOrder = $toolbar->getCurrentOrder();
        $currentDirection = $toolbar->getCurrentDirection();
        $result = $proceed($collection);
            
        if ($currentOrder) {
            switch ($currentOrder) {
            
            case 'newest':
                $this->_collection
                    ->getSelect()
                    ->order('e.created_at DESC');
            break;
            
            case 'oldest':
                $this->_collection
                    ->getSelect()
                    ->order('e.created_at ASC');

            break;
            
            case 'price_desc':
                $this->_collection
                    ->getSelect()
                    ->order('price_index.min_price DESC');
            break;
            
            case 'price_asc':
                $this->_collection
                    ->getSelect()
                    ->order('price_index.min_price ASC');
            break;
            
            case 'name_az':
                $this->_collection
                    ->getSelect()
                    ->order('e.name ASC');
            break;
            
            case 'name_za':
                $this->_collection
                    ->getSelect()
                    ->order('e.name DESC');
            break;
            
            default:        
                $this->_collection
                    ->setOrder($currentOrder, $currentDirection);
            break;
            
            }
        }
        //var_dump((string) $this->_collection->getSelect()); You can use this to get a list of all the available sort fields
        return $result;
    }
}
18
  • I followed above steps replace code from {vendor}/{modulename} to our {vendor}/{modulename}. after creating attributes form backend. but it seems no luck. Commented Dec 16, 2017 at 7:26
  • @AnikeshShahi what do you mean "after creating attributes from backend"? This works for sure in Magento 2.2. I suggest you double check if you changed all {vendor} & {modulename} with your own. And make sure you did setup:upgrade, setup:di:compile, clear static files and cache.
    – Lez
    Commented Dec 17, 2017 at 20:07
  • I already checked, attributes shows in drop down tool bar but not filtering as per selection. Commented Dec 18, 2017 at 5:08
  • @AnikeshShahi if you see the new options, but they don't work, you might have made a mistake in Toolbar.php or maybe you have conflicting modules (which both change the functionality of the toolbar). Maybe you could edit your question with your attempted solution and I could help you locate the mistake.
    – Lez
    Commented Dec 18, 2017 at 8:40
  • I want to sort the collection by custom price using default sort by price. Have you any solution? @Lez Commented Apr 18, 2018 at 6:04
1

You have to override \Magento\Catalog\Block\Product\ProductList\Toolbar and Magento\Catalog\Model\Config.

etc/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
   <preference for="Magento\Catalog\Block\Product\ProductList\Toolbar" type="{Vendor}\{Module}\Block\Plugin\Product\ProductList\Toolbar" />
    <type name="Magento\Catalog\Model\Config">
       <plugin name="override-config" type="{Vendor}\{Module}\Model\Plugin\Config" sortOrder="10"/>
    </type>
</config>

{vendor}{module}\Block\Plugin\Product\ProductList\Toolbar.php

namespace {Vendor}\{module}\Block\Plugin\Product\ProductList;

class Toolbar extends \Magento\Catalog\Block\Product\ProductList\Toolbar {
  public function setCollection($collection) {
    $this->_collection = $collection;
    $this->_collection->setCurPage($this->getCurrentPage());
    $limit = (int) $this->getLimit();
    if ($limit) {
        $this->_collection->setPageSize($limit);
    }
    if ($this->getCurrentOrder()) {
        switch ($this->getCurrentOrder()) {
            case 'created_at':
                if ($this->getCurrentDirection() == 'desc') {
                    $this->_collection
                            ->getSelect()
                            ->order('e.created_at DESC');
                } elseif ($this->getCurrentDirection() == 'asc') {
                    $this->_collection
                            ->getSelect()
                            ->order('e.created_at ASC');
                }
                break;
            case 'in_stock':
                $this->_collection
                        ->getSelect()
                        ->joinLeft(
                                ['admin' => $collection->getTable('cataloginventory_stock_item')], 'e.entity_id = admin.product_id')->where('admin.is_in_stock = 1');
                break;
            case 'out_stock':
                $this->_collection
                        ->getSelect()
                        ->joinLeft(
                                ['admin' => $collection->getTable('cataloginventory_stock_item')], 'e.entity_id = admin.product_id')->where('admin.is_in_stock = 0');
                break;
            default:
                $this->_collection->setOrder($this->getCurrentOrder(), $this->getCurrentDirection());
                break;
        }
    }
    return $this;
  }
}

{Vendor}{Module}\Model\Plugin\Config.php

namespace {Vendor}\{Module}\Model\Plugin;
class Config {

   public function afterGetAttributeUsedForSortByArray(
   \Magento\Catalog\Model\Config $catalogConfig, $options
   ) {

    $options['created_at'] = __('Newest');
    $options['in_stock'] = __('In Stock');
    $options['out_stock'] = __('Out of Stock');
    return $options;
   }
}
3
  • Small remark. You need join cataloginventory_stock_status not cataloginventory_stock_item table
    – KAndy
    Commented Dec 9, 2016 at 14:06
  • Have you any solution for it? i have for cataloginventory_stock_item. and its working fine Commented Dec 9, 2016 at 14:22
  • I want to sort the collection by custom price using default sort by price. Have you any solution? @Lez Commented Apr 18, 2018 at 6:05
0

I did something similar using following code.

app/code/Anshu/Sortby/registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Anshu_Sortby',
    __DIR__
);

app/code/Anshu/Sortby/etc/module.xml

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Anshu_Sortby" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Catalog"/>
        </sequence>
    </module>
</config>

app/code/Anshu/Sortby/etc/di.xml

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">   <preference for="Magento\Catalog\Block\Product\ProductList\Toolbar" type="Anshu\Sortby\Block\Catalog\Product\ProductList\Toolbar" />
    <preference for="Magento\Catalog\Model\Config" type="Anshu\Sortby\Model\Config" />
</config>

app/code/Anshu/Sortby/Model/Config.php

<?php

namespace Anshu\Sortby\Model;

class Config extends \Magento\Catalog\Model\Config
{

    /**
     * Retrieve Attributes Used for Sort by as array
     * key = code, value = name
     *
     * @return array
     */
    public function getAttributeUsedForSortByArray()
    {
        $options = ['position' => __('Most Popular')];
        foreach ($this->getAttributesUsedForSortBy() as $attribute) {
            /* @var $attribute \Magento\Eav\Model\Entity\Attribute\AbstractAttribute */
            $options[$attribute->getAttributeCode()] = $attribute->getStoreLabel();
        }

        $options['position'] = __('Most Popular');
        $options['price'] =  __('Price: Low to High');
        $options['price_desc'] = __('Price: High to Low');
        return $options;
    }

}

app/code/Anshu/Sortby/Block/Catalog/Product/ProductList/Toolbar.php

<?php

namespace Anshu\Sortby\Block\Catalog\Product\ProductList;

use Magento\Catalog\Helper\Product\ProductList;
use Magento\Catalog\Model\Product\ProductList\Toolbar as ToolbarModel;

class Toolbar extends \Magento\Catalog\Block\Product\ProductList\Toolbar
{
    /**
     * Set collection to pager
     *
     * @param \Magento\Framework\Data\Collection $collection
     * @return $this
     */
    public function setCollection($collection)
    {
        $this->_collection = $collection;

        $this->_collection->setCurPage($this->getCurrentPage());

        // we need to set pagination only if passed value integer and more that 0
        $limit = (int)$this->getLimit();
        if ($limit) {
            $this->_collection->setPageSize($limit);
        }
        if ($this->getCurrentOrder()) {
            switch ($this->getCurrentOrder()) {
                case 'price':
                    $this->_collection->setOrder($this->getCurrentOrder(), 'ASC');
                    break;

                case 'price_desc':
                    $this->_collection
                            ->getSelect()
                            ->order('price_index.price DESC');

                default:
                    //$this->_collection->setOrder($this->getCurrentOrder(), $this->getCurrentDirection());
                    $this->_collection->setOrder($this->getCurrentOrder(), 'DESC');
                break;
            }
        }

        return $this;
    }

}

I hope this will be helpful.

3

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.