9 min read

 

(For more resources on this subject, see here.)

Introduction

Yii have a useful library called Zii. It’s bundled with framework and includes some classes aimed to make the developer’s life easier. Its most handy components are grids and lists which allow you to build data in both the admin and user parts of a website in a very fast and efficient way. In this article you’ll learn how to use and adjust these components to fit your needs. Also you’ll learn about data providers. They are part of the core framework, not Zii, but since they are used extensively with grids and lists, we’ll review them here.

We’ll use Sakila sample database version 0.8 available from official MySQL website: http://dev.mysql.com/doc/sakila/en/sakila.html.

Using data providers

Data providers are used to encapsulate common data model operations such as sorting, pagination and querying. They are used with grids and lists extensively. Because both widgets and providers are standardized, you can display the same data using different widgets and you can get data for a widget from various providers. Switching providers and widgets is relatively transparent.

Currently there are CActiveDataProvider, CArrayDataProvider, and CSqlDataProvider implemented to get data from ActiveRecord models, arrays, and SQL queries respectively.

Let’s try all these providers to fill a grid with data.

Getting ready

  • Create a new application using yiic webapp as described in the official guide.
  • Download the Sakila database from http://dev.mysql.com/doc/sakila/en/sakila.html and execute the downloaded SQLs: first schema then data.
  • Configure the DB connection in protected/config/main.php.
  • Use Gii to create a model for the film table.

How to do it…

  1. Let’s start with a view for a grid controller. Create protected/views/grid/index.php:

    <?php $this->widget('zii.widgets.grid.CGridView',
    array('dataProvider' => $dataProvider,
    ))?>

  2. Then create a protected/controllers/GridController.php:

    <?php
    class GridController extends Controller
    {
    public function actionAR()
    {
    $dataProvider = new CActiveDataProvider('Film', array(
    'pagination'=>array(
    'pageSize'=>10,
    ),
    'sort'=>array(
    'defaultOrder'=> array('title'=>false),
    )
    ));

    $this->render('index', array(
    'dataProvider' => $dataProvider,
    ));
    }

    public function actionArray()
    {
    $yiiDevelopers = array(
    array(
    'name'=>'Qiang Xue',
    'id'=>'2',
    'forumName'=>'qiang',
    'memberSince'=>'Jan 2008',
    'location'=>'Washington DC, USA',
    'duty'=>'founder and project lead',
    'active'=>true,
    ),
    array(
    'name'=>'Wei Zhuo',
    'id'=>'3',
    'forumName'=>'wei',
    'memberSince'=>'Jan 2008',
    'location'=>'Sydney, Australia',
    'duty'=>'project site maintenance and development',
    'active'=>true,
    ),
    array(
    'name'=>'Sebastián Thierer',
    'id'=>'54',
    'forumName'=>'sebas',
    'memberSince'=>'Sep 2009',
    'location'=>'Argentina',
    'duty'=>'component development',
    'active'=>true,
    ),
    array(
    'name'=>'Alexander Makarov',
    'id'=>'415',
    'forumName'=>'samdark',
    'memberSince'=>'Mar 2010',
    'location'=>'Russia',
    'duty'=>'core framework development',
    'active'=>true,
    ),
    array(
    'name'=>'Maurizio Domba',
    'id'=>'2650',
    'forumName'=>'mdomba',
    'memberSince'=>'Aug 2010',
    'location'=>'Croatia',
    'duty'=>'core framework development',
    'active'=>true,
    ),
    array(
    'name'=>'Y!!',
    'id'=>'1644',
    'forumName'=>'Y!!',
    'memberSince'=>'Aug 2010',
    'location'=>'Germany',
    'duty'=>'core framework development',
    'active'=>true,
    ),
    array(
    'name'=>'Jeffrey Winesett',
    'id'=>'15',
    'forumName'=>'jefftulsa',
    'memberSince'=>'Sep 2010',
    'location'=>'Austin, TX, USA',
    'duty'=>'documentation and marketing',
    'active'=>true,
    ),
    array(
    'name'=>'Jonah Turnquist',
    'id'=>'127',
    'forumName'=>'jonah',
    'memberSince'=>'Sep 2009 - Aug 2010',
    'location'=>'California, US',
    'duty'=>'component development',
    'active'=>false,
    ),
    array(
    'name'=>'István Beregszászi',
    'id'=>'1286',
    'forumName'=>'pestaa',
    'memberSince'=>'Sep 2009 - Mar 2010',
    'location'=>'Hungary',
    'duty'=>'core framework development',
    'active'=>false,
    ),
    );

    $dataProvider = new CArrayDataProvider(
    $yiiDevelopers, array(
    'sort'=>array(
    'attributes'=>array('name', 'id', 'active'),
    'defaultOrder'=>array('active' => true, 'name' => false),
    ),
    'pagination'=>array(
    'pageSize'=>10,
    ),
    ));

    $this->render('index', array(
    'dataProvider' => $dataProvider,
    ));
    }

    public function actionSQL()
    {
    $count=Yii::app()->db->createCommand('SELECT COUNT(*)
    FROM film')->queryScalar();
    $sql='SELECT * FROM film';
    $dataProvider=new CSqlDataProvider($sql, array(
    'keyField'=>'film_id',
    'totalItemCount'=>$count,
    'sort'=>array(
    'attributes'=>array('title'),
    'defaultOrder'=>array('title' => false),
    ),
    'pagination'=>array(
    'pageSize'=>10,
    ),
    ));

    $this->render('index', array(
    'dataProvider' => $dataProvider,
    ));
    }
    }

  3. Now run grid/aR, grid/array and grid/sql actions and try using the grids.

    Yii 1.1: Using Zii Components

How it works…

The view is pretty simple and stays the same for all data providers. We are calling the grid widget and passing the data provider instance to it.

Let’s review actions one by one starting with actionAR:

$dataProvider = new CActiveDataProvider('Film', array(
'pagination'=>array(
'pageSize'=>10,
),
'sort'=>array(
'defaultOrder'=>array('title'=>false),
)
));

CActiveDataProvider works with active record models. Model class is passed as a first argument of class constructor. Second argument is an array that defines class public properties. In the code above, we are setting pagination to 10 items per page and default sorting by title.

Note that instead of using a string we are using an array where keys are column names and values are true or false. true means order is DESC while false means that order is ASC. Defining the default order this way allows Yii to render a triangle showing sorting direction in the column header.

In actionArray we are using CArrayDataProvider that can consume any array.

$dataProvider = new CArrayDataProvider($yiiDevelopers, array(
'sort'=>array(
'attributes'=>array('name', 'id', 'active'),
'defaultOrder'=>array('active' => true, 'name' => false),
),
'pagination'=>array(
'pageSize'=>10,
),
));

First argument accepts an associative array where keys are column names and values are corresponding values. Second argument accepts an array with the same options as in CActiveDataProvider case.

In actionSQL, we are using CSqlDataProvider that consumes the SQL query and modifies it automatically allowing pagination. First argument accepts a string with SQL and a second argument with data provider parameters. This time we need to supply calculateTotalItemCount with total count of records manually. For this purpose we need to execute the extra SQL query manually. Also we need to define keyField since the primary key of this table is not id but film_id.

To sum up, all data providers are accepting the following properties:

pagination

CPagination object or an array of initial values for new instance of CPagination.

sort

CSort object or an array of initial values for new instance of CSort.

totalItemCount

We need to set this only if the provider, such as CsqlDataProvider, does not implement the calculateTotalItemCount method.

There’s more…

You can use data providers without any special widgets. Replace protected/views/grid/ index.php content with the following:

<?php foreach($dataProvider->data as $film):?>
<?php echo $film->title?>
<?php endforeach?>

<?php $this->widget('CLinkPager',array(
'pages'=>$dataProvider->pagination))?>

Further reading

To learn more about data providers refer to the following API pages:

 

Using grids

Zii grids are very useful to quickly create efficient application admin pages or any pages you need to manage data on.

Let’s use Gii to generate a grid, see how it works, and how we can customize it.

Getting ready

Carry out the following steps:

  • Create a new application using yiic webapp as described in the official guide.
  • Download the Sakila database from http://dev.mysql.com/doc/sakila/en/sakila.html. Execute the downloaded SQLs: first schema then data.
  • Configure the DB connection in protected/config/main.php.
  • Use Gii to create models for customer, address, and city tables.

How to do it…

  1. Open Gii, select Crud Generator, and enter Customer into the Model Class field. Press Preview and then Generate.
  2. Gii will generate a controller in protected/controllers/CustomerController.php and a group of views under protected/views/customer/.
  3. Run customer controller and go to Manage Customer link. After logging in you should see the grid generated:

    Yii 1.1: Using Zii Components

How it works…

Let’s start with the admin action of customer controller:

public function actionAdmin()
{
$model=new Customer('search');
$model->unsetAttributes(); // clear any default values
if(isset($_GET['Customer']))
$model->attributes=$_GET['Customer'];

$this->render('admin',array(
'model'=>$model,
));
}

Customer model is created with search scenario, all attribute values are cleaned up, and then filled up with data from $_GET. On the first request $_GET is empty but when you are changing the page, or filtering by first name attribute using the input field below the column name, the following GET parameters are passed to the same action via an AJAX request:

Customer[address_id] =
Customer[customer_id] =
Customer[email] =

Customer[first_name] = alex
Customer[last_name] =
Customer[store_id] =
Customer_page = 2
ajax = customer-grid

Since scenario is search, the corresponding validation rules from Customer::rules are applied. For the search scenario, Gii generates a safe rule that allows to mass assigning for all fields:

array('customer_id, store_id, first_name, last_name, email,
address_id, active, create_date, last_update', 'safe',
'on'=>'search'),

Then model is passed to a view protected/views/customer/admin.php. It renders advanced search form and then passes the model to the grid widget:

<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'customer-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
'customer_id',
'store_id',
'first_name',
'last_name',
'email',
'address_id',
/*
'active',
'create_date',
'last_update',
*/
array(
'class'=>'CButtonColumn',
),
),
)); ?>

Columns used in the grid are passed to columns. When just a name is passed, the corresponding field from data provider is used.

Also we can use custom column represented by a class specified. In this case we are using CButtonColumn that renders view, update and delete buttons that are linked to the same named actions and are passing row ID to them so action can be done to a model representing specific row from database.

filter property accepts a model filled with data. If it’s set, a grid will display multiple text fields at the top that the user can fill to filter the grid.

The dataProvider property takes an instance of data provider. In our case it’s returned by the model’s search method :

public function search()
{
// Warning: Please modify the following code to remove attributes
that
// should not be searched.

$criteria=new CDbCriteria;

$criteria->compare('customer_id',$this->customer_id);
$criteria->compare('store_id',$this->store_id);
$criteria->compare('first_name',$this->first_name,true);
$criteria->compare('last_name',$this->last_name,true);
$criteria->compare('email',$this->email,true);
$criteria->compare('address_id',$this->address_id);
$criteria->compare('active',$this->active);
$criteria->compare('create_date',$this->create_date,true);
$criteria->compare('last_update',$this->last_update,true);

return new CActiveDataProvider(get_class($this), array(
'criteria'=>$criteria,
));
}

This method is called after the model was filled with $_GET data from the filtering fields so we can use field values to form the criteria for the data provider. In this case all numeric values are compared exactly while string values are compared using partial match.

LEAVE A REPLY

Please enter your comment!
Please enter your name here