On this post, the Yii blog demo, which comes with the Yii download, will be used as an example.
There is a question I heard a couple of times on the Yii channel @ FreeNode. The question we're talking about is: 'How do I add a button (preferably an AJAX-button) to my page which filters the CGridView?'. I'll be answering this question in this post.
Scenario
When we open up 'index.php?r=post/admin' in our blog demo, we'll see that we have a CGridView with a bunch of posts in it. What we want to do is, get rid of the Post Status dropdown and add 'Draft', 'Published', 'Archived' buttons above our CGridView. When we click 'Draft', only drafts will be displayed. The same goes for 'Published' and 'Archived'. We don't want the complete page to be refreshed while doing this.
STEP 1: Adding an ID to the CGridView
We'll start by adding an ID to our CGridView. This will make things much easier later on.
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'post-grid', // This line is added!
'dataProvider'=>$model->search(),
'filter'=>$model,
...
STEP 2: Adding the buttons
Next we'll add the buttons that will do the filtering. You can place them anywhere. I decided to put them above the CGridView. The buttons won't do anything for now.
echo CHtml::button('Draft');
echo CHtml::button('Published');
echo CHtml::button('Archieved');
Add those lines above your CGridView, refresh and you'll see the 3 buttons. When you click them nothing will happen.
STEP 3: Adding the filtering functionality
In this step we'll be make the buttons filter the CGridView results. For this function to work, I've written a small JavaScript function which I register by calling 'CClientScript::registerScript' :
Yii::app()->clientScript->registerScript('updateGridView', '
$.updateGridView = function(gridID, name, value) {
$("#"+gridID+" input[name="+name+"], #"+gridID+" select[name="+name+"]).val(value);
$.fn.yiiGridView.update(gridID, {data: $.param($("#"+gridID+" .filters input, #"+gridID+" .filters select"))});
}
', CClientScript::POS_READY);
Basically, what it does is: Fill in the value in the right filter and refresh the CGridView.
Now change your buttons code to this:
echo CHtml::button('Draft', array(
'onclick'=>
'$.updateGridView("post-grid", "Post\[PostStatus\]", "'.Post::STATUS_DRAFT.'"); '
));
echo CHtml::button('Published', array(
'onclick'=>
'$.updateGridView("post-grid", "Post\[PostStatus\]", "'.Post::STATUS_PUBLISHED.'"); '
));
echo CHtml::button('Archived', array(
'onclick'=>
'$.updateGridView("post-grid", "Post\[PostStatus\]", "'.Post::STATUS_ARCHIVED.'"); '
));
Here we simply call the updateGridView function with the right parameters. Save, refresh and you'll see the buttons do it's job.
If you will keep the filter for the PostStatus inside your CGridView, you'll be done now and you won't have to follow the next steps. Else, continue to step 4.
STEP 4: Removing the filter from the CGridView
In this step we'll be removing the Status filter from the CGridView without breaking the filtering functionality we just added. If we simply remove the filter without doing anything else, the buttons we just created will throw a JavaScript error when you try to use them. This makes sense, because when we click the button, it will try to set the value of the dropdown. But since the dropdown is not there anymore you'll get that error. To avoid this from happening we have to do a couple of things. First we'll start by adding a hidden field to the filters. This hidden field will keep the value for the filter. And so instead of changing the value of the dropdown, we'll be changing the value of the hidden field. We do this by adding a new JavaScript function and calling it immediately:
Yii::app()->clientScript->registerScript('updateGridView', '
$.appendFilter = function(name) {
$("#post-grid .filters").append("<input type=\"hidden\" name=\""+name+"\" />");
}
$.appendFilter("Post[status]");
', CClientScript::POS_READY);
Save. Refresh. Now fire up firebug. Browse to TD with the filter class inside your CGridView. You'll see that at the end of this TD, a hidden inputfield is added. Now click on one of the 3 buttons and watch carefully what happens: the input field is gone!!! The reason why this happens is because as soon as we call $.fn.yiiGridView.update , the complete gridview is renewed. Which means our filter is not there because we didn't add it after updating. To get this right, we'll have to make use of the afterAjaxUpdate event on the CGridView. We'll be doing that like this:
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'post-grid',
'afterAjaxUpdate'=>'function(slash, care) { $.appendFilter("Post[status]"); }',
Save. Refresh. Fire up firebug, browse to your new hidden filter. Now click one of the filter buttons. You'll notice that the value of the input field is empty after we update. This happens because after an ajax call we add a new input field. So if you click the Archived button now, you shouldn't see any posts. But when you enter 'T' in the input box for the post title, you'll see a post popping up. Because it forgets the value of the input box, it will search in all posts. What we want in this case, is to just search inside the 'Archived' posts. We can do this by simply remembering the value of the filter box we added.
We'll have to change our javascript functions/calls for this:
Yii::app()->clientScript->registerScript('appendFilter', '
$.postFilterStatus = "";
$.appendFilter = function(name, varName) {
var val = eval("$."+varName);
$("#post-grid .filters").append("<input id=\""+varName+"\" type=\"hidden\" name=\""+name+"\" value=\""+val+"\" />");
}
$.updateGridView = function(gridID, varName, value) {
$("#"+varName).val(value);
eval("$."+varName+"=value;");
$.fn.yiiGridView.update(gridID, {data: $.param($("#"+gridID+" .filters input, #"+gridID+" .filters select"))});
}
$.appendFilter("Post[status]", "postFilterStatus");
', CClientScript::POS_READY);
We're now keeping a jQuery variable named 'postFilterStatus'. This variable basically stores the value of the input field. The appendFilter function is adjusted to take the name of that variable. The updateGridView is also adjusted to take the name of that variable, but it also populates that variable when it's called.
We now change our buttons to:
echo CHtml::button('Draft', array(
'onclick'=>'
$.updateGridView("post-grid", "postFilterStatus", "'.Post::STATUS_DRAFT.'");
'));
echo CHtml::button('Published', array(
'onclick'=>'
$.updateGridView("post-grid", "postFilterStatus", "'.Post::STATUS_PUBLISHED.'");
'));
echo CHtml::button('Archived', array(
'onclick'=>'
$.updateGridView("post-grid", "postFilterStatus", "'.Post::STATUS_ARCHIVED.'");
'));
...and our afterAjaxUpdate to:
'afterAjaxUpdate'=>'function(slash, care) { $.appendFilter("Post[status]", "postFilterStatus"); }',
Save. Refresh. And you got it!
Now if your CGridView always has at least 1 default filter, you can stop here. If you don't always have 1 default filter or if you're not sure, move on to step 5.
STEP 5: Adding the ability to use filter buttons without any default filters
In this step I'll explain how to get the buttons to work in all times. So after this step, your buttons will be able to filter no matter if the CGridView has it's default filters or not.
We'll start by adding 2 divs that will keep the hidden fields in it:
<div id="filtersParent" style="display:none;"><div class="filters"></div></div>
We'll start by changing the appendFilter function so that it takes the id of the parent of the filters div.
$.appendFilter = function(filterParentID, name, varName) {
var val = eval("$."+varName);
var stringToAppend = "<input id=\""+varName+"\" type=\"hidden\" name=\""+name+"\" value=\""+val+"\" />";
$("#"+filterParentID+" .filters").append(stringToAppend);
}
We also need to update the updateGridView function so that it also takes a parent ID:
$.updateGridView = function(gridID, filterParentID, varName, value) {
$("#"+varName).val(value);
eval("$."+varName+"=value;");
$.fn.yiiGridView.update(gridID, {data: $.param($(
"#"+grid+" .filters input, "+
"#"+grid+" .filters select, "+
"#"+filterParentID+" .filters input, "+
"#"+filterParentID+" .filters select, "
))});
}
Now update the calls to appendFilter to:
$.appendFilter("filtersParent", "Post[status]", "postFilterStatus");
...and update the buttons to use the new updateGridView function:
echo CHtml::button('Draft', array(
'onclick'=>'
$.updateGridView("post-grid", "filtersParent", "postFilterStatus", "'.Post::STATUS_DRAFT.'");
'));
echo CHtml::button('Published', array(
'onclick'=>'
$.updateGridView("post-grid", "filtersParent", "postFilterStatus", "'.Post::STATUS_PUBLISHED.'");
'));
echo CHtml::button('Archived', array(
'onclick'=>'
$.updateGridView("post-grid", "filtersParent", "postFilterStatus", "'.Post::STATUS_ARCHIVED.'");
'));
Also change the afterAjaxUpdate to look like this:
...
'afterAjaxUpdate'=>'
function(slash, care) {
if ($("#postFilterStatus").length == 0)
$.appendFilter("filtersParent", "Post[status]", "postFilterStatus");
}',
...
Last but not least, disable default filter to proof that it works:
...
'filter'=>null,
...
Now save, refresh, enjoy Yii!