Magento – Magento 2: Is there a way to add a child block when the parent isn’t defined in a layout

blockslayoutmagento-2.1

Using 2.1.3, I am loading admin tab content by calling a block directly in the Tabs.php file:

protected function _beforeToHtml()
{
    $blockClass = "MyNamespace\MyModule\Block\Adminhtml\Section\Edit\Tabs\BlockClassName";
    $this->addTab(
       'tab_title',
        [
            'label' => __('Tab Label'),
            'title' => __('Tab Title'),
            'content' => $this->getLayout()->createBlock($blockClass)->toHtml(),
            'active' => true
        ]
    );
}

Within the template phtml file I've set in the BlockClassName.php file, I'd like to use $block->getChildHtml('child_block_name').

I've tried using the addChild (per Adding a Child Block Programmatically) to the BlockClassName::_construct(), but am getting an error:

vendor/magento/framework/Data/Structure.php(603): Magento\Framework\Data\Structure->_assertElementExists(NULL);

Where it looks like the vendor/magento/framework/View/Element/AbstractBlock::setChild() is trying to pass in the block's layout name (line 380) – do I need to define the block in a layout in order to add a child block?

Best Answer

No. Magento uses one central layout tree, not multiple depending where they are defined. However, if you look in \Magento\Framework\View\Layout\Generator\Block::createBlock(), you'll notice that the block is created (and the constructors are called) before the name is set:

public function createBlock($block, $name, array $arguments = [])
{
    $block = $this->getBlockInstance($block, $arguments);
    $block->setType(get_class($block));
    $block->setNameInLayout($name);
    $block->addData(isset($arguments['data']) ? $arguments['data'] : []);
    return $block;
}

The getBlockInstance() method creates the block and calls the constructors. Then, the name is set.

Next, the block is set in layout, in the \Magento\Framework\View\Layout\Generator\Block::process() method. Later on in that method, the setLayout() method is called on each block, which provides the block with an instance of Magento\Framework\View\Layout and calls the _prepareLayout() method on each block. The right place to set children is inside your block's _prepareLayout() method, since this will be called after the name is set, the block is set in layout, and the block has a reference to the layout object.

I'd do it like this:

protected function _prepareLayout()
{
    $childBlock = $this->getLayout()->createBlock('Your\Class');
    $this->addChild($childBlock);
}

Please let me know if you have any questions!

Related Topic