renderLayout
Before we begin, let us sum up what we know about loadLayout since in previous parts pretty much information has been provided.
- Mage::getSingleton(‘core/layout’)->getNode()
- We declared all Block classes and executed all found Actions.
- Each Block knows its descendants. So now, launching the main block rendering we will step by step render the entire page.
- The initial block for rendering has been defined. <block … name=”root” output=”toHtml”>
All this, and even more you can find in the loadLayout chapter. That was all pretty interesting, but we still have more to show you!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public function renderLayout($output='') { ... $this->_renderTitles(); if (''!==$output) { $this->getLayout()->addOutputBlock($output); } Mage::dispatchEvent('controller_action_layout_render_before'); Mage::dispatchEvent('controller_action_layout_render_before_'.$this->getFullActionName()); … $output = $this->getLayout()->getOutput(); Mage::getSingleton('core/translate_inline')->processResponseBody($output); $this->getResponse()->appendBody($output); ... } |
1. Declaring a page title
$this->_renderTitles();
It is all simple here. Title is usually defined in the ‘head’ Block via the command
$this->getLayout()->getBlock(‘head’)-> setTitle(‘Our own title’); but using controllers in Magento you can extend the functionality to work with titles. In your controllers you can set several more additional titles using the function $this->_title(), which will add extra titles to the array $this->_titles. And if necessary the function $this->_renderTitles() will replace the title during the rendering process.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
protected function _renderTitles() { ... $titleBlock = $this->getLayout()->getBlock('head'); ... if (!$this->_removeDefaultTitle) { $title = trim($titleBlock->getTitle()); if ($title) { array_unshift($this->_titles, $title); } } $titleBlock->setTitle(implode(' / ', array_reverse($this->_titles))); } |
These are some examples provided by Magento developers:
$this->_title(‘foo’)->_title(‘bar’); | bar / foo / <default title> |
$this->_title()->_title(‘foo’)->_title(‘bar’); | bar / foo |
$this->_title(‘foo’)->_title(false)->_title(‘bar’); | bar / <default title> |
<default title> – the title is taken from the ’head’ Block
2. It is possible to pass the name of an additional ‘Output Block’ into the renderLayout function.
$this->getLayout()->addOutputBlock($output);
As we have found out in the previous chapter there can be several of such blocks that Magento starts the rendering with. And, as we shall see, it will output them one by one. But let us move on.
3. Now we are ready to launch the whole process.
$output = $this->getLayout()->getOutput();
Mage::getSingleton(‘core/translate_inline’)->processResponseBody($output);
$this->getResponse()->appendBody($output);
1 2 3 4 5 6 7 8 9 10 11 |
public function getOutput() { $out = ''; if (!empty($this->_output)) { foreach ($this->_output as $callback) { $out .= $this->getBlock($callback[0])->$callback[1](); } } return $out; } |
Now from the cycle we fetch the names of the initial parent blocks together with the names of the functions which start their rendering. For the root Block the line
$out .= $this->getBlock($callback[0])->$callback[1]();
transforms into the function execution
$out .= $this->getBlock(‘root’)->toHtml();
As a result we get HTML which we are ready to output to the screen through the command $this->getResponse()->appendBody($output); However the developers have implemented one more feature here:
Mage::getSingleton(‘core/translate_inline’)->processResponseBody($output);
It allows editing word translations without the FTP access to the localization files . If the function is enabled, then additional scripts for ‘Inline’ translation will be added to the final HTML. But localization is not the topic of our todays discussion, so I will not go deep into it.
Instead, I would like to draw your attention to the function Mage::getSingleton(‘core/layout’)->getBlock(‘root’)->toHtml();
since we need to know what kind of Pandora’s box we are going to open.
toHtml()
Block ‘root’ – is an instance of the Mage_Page_Block_Html class. It has inherited the toHtml ‘final’ function from the class Mage_Core_Block_Abstract and by default it is the starting point for rendering any Magento Block into HTML .
1 2 3 4 |
final public function toHtml() { Mage::dispatchEvent('core_block_abstract_to_html_before', array('block' => $this)); ... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$html = $this->_loadCache(); if ($html === false) { ... $this->_beforeToHtml(); $html = $this->_toHtml(); $this->_saveCache($html); ... } $html = $this->_afterToHtml($html); ... self::$_transportObject = new Varien_Object; ... self::$_transportObject->setHtml($html); Mage::dispatchEvent('core_block_abstract_to_html_after', array('block' => $this, 'transport' => self::$_transportObject)); $html = self::$_transportObject->getHtml(); return $html; } |
To make HTML output more convenient there are additional observers and functions available:
- Observer ‘core_block_abstract_to_html_before’ allows you to add last changes into the block before HTML output.
- The function _beforeToHtml() described in the block can also help to make the final preparations.
- Using the function _toHtml() we get the HTML itself.
- The function _afterToHtml($html) allows the Block to add any changes to its own HTML.
- Observer ‘core_block_abstract_to_html_before’ also allows making necessary changes into the generated HTML. For this purpose additional object Varien_Object is defined and passed into observer. All is made to return the edited HTML. dispatchEvent does not return anything itself, and when passing the object the pointer gets passed as well, which is what we need.
- You must have noticed as well, that all blocks work with their cache exactly here. ( functions $this->_loadCache() and $this->_saveCache($html) )
Let’s see closer how $this->_toHtml() works by default. Of course if it has not been redefined. There is no need to check in the Abstract class. But when creating our own block we do not inherit it from Abstract. Mage_Core_Block_Template can help us! Since it is the basis for many classes.
1 2 3 4 5 6 7 8 |
protected function _toHtml() { if (!$this->getTemplate()) { return ''; } $html = $this->renderView(); return $html; } |
Checked for the template. It is available there, so, let’s continue.
1 2 3 4 5 6 |
public function renderView() { $this->setScriptPath(Mage::getBaseDir('design')); $html = $this->fetchView($this->getTemplateFile()); return $html; } |
The function setScriptPath sets the basic folder to search for templates, in our case this is app/design
1 2 3 4 5 6 7 |
public function setScriptPath($dir) { $scriptPath = realpath($dir); … $this->_viewDir = $dir; … } |
We continue using the variable $this->_viewDir . This will be the first part of the URL for the loaded template. Second part – this is $this->getTemplateFile() which we pass to the function $this->fetchView(…)
1 2 3 4 5 6 7 8 9 10 |
public function getTemplateFile() { $params = array('_relative'=>true); $area = $this->getArea(); if ($area) { $params['_area'] = $area; } $templateName = Mage::getSingleton('core/design_package')->getTemplateFilename($this->getTemplate(), $params); return $templateName; } |
In this function we used the class Mage_Core_Model_Design_Package again, which receives the path to the template file for the proper theme (all this we described in the first part).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public function fetchView($fileName) { ... ob_start(); if ($this->getShowTemplateHints()) { echo ... {$fileName} ... ; ... echo ... {$thisClass} ... ; } try { $includeFilePath = realpath($this->_viewDir . DS . $fileName); if (strpos($includeFilePath, realpath($this->_viewDir)) === 0 || $this->_getAllowSymlinks()) { include $includeFilePath; } ... } catch (Exception $e) { ob_get_clean(); throw $e; } ... $html = ob_get_clean(); return $html; } |
So now we have come to discover what exactly solves all problems – this is fetchView.
$this – is the current Block.
include [path to the template]; Will process the template code so that all Block functions will be available in it.
But we do not need to output the code yet, just need to assemble it into a variable. And this is not a problem! ob_start() and ob_get_clean() are our best friends. They can stop the output and save everything into the buffer and if necessary write from the buffer into a variable. Pretty cool, yeah!
But look, here we have hints to be added into HTML if ($this->getShowTemplateHints()) { … } (these are tips which appear in the admin panel and describe which file and block works with the highlighted part of HTML.)
So now we have the current page. It is time to finish now. Our final chapter we will devote to the class Mage_Core_Model_Design_Package, which we have not described yet.
…to be continued