PHP
Article

Rendering Data in Yii 2 with GridView and ListView

By Arno Slatius

In my previous article about Yii 2.0, I introduced the ActiveRecord implementation. Once you have some data in your database you’ll want to be able to show it. Yii uses DataProviders to interact with data sources and it provides some widgets to output the data. Of these, the ListView and GridView provide the most functionality.

Yii logo

The example

I’m going to work with the database example that I introduced in my previous article again. Not very original, but quite useful as an example; a blog like SitePoint.

SitePoint Yii 2.0 blog example

A small wrap-up of this database design and its Yii models which I’ll be using in this example:

  • The Authors model has one relation method call getArticles() that provides an Articles[] attribute to the model.
  • The Articles model has one relation method call getAuthor() that provides the Author and a method getTags() that provides the Tags[].
  • The Tags model has one relation method call getArticles that provides Articles[].

I won’t be using the ArticlesTags table since it is just used for an N:M relation. Of course, there is a model available for it which is used in the relation definition of the other models.

DataProviders

There are three different types:

  • The ActiveDataProvider is fed through an ActiveQuery instance and usually holds an array of models. You’ll normally use it to hold and render data that can be built from normal ActiveRecord instances with their relations.
  • The ArrayDataProvider is built using an array of data. This is quite useful when building all sorts of reports based on multiple models and/or custom aggregations.
  • The SqlDataProvider is a very useful one for the SQL masters among us. It is an easy way to get the data of those complex queries.

They don’t have huge differences, except slightly in views. The data provider itself is a base component that holds data and handles pagination and sorting.

ActiveDataProvider

I already explained that this is populated with an ActiveQuery object. Any model::find() will return an ActiveQuery object so creating one is quite simple.

/* A dataprovider with all articles */
$dataProvider = new ActiveDataProvider([
    'query' => Articles::find()->with('tags'),
]);

/* Get all the articles for one author by using the author relation define in Articles */
$dataProvider = new ActiveDataProvider([
    'query' => Articles::find()->with('author')->where(['Name'=>'Arno Slatius']),
]);

/* Be careful with this! */
$dataProvider = new ActiveDataProvider([
    'query' => Articles::find()->with('author')->where(['Name'=>'Arno Slatius'])->orderBy('Published'),
]);

The last example is one to be careful with, you’ll limit the abilities of your DataProvider since it can do sorting on its own. Keep this in mind, I’ll address it later on.

SqlDataProvider

This one will gather your data based on a SQL statement. My example doesn’t really justify the use of SQL but I’ll do it anyway to show something and throw in some aggregation;

$dataProvider = new SqlDataProvider([
    'sql' => 'SELECT Name, Title, COUNT(ArticleTags.ID) AS TagCount ' . 
             'FROM Authors ' .
             'INNER JOIN Articles ON (Authors.ID = Articles.AuthorID) ' .
             'INNER JOIN ArticleTags ON (Articles.ID = ArticleTags.ID) ' .
             'WHERE Name=:author' .
             'GROUP BY ArticleID',
    'params' => [':author' => 'Arno Slatius'],
]);

There are good arguments to use an SQL query as input for your data provider. ActiveRecord is a very nice way to work with databases, but you might find that defining the criteria for larger databases gets somewhat complex. If you are good at writing SQL and you need a lot of joins, unions and/or aggregations you might find it easier to build an SQL query first and simply use that as an input to your DataProvider.

ArrayDataProvider

This might be the most useful component of the bunch. Anything that you’d normally put in a table that requires a bit more work to compose is well suited for an array data provider.
The documentation uses quite a simple example inserting a few models in an array like so:

$dataProvider = new ArrayDataProvider([
    'allModels' => Authors::find()->all(),
]);

That’ll fill the provider with all the authors. Very simple indeed, but let’s make it more practical and do something that this component is quite useful for; I want to output a list of all authors with their article count and last published date. I’m going to do this by extending the ArrayDataProvider and creating my own. I use the init() function which is available on almost all components. This is called when the class is created and used (in this case) to prepare the data in the provider.

namespace app\data;

use \app\models\Authors;
use \app\models\Articles;

class AuthorsReportProvider extends \yii\data\ArrayDataProvider
{
	/**
     * Initialize the dataprovider by filling allModels
     */
    public function init()
    {
        //Get all all authors with their articles
	    $query = Authors::find()->with('articles');
		foreach($query->all() as $author) {
			
			//Get the last published date if there are articles for the author
			if (count($author->articles)) {
				$lastPublished = $query->max('Published');
			} else {
				$lastPublished = null;
			}

			//Add rows with the Author, # of articles and last publishing date
			$this->allModels[] = [
				'name' => $authors->Name,
				'articles' => count($author->articles),
				'last' => $lastPublished,
			];
		}
	}
}

You can see that the variable containing all rows for the table to be shown, $this->allModels, is filled with the data we need.

Visualizing the data

We’ve got a data provider but now we want to render what’s in there, right? Yii provides two components to visualize the data put into the data provider.

  • The GridView will put the data in an HTML table. If properly configured, it’ll automatically add headers that you can click to change the sorting, pagination to limit the number of items shown at once to the user and a summary showing the total number of results available.
  • The ListView allows you to specify a partial view with which you can render each of the models in the data provider in a specific way. A very common way to go about this is using this to render the content of <li> elements of an ordered or unordered list.

It’s probably best illustrated with a few examples. Let’s assume the following data provider:

/* A dataprovider with all articles */
$dataProvider = new ActiveDataProvider([
    'query' => Articles::find()->with('tags','authors'),
]);

Note that I immediately make ActiveRecord execute a join of the Articles on the ArticleTags and Tags table by specifying the with() method. This is known as ‘eager loading’, making the related data available immediately. When done without the with() you’d still be able to use the data available from the author and tags relation in your views, but it would be loaded run time, also known as ‘lazy loading’. If you know you’re going to use related data, use with(). Eager loading performs a lot better than lazy loading.

In the view, we’d render a table with a GridView. I’m showing a few columns in increasing complexity, a simple list of the article first:

<?= GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        'ID',
        'Author',
        'Title',
        'Published:date',
    ],
]); ?>

Which will look like this:

Yii 2.0 GridView Blog Articles Example

I do admit that I cheated slightly by defining a page size of 6 and three columns by which it can sort; more on that later. Note the summary at the top, the sort links in the column headers and the pager at the bottom. Everything is formatted in a localized format (Dutch for me) where applicable. I needed no additional code to get the handling of links for the sorting or next pages working. The links are all automatically generated by the widget and actions are handled by the DataProvider. This component makes life really easy out of the box.

Since I joined the articles with other tables, we can use that data as well in a very easy table.column manner. Formatting of the data can also be customized using anonymous functions:

<?= GridView::widget([
		'dataProvider' => $dataProvider,
        'columns' => [
	        'Title',
	        'author.Name',
	        [
	            'attribute' => 'author.Twitter',
	            'type' => 'html',
	            'value' => function($model) {
		            return Html::link('@' . $model->author->Twitter, 'http://twitter.com/' . $model->author->Twitter);
	            }
	        ],
	        [
		        'attribute' => 'tags',
		        'value' => function($model) {
			        return implode(', ', ArrayHelper::map($model->tags, 'ID', 'Tag'));
			    }
			],
			'Published:date',
		],
	]); ?>

The key, when defining the output for the columns, is to remember that each extends from yii\grid\Column so always look back at that class to see all the attributes and functions you can use. For instance, yii\grid\DataColumn has a $footer attribute. Add that to the configuration array of a column to set the footer text, and don’t forget to set showFooter to true in the GridView.

The GridView actually has quite a few extra attributes through which you can configure it. I’ll use another example:

<?= GridView::widget([
		'dataProvider' => $dataProvider,
        'columns' => [
        ...
        ],
        'layout' => '{summary}{items}{pager}',
		'filterPosition' => FILTER_POS_FOOTER,
		'rowOptions' => function ($model, $key, $index, $grid) {
		},
		'emptyText' => '-',
		'showFooter' => true,
    ]); ?>

The layout attribute is a very useful one. It allows you to change the way the basic elements appear. You can add {summary}, {errors}, {items}, {sorter}, {pager} some even multiple times; you might want a pager above and below the items list, the same might go for the summary which basically shows the total number of results and the number of currently shown results. Errors are a list of errors that the filter outputs (if any).
filterPosition allows you to move the filter to the bottom, or inside the body. Normally it would be included in the header part of the table, etc.

Customizing Sorting and Paging

The GridView will do sorting and pagination for you by default. Its functionality is actually provided through configuration of the yii\data\BaseDataProvider which can be initialized by supplying additional parameters during the class’ initialization. Let’s start with simple pagination; by default, it is set to defaultPageSize which normally gives 20 results per page. Change it by supplying a pageSize, or use it to display everything and turn off pagination:

/* show 80 items per page */
$dataProvider = new ActiveDataProvider([
    'query' => Articles::find()->with('tags'),
    'pagination' => [
	    'pagesize' => 80,
    ],
]);

/* disable pagination, shows all items at once */
...
    'pagination' => false,
...
    'pagination' => [
	    'pagesize' => -1,    //Alternate method of disabling paging
    ],
...

Sorting has quite a few more options which depend on the data you supply and display. First, you can customize the sorting. You can supply multiple attributes to sort on for each column. When you use the GridView you might find that the default sort handling sometimes fails on more complex attributes or on values that you output using unnamed functions. In these situations, supplying sort attributes also helps.

Let’s go over some code:

$dataProvider = new ActiveDataProvider([
    'query' => Articles::find()->with('authors'),
    'sort' => [
	    'defaultOrder' => [
	        'Published' => SORT_DESC,
	        'LastEdited' => SORT_DESC,
	    ],
	    'attributes' => [
		    'name' => [
                'asc' => [
                    'author.Name' => SORT_ASC, 
                    'Published' => SORT_ASC,
                    'Title' => SORT_ASC,
                ],
                'desc' => [
                    'author.Name' => SORT_DESC
                    'Published' => SORT_DESC,
                    'Title' => SORT_DESC,
                ],
            ],
            ... etc ...
	    ],
    ],
]);

The defaultOrder changed, as you might guess, the initial ordering of the GridView, should it be rendered. In this example the ordering will be on ‘Published’ decending and ‘LastEdited’ descending next.

attributes will contain a list of all sortable attributes from the GridView’s columns (the attribute names here should match the attribute names in the GridView). You can independently define how the ascending and descending result will be sorted – again, this can be done on multiple columns in your DB.

One thing to note that for pagination to work the DataProvider needs to know the total number of results available. The ActiveDataProvider and SqlDataProvider are able to do it by default using database queries. If you use the ArrayDataProvider, you might need to do some additional work depending on how you fill the provider.

Conclusion

The ListView and GridView are very practical widgets which allow you to quickly show the data that you want. If you want even more from your GridView, then be sure to check out the ‘GridView on steroids’ made by Kartik Visweswaran. This adds a lot of additional functionality to your grids allowing the user to fully customize the way the data is shown. Be sure to check out all the other components from Kartik as well!

Are there more Yii 2.0 topics that you would like to see covered? Suggestions are welcome!

  • KBT Co

    Hi Arno,

    Thanks for the great post. I am fairly new to Yii and I was wondering if you could kindly do a tutorial on how to implement a modal popup over gridview so that one can perform “add, view, update” records with it.

    Thanks in advance!!

    KB.

  • Jonathan Morales Salazar

    thank you, it works perfect.

  • http://tberthold.com Tom Berthold

    Nice article. I almost always display tabular data with a datagrid. Check out my open source project: mysqlgrid.com It is hands down the easiest way to implement sortable, searchable, paginated datagrids in PHP, and works with or without a framework.

    • http://www.slatius.nl Arno Slatius

      You make it seem that your project has something to do with Yii, but this isn’t related at all! This is a lame attempt to attract traffic.

      I would seriously vote _against_ your project; the code looks quite bad and anyone would have a hard time integrating it.

      • http://tberthold.com Tom Berthold

        Well I thought it was relevant to the subject of datagrids in general. Sorry you don’t like my project, but it is actually very easy to integrate and I have been using it in a production capacity for several weeks with good success. I would welcome any constructive ideas on how I could make it better. “The code looks quite bad” is not very helpful. If you ever tried it you might be surprised how well it actually works.

        • http://www.slatius.nl Arno Slatius

          Oke, I’ll bite…
          It is related, I’ll give you that and the demo certainly looks alright. But if I would even consider using it I’d look at the code first and that horrified me.
          My criticism:
          * PSR; read it, understand it, use it! It might be a bit of a burden at first but you’ll appreciate it in the long run. I suggest you start with PSR-2.
          * Comment in your code, absolutely none!? Seriously? You consider that acceptable? You share your code through Github, if you want people to work with it, help them!
          * Splitting view & controller code, I personally get lost instantly.
          * Generating JavaScript with PHP, a lot of people have trouble enough understanding them separately. Although technically interesting, it certainly does not make it easier.

          Perhaps I’m not the best benchmark for quality we probably don’t fully agree, but I looked away shaking my head within a few seconds.

          • http://tberthold.com Tom Berthold

            Thanks for the feedback Arno. I’ll study the PSR standards and try to use these to make the code more readable, and add more meaningful comments.

            I think part of the reason it has a “hacked together” look is because I incorporated a couple of other open source projects and had to accommodate some peculiar issues there. From what you pointed out I realize the code may be difficult to modify, but hopefully it will be found useful as a stand-alone module that “just works”.

          • http://tberthold.com Tom Berthold

            Thanks for the feedback Arno. I’ll study the PSR standards and try to use these to make the code more readable, and add more meaningful comments.

            I think part of the reason it has a “hacked together” look is because I incorporated a couple of other open source projects and had to accommodate some peculiar issues there. From what you pointed out I realize the code may be difficult to modify, but hopefully it will be found useful as a stand-alone module that “just works”.

    • http://tberthold.com Tom Berthold

      BTW I have used datagrids with Yii 1.0 in multiple projects. While I found them very wonderful from a user’s perspective I also found them rather cumbersome to work with as a developer. This was actually what inspired me to write my own datagrid tool. I have not yet used the grid objects in Yii 2. I appreciate you taking the time to discuss some of those details in your article.

  • M Rios

    Thanks. :)
    In Yii2 the correct method to build a link is Html::a(…)

    • http://www.slatius.nl Arno Slatius

      You are correct, Html::link is Yii 1.x

  • Artem Khodos

    Thanks for tutorial! Is there a way to make prehandle for some column? For example what if i want to see in the grid first few worlds from the article body.

  • Jake Rossi

    Great post!

  • YII Developer

    i need to show page number like <<(FIRST PAGE) (NEXT PAGE) >>(LAST PAGE) can you suggest me for this ?

  • Judd Bundy

    In an example above there is a column configuration with ‘type’ => ‘html’, I think it should be ‘format’ => ‘html’,

  • Sky

    how i want to resize the form column

  • http://www.slatius.nl Arno Slatius

    In a way, you already gave the answer yourself; you’re echoing directly from a controller. You shouldn’t do that because your circumventing the rendering process. The best way should be to echo in a view. You could also try a `return gridview()` instead of the `echo`, I think that’ll work as well, can’t try it atm.

    • Guillermo

      Thank you Arno. I tried “return” instead of “echo” (as they do in the yii tour) but the result was the same. But when I created the View it worked as expected. It’s the first time I use the MVC model and I’m still a bit confused ;)

  • Yermek Sakiyev

    Hello, I have used ‘filterPosition’ => GridView::FILTER_POS_HEADER,
    however, I cannot see the filter row. How do I enable it? thanks

  • Amjad Sarwar

    how to use SerialColumn in list view

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.