A Single Page with a List and a Detail

You are viewing revision #1 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version.

next (#2) »

  1. Original Index Page
  2. Sub View for Detail
  3. Modifying Index Action
  4. Ajax Action
  5. Ajax Updating
  6. Conclusion

CRUD generator of Gii has done a wonderful job for you, and you already have a list of items in "index" page and a detailed view of a specified item in "view" page.

But you may want to show both of them in a single page, in which you can click on an item in the grid to see its detail in the same page on the fly, without having to go to another page.

Is it possible with Yii? Yes, it is.

Let's start with the riviewing of the current code.

Original Index Page

For example, the following is an "index" page that Gii has generated for you.

<?php

use yii\helpers\Html;
use yii\grid\GridView;
use yii\widgets\Pjax;
/* @var $this yii\web\View */
/* @var $searchModel app\models\PostSearch */
/* @var $dataProvider yii\data\ActiveDataProvider */

$this->title = 'Posts';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="post-index">

    <h1><?= Html::encode($this->title) ?></h1>
    <?php // echo $this->render('_search', ['model' => $searchModel]); ?>

    <p>
        <?= Html::a('Create Post', ['create'], ['class' => 'btn btn-success']) ?>
    </p>
<?php Pjax::begin(); ?>
    <?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],

            'id',
            'date',
            'title',
            // 'body',
            // 'created_at',
            // 'created_by',
            // 'updated_at',
            // 'updated_by',

            ['class' => 'yii\grid\ActionColumn'],
        ],
    ]); ?>
<?php Pjax::end(); ?>
</div>

As you see, this page lists the posts in a GridView. It shows only a few attributes of the post because of the limited space. You can see "date" and "title" in the grid, but you have to go to a "view" page to see other attributes like "body" and "created_by".

Now we want to show all the attributes in a block on the same page when you click on an post in the grid.

Sub View for Detail

The first thing we want to do is to create a sub view that shows all the attributes of a post. We will name it as "_view.php".

<?php

use yii\helpers\Html;
use yii\widgets\DetailView;

/* @var $this yii\web\View */
/* @var $model app\models\Post */

?>
<h2><?= Html::encode($model->date . ' : ' . $model->title) ?></h2>

<?= DetailView::widget([
    'model' => $model,
    'attributes' => [
        'id',
        'date',
        'title',
        'body',
        'created_at',
        'created_by',
        'updated_at',
        'updated_by',
    ],
]) ?>

It uses a DetailView to show all the attributes of a post. As you may notice, this is almost the same with "view.php" that Gii has generated for you. You can make it very easily with copy-and-pasting.

Now we call this sub view from inside the "index.php" view script.

...

/* @var $post app\models\Post */

...

<?php Pjax::begin(); ?>
    <?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],

            'id',
            'date',
            'title',

            ['class' => 'yii\grid\ActionColumn'],
        ],
    ]); ?>
<?php Pjax::end(); ?>

<div id="post-detail">
    <?php echo $this->render('_view', ['model' => $post]); ?>
</div>

The sub view is located after the grid. You may locate it before the grid if you prefer. But the important point is that the sub view should not be inside the Pjax area.

You may be aware that we have to supply additonal parameter "post" to the index view for the sub view to work as expected. We will modify the "index" action next.

Modifying Index Action

The original controller code goes like this:

/**
 * Lists all Post models.
 * @return mixed
 */
public function actionIndex()
{
    $searchModel = new PostSearch();
    $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

    return $this->render('index', [
        'searchModel' => $searchModel,
        'dataProvider' => $dataProvider,
    ]);
}

We want to get a post instance that is to be passed to the view. For the moment, It's enough for us to have something like the following with a quick-and-dirty trick:

/**
 * Lists all Post models.
 * @return mixed
 */
public function actionIndex()
{
    $searchModel = new PostSearch();
    $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
    $post = new Post();  /* get an empty post instance */
    // $post = Post::findOne(1); /* get a post with id = 1 */

    return $this->render('index', [
        'searchModel' => $searchModel,
        'dataProvider' => $dataProvider,
        'post' => $post,
    ]);
}

Now your index page will start to show a post after the grid, although the post is fixed to an empty one(or one with its id being 1).

Ajax Action

We are now going to create an ajax action that renders the selected post:

/**
 * Displays a single Post model as an ajax response.
 * @param integer $id
 * @return mixed
 */
public function actionAjaxView($id)
{
    return $this->renderPartial('_view', [
        'model' => $this->findModel($id),
    ]);
}

It's almost like the existing actionView() method. It displays "_view.php" without the enclosing layout.

Ajax Updating

Now, here comes a magic of ajax updating. Insert the following lines in "index.php":

...
$this->title = 'Posts';
$this->params['breadcrumbs'][] = $this->title;

$ajax_url = yii\helpers\Url::to(['ajax-view']); // #1
$csrf_param = Yii::$app->request->csrfParam;  // #2
$csrf_token = Yii::$app->request->csrfToken;

$this->registerJs("
$('div.post-index').on('click', 'tr', function() { // #3
    var id = $(this).data('key'); // #4
    $.ajax({
		'type' : 'GET',
		'url' : '$ajax_url',
		'dataType' : 'html',
		'data' : {
			'$csrf_param' : '$csrf_token', // #2
			'id' : id
		},
		'success' : function(data){ // #5
			$('#post-detail').html(data);
		}
    });
});
");

?>

<div class="post-index">

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

Here are some tips and hints.

  1. yii\helpers\Url::to() generates an url string that can be used in javascript.

  2. Because we need to have CSRF protection token sent in the ajax request, we get its parameter name and value from the Request application component.

  3. We have attached an 'click' event hander on the rows of the GridView table. You might think "$('tr').on('click', function(){ ..." may also work, but it would not. The event handler should be attached to an element that is not inside the Pjax area.

  4. The key attribute (usually 'id') of the row can be retrieved in this way.

  5. If the ajax call has been successful, the contents of "post-detail" div will be replaced by the ajax response that had been rendered by "actionAjaxView" method.

Conclusion

That's all. There's nothing so complicated here.

You may want to do something more to improve your page. The first thing you want to do might be supplying more appropriate initial display of the selected post. It's up to your needs.

Have fun.