Szukanie po relacji w CGridView

Założenie: Mamy dwie tabele, jedna z produktami, druga z typami produktów, które są w relacji productTypeId należy do typeId.

Product

productId

productName

productTypeId

Type

typeId

typeName

Szbkie przypomnienie, jak używać filtrów na modelach w cgridview.

Tworzymy model:




class Product extends CActiveRecord

{

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	public function tableName()

	{

		return 'Product';

	}


	public function rules()

	{

		return array(

			array('productId, , productName, productTypeId', 'required'),

			array('productId, , productName', 'safe', 'on'=>'search'),

		);

	}

	public function attributeLabels()

	{

		return array(

			'productName' => 'Nazwa Produktu',

			'productType' => 'Typ'

		);

	}


	public function relations()

	{

		return array

		(

			'type' => array(self::BELONGS_TO, 'Type', 'productTypeId')

		);

	}


	public function search()

	{

		$criteria=new CDbCriteria;


		$criteria->compare('productId',$this->productId);

		$criteria->compare('productName',$this->productName,true);

		$criteria->compare('productTypeId',$this->productTypeId,true);


		return new CActiveDataProvider(get_class($this), array(

			'criteria'=>$criteria,

		));

	}


}




Teraz nasz kontroller, którego zadaniem jest przyjąć dane z geta, wysyłane z CGridView, nakarmić nimi model, i wyrenderować widok:




	public function actionShowProducts()

	{

		$model=new Product('search');

		$model->unsetAttributes();

			

		if(isset($_GET['Product']))

		{

			$model->attributes=$_GET['Product'];

		}

			

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

	}



Następnie nasz widok:




$this->widget('zii.widgets.grid.CGridView', array(

	'id'=>'product-grid',

	'dataProvider'=>$model->search(),

	'itemsCssClass'=>'gridview',

	'filter'=>$model,

	'columns'=>array(


		'productName',

		'productTypeId',

		)

));


?>




Sortowanie działa nam na modelu więc możemy filtrować po typeId wpisując cyfry, ale co jeśli chcemy szukać po jego nazwie? I tu właśnie wkracza mój szalony umysł ]:->

Potrzebujemy modyfikacji w modelu i w widoku, a więc do dzieła !

Najpier w modelu deklarujemy pulbliczne własności klasy, odpowiadające nazwie pola po którym chcemy szukać, będzie to abstrakcyjne pole klasy Produkt.




class Product extends CActiveRecord

{

	public $typeName;

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}

.....



Następnie dopisujemy to pole do rules modelu:




...


public function rules()

	{

		return array(

			array('productId, , productName, productTypeId', 'required'),

			array('typeName','length','max'=>50)

			array('productId, , productName', 'safe', 'on'=>'search'),

		);

	}


...



Teraz modyfikujemy metodę search:




...


public function search()

	{

		$criteria=new CDbCriteria;


		$criteria->compare('productId',$this->productId);

		$criteria->compare('productName',$this->productName,true);

		$criteria->compare('productTypeId',$this->productTypeId,true);

		$criteria->join = 'JOIN Type ON Type.typeId=productTypeId';

		$criteria->compare('Type.name',$this->typeName,true);

		return new CActiveDataProvider(get_class($this), array(

			'criteria'=>$criteria,

		));

	}


...



Dodałem dwie linijki




	$criteria->join = 'JOIN Type ON Type.typeId=productTypeId';

	$criteria->compare('Type.name',$this->typeName,true);



Pierwsza dołącza do zapytania tabele Type, druga sprawdza czy wprowadzony przez nas ciąg jest podobny do tego w bazie, parametr true na końcu. Ustawiony na false szuka dokładnego wyniku.

Modyfikacja nr 2 to widok, musimy napisać customowy filtr.




$this->widget('zii.widgets.grid.CGridView', array(

	'id'=>'product-grid',

	'dataProvider'=>$model->search(),

	'itemsCssClass'=>'gridview',

	'filter'=>$model,

	'columns'=>array(


	'productName',

	 array(

		'type'=>'raw',

		'header'=>'Nazwa Typu',

		'name'=>'typeName',

		'value'=>'(empty($data->type->typeName) ? "brak" : $data->type->typeName)',

		'filter'=>'<input name="Product[typeName]" value="'.$model->typeName.'"/>',

		)

	)

));



Zamiast pola productTypeId, wstawiłem array, header wiadomo nagłówek, name, podajemy nazwe naszej virtualnej właściwości tabeli Product. To działa tak samo jak opisany przeze mnie wcześniej model do logowania Login By Form Model

Więcej na mojej stronie log-this.com

Pierwsza uwaga:


$criteria->join = 'JOIN Type ON Type.typeId=productTypeId';

spowoduje, że rekordy Produkt, które nie mają typu albo błędną referencje nie zostaną nigdy wyświetlone (lepszy byłby LEFT OUTER JOIN).

czytelniej można to samo zapisać:


$criteria->with = array( 'type' );

$criteria->compare('type.name',$this->typeName,true);

skoro mamy przecież zdefiniowaną taką relację w Produkcie.

Druga sprawa - sortowanie. W tym wypadku sortowanie nie będzie działać wcale na kolumnie z typem, a czasem mogłoby się przydać. Aby zadziałało należy przede wszystkim w definicji kolumny grida wskazać kolumne bedaca kluczem obcym, czyli productTypeId:


array(

                'type'=>'raw',

                'header'=>'Nazwa Typu',

                'name'=>'productTypeId',

                'value'=>'(empty($data->type->typeName) ? "brak" : $data->type->typeName)',

                'filter'=>'<input name="Product[typeName]" value="'.$model->typeName.'"/>',

                )

        )

Ale to jeszcze nie zadziała tak, jakbyśmy chcieli, gdyż sortowanie będzie polegało na ustawieniu w kolejności wartości klucza obcego. Końcowym posunięciem jest wskazanie w definicji CActiveDataProvidera jak faktycznie ma być realizowane sortowanie:




...

return new CActiveDataProvider(get_class($this), array(

                        'criteria'=>$criteria,

                        'sort'=>array(

				'attributes'=>array(

					'productTypeId'=>array(

						'asc'=>'type.typeName',

						'desc'=>'type.typeNameDESC',

					),

					'*',

				),

			),

                ));



powyższy kod sprawi, że kiedy będziemy próbować sortować po kolumnie productTypeId, zamiast po wartościach tej kolumny zostanie zastosowane odpowiednie sortowanie po relacji "type". Ostatni wpis "*" powoduje że pozostałe kolumny są traktowane standardowo - bez tego wpisu można by było sortować tylko po kolumnie "productTypeId".

Tego mechanizmu można też użyć do "poprawiania" mechanizmów sortowania w ramach jednego modelu, np. kiedy sortujemy po nazwisku pracownika, automatycznie w drugiej kolejności warto byłoby sortować po jego imieniu, wystarczy w tym celu podać:




'sort'=>array(

	'attributes'=>array(

		'nazwisko'=>array(

			'asc'=>'nazwisko, imie',

			'desc'=>'nazwisko desc, imie desc',

		),

		'*',

	),

),