Yii 2 Views and Layouts in Depth

Views and Layouts - The basic building blocks of UI

The View file forms the basic building blocks of the UI for any MVC based web application. View files are the combination of PHP and HTML code which help to create the desired look of the website.

In the same fashion, we have the Layouts. Layouts are also a type of view file. But, as view file gets placed within a directory of the corresponding controller, layout files are shared by the entire app. In simple terms, layout file is a parent wrapper for the entire site's templating system. The view file is rendered within the layout.

We have seen the frontend/views/site/index.php file in previous article. This is the main view file used by the entire frontend app. Once again open the layout file i.e. frontend/views/layout/main.php

Yii 2 Main Layout

Take a look at the following code from the same main.php file


<?php

/* @var $this \yii\web\View */
/* @var $content string */

use yii\helpers\Html;
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use yii\widgets\Breadcrumbs;
use frontend\assets\AppAsset;
use common\widgets\Alert;

AppAsset::register($this);
?>
<?php $this->beginPage() ?>
<!DOCTYPE html>
<html lang="<?= Yii::$app->language ?>">
<head>
<meta charset="<?= Yii::$app->charset ?>">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?= Html::csrfMetaTags() ?>
<title><?= Html::encode($this->title) ?></title>
<?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>

<div class="wrap">
<?php
NavBar::begin([
'brandLabel' => 'My Company',
'brandUrl' => Yii::$app->homeUrl,
'options' => [
'class' => 'navbar-inverse navbar-fixed-top',
],
]);
$menuItems = [
['label' => 'Home', 'url' => ['/site/index']],
['label' => 'About', 'url' => ['/site/about']],
['label' => 'Contact', 'url' => ['/site/contact']],
];
if (Yii::$app->user->isGuest) {
$menuItems[] = ['label' => 'Signup', 'url' => ['/site/signup']];
$menuItems[] = ['label' => 'Login', 'url' => ['/site/login']];
} else {
$menuItems[] = '<li>'
. Html::beginForm(['/site/logout'], 'post')
. Html::submitButton(
'Logout (' . Yii::$app->user->identity->username . ')',
['class' => 'btn btn-link logout']
)
. Html::endForm()
. '</li>';
}
echo Nav::widget([
'options' => ['class' => 'navbar-nav navbar-right'],
'items' => $menuItems,
]);
NavBar::end();
?>

<div class="container">
<?= Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]) ?>
<?= Alert::widget() ?>
<?= $content ?>
</div>
</div>

<footer class="footer">
<div class="container">
<p class="pull-left">&copy; My Company <?= date('Y') ?></p>

<p class="pull-right"><?= Yii::powered() ?></p>
</div>
</footer>

<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>

Looks complex? Let me explain it one by one. This is just the normal PHP file with lots of Yii 2 code in it. In the top, you can see mulitple namespace imported with the use keyword.

What are namespaces?

In the broadest definition namespaces are a way of encapsulating items. In simple terms, Namespace is the way of grouping the classes in a logical manner in order to avoid ambiguity with the same class names in a single project.

The next line registers all the assets defined in frontend/assets/AppAsset class. This is the class where we declare the .css and .js files to be used in the web app.


AppAsset::register($this);

It is not the only place where we can register our .css and .js files. We can do the same in the view file too. But, AppAsset provides more features like minify, compress, bundle, etc. We'll get back to AppAsset in later articles. For now, remember that this line registers the assets and does not actually puts it in the view i.e. it is not responsible to add it to the final HTML code.


<?php $this->beginPage() ?>

This method triggers the EVENT_BEGIN_PAGE event indicating the beginning of a page. This method should be called at the very beginning of the layout, even before html tag.


public function beginPage()
{
ob_start();
ob_implicit_flush(false);

$this->trigger(self::EVENT_BEGIN_PAGE);
}

The above code is executed when the beginPage() method is called. Here, the output buffer is started and is set to explicit flush, i.e. all the data that is to be sent to the client is stored in an internal buffer and held there until flushed explicitly by ob_end_flush()


<html lang="<?= Yii::$app->language ?>">

The lang attribute is used to assist the browsers and the search engines in detecting the language of the web page. Here, we can set the language by Yii::$app->language or directly as <html lang="en-US">. Also, if you don't want to hardcode the language value, it can be set in the frontend/config/main.php or frontend/config/main-local.php file. Take a look at the snap below.

Yii 2 lng Configuration


<meta charset="<?= Yii::$app->charset ?>">

The charset meta tag, specifies the character encoding for the HTML document i.e. what character set is used by web page. You can setup this in the main.php configuration file as shown below.

Yii 2 Charset Config

Take a look at the following view-source output of our index page at http://localhost/advanced/frontend/web/, which shows that both the lng and charset values are used form the configuration file.

Yii 2 Charset and lng View-Source


<?= Html::csrfMetaTags() ?>

CSRF meta tags are the csrf-param and csrf-token in the header section of the web page. If you check the above snap, on the 7th and 8th line you'll see the following code. This is used to avoid the Cross-Site Request Forgery (CSRF). This topic is out of the scope of this article and will be covered in future articles.


<meta name="csrf-param" content="_csrf-frontend">
<meta name="csrf-token" content="1CHEdpE9gaWPKNBADyDDxIyfYr9gPeNmSI81CaudaCpIIxY2oYo0yuwTEZriAM68xNohIsn1Xw9Hqwwb4pRftQ==">

The above tags are generated automatically buy the framework code. The csrf-token value is refreshed every time the page is refreshed or in other words the request is served by the server. Take a look at the following code where the actual code is generated and injected in the layout's head section.


public static function csrfMetaTags()
{
$request = Yii::$app->getRequest();
if ($request instanceof Request && $request->enableCsrfValidation) {
return static::tag('meta', '', ['name' => 'csrf-param', 'content' => $request->csrfParam]) . "\n "
. static::tag('meta', '', ['name' => 'csrf-token', 'content' => $request->getCsrfToken()]) . "\n";
} else {
return '';
}
}
 

<title><?= Html::encode($this->title) ?></title>

Coming to the next line, we have a simple title tag, which serves as a title of the web page. This can be set from the view file. If you check your frontend/views/site/index.php file, you'll find the below code in the top section of the file enclosed in PHP tag.


<?php

/* @var $this yii\web\View */

$this->title = 'My Yii Application';
?>

$this refers to the current View component managing and rendering this view template. Thus, for each page you can have different title setup. In case $this->title is not initilaised it is returned as NULL and no value is set between <title></title> tag.


<?php $this->head() ?>

This method generates all the content of the head like the link tags, meta tags or any registered css or .css file from the view file. It should be called within the <head> section of an HTML page. If you deep dive into the Yii 2 code, you reach the following function where all the header links, tags, script files, etc. are generated and sent for rendering.


protected function renderHeadHtml()
{
$lines = [];
if (!empty($this->metaTags)) {
$lines[] = implode("\n", $this->metaTags);
}

if (!empty($this->linkTags)) {
$lines[] = implode("\n", $this->linkTags);
}
if (!empty($this->cssFiles)) {
$lines[] = implode("\n", $this->cssFiles);
}
if (!empty($this->css)) {
$lines[] = implode("\n", $this->css);
}
if (!empty($this->jsFiles[self::POS_HEAD])) {
$lines[] = implode("\n", $this->jsFiles[self::POS_HEAD]);
}
if (!empty($this->js[self::POS_HEAD])) {
$lines[] = Html::script(implode("\n", $this->js[self::POS_HEAD]), ['type' => 'text/javascript']);
}

return empty($lines) ? '' : implode("\n", $lines);
}

<?php $this->beginBody() ?>

This notifies that the body of the layout has started and should be called at the beginning of the <body> section. It triggers the EVENT_BEGIN_BODY event and generates the HTML code that will be added just after the opening of the <body> tag. For example, you can add the javascript code just after the body tag is started.


protected function renderBodyBeginHtml()
{
$lines = [];
if (!empty($this->jsFiles[self::POS_BEGIN])) {
$lines[] = implode("\n", $this->jsFiles[self::POS_BEGIN]);
}
if (!empty($this->js[self::POS_BEGIN])) {
$lines[] = Html::script(implode("\n", $this->js[self::POS_BEGIN]), ['type' => 'text/javascript']);
}

return empty($lines) ? '' : implode("\n", $lines);
}

Take a look at the above code which shows the code that is executed when beginBody() method is called. As you can see, if the JS or CSS code has the position value set as POS_BEGIN then, that particular file or the code is added just after the body tag is opened. In coming articles we'll get to know what the position is and how can we use it to register the assets.


<?php
NavBar::begin([
'brandLabel' => 'My Company',
'brandUrl' => Yii::$app->homeUrl,
'options' => [
'class' => 'navbar-inverse navbar-fixed-top',
],
]);
$menuItems = [
['label' => 'Home', 'url' => ['/site/index']],
['label' => 'About', 'url' => ['/site/about']],
['label' => 'Contact', 'url' => ['/site/contact']],
];
if (Yii::$app->user->isGuest) {
$menuItems[] = ['label' => 'Signup', 'url' => ['/site/signup']];
$menuItems[] = ['label' => 'Login', 'url' => ['/site/login']];
} else {
$menuItems[] = '<li>'
. Html::beginForm(['/site/logout'], 'post')
. Html::submitButton(
'Logout (' . Yii::$app->user->identity->username . ')',
['class' => 'btn btn-link logout']
)
. Html::endForm()
. '</li>';
}
echo Nav::widget([
'options' => ['class' => 'navbar-nav navbar-right'],
'items' => $menuItems,
]);
NavBar::end();
?>

This above code is to generate the navbar as per the bootstrap navbar element. Based on whether the user is logged in or not the navbar displays different menus. This section is left for the readers to explore. 


<?= Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]) ?>

Breadcrumbs are the integral part of any web app for the user to navigate around the web pages of the website. Breadcrumbs is a class which extends the Widget class and provides the functionality to display the breadcrumbs on the webpage. The above code checks whether any breadcrumbs are set in the view file and if yes pass it to the widget() method else it passes an empty array. 


<?= Alert::widget() ?>

The Alert widget displays all the flash messages in the order they are set. The flash message are one of the best way to set session variable for one time use and can be set as \Yii::$app->session->setFlash(key, message); In order to use the Alert widget the message must be set as \Yii::$app->getSession()->setFlash('error', 'This is the message'); where the error can be replaced with success, info, warning or danger as per the Bootstrap CSS alert classes.


<?= $content ?>

This is the most important part of our layout file. This is where the entire content of the view file is actually rendered. The $content variable is passed from the yii\base\Controller::render() methode to the layout and represents the rendered content of the view file. Along with $this, $content is readily accessable within the layout. The former is the view component and latter contains the result of the view which is rendered in the controller.


public function render($view, $params = [])
{
$content = $this->getView()->render($view, $params, $this);
return $this->renderContent($content);
}

The render method takes first parameter as the view file and the second parameter is an array passed from the controller to the view. Now take a look at the code below.

Yii 2 SiteController

This is how we have passed the name of the view file, index and other variables, title, subTitle, dateTime. Once the rendering of the view file is done, it is stored in $content and pushed into the layout file for futher processing/rendering.


<footer class="footer">
<div class="container">
<p class="pull-left">&copy; My Company <?= date('Y') ?></p>

<p class="pull-right"><?= Yii::powered() ?></p>
</div>
</footer>

This is a normal footer for any web app and can be customised as per our requirements. Yii::powered() prints the line Powered by Yii Framework within the footer. 


<?php $this->endBody() ?>

This method indicates the end of the <body> and should be called before colsing of the <body> tag in the layout file. It triggers the EVENT_END_BODY event which generates all the HTML code responsible for registration of the CSS or JS code and files at the end of the page.  


protected function renderBodyEndHtml($ajaxMode)
{
$lines = [];

if (!empty($this->jsFiles[self::POS_END])) {
$lines[] = implode("\n", $this->jsFiles[self::POS_END]);
}

if ($ajaxMode) {
$scripts = [];
if (!empty($this->js[self::POS_END])) {
$scripts[] = implode("\n", $this->js[self::POS_END]);
}
if (!empty($this->js[self::POS_READY])) {
$scripts[] = implode("\n", $this->js[self::POS_READY]);
}
if (!empty($this->js[self::POS_LOAD])) {
$scripts[] = implode("\n", $this->js[self::POS_LOAD]);
}
if (!empty($scripts)) {
$lines[] = Html::script(implode("\n", $scripts), ['type' => 'text/javascript']);
}
} else {
if (!empty($this->js[self::POS_END])) {
$lines[] = Html::script(implode("\n", $this->js[self::POS_END]), ['type' => 'text/javascript']);
}
if (!empty($this->js[self::POS_READY])) {
$js = "jQuery(document).ready(function () {\n" . implode("\n", $this->js[self::POS_READY]) . "\n});";
$lines[] = Html::script($js, ['type' => 'text/javascript']);
}
if (!empty($this->js[self::POS_LOAD])) {
$js = "jQuery(window).on('load', function () {\n" . implode("\n", $this->js[self::POS_LOAD]) . "\n});";
$lines[] = Html::script($js, ['type' => 'text/javascript']);
}
}

return empty($lines) ? '' : implode("\n", $lines);
}

Going into code we have renderBodyEndHtml() method which is executed when endBody() method is called. This method is responsible for rendering the assets which are registered by AppAsset and have position value set as POS_END, POS_READY and POS_LOAD. By default, the methods registerJs() or registerJsFile() has POS_END and are added when the endBody() is called, whereas the registerCss() or registerCssFile() are added when the $this->head() method is called. 


<?php $this->endPage() ?>

This method marks the end of the web page i.e. the end of the HTML tag. It should be called after closing the <html> tag. It triggers EVENT_END_PAGE event which indicates the end of the page.


public function endPage()
{
$this->trigger(self::EVENT_END_PAGE);
ob_end_flush();
}

Above code is what is executed when we have endPage() method. ob_end_flush() finally sends the entire content of the buffer to the client and this is where we get the output on the web browser!