I am currently working on a web application (written in PHP, based on Yii), which, among other things, lets you connect different price values for products. Each product can have multiple prices, but the system is built in a way that I can easily (and correctly) determine the type of the price field - so while each product can have multiple price fields, it can only have one of each type of price fields.
The part where I stuck is when I have to display the stored value of these fields in a list, and order the list view by them - I can display them correctly and sort them by one column, but as soon as I try to make the list to be sortable by all of the columns (not at the same time, of course), the rows start to display the wrong values.
Here are the relevant pieces of code:
In the model, at relations:
'productPriceConnects' => array(Product::HAS_MANY, 'ProductPriceConnect', 'product_id'),
'price1' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceBeszerzesi.product_price_field_id=:product_price_field_id AND priceBeszerzesi.active = 1',
'params' => array(':product_price_field_id' =>ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 6))->id),
'alias' => 'priceBeszerzesi',
),
'price2' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceEladasi.product_price_field_id=:product_price_field_id AND priceEladasi.active = 1',
'params' => array(':product_price_field_id' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 1))->id),
'alias' => 'priceEladasi'
),
'price3' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceAkcios.product_price_field_id=:product_price_field_id AND priceAkcios.active = 1',
'params' => array(':product_price_field_id' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 5))->id),
'alias' => 'priceAkcios'
),
In the model, at search:
...
$criteria->with('price1', 'price2', 'price3);
...
$criteria->compare('price1.price', $this->beszerzesi, true);
$criteria->compare('price2.price', $this->eladasi, true);
$criteria->compare('price3.price', $this->akcios, true);
...
$sort->attributes = array(
'price1' =>array(
'asc' => 'priceBeszerzesi.price ASC',
'desc' => 'priceBeszerzesi.price DESC',
),
'price2' =>array(
'asc' => 'priceEladasi.price ASC',
'desc' => 'priceEladasi.price DESC',
),
'priceAkcios' =>array(
'asc' => 'price3.price ASC',
'desc' => 'price3.price DESC',
),
'*'
);
...
return new CActiveDataProvider($this, array(
'criteria' => $criteria,
'sort' => $sort,
'pagination' => array(
'pageSize' => 10,
),
)
);
The columns' data in gridview:
'eladasi' => array(
'name' => 'price2',
'header' => 'Eladási ár',
'type' => 'raw',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'value' => '!empty($data->price2->price) ? $data->price2->price == "" ? "-" : $data->price2->price : "-"',
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
'akcios' => array(
'name' => 'priceAkcios',
'header' => 'Akciós',
'value' => '!empty($data->price3->price) ? $data->price3->price == "" ? "-" : $data->price3->price : "-"',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
'beszerzesi' => array(
'name' => 'price1',
'header' => 'Beszerzési ár',
'type' => 'raw',
'value' => '!empty($data->price1->price) ? $data->price1->price == "" ? "-" : $data->price1->price : "-"',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
This code makes it possible to sort the list by all three of the relation-dependent columns, but every column displays the same value - the value of the last relation in the with array. If the last element in the array is price3, then the columns display the value of the price3 relation.
When I remove all relation names from the with array expect one, I can sort the list by that column, but not the others.
My question is this:
Is there any way to
1) surely add any number of relations to a model, connecting to the same db field, but depending on conditions,
2) and display these values WHILE enabling sorting based on them?
Find solution below:
I created these tables at my system and used your relation and gridview code. I made some changes in that code and now in below code searching and sorting is working perfectly.
I defined three variable in model class i.e.
public $beszerzesi;
public $eladasi;
public $akcios;
Then I changed the param's name in relation arraye used with left join. This was the main issue in your code. You used same name for parameters i.e. :product_price_field_id I assigned different name for each parameters. While yii prepare sql query it will replace the parameters which are assigned to query. In your case it was replacing same value for all three parameters.
Also i made some changes in sort and compare attributes passed in CActiveDataProvider. You can find all changes in below model file.
Product.php
<?php
/**
* This is the model class for table "product".
*
* The followings are the available columns in table 'product':
* #property integer $id
* #property string $name
*/
class Product extends CActiveRecord
{
public $beszerzesi;
public $eladasi;
public $akcios;
/**
* #return string the associated database table name
*/
public function tableName()
{
return 'product';
}
/**
* #return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('name', 'required'),
array('name', 'length', 'max' => 100),
// The following rule is used by search().
// #todo Please remove those attributes that should not be searched.
array('id, name, beszerzesi, eladasi,akcios', 'safe', 'on' => 'search'),
);
}
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'productPriceConnects' => array(Product::HAS_MANY, 'ProductPriceConnect', 'product_id'),
'price1' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceBeszerzesi.product_price_field_id=:product_price_field_id1 AND priceBeszerzesi.active = 1',
'params' => array(':product_price_field_id1' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 6))->id),
'alias' => 'priceBeszerzesi',
),
'price2' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceEladasi.product_price_field_id=:product_price_field_id2 AND priceEladasi.active = 1',
'params' => array(':product_price_field_id2' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 1))->id),
'alias' => 'priceEladasi'
),
'price3' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
'joinType' => 'LEFT JOIN',
'on' => 'priceAkcios.product_price_field_id=:product_price_field_id3 AND priceAkcios.active = 1',
'params' => array(':product_price_field_id3' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 5))->id),
'alias' => 'priceAkcios'
),
);
}
/**
* #return array customized attribute labels (name=>label)
*/
public function attributeLabels()
{
return array(
'id' => 'ID',
'name' => 'Name',
);
}
/**
* Retrieves a list of models based on the current search/filter conditions.
*
* Typical usecase:
* - Initialize the model fields with values from filter form.
* - Execute this method to get CActiveDataProvider instance which will filter
* models according to data in model fields.
* - Pass data provider to CGridView, CListView or any similar widget.
*
* #return CActiveDataProvider the data provider that can return the models
* based on the search/filter conditions.
*/
public function search()
{
// #todo Please modify the following code to remove attributes that should not be searched.
$criteria = new CDbCriteria;
$criteria->with = array('price1', 'price2', 'price3');
$criteria->compare('id', $this->id);
$criteria->compare('name', $this->name, true);
$criteria->compare('priceBeszerzesi.price', $this->beszerzesi, true);
$criteria->compare('priceEladasi.price', $this->eladasi, true);
$criteria->compare('priceAkcios.price', $this->akcios, true);
// $criteria->attributes = ;
return new CActiveDataProvider($this, array(
'criteria' => $criteria,
'sort' => array(
'attributes' => array(
'beszerzesi' => array(
'asc' => 'priceBeszerzesi.price',
'desc' => 'priceBeszerzesi.price DESC',
),
'eladasi' => array(
'asc' => 'priceEladasi.price',
'desc' => 'priceEladasi.price DESC',
),
'akcios' => array(
'asc' => 'priceAkcios.price',
'desc' => 'priceAkcios.price DESC',
),
'*'
)
),
'pagination' => array(
'pageSize' => 10,
),
)
);
}
/**
* Returns the static model of the specified AR class.
* Please note that you should have this exact method in all your CActiveRecord descendants!
* #param string $className active record class name.
* #return Product the static model class
*/
public static function model($className = __CLASS__)
{
return parent::model($className);
}
}
gridview code
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id' => 'product-grid',
'dataProvider' => $model->search(),
'filter' => $model,
'columns' => array(
'id',
'name',
'eladasi' => array(
'name' => 'eladasi',
'header' => 'Eladási ár',
'type' => 'raw',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'value' => '!empty($data->price2->price) ? $data->price2->price == "" ? "-" : $data->price2->price : "-"',
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
'akcios' => array(
'name' => 'akcios',
'header' => 'Akciós',
'value' => '!empty($data->price3->price) ? $data->price3->price == "" ? "-" : $data->price3->price : "-"',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
'beszerzesi' => array(
'name' => 'beszerzesi',
'header' => 'Beszerzési ár',
'type' => 'raw',
'value' => '!empty($data->price1->price) ? $data->price1->price == "" ? "-" : $data->price1->price : "-"',
'headerHtmlOptions' => array('class' => 'auto-width text-center'),
'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
array(
'class' => 'CButtonColumn',
),
),
)); ?>
You can found step by step guide for the searching and sorting on relation data at Searching and sorting by related model in CGridView | Wiki | Yii PHP Framework
I created a simple gridview using a custom query. This is working fine for me but search box doesn't work. How do I make the search box work?
Here is my code:
UserController.php
public function actionAdmin()
{
$sql = 'SELECT * FROM user';
$rawData = Yii::app()->db->createCommand($sql);
$count = Yii::app()->db->createCommand('SELECT COUNT(*) FROM (' . $sql . ') as count_alias')->queryScalar();
$model = new CSqlDataProvider($rawData, array(
'keyField' => 'id',
'totalItemCount' => $count,
'sort' => array(
'attributes' => array(
'id','title', 'type'
),
'defaultOrder' => array(
'id' => CSort::SORT_ASC, //default sort value
),
),
'pagination' => array(
'pageSize' => 10,
),
));
$this->render('admin', array(
'model' => $model,
));
}
Admin.php (View file)
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'user-grid',
'dataProvider'=>$model,
'filter'=>$model,
'columns'=>array(
array('header'=>'firstname','value'=>'$data["firstname"]'),
array('header'=>'lastname','value'=>'$data["lastname"]'),
array('header'=>'username','value'=>'$data["username"]'),
),
)); ?>
I have User model which contain a function that calculates the average revenue per user. Now i want apply sorting in CGridView on the column which is associated with getAvgRevenue() function. While license is relation in the User model.
Here is my code,
public class User{
$user_id;
$email;
public function getAvgRevenue(){
$count = 0;
$revenue = 0;
foreach ($this->license as $license){
$revenue += $license->price;
$count++;
}
if($count!= 0){
$averageRevenue = $revenue/$count;
return $averageRevenue;
}
else{
return null;
}
}
}
In Controller
$modelUser = new CActiveDataProvider('User', array(
'sort' => array(
'attributes' => array(
'user_id',
'email',
'averagerevenue'
),
'defaultOrder' => array(
'user_id' => CSort::SORT_ASC,
),
),
));
In view
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id' => 'user-grid',
'dataProvider' => $modelUser,
'columns' => array(
array(
'header' => 'User ID',
'name' => 'user_id',
),
array(
'header' => 'Email',
'name' => 'email',
),
array(
'header'=>'Average Revenue',
'value'=>'$data->averagerevenue',
),
)
));
?>
Sorting is applicable on user_id and email but Average Revenue column is not sortable. how to specify model method in sort() of CActiveDataprovider
Please help me to solve the problem.
Try this:
$modelUser = new CActiveDataProvider('User', array(
'sort' => array(
'attributes' => array(
'user_id',
'email',
'avgRevenue' //here's the change for you
),
'defaultOrder' => array(
'user_id' => CSort::SORT_ASC,
),
),
));
And your gridview column should be:
array(
'header'=>'Average Revenue',
'value'=>'avgRevenue',
),
and you can read more info on it over here:
http://www.yiiframework.com/wiki/167/understanding-virtual-attributes-and-get-set-methods/
I am trying to validate a multiply select using input filter, but every time I see a error. The error is "notInArray":"The input was not found in the haystack".(I use ajax but it doesn`t metter).
I will show part of my code to be more clear.
in Controller:
if ($request->isPost()) {
$post = $request->getPost();
$form = new \Settings\Form\AddUserForm($roles);//
$form->get('positions')
->setOptions(
array('value_options'=> $post['positions']));
//.... more code...
When I put print_r($post['positions']); I see:
array(0 => 118, 1 => 119)
in ..../form/UserForm.php I create the multiply element
$this->add(array(
'type' => 'Zend\Form\Element\Select',
'attributes' => array(
'multiple' => 'multiple',
'id' => 'choosed_positions',
),
'required' => false,
'name' => 'positions',
));
and in the validation file the code is:
$inputFilter->add($factory->createInput(array(
'name' => 'positions',
'required' => false,
'validators' => array(
array(
'name' => 'InArray',
'options' => array(
'haystack' => array(118,119),
'messages' => array(
'notInArray' => 'Please select your position !'
),
),
),
),
What can be the reason every time to see this error, and how I can fix it?
By default selects have attached InArray validator in Zend Framework 2.
If you are adding new one - you will have two.
You should disable default one as follow:
$this->add(array(
'type' => 'Zend\Form\Element\Select',
'options' => array(
'disable_inarray_validator' => true, // <-- disable
),
'attributes' => array(
'multiple' => 'multiple',
'id' => 'choosed_positions',
),
'required' => false,
'name' => 'positions',
));
And you should get rid of the additional error message.
Please let us know if that would helped you.
Below is my code in view.php file
$this->widget('zii.widgets.grid.CGridView', array(
'id' => 'myGrid',
'dataProvider' => $model->search(),
'filter' => $model,
'enableSorting' => true,
'columns' => array(
array(
'name' => 'id',
'header' => 'ID',
'value' => $data->id,
),
array(
'header' => 'Value',
'value' => '$data->getValue($data->id)', //getValue($id) is a custom method in the model.php file which returns a value after some calculations
'filter' => $model->listOfFilterValues(), //listOfFilterValues() is a custom method in the model.php file which returns a CHtml::listData
),
),
)
);
As you can observe, I am using a custom method in model.php file to get the Value column(because i cannot get it from a query).
The gridView appears and the dropdown list appears. Works fine till now.
The problem is that the filtering using the dropdown (in Value column) doesnt work. (because its not a column from the query output)
And also the sort on the Value column(when i click on the column header) doesnt work.
Is there a way to get this done? Your help is highly appreciated. Thank you.
Try this:
In Model:
class Plans extends BasePlans
{
...
public $planTypeSearch;
...
public function search() {
...
$criteria->compare('plan.plan_type', $this->planTypeSearch, true);
...
),
return new CActiveDataProvider($this, array(
'criteria' => $criteria,
'sort'=>array(
'attributes'=>array(
'planTypeSearch'=>array(
'asc'=>'plan.plan_type',
'desc'=>'plan.plan_type DESC',
),
),
),
);
In your view:
array(
'name'=>'planTypeSearch',
'header'=> 'Plan Type',
'value'=>'(isset($data->plan)) ? $data->plan->plan_type: null',
'filter'=>isset($plans) ? CHtml::listData($plans, 'plan_type','plan_type') : NULL,
),