Trong mô hình MVC, View là một thành phần không thể thiếu trong việc hiển thị dữ liệu tới người dùng. Tại các ứng dụng web, view được tạo cùng với các mẫu giao diện là những file PHP chưa mã HTML và PHP trong đó. Việc tùy chỉnh các thành phần của view sao cho phù hợp với dự án cũng là điều cần thiết cho các anh em đồng code.

Cách thức hoạt động của View

Như đã nói ở trên thì View trong Yii2 là 1 file PHP bao gồm các thành phần HTML và có thể có thêm PHP trong đó, mặc định các file views thành phần sẽ được đặt ở trong backend/views/ControllerID hoặc frontend/views/ControllerIDControllerID ở đây chính là tên của controller, ví dụ: tại hạ có controller là SiteController thì ở views sẽ có thêm thư mục views/site để chứa các file view của SiteController.

Để xuất 1 view ra màn hình hiển thị cho người dùng, các đồng code sẽ sử dụng cấu trúc mã code như sau

methodName($view, $params = [])

Ở đây methodName chính là các phương thức để xuất các view, $view là tên view hoặc đường dẫn đến view, $param là các tham số dữ liệu truyền ra view để hiển thị.

methodName bao gồm các phương thức sau:

- render(): Xuất bản view và gán vào cùng layout. Ví dụ tại hạ có view là test.php đặt trong frontend/views/site và được gọi bởi actionTest trong SiteController có nội dung như sau:

yii themes

Trong actionTest của SiteController:

<?php
namespace frontend\controllers;

use Yii;
use yii\base\InvalidArgumentException;
use yii\web\BadRequestHttpException;
use yii\web\Controller;
use yii\filters\VerbFilter;
use yii\filters\AccessControl;
/**
 * Site controller
 */
class SiteController extends Controller
{
   //........................
    public function actionTest()
    {
        return $this->render('test', ['data' => ['name' => 'abc', 'phone' => '0123456789']]);
    }
}

actionTest sẽ trả về nội dung của view test và gán nội dung này vào layout. Layout là 1 thành phần mặc định được tạo ra sẵn khi khởi tạo, các anh em đồng code có thể xem ở backend/views/layouts/main.php hoặc frontend/views/layouts/main.php. Ở đây tại hạ demo nên sẽ dùng ở frontend cho tiện =)).

Layout mặc định sẽ là file main.php có nội dung như sau:

<?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">
    <?php $this->registerCsrfMetaTags() ?>
    <title><?= Html::encode($this->title) ?></title>
    <?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>

<div class="wrap">
    <?php
    NavBar::begin([
        'brandLabel' => Yii::$app->name,
        '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; <?= Html::encode(Yii::$app->name) ?> <?= date('Y') ?></p>

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

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

Cũng giống như các file view thành phần layout là một file PHP chứa cả HTML lẫn PHP trong đó, nó là 1 view đặc biệt chứa các thành phần sử dụng chung của các view khác để tránh lặp lại ở mỗi view khi xuất ra.

AppAsset::register($this) này là đăng ý load ra các file css hoặc js mà các đồng code đã config trong frontend/assets và backend thì tương tự

<?php

namespace frontend\assets;

use yii\web\AssetBundle;

/**
 * Main frontend application asset bundle.
 */
class AppAsset extends AssetBundle
{
    public $basePath = '@webroot';
    public $baseUrl = '@web';
    public $css = [
        'css/site.css',
    ];
    public $js = [
    ];
    public $depends = [
        'yii\web\YiiAsset',
        'yii\bootstrap\BootstrapAsset',
    ];
}

Các file css hoặc js này sẽ được tạo ra và phân chia đúng chỗ, ví dụ các file css sẽ nằm trong thẻ head còn các file javascript thì sẽ nằm trên thẻ đóng body để giảm tải khi load trang.

- Tiếp đến các thành phần như beginPage(), beginBody(), endPage(), endBody() chủ yếu là các thành phần đóng mở các cặp thẻ, cái này anh em dùng HTML thuần cũng được =)). Các phần về Navbar hay Nav thì thuộc về các thành phần trong boostrap mà Yii đã build sẵn.

- $content là phần quan trong tại hạ muốn nói, nó chính là nơi mà khi hàm render() chạy nội dung của view sẽ được đổ ra vào đây trước khi hiển thị ra cho người dùng

Các đồng code có thể thấy ở đây khi tại hạ hiển thị view Test ra

yii themes

Các thành phần xanh bao ngoài cùng chính là nội dung của file layout, phần bôi đỏ chính là nội dung của file view Test còn phần xanh lam là phần load các file trong asset - ở mỗi version các file trong assets sẽ được tạo ra và năm trong các thư mục có tên mã hóa.

- Như ở actionTest tại hạ có thêm 1 param là 1 mảng data để truyền ra View Test, các đồng code có thể sử dụng các param truyền ra như sau:

<?php

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

use yii\helpers\Html;
use yii\helpers\Url;

$this->title = 'Test';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-test">
    <h1><?= Html::encode($this->title) ?></h1>

    <p>This is the Test page.</p>
    <p><?php echo $data['name']?></p>
    <p><?php echo $data['phone']?></p>
</div>

Ngoài việc sử dụng file layout mặc định cũng như xuất view từ controller thì các đồng code có thể xuất view từ View, các đồng code có thể tách các phần như headerfooter hoặc sidebar ra 1 file riêng như sau 

yii2 themes

Nội dung file main.php:

<?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">
    <?php $this->registerCsrfMetaTags() ?>
    <title><?= Html::encode($this->title) ?></title>
    <?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>

<div class="wrap">
    <?php echo $this->render('header')  ?> // tách thành file header.php

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

<?php echo $this->render('footer')  ?> // tách thành file footer.php

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

Tại hạ đã tách thành 2 file header.phpfooter.php và gọi lại, nó cũng giống như việc các đồng code sử dụng include hoặc require trong PHP thuần để load 1 file vào thôi.

File header.php

<?php
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use yii\helpers\Html;
    NavBar::begin([
        'brandLabel' => Yii::$app->name,
        '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();
    ?>

File footer.php

<?php
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use yii\helpers\Html;
?>
<footer class="footer">
    <div class="container">
        <p class="pull-left">&copy; <?= Html::encode(Yii::$app->name) ?> <?= date('Y') ?></p>

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

Trên đây tại hạ đã đưa ra cách xuất view trong Controller và View. Ngoài ra để xuất view ở các vị trí khác các đồng code có thể sử dụng Yii::$app->view để load các view như sau

Bằng cách sử dụng phương thức renderFile() để xuất bản một view được mô tả trong các đường dẫn của view hoặc alias.

// hiển thị ra file view "@app/views/site/test.php"
echo \Yii::$app->view->renderFile('@app/views/site/test.php');

- renderAjax(): xuất bản view không gán vào layout nhưng nó sẽ gọi tất cả các file kịch bản JS/CSS đã đăng ký trước đó của view. Xuất bản này thường được dùng để phản hồi các yêu cầu của AJAX Web. Phương thức này hỗ trơ cho việc các đồng code muốn chuyển sang view khác mà không phải load lại trang. Thường nó sẽ load các view phụ.

Để rõ hơn cách hoạt động, tại hạ sẽ tạo thêm 1 view là sub_test.php và hiển thị nó ra view test.php

actionSubTest trong SiteController

<?php
namespace frontend\controllers;

use Yii;
use yii\base\InvalidArgumentException;
use yii\web\BadRequestHttpException;
use yii\web\Controller;
use yii\filters\VerbFilter;
use yii\filters\AccessControl;
/**
 * Site controller
 */
class SiteController extends Controller
{
   //........................
    public function actionSubTest()
    {
        return $this->renderAjax('sub_test');
    }
}

File sub_test.php

<?php

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

use yii\helpers\Html;
use yii\helpers\Url;

$this->title = 'Sub Test';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-sub-test">
    <h1><?= Html::encode($this->title) ?></h1>

    <p>This is the Sub Test page.</p>

</div>

File test.php

<?php

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

use yii\helpers\Html;
use yii\helpers\Url;

$this->title = 'Test';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-test">
    <h1><?= Html::encode($this->title) ?></h1>

    <p>This is the Test page.</p>
    <div id="sub-test">
    </div>
    <button id="getView" type="button">Click</button>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
    $('#getView').on('click',function(){
        $.get('/advanced/site/sub-test',function($res){
            $('#sub-test').html($res);
        })
    })
</script>

Ở đây do file jquery mặc định nằm dưới của view Test nên muốn dùng Jquery thì không được nên tại hạ gọi tạm link Jquery online của google để test. Khi click vào button Click thì 1 request sẽ gọi đến actionSubTest trong SiteController và sẽ trả về HTML của view sub_test hiển thị vào div có id là sub-test như kết quả dưới đây:

yii themes

Ngoài renderAjax() thì các anh em đồng code có thể dùng renderPartial() nhưng khác ở chỗ là nó sẽ chỉ lấy ra nội dung của view mà không load các file đã đăng ký như sau

Tại hạ đăng ký 1 file css trong view sub_test.php

<?php

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

use yii\helpers\Html;
use yii\helpers\Url;

$this->title = 'Sub Test';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-sub-test">
    <h1><?= Html::encode($this->title) ?></h1>

    <p>This is the Sub Test page.</p>

</div>
<?php $this->registerCssFile(Yii::$app->request->baseUrl.'/css/test.css',[ 'depends' => ['frontend\assets\AppAsset'], 'position' => \yii\web\View::POS_HEAD ]); ?>

- Trường hợp dùng renderAjax() các đồng code sẽ thấy nó sẽ lấy cả file test.css

yii theme

 

- Trường hợp sử dụng với renderPartial() thì sẽ không có file test.css và cả các file đã config trong asset luôn, tại hạ khuyến khích các đồng code nên dùng phương thức này cho việc dùng Ajax để tránh bị ghi lặp lại các file css hoặc js

yii theme

 

- Còn 1 phương thức cuối cùng để xuất bản view là renderContent() với đầu vào là 1 chuỗi, có thể là HTML và nó sẽ được đổ ra biến $content trong layout hiện tại, giống như phương thức render() nhưng nó không đọc nội dung từ file view mà nó lấy chính nội dung được truyền vào để gán vào layout.

Ví dụ actionTest tại hạ đổi lại như sau

<?php
namespace frontend\controllers;

use Yii;
use yii\base\InvalidArgumentException;
use yii\web\BadRequestHttpException;
use yii\web\Controller;
use yii\filters\VerbFilter;
use yii\filters\AccessControl;
/**
 * Site controller
 */
class SiteController extends Controller
{
   //........................
    public function actionTest()
    {
        return $this->renderContent('<strong>test'</strong>);
    }
}

Và kết quả là chữ test in đậm

yii2 themes

Sử dụng các thành phần View

Phần tùy chỉnh giao diện chính là phần này mà tại hạ muốn giới thiệu tới các đồng code sau khi đã tìm hiểu hoạt động của View trong Yii2. Có một số thành phần hỗ trợ việc sử dụng view dướii đây:

  • theming: cho phép xây dựng và thay đổi theme cho các trang Web.
  • fragment caching: cho phép xử lý cache các fragment trong các trang Web.
  • client script handling: hỗ trợ đăng ký vào xuất bản các nội dung về CSS và JavaScript.
  • asset bundle handling: hỗ trợ việc đăng ký và xuất bản các asset bundles.
  • alternative template engines: cho phép sử dụng các bộ giao diện, chẳng hạn như Twig, Smarty.

Trong những thành phần trên, tại hạ muốn giới thiệu thành phần theming giúp việc quản lý phân chia các theme theo ý muốn

Để sử dụng theming các đồng code cần config trong file frontend/config/main.php như sau:

[
    // ...
    'components' => [
       'view' => [
            'theme' => [
                'pathMap' => [
                    '@app/views' => '@app/themes'
                ],
            ],
        ],
        // ...
    ],
]

và cấu trúc thư mục sẽ có thêm 1 folder themes như sau:

yii themes

Trong thư mục này tất cả các view sẽ giống hệt các view trong thư mục views. Các đồng code có thể thay đổi đường dẫn khác ở pathMap để có sự phân chia như ý =)).

Tổng kết 

Như vậy là tại hạ đã tổng hợp các cách xuất bản View trong YIi2 và tùy chỉnh phân chia thư mục view trong ứng dụng. Bài viết chỉ tóm lược các phần tại hạ hay sử dụng, ngoài ra Yii2 còn rất nhiều phần hay về View các đồng code có thể tham khảo tại đây