You are viewing revision #8 of this wiki article.
This is the latest version of this article.
You may want to see the changes made in this revision.
Using filters on CGridView with CActiveDataProvider is easy, but with CArrayDataProvider it is a bit tricky.
To use the filters you have to create a separate model class. I used some code from the following forum topic: link
Note: Normally, when you have an array of CModels, you should use CActiveDataProvider instead!
Here is some example code:
Model ¶
Copy and include this class into your application.
<?php
/**
* Filterform to use filters in combination with CArrayDataProvider and CGridView
* @see http://www.yiiframework.com/wiki/232/using-filters-with-cgridview-and-carraydataprovider/
*/
class FiltersForm extends CFormModel
{
/**
* @var array filters, key => filter string
*/
public $filters = array();
/**
* Override magic getter for filters
* @param string $name
*/
public function __get($name)
{
if (!array_key_exists($name, $this->filters)) {
$this->filters[$name] = '';
}
return $this->filters[$name];
}
/**
* Override magic setter for filters
* @param string $name
* @param mixed $value
*/
public function __set($name, $value)
{
$this->filters[$name] = $value;
}
/**
* Filter input array by key value pairs
* @param array $data rawData
* @return array filtered data array
*/
public function filter(array $data)
{
foreach ($data AS $rowIndex => $row) {
foreach ($this->filters AS $key => $searchValue) {
if (!is_null($searchValue) AND $searchValue !== '') {
$compareValue = null;
if ($row instanceof CModel) {
if (isset($row->$key) == false) {
throw new CException("Property " . get_class($row) . "::{$key} does not exist!");
}
$compareValue = $row->$key;
} elseif (is_array($row)) {
if (!array_key_exists($key, $row)) {
throw new CException("Key {$key} does not exist in array!");
}
$compareValue = $row[$key];
} else {
throw new CException("Data in CArrayDataProvider must be an array of arrays or an array of CModels!");
}
if (stripos($compareValue, $searchValue) === false) {
unset($data[$rowIndex]);
}
}
}
}
return $data;
}
}
(By @KonApaz) If you want to apply full comparisons (>, <, >=, <=, =) use this
if (substr($searchValue, 0, 2) == '<=') {
if ($compareValue > substr($searchValue, 2)) {
unset($data[$rowIndex]);
}
} else if (substr($searchValue, 0, 2) == '>=') {
if ($compareValue < substr($searchValue, 2)) {
unset($data[$rowIndex]);
}
} else if ($searchValue[0] == '<') {
if ($compareValue >= substr($searchValue, 1)) {
unset($data[$rowIndex]);
}
} else if ($searchValue[0] == '>') {
if ($compareValue <= substr($searchValue, 1)) {
unset($data[$rowIndex]);
}
} else if (stripos($compareValue, $searchValue) === false) {
unset($data[$rowIndex]);
}
Controller ¶
// Create filter model and set properties
$filtersForm=new FiltersForm;
if (isset($_GET['FiltersForm']))
$filtersForm->filters=$_GET['FiltersForm'];
// Get rawData and create dataProvider
$rawData=User::model()->findAll();
$filteredData=$filtersForm->filter($rawData);
$dataProvider=new CArrayDataProvider($filteredData);
// Render
$this->render('index', array(
'filtersForm' => $filtersForm,
'dataProvider' => $dataProvider,
));
View ¶
$columns = array(
array(
'header'=>CHtml::encode('Name'),
'name'=>'username',
),
array(
'header'=>CHtml::encode('Organisation'),
'name'=>'organisation',
),
);
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'area-grid',
'dataProvider'=>$dataProvider,
'columns'=>$columns,
'filter'=>$filtersForm,
));
Can't get it to work..
I copied it over 1:1, literally. Yet it still refuses to do any filtering whatsoever.
It shows all data, and the filter box appears. However when I enter text in it, no filtering occurs. There is no change whatsoever.
Any ideas what could be the problem?
Updated your code..
I had to modify the filter() method to this:
/** * Filter input array by key value pairs * @param CarrayDataProvider $data * @return CarrayDataProvider Filtered data array */ public function filter(CArrayDataProvider $data) { $temp = $data->getData(); foreach ($temp AS $index => $item) { foreach ($this->filters AS $key => $value) { if($value == '') continue; // bypass empty filter $test = false; // value to test for if($item instanceof CModel) { if(isset($item->$key) == false ) throw new CException("Property ".get_class($item)."::{$key} does not exist!"); $test = $item->$key; } elseif(is_array($item)) { if(!array_key_exists($key, $item)) throw new CException("Key {$key} does not exist in Array!"); $test = $item[$key]; } else throw new CException("Data in CArrayDataProvider must be an array of arrays or CModels!"); if(stripos($test, $value) === false) unset($temp[$index]); } } $data->setData(array_values($temp)); return $data; }
and the controller Render-call to this:
$this->render('missingcomputers', array( 'dataProvider' => $filtersForm->filter($dataProvider), 'filtersForm' => $filtersForm, ));
minor fix on FiltersForm Class
i use Chessspider approach, but with minor modification on the filter() function to refresh dataprovider rawData so the pagination will be correct and filter can be perform nicely as we want to.
so here's the filter() function
/** * Filter input array by key value pairs * @param CarrayDataProvider $data * @return CarrayDataProvider Filtered data array */ public function filter(CArrayDataProvider $data) { $temp = $data->rawData; foreach ($temp AS $index => $item) { foreach ($this->filters AS $key => $value) { if($value == '') continue; // bypass empty filter $test = false; // value to test for if($item instanceof CModel) { if(isset($item->$key) == false ) throw new CException("Property ".get_class($item)."::{$key} does not exist!"); $test = $item->$key; } elseif(is_array($item)) { if(!array_key_exists($key, $item)) throw new CException("Key {$key} does not exist in Array!"); $test = $item[$key]; } else throw new CException("Data in CArrayDataProvider must be an array of arrays or CModels!"); if(stripos($test, $value) === false) unset($temp[$index]); } } $data->rawData = array_values($temp); return $data; }
Pagination
Thanks for your nice post.
You will also need to set "totalItemCount" before returning data in the "filter" function, to get pagination works correctly after filtering.
Re: Can't get it to work..
Whoops, totally missed out on this wiki article. Just updated wrong example code in Controller, it was not using critical filter function!
I looked at the other comments but decided to keep the article as is: filter the data BEFORE creating the data provider, bit easier. :)
Code is not worrking
Following my code which i have use. I dont get what is an issue. I am constantly getting same error as : Property "Branch.id" is not defined. There no field in my branch table name as id.
FiltersForm model file
class FiltersForm extends CFormModel
{
public $filters = array(); /** * Override magic getter for filters */ public function __get($name) { //print_r($name); exit; if(!array_key_exists($name, $this->filters)) $this->filters[$name] = null; return $this->filters[$name]; } /** * Filter input array by key value pairs * @param array $data rawData * @return array filtered data array */ public function filter(array $data) { foreach($data AS $rowIndex => $row) { foreach($this->filters AS $key => $value) { // unset if filter is set, but doesn't match if(array_key_exists($key, $row) AND !empty($value)) { if(stripos($row[$key], $value) === false) unset($data[$rowIndex]); } } } return $data; }
}
Controller Action
public function actionAdminNew() { $filtersForm=new FiltersForm; if (isset($_GET['FiltersForm'])) $filtersForm->filters=$_GET['FiltersForm']; // Get rawData and create dataProvider $rawData=Branch::model()->findAll(); $filteredData=$filtersForm->filter($rawData); $dataProvider=new CArrayDataProvider($filteredData); // Render $this->render('adminNew', array( 'filtersForm' => $filtersForm, 'dataProvider' => $dataProvider, )); }
view file :
<?php
$columns = array(
array( 'header'=>CHtml::encode('branch id'), 'name'=>'branch_id', ), array( 'header'=>CHtml::encode('branch name'), 'name'=>'branch_name', ),
);
//print_r($dataProvider); exit;
?>
<?php
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'branch-grid', 'dataProvider'=>$dataProvider, 'columns'=>$columns, 'filter'=>$filtersForm,
));
?>
Re: Code is not worrking
Your problem has nothing to do with the filtering, I think (try setting filter=>false). Have you set your column branch_id in table Branch to be the primary key? Also, consider naming the column "id", calling it branch_id is redundant, and it plays nice with Yii's default expectations.
You can also try to set the property keyAttribute to 'branch_id' in the construction of the dataprovider: http://www.yiiframework.com/doc/api/1.1/CActiveDataProvider#keyAttribute-detail
Code is not worrking
@marcovtwout : Thanx for quick response. I have already declare branch_id as primary key in branch table. And now i have also set filter false in columns attributes. But still getting same result.
$columns = array(
array( 'header'=>CHtml::encode('branch name'), 'name'=>'branch_name',
'filter'=>false,
);1.
Getting confusion in table column name
@marcovtwout : i got little bit solution regarding this issue. when i will change my table column name branch_id to id then above issue will solve except the filter is not working...
the problem is not the filters
@karmraj : the problem is not with the filters, but with CArrayProvider you created.
try something like this :
$dataProvider=new CArrayDataProvider($filteredData, array('keyField'=>'branch_id'));
when you dont have column 'id' in your data, then you need to defined the unique key for CArrayDataProvider manually, as the default value is column with the name 'id'. In the example above the keyField is assigned to branch_id column, change the value "branch_id' to whatever primary key you have in you data.
Re: Little fix to work with comparison operators
Blacklisting possible user input and using eval(), damn scary indeed!
Why don't make a more rigid and secure code using switch with cases for your comparison operators, at the same time teaching readers how to do this properly?
Thanks
Awesome post :D
Worked \m/
Thanks..
Updated wiki article
Updated the code with info from xtremagix. Indeed, it was only working with array of arrays, not array of CModels.
Note: Normally, when you have an array of CModels, you should use CActiveDataProvider instead!
change search criteria
Hi,
How could search by criteria: greater than, less than, or just compare dates "from_date, to_date"
thanks in advance,
Regards.
works!!
thanks.
Re: change search criteria
If you want to do more advanced comparisons, you could expand the comparison code, for example like this:
if ($searchValue[0] == '<') { // implement } else if ($searchValue[0] == '>') { // implement } else if (stripos($compareValue, $searchValue) === false) { unset($data[$rowIndex]); }
RE: > < >= <= comparisons
Hi marcovtwout
you are right!
the direction of inequality were opposed
http://www.yiiframework.com/wiki/revision/?id=232&r1=6
I changed it on my project but I forgot to update the wiki :)
@KonApaz
Full comparison is fixed now - there still were some issues with it. I suppose it was just a typo appeared moving the code from your project - in order of variables names at the second level conditions.
RE: 1560
Hi yugene
In my first update I have the correct inequality
http://www.yiiframework.com/wiki/revision/?id=232&r1=5
You have update the same comparison that I wrote in the first time, reversing the order of variables. (Thanks!)
For example
substr($searchValue, 2) < $compareValue
and
$compareValue > substr($searchValue, 2)
Is the same thing.
I don't remember why I had agreed with
http://www.yiiframework.com/wiki/revision/?id=232&r1=6
Let explain the logic, is a little confusing for a lot of programmers.
if (substr($searchValue, 0, 2) == '<=') { if (substr($searchValue, 2) < $compareValue) { unset($data[$rowIndex]); } } ...
If:
the comparison in text(or whatever) is <=50 (we want the rows of which the specific column is equal or less than 50)
Then:
remove the row that ($compareValue > 50 or 50 < $compareValue) that means rows of which the specific column is greater than 50.
So the http://www.yiiframework.com/wiki/revision/?id=232&r1=6 or the currently updated wiki are the correct!
RE: update #8
In the updated line
!is_null($searchValue) $searchValue !== '')
I agree with $searchValue !== '' instead empty function
http://php.net/manual/en/function.empty.php (empty returns false for the number 0)
But, Is there case that is_null($searchValue) return true in this code ?
I have never seen such a case or I can't remember, dο you?
Thanks :)
@KonApaz
is_null was a precaution not to miss anything, so, yes, !== '' should work fine alone. Actually, I started with this check only first in my code, but then added is_null check.
Comparison -- yes, your first variant was a correct one as well :)
btw, don't know why, but new order of vars is easier for me to catch right away to understand if comparison is built correctly.
Re: is_null
It's not unexpected that someone would set a search value as null (as in, ignore), so I would definitely leave it in.
RE: Re: is_null
As far as i know (in this part of the code) the $searchValue is always a string (empty or not)
So I thing the "is_null" could be ommited
Re: RE: Re: is_null
When normally submitted through the html form, yes. But what happens if the value is arbitratily set to null? (for example, a programmer trying to override a certain search value). All results will be filtered. So leave it in there, and remember http://en.wikipedia.org/wiki/Defensive_programming
RE: Re: RE: Re: is_null
foreach ($this->filters AS $key => $searchValue)
protects for null $searchValue
how to inject 'null' value without $key ?
form[attr1] = value1
form[attr2] = value1
if (form[attr3] = value1;) is missing the above foreach not includes the attr3 in the iterator.
if form[attr3] has no-value will be empty string '' not null
So do you know how can pass null value (not 'null' string) in this case ?
Also if it nessesar to check the values, using rules and validate method are the suitable way.
What validators ? (#15274)
According to models (as usually)
the validator for searching is
array('attr1, attr2, attr3', 'safe', 'on' => 'search'),
so no other special validator is required (in most cases)
Re: RE: Re: RE: Re: is_null
FiltersForm is currently not doing any validation, so you cannot apply any rules. Also, null is a perfectly valid value, it does not have to be rejected.
One possible use case where someone edits your controller action:
if (isset($_GET['FiltersForm'])) $filtersForm->filters=$_GET['FiltersForm']; // programmer Bob making sure that adminNote cannot be freely searched if (/*not admin*/) $filtersForm->filters['adminNote'] = null;
Ofcourse I agree setting and validating rules is the recommended way of validating search values.
This discussion is becoming too long for the small point I was trying to make, so I'm gonna leave it at this. Conclusion: defensive programming..
i would also add strong to the found objects
add this code to make the found string be bold:
if (stripos($compareValue, $searchValue) === false) { unset($data[$rowIndex]); } else { $data[$rowIndex][$key] = str_ireplace($searchValue,'<strong>'.$searchValue.'</strong>',$data[$rowIndex][$key]); }
Re: i would also add strong to the found objects
Please, don't do it like that. Add highlighting at output time, not at filter time.
If you really want to add highlighting while processing the filter, at least store the results in a different array. And don't forget CHtml::encode..
filter row doesn't show
Hi, this is exactly what I needed, but it doesn't run for me.
I've implemented the exact code you specified, with just one difference: in the controller, instead of this row, that returns array of active record
I used this one, that returns array of array
I don't have an active record; if so, I don't need to use this code to have filters on grid.
However, filter row doesn't appear in the grid. What's wrong?
Yii2 ?
Does anybody have experience how to do this with yii-2?
gb5256
Empty lines in CGridView
Hi!
Thanks for the solution, it works flawlessly! Though I experienced the following in 1.1.16:
After filtering, you have empty lines in the result list if you skipped eg. the first 10 rows. You have to reindex the numerical keys.
From:
return $data;
To:
return array_values($data);
For data as array of objects:
elseif (is_object($row)) { if (!property_exists($row, $key)) { throw new \CException("Key {$key} does not exist in object!"); } $compareValue = $row->{$key}; }
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.