Magento 2 How to add custom sort by option
If you want to use an attribute like created_at
that it isn't in admin->stores->(attribute) product, because attributes defined in admin have the setting Sorting in Product Listing = Yes/No
, you have to work with these two files:
\vendor\magento\module-catalog\Block\Product\ProductList\Toolbar.php
\vendor\magento\module-catalog\Model\Config.php
In Toolbar.php
you can see
$this->_availableOrder = $this->_catalogConfig->getAttributeUsedForSortByArray();
it calls getAttributeUsedForSortByArray()
from Config.php
that returns array of available attributes to sort listing collection.
Now, you have to add your created_at
attribute here.
How?
I did it with a plugin
/**
* Add sort order option created_at to frontend
*/
public function afterGetAttributeUsedForSortByArray(
\Magento\Catalog\Model\Config $catalogConfig,
$options
) {
$options['created_at'] = __('New');
return $options;
}
You inserted created_at
in available attributes to sort, now you have only to build your custom collection to use it.
Here i choose to override \vendor\magento\module-catalog\Block\Product\ProductList\Toolbar.php
with mine Toolbar.php
and override setCollection()
/**
* 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);
}
// switch between sort order options
if ($this->getCurrentOrder()) {
// create custom query for created_at option
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;
default:
$this->_collection->setOrder($this->getCurrentOrder(), $this->getCurrentDirection());
break;
}
}
// echo '<pre>';
// var_dump($this->getCurrentOrder());
// var_dump((string) $this->_collection->getSelect());
// die;
return $this;
}
That's all, for me works like a charm.
We can achieve it by using Plugins. Please create following files in your module.
app/code/Package/CustomToolbar/etc/di.xml
<type name="Magento\Catalog\Model\Config">
<plugin name="Package_CustomToolbar::addCustomOptions" type="Package\CustomToolbar\Plugin\Model\Config" />
</type>
<type name="Magento\Catalog\Block\Product\ProductList\Toolbar">
<plugin name="Package_CustomToolbar::addPriceDecendingFilterInToolbar" type="Package\CustomToolbar\Plugin\Product\ProductList\Toolbar" />
</type>
app/code/Package/CustomToolbar/Plugin/Model/Config.php
namespace Package\CustomToolbar\Plugin\Model;
use Magento\Store\Model\StoreManagerInterface;
class Config
{
protected $_storeManager;
public function __construct(
StoreManagerInterface $storeManager
) {
$this->_storeManager = $storeManager;
}
/**
* Adding custom options and changing labels
*
* @param \Magento\Catalog\Model\Config $catalogConfig
* @param [] $options
* @return []
*/
public function afterGetAttributeUsedForSortByArray(\Magento\Catalog\Model\Config $catalogConfig, $options)
{
$store = $this->_storeManager->getStore();
$currencySymbol = $store->getCurrentCurrency()->getCurrencySymbol();
//Remove specific default sorting options
unset($options['position']);
unset($options['name']);
unset($options['price']);
//Changing label
$customOption['position'] = __('Relevance');
//New sorting options
$customOption['price_desc'] = __($currencySymbol.' (High to Low)');
$customOption['price_asc'] = __($currencySymbol.' (Low to High)');
//Merge default sorting options with custom options
$options = array_merge($customOption, $options);
return $options;
}
}
app/code/Package/CustomToolbar/Plugin/Product/ProductList/Toolbar.php
namespace Package\CustomToolbar\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 $subject,
\Closure $proceed,
$collection
) {
$currentOrder = $subject->getCurrentOrder();
$result = $proceed($collection);
if ($currentOrder) {
if ($currentOrder == 'price_desc') {
$subject->getCollection()->setOrder('price', 'desc');
} elseif ($currentOrder == 'price_asc') {
$subject->getCollection()->setOrder('price', 'asc');
}
}
return $result;
}
}
This is working fine for me without rewriting any Magento class.
If you want to use just Create At attribute, you can activate this attribute in admin panel in sorting options.
Example:
<?php
namespace Vendor\Module\Setup;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\UpgradeDataInterface;
class UpgradeData implements UpgradeDataInterface
{
protected $eavSetupFactory;
/**
* UpgradeData constructor.
*
* @param EavSetupFactory $eavSetupFactory
*/
public function __construct(
EavSetupFactory $eavSetupFactory
) {
$this->eavSetupFactory = $eavSetupFactory;
}
/**
* @param ModuleDataSetupInterface $setup
* @param ModuleContextInterface $context
*/
public function upgrade(
ModuleDataSetupInterface $setup,
ModuleContextInterface $context
) {
/** @var EavSetup $eavSetup */
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
if (version_compare($context->getVersion(), '2.1.1', '<')) {
try {
$entityType = $eavSetup->getEntityTypeId('catalog_product');
$label = 'Created At';
$eavSetup->updateAttribute($entityType, 'created_at', 'frontend_label', $label, null);
$eavSetup->updateAttribute($entityType, 'created_at', 'used_for_sort_by', 1, null);
} catch (LocalizedException $e) {
}
}
}
}
This code from Setup/UpgradeData.php, but will be better to use InstallData.php instead.