Create Dynamic Custom Tabs on Product View Page
Today I’m creating new post on request from one of my fellow. He asked me how can we achieve dynamic multi tabs at product view page using best practices and without interfering with the core.
So here I’m with the new post now.
Lets go a head and initialize our module:
<?xml version="1.0" ?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Arsal_CustomTab" setup_version="1.0.0"> <sequence> <module name="Magento_Catalog" /> </sequence> </module> </config>
Register our module: (Arsal/Customtab/registration.php)
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Arsal_CustomTab', __DIR__ );
For demonstration purposes I’m creating only a Config model here to retrieve values quickly in the form of array, which will be further used to push to tabs.
So let go ahead and create a new Config model Arsal/CustomTab/Model/TabConfig.php:
<?php namespace Arsal\CustomTab\Model; class TabConfig { /** * @var array $tabs */ private $tabs = [ 'tabA' => [ 'title' => 'Custom Tab A', 'description' => 'Custom Tab A is right here !', 'sortOrder' => 50 ], 'tabB' => [ 'title' => 'Recently Viewed', 'type' => 'template', 'data' => [ "type" => "Magento\Reports\Block\Product\Widget\Viewed", "name" => "custom.recently.view.products", "template" => "Magento_Reports::widget/viewed/content/viewed_list.phtml" ], 'description' => '', 'sortOrder' => 45 ], 'tabC' => [ 'title' => 'Lorem Ipsum Tab', 'type' => 'template', 'data' => [ "type" => "Magento\Framework\View\Element\Template", "name" => "lorem.ipsum", "template" => "Arsal_CustomTab::template_c.phtml" ], 'description' => '', 'sortOrder' => 45 ] ]; /** * @return array */ public function getTabs() { return $this->tabs; } }
Next step is the injection of this data to block and adding these blocks will be treated as child of block product.info.details
To achieve this we need to observe the generation of layouts after event i.e `layout_generate_blocks_after`. So let’s move a head and create an event observer.
Create events.xml inside Arsal/CustomTab/etc/:
<?xml version="1.0"?> <!-- /** * @module: Arsal_CustomTab * @author: Arsalan Ajmal */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> <event name="layout_generate_blocks_after"> <observer name="arsal_layout_generate_blocks_after" instance="Arsal\CustomTab\Observer\NewTab" /> </event> </config>
Now create observer class NewTab.php inside Arsal/CustomTab/Observer:
<?php /** * @author Arsalan Ajmal * @module Arsal_CustomTab */ namespace Arsal\CustomTab\Observer; use Arsal\CustomTab\Model\TabConfig; use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; class NewTab implements ObserverInterface { /** * @var string PARENT_BlOCK_NAME */ const PARENT_BlOCK_NAME = 'product.info.details'; /** * @var string RENDERING_TEMPLATE */ const RENDERING_TEMPLATE = 'Arsal_CustomTab::tab_renderer.phtml'; /** * @var TabConfig $tabs */ private $tabs; /** * NewTab constructor. * @param TabConfig $tabs */ public function __construct(TabConfig $tabs) { $this->tabs = $tabs; } /** * @param Observer $observer */ public function execute(Observer $observer) { /** @var \Magento\Framework\View\Layout $layout */ $layout = $observer->getLayout(); $blocks = $layout->getAllBlocks(); foreach ($blocks as $key => $block) { /** @var \Magento\Framework\View\Element\Template $block */ if ($block->getNameInLayout() == self::PARENT_BlOCK_NAME) { foreach ($this->tabs->getTabs() as $key => $tab) { $block->addChild( $key, \Magento\Catalog\Block\Product\View::class, [ 'template' => self::RENDERING_TEMPLATE, 'title' => $tab['title'], 'jsLayout' => [ $tab ] ] ); } } } } }
Now create phtml rendering template: Arsal/CustomTab/view/frontend/templates/tab_renderer.phtml:
<?php /** * @var \Magento\Catalog\Block\Product\View $block */ ?> <?php if (!empty($block->getJsLayout())) { $jsLayout = \Zend_Json::decode($block->getJsLayout()); foreach ($jsLayout as $layout) { if (isset($layout['type']) && 'template' === $layout['type'] && isset($layout['data'])){ echo $this->getLayout()->createBlock($layout['data']['type']) ->setDisplayType($layout['data']['name']) ->setTemplate($layout['data']['template'])->toHtml(); } else { ?> <h1><?= $layout['title']; ?></h1> <div><?= $layout['description']; ?></div> <?php } } }
We are step behind rendering the blocks. To render these blocks we need to add block names to grouped child data. The best way we can do with it is to add these blocks name via interceptor (plugin) to grouped data.
First create plugin configuration: Arsal/CustomTab/etc/frontend/di.xml:
<?xml version="1.0"?> <!-- /** * @module: Arsal_CustomTab * @author: Arsalan Ajmal */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Catalog\Block\Product\View\Details"> <plugin name="arsal_product_view_description" type="Arsal\CustomTab\Plugin\Description" /> </type> </config>
Create Plugin instance class: Arsal/CustomTab/Plugin/Description.php:
<?php namespace Arsal\CustomTab\Plugin; use Arsal\CustomTab\Model\TabConfig; class Description { /** * @var TabConfig $tabs */ private $tabs; /** * Description constructor. * @param TabConfig $tabs */ public function __construct( TabConfig $tabs ) { $this->tabs = $tabs; } /** * @param \Magento\Catalog\Block\Product\View\Details $subject * @param array $result * @return array */ public function afterGetGroupSortedChildNames( \Magento\Catalog\Block\Product\View\Details $subject, $result ) { if (!empty($this->tabs->getTabs())) { foreach ($this->tabs->getTabs() as $key => $tab) { $sortOrder = isset($tab['sortOrder']) ? $tab['sortOrder'] : 45; $result = array_merge($result, [ $sortOrder => 'product.info.details.' . $key]); } } return $result; } }
Now create template c renderer template:
<?php /** * @var \Magento\Framework\View\Element\Template $block */ ?> <h3>Custom Block</h3> <div> <h4>What is Lorem Ipsum?</h4> <p> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. </p> </div>
That’s all. Your custom dynamic tabs are there on product view page now 🙂
12 Comments
Great Work !!
Thanks for appreciation
in custom tab, can we create a button and can I give a link of other website.
Yes you can do it. All you have to do is to specify you custom html in description or may be you can create custom template and put your button right there.
Hi,
wonderful article!
but is it possible to use table collection instead of using static tab content in Tabconfig model.
i tried but it doesn’t worked.
Please help.
Thank you in advance.
Hello Fena,
Thank you, I am glad article is helpful. I am soon writing Part b of this post to explain how to retrieve data from a table collection instead of static data from TabConfig model.
Thank you, Waiting for your article .
and if possible please add product attributes in tab also.
Hello Fena, wait is over. Here is link to the article: https://knowthemage.com/create-dynamic-custom-tabs-on-product-page-part-2/.
I hope it will be helpful.
This article was helpful. But can i know how to get these custom tabs below? (I want them below details tab i.e in the next line. Not with other default tabs.)
Hi Asha,
Thanks for your comment. Well in order to achieve that you have to do some customization. The code below will do that for you, but you might need further customization for this.
Now inside your module first go to file ‘Arsal\CustomTab\etc\frontend\di.xml’ and replace with following code:
Now create a Class ‘DescriptionProductGetAfter’ inside ‘PME\CustomTab\Plugin’:
Create ‘DescriptionTab.php’ inside ‘Arsal\CustomTab\Block’:
Now create ‘details.phtml’ inside ‘PME\CustomTab\view\frontend\templates’:
Thank you.
Sure, anytime.