9

So, I want to add a custom layout handle for all category view pages.. the handle that is supposed to be loaded depends on certain category parameters, so the handle needs to be added programmatically with $page->addPageLayoutHandles()

Seems easy..? Apparently not

Magento 2 provides a nice Plugin system that I was naturally going to use, just define a afterExecute() plugin to be run after the original category execute() and push any updates into the Page object from there.

Unfortunately, it does not quite work that way.. reason being that the original execute() method (at the very end) will execute $page->getConfig()->addBodyClass() -- a call to this method will automatically force the layout to be completely loaded and generated, so any subsequent attempts to add new layout handles to Page are completely useless. I looked around to find any not-so-elegant-ways to achieve the same thing (still using plugins).. did not find any.

I ended up running my own controller for category view, however, I would not prefer to leave it that way.

So my question is.. how can I add new page layout handles (programmatically) for category view? and do it elegantly.

2
  • What Magento version are you using? Because where can you see that $page->getConfig()->addBodyClass() loads and generates your layout? I'm having a similar task at the moment, only with CMS pages. Jan 25, 2017 at 10:00
  • I am so glad you noted this, as you saved me a bunch of grief. Just FYI: the category view controller also calls the $page->getConfig()->addBodyClass() which makes a plugin useless for accomplishing this. Feb 7, 2018 at 18:25

3 Answers 3

17

The XML way

Well an easy way is to create the following file in your module folder: view/frontend/layout/catalog_category_view.xml with the following content:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="your_custom_handle"/>
</page>

It is not more or less elegant than the PHP way and according to what you found it is safer.

The PHP way

Unfortunately in your case, it seems like PHP is the only way to achieve dynamically create handles based on the parameters the category has.

Via plugins

Instead of creating a plugin on the execute() method of your action class, you could directly create a plugin on the addPageLayoutHandles() method of Magento\Framework\View\Result\Page

Main problem is that it will be called every time this method is called and you will have to add some conditions in your plugin code to ensure you're on a category view page.

Via preferences

Another way of doing it would be to use preferences for the category view action class:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Catalog\Controller\Category\View"
                type="Vendor\Module\Controller\Category\View"/>
</config>

Then in your custom controller class you simply override the execute() method by copying/pasting the original method and add your modifications directly in this method.

Main problem is that when you will upgrade your Magento install, if changes are added to the original native Magento action class, it won't reflect in your custom action class.

5
  • The issue is.. it needs to be programatically added. Simply because the handle being loaded depends on the parameters that the category has. Jun 27, 2016 at 10:07
  • @KristjanH. hmmm I see it's embarassing, let me dig further Jun 27, 2016 at 10:13
  • @KristjanH. see my update Jun 27, 2016 at 10:26
  • 1
    Using <preference>.. that is, what I did just before I wrote the question here, however, I'm not happy with that. Same with using a plugin on addPageLayoutHandles, I don't want to call a plugin all the time, even if the performance penalty is non-existent, it's just not right. Let's see if any other solutions appear here. Jun 27, 2016 at 10:40
  • Did you try any other way after this? Dec 12, 2016 at 10:53
11

I had a similar problem. for categories that do not show product listings I needed an extra handle. after failing to add the handle through the category's Custom Layout XML, I ended up adding it in an observer on the layout_load_before event:

class LayoutLoadBefore implements \Magento\Framework\Event\ObserverInterface
{
    /**
     * @var \Magento\Framework\Registry
     */
    protected $_registry;

    public function __construct(
        \Magento\Framework\Registry $registry
    )
    {
        $this->_registry = $registry;
    }

    /**
     * add a custom handle to categories of page type 'PAGE'
     *
     * @param \Magento\Framework\Event\Observer $observer
     * @return $this
     */
    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        $action = $observer->getData('full_action_name');
        if ($action != 'catalog_category_view')
            return $this;

        $category = $this->_registry->registry('current_category');
        if (!$category)
            return $this;

        if ($category->getDisplayMode() == $category::DM_PAGE) {
            $layout = $observer->getData('layout');
            $layout->getUpdate()->addHandle('catalog_category_view_cms');
        }

        return $this;
    }
}

there is a small performance hit involved as the observer is called for every pageview. unfortunately, all 'category_view' related events seem either too early (category not yet loaded) or too late (layout already processed).

1
  • Thanks! I had a similar problem (magento.stackexchange.com/questions/156231/…) and a plugin on \Magento\Cms\Controller\Page\View::execute() and \Magento\Framework\View\Result\Page::addPageLayoutHandles() or render() didn't do the trick. This last-resort solution was the only solution that worked for me. Jan 25, 2017 at 10:07
2

Let's try observing event layout_load_before. Execute function will be as same as this:

public function execute(\Magento\Framework\Event\Observer $observer)
    {
        if($observer->getFullActionName() == 'catalog_category_view'){
            $observer->getLayout()->getUpdate()->addHandle('your_custom_handles');
        }
    }

Hope this will help

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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