Alapesetben lista v. grid lapozásakor AJAX kérést küldünk a szervernek. Ekkor a szerver az egész oldal tartalmát újra lerendereli a kapott kérés paramétereinek megfelelően. Miután a szerver visszaküldte a kész oldalt, a widget kiválasztja a neki szükséges részt, és frissíti magát. Miért rossz ez? Amikor a szerver az egész oldalt elkészíti, akkor az oldalon található összes utasítást végrehajtja: lefuttatja a widget-eket, elvégzi az adatbázis lekérdezéseket, újra létrehozza az objektumokat, azaz rengeteg olyan munkát elvégez, amire nincs szükség, ezzel is terhelve a rendszer erőforrásait.
Filterek segítségével, melyek a controller action-ök előtt hajtódnak végre, szerencsére el tudjuk kerülni ezeket a felesleges munkákat. Az ötlet az, hogy készítünk egy metódust, mely kizárólag a grid/list nézetet adja vissza, és AJAX kérések esetén ezt a metódust haszáljuk a kérés teljesítéséhez.
Tegyük fel, hogy a User model-t listázó grid/listview-val dolgozunk. A UserController-en belül létre kell hoznunk a két metódust, melyek a lista/grid ajax-os renderelését végzik:
public function getListViewForUserList() {
$dataProvider = new CActiveDataProvider('User');
$this->renderPartial('/user/_listView', array(
'dataProvider' => $dataProvider,
// Kézzel beállítjuk az ID-ket, hogy hivatkozhassunk rájuk. FONTOS!
'htmlOptions' => array(
'id' => 'UserList',
),
));
}
public function getGridViewForUserGrid() {
$model = new User('search');
$model->unsetAttributes();
if (isset($_GET['User'])) {
$model->attributes = $_GET['User'];
}
$this->renderPartial('/user/_gridView', array(
'model' => $model,
// Kézzel beállítjuk az ID-ket, hogy hivatkozhassunk rájuk. FONTOS!
'htmlOptions' => array(
'id' => 'UserGrid',
),
));
}
az id-ket az eredeti action-ökben is be kell kézzel állítani, hogy jól jöjjenek létre.
public function actionIndex() {
$dataProvider = new CActiveDataProvider('User');
$this->render('index', array(
'dataProvider' => $dataProvider,
'htmlOptions' => array(
'id' => 'UserList',
),
));
}
public function actionAdmin() {
$model = new User('search');
$model->unsetAttributes();
if (isset($_GET['User'])) {
$model->attributes = $_GET['User'];
}
$this->render('admin', array(
'model' => $model,
'htmlOptions' => array(
'id' => 'UserGrid',
),
));
}
Ezt követően létrehozzuk a view file-okat, amiket az új metódusaink használnak:
Alapértelmezett esetben a protected/views/user/ mappán belül hozzuk létre a _gridView.php és _listView.php file-okat az alábbi tartalommal:
_gridView.php
<?php
// A $htmlOptions-ben adjuk át a widget id-ját, hogy később tudjunk rá hivatkozni.
$this->widget('zii.widgets.grid.CGridView', array(
'htmlOptions' => $htmlOptions,
'dataProvider' => $model->search(),
'filter' => $model,
'columns' => array(
'id',
'username',
'password',
'email',
array(
'class' => 'CButtonColumn',
),
),
));
_listView.php
<?php
// A $htmlOptions-ben adjuk át a widget id-ját, hogy később tudjunk rá hivatkozni.
$this->widget('zii.widgets.CListView', array(
'htmlOptions' => $htmlOptions,
'dataProvider' => $dataProvider,
'itemView' => '_view',
));
Miután elkészítettük a két view file-t, akkor egyből fel is tudjuk őket használni az eredeti grid és lista nézetekben. Ez fontos lépés, mert így bebiztosítjuk, hogy az eredeti nézetek esetében is az általunk megszabott id-vel jöjjenek létre a widget-ek, valamit a kódunk is tisztábbá és könnyebben kezelhetővé válik, mivel ugyanazt a nézetet nem tároljuk több helyen egyszerre.
protected/views/user/admin.php
...
<?php
$this->widget('zii.widgets.grid.CGridView', array(
'id' => 'user-grid',
'dataProvider' => $model->search(),
'filter' => $model,
'columns' => array(
'id',
'username',
'password',
'email',
array(
'class' => 'CButtonColumn',
),
),
));
HELYETT
<?php
$this->renderPartial('/user/_gridView', array(
'model' => $model,
'htmlOptions' => $htmlOptions,
));
Hasonlóan protected/views/user/index.php
<?php
$this->widget('zii.widgets.CListView', array(
'dataProvider' => $dataProvider,
'itemView' => '_view',
));
HELYETT
...
<?php
$this->renderPartial('/user/_listView', array(
'dataProvider' => $dataProvider,
'htmlOptions' => $htmlOptions,
));
A filter, ami AJAX kérés esetén a megfelelő metódust hívja meg. A protected mappán belül hozzunk létre egy filters mappát, és ebben egy file-t AjaxViewFilter.php néven a következő kóddal:
class AjaxViewFilter extends CFilter {
protected function preFilter($filterChain) {
// POST kérésekkel nem foglalkozzunk.
if (Yii::app()->request->isPostRequest) {
return parent::preFilter($filterChain);
}
if (Yii::app()->request->isAjaxRequest && !empty($_GET['ajax'])) {
// A GET kérés 'ajax' paramétere tartalmazza a widget ID-jét.
$id = $_GET['ajax'];
// Mivel a UserController-ben beállítottuk, hogy a widget-ek milyen ID-vel jöjjenek
// létre, ezért itt most egyszerűen szét tudjuk választani őket.
stripos($id, 'grid') !== false ? $view = 'GridView' : $view = 'ListView';
// Felépítjük az AJAX kérést kiszolgáló metódus nevét.
// (Ennek a mintájára hoztuk létre a UserController-ben a két új metódusunkat.)
$method = "get{$view}For{$id}";
// Ha létezik a metódus, akkor meghívjuk, egyébként jelezzük, hogy nem létezik.
if (method_exists($filterChain->controller, $method)) {
$filterChain->controller->$method();
Yii::app()->end();
} else {
$className = get_class($filterChain->controller);
throw new CHttpException(400, "C{$view} handler function {$method} not defined in {$className}.");
}
}
return parent::preFilter($filterChain); } }
Ez a filter az összes model grid/listájának a szűrésére alkalmas, ha a $method = „get{$view}For{$id}”; minta alapján nevezzük el a controller-ekben az AJAX kéréseket teljesítő metódusainkat.
Végezetül beállítjuk a UserController-ben az új filterünket:
...
public function filters() {
return array(
'accessControl', // perform access control for CRUD operations
'postOnly + delete', // we only allow deletion via POST request
// Itt állítjuk be a filtert (az index és admin action-öket figyeli)
array('application.filters.AjaxViewFilter + index, admin'),
);
}