« Vissza

Hogyan kerüljük el az egész oldal render-elését CGridView és CListView AJAX-os hívásakor?

Informatikai alapok

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'),
);
}

Kapcsolódó cikkek