CGridView Filter with AutoComplete

Hi all,

my question is, how I can get an AutoComplete feature (i.e. CJuiAutoComplete) as input field in CGridView filter bar? Would it even be possible to use the ComboBox variant of this widget (jqueryui.com/demos/autocomplete/#combobox)?

Thanks a lot,

mubo

Hi there,

I don’t know how about jQuery Auto Complete with or without combobox mode, but adding typical combo box instead of normal search input box is for sure possible - and with current implementation of CGridView - you don’t even have to install any additional extension.

Sorry - I don’t have time for digging documentation for you right know. But I’m pretty sure that if you read CGridView description in Class Reference thoroughly and do some search in this forum, you will for sure find answer as I saw many examples of doing so with only pure CGridView.

Cheers,

Trejder

Sorry, maybe I should have described my problem a little bit more detailed. IMHO the difference between a combo box and a dropdown list is that former one offers the additional possibility to type in a textfield to select between different entries from the included dropdown list. This is especially usefull for long lists with many entries where scrolling takes longer than typing parts of the desired entry. That’s the reason why I’m looking for either AutoComplete (with or without combobox) or a standard combobox (which AFAIK doesn’t exist in pure html).

I know how to get a simple dropdown list in CGridView filter bar:


'filter' => CHtml::listData(SomeModel::model()->findAll(), 'id', 'someField'),

But my problem starts if I want to use CJuiAutoComplete instead. The Class Reference suggests that it is possible to specify HTML code for the filter property. I just don’t know how to retrieve the HTML code for CJuiAutoComplete to use it as filter property value. This is my working code for CJuiAutoComplete outside CGridView:




$this->widget('zii.widgets.jui.CJuiAutoComplete', array(

	'name'=>'someField-ac',

	'value'=>'',

	'model'=>$model->someRelation,

	'attribute'=>'someField',


	'source'=>$this->createUrl('someModelController/autoCompleteId'),


	// additional javascript options for the autocomplete plugin

	'options'=>array(

		'showAnim'=>'fold',

						

		'select'=>"js:function(event, ui) {

			$('#OtherModels_someField_id').val(ui.item.id)

			}",

		),

	));



someModelController/autoCompleteId is retrieving the JSON data for AutoComplete plugin:




/**

 * Performs AJAX AutoComplete request

 * returns array including IDs

 */

public function actionAutoCompleteId() 

{

	$res =array();

	$arr =array();


	if (isset($_GET['term'])) {

		// http://www.yiiframework.com/doc/guide/database.dao

		$condition ="SELECT id, someField FROM {{someModel}} WHERE someField LIKE :someField";

		$params = array(":someField"=>'%'.$_GET['term'].'%');

		$rows = SomeModel::model()->findAllBySql($condition,$params);


		foreach ($rows as $row)

		{

			$arr[] = array(

				'label'=>$row->someField,  // label for dropdown list

				'value'=>$row->someField,  // value for input field

				'id'=>$row->id,            // return value from autocomplete

			);

		};

	}


	echo CJSON::encode($arr);

	Yii::app()->end();

}



I was looking around for quite a while before posting but couldn’t find what I was looking for, just code for simple dropdown list. As I’m just doing my first steps with yii, maybe it is quite easy to achieve what I’m looking for. Would be very nice if somebody could help me!

Best regards, mubo

PS: Another question would be how to get the ComboBox option of jquery autocomplete implemented, but I would be first satisfied with just the standard CJuiAutoComplete replacing textfield or dropdown list in CGridView filter bar respectively.

EDIT: PPS: If it makes any difference the solution could also use ejui-autocomplete-fk-field extension.

Just a notice, in case you would omit this. CHtml::listData is only a helper class for generating combo-box-compatibile array code out of model. You can of course leave it and write you own method for generating this code.

My opinion is that you can’ do this using base CGridView component and the only way I would see to solve it, is to write own widget class extending from original CGridView, where you would overload all methods related for generating filter row to suit your needs - i.e.: for using CJuiAutoComplete (in any mode) or any other extension instead of build-in one combobox/input field.

… i.e. there is NO way to retrieve the HTML code of CJuiAutoComplete widget in a way to use it as filter property value? Or is there any problem with loading the corresponding javascripts then? Seems to be a strange limitation of CGridView to me.

First of all. There’s always a way to get result (output) of any HTML generating class/function, not only those out of Yii - just prevent buffer from being send to a browser, capture it, do whatever you want with it and then finally send it to browser. Look for these functions - ob_get_clean, ob_clean and ob_start. And similar.

Second of all. It is hard to directly answer you, if this, what you are saying is a true limitation of CGridView? When I begin my journey with Yii I was thinking just like you wrote. That anything or nearly anything must be possible with base widgets / framework classes and anything that was not possible, was missing, I was considering as flaw. Right know I changed my mind and I’m thinking that whatever I want to have and what is not there, I can achieve by overwriting / overloading basic implementations with my own one. In fact, that is probably most powerful feature of object-oriented programming and the sense of using frameworks.

Remember that, whatever Yii is, it is just a skeleton with implementation in the first place as open for overloading as possible and with satisfying as many user-made implementations / overloads as possible. So try to not consider it as a flaw but rather like an open gate leaving you all the power of class overloading to do whatever you want to do.

This concepts comes out of that right know you only need to change basic input box / combobox with CJuiAutoComplete. But during developing process I’m pretty sure that there will be more and more things that you find in CGridView or any other class missing / different that you would like to have it. And in most cases (I would even assume that 9 out of 10) you will realize that overloading CGridView with own class is the only sensible way of programming.

That’s for the whole picture. And to answer your particular question. Remember that CJuiAutoComplete is generating everything you need, including inputbox to which autocomplete feature will be attached (I was kind of surprised finding this). But it is again just another helper class and you don’t have to use it at all. In your case I would take a look at CGridView/.filterCssClass, I would add a specific class name to it and then I would register my own jQuery code (using ClientScript component) that would catch filters box in that filter row and add auto-complete feature to them on your own, using pure jQuery code and not looking at CJuiAutoComplete in this case.

Thanks a lot for that great answer first!

I intend to try both approaches as I like the way of CForm I can specify there any CInputWidget/CJuiInputWidget class as input element type property. This offers great possibilities and works flawless e.g. for CJuiAutoComplete or EJuiAutoCompleteFkField (with bug fix applied as posted in bug reports section). Maybe I just have to implement that feature for CGridView filter bar. ::)

Good luck then, and if anything goes wrong - don’t hesitate to ask here. There are a lot of cool people here, always wanting to help! :]

A bit off-topic: Did you use this sign - ::slight_smile: - intentionally or by mistake? Because I like it very much and I’m also using it and I thought I was the only one! ::slight_smile:

There is now the situation that I got stuck with it.

My implementation of an extended DataColumn class looks like this:




<?php


Yii::import('zii.widgets.grid.CDataColumn');

class EDataColumnExt extends CDataColumn {


	public function renderFilterCellContent() {


		if($this->filter!==false && $this->grid->filter!==null && $this->name!==null && is_int(strpos($this->name,'.')))

		{

			if(isset($this->filter['type'])) {

				$type = $this->filter['type'];

				unset($this->filter['type']);

				$attributes=$this->filter;

// 				$attributes['model']=$this->getParent()->getModel();

// 				$attributes['attribute']=$this->name;

				ob_start();

				$this->grid->owner->widget($type, $attributes);

				echo ob_get_clean();

			}

// 			else

// 				echo $this->filter;

		}

		else

			parent::renderFilterCellContent();


	}


}


?>



This basic implementation just covers relational columns which aren’t supported by CDataColumn::renderFilterCellContent() at all. My aim was to provide support for any CInputWidget or CJuiInputWidget as filter input widget as in CForm.

This works e.g. for CJuiAutoComplete:




'columns'=>array(

		array(

			'class'=>'EDataColumnExt',

			'name'=>'relation.field',

			'filter'=>array(

				'type'=>'zii.widgets.jui.CJuiAutoComplete',

				'model'=>SomeModel::model(),

				'attribute'=>'field',

				'source'=>Yii::app()->createUrl('controller/autoCompleteId'),


				'options'=>array(

						'showAnim'=>'fold',

				),

			),

		),

),



For real filter functionality there has to be an additional hidden field to store the foreign key in as implemented in EJuiAutoCompleteFkField. This works with some minor modifications.

The problem now is that autoComplete only works until any ajax update has been performed (filtering, sorting, paging). Other javascript functions bound to the corresponding input fields are still called (correctly) afterwards.

It would be great if anybody would have an idea how to deal with this issue!

Best regards, mubo

After some discussion on irc the problem is identified to be unbinding of events upon ajax update. In principle there is a way to circumvent this by using jQuerys .live() event handler attachment. CJuiAutoComplete is not using this approach. Although CJuiAutoComplete class doesn’t contain much code I don’t want to change it, but would like to use as it is. Same problem could rise again with next widget not making use of .live(). So I was thinking of making a call to the jQuery(function($) { … } stuff (which should usually be loaded after DOM is ready) from within afterAjaxUpdate. I tried this in CGridView configuration array:


'afterAjaxUpdate' => "js:function(id, data) {\$(document).ready();}",

But indeed it is not working (I guess $(document).ready() is just the wrong function to call, but I don’t know better). Is there anybody who knows how to deal with this issue?

Thanks a lot in advance!

mubo

After having read about the global jquery ajaxSuccess event my idea is to extend CJuiAutoComplete as follows:




<?php


Yii::import('zii.widgets.jui.CJuiAutoComplete');


class EJuiAutoCompleteExt extends CJuiAutoComplete {


	public function run()

	{

		$reinitAfterAjaxUpdate = false;


		if(isset($this->options['reinitAfterAjaxUpdate'])) {

			$reinitAfterAjaxUpdate = $this->options['reinitAfterAjaxUpdate'];

			unset($this->options['reinitAfterAjaxUpdate']);

		};




		parent::run();




		if($reinitAfterAjaxUpdate) {

	

			// some things needed from parent have to be done here again

			list($name,$id)=$this->resolveNameID();


			if($this->sourceUrl!==null)

				$this->options['source']=CHtml::normalizeUrl($this->sourceUrl);

			else

				$this->options['source']=$this->source;


			$options=CJavaScript::encode($this->options);




			// global ajaxSuccess event is used here 

			// check if autocomplete is enabled for selector

			// in case it is not initialized this will be done now

			$js = "$('div#content').ajaxSuccess(

					function(event,request,settings){

						var init = (!(!($('#{$id}'))) && ($('#{$id}').autocomplete( 'option', 'disabled' ) == false));

						if(!init) {

							jQuery('#{$id}').autocomplete($options);

						};

					}

				);";


			$cs = Yii::app()->getClientScript();

			$cs->registerScript(__CLASS__.'#'.$id.'_ajaxSuccess', $js, CClientScript::POS_READY);


		}


	}


}


?>



Basically it implements the option reinitAfterAjaxUpdate. If this is set true ajaxSuccess is attached to div#content and implements a js function checking if autocomplete is initialized for my CJuiAutoComplete widget. If not (which is the case after ajaxUpdate) autocomplete is reinitialized.

This works right now. But I am not happy about the attachment of ajaxSuccess to div#content. Is there any better choice? My autocomplete widget is not a good choice as ajaxSuccess will then not be called after ajaxUpdate. In jquery 1.5.1 (yii 1.1.7) I could maybe inject this js to the grid view ajax success event directly as it accepts an array of callbacks then. The second is if the way I check if autocomplete is initialized is "good" style?

Any comments are highly appreciated!

Bests, mubo

that’s funny ::)

I am having exactly the same situation like Mubo,

Although the extending seems appealing but I think that using Trejder approach is neater and more straightforward and way much easier. I’ll go for it. ::)

Hi

I i’m trying to setup a cgridview with a cjuiautocomplete in the filters and an action to filter the content according to user input

I managed to get to the point where the filter appears as it should and the autocomplete works as it should with the calls to the autocomplete action to flter the correct fields according to user input

However what happens is that when and option is selected from the cjuiautocomplete dropdown the value is copied to the filter field but this happens before the Cgridview URL is built which means that the cgridview ajax update does not include the selection from the cjuiautocomplete dropdown but instead it includes only the user input in the filter text field.

I managed to find out that this is because the event that triggers the cgridview ajax update is .change() (live event in the body element) and i believe that this event is triggered when the filter text field looses focus.

Am i right ?

How can i solve this ?

My Cgridview definition looks likes this:





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

	'id'=>'activations-grid',

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

	'filter'=>$model,

	'columns'=>array(

		array(

        	'name'=>'id_client',

        	'value'=>'CHtml::value($data,\'idClient.name\')',

			'filter'=> $this->widget('zii.widgets.jui.CJuiAutoComplete', array(

				'model'=>$model,

				'attribute'=>'client_name',

				'source'=>$this->createUrl('/backoffice/clients/autoComplete'),

				// additional javascript options for the autocomplete plugin

				'options' => array(

					'showAnim'=>'fold',

				),

				'htmlOptions' => array(

				),

			),true),

		),

...




hi Artur Oliveira!

I use this too. auto complete work but why it not search?? sorry for my bad english.

Thank you for your hack!

But your js code fails with error and binded to $(‘div#content’)

I modified it to:




$(document).ajaxSuccess(function(event,request,settings){

	var url = settings.url;

	if(url.split('&ajax=').length > 1) {

		var parts = url.split('&ajax=');

		var gridView = $('#' + parts[1]);

		if(gridView.length > 0) {

			jQuery('#{$id}').autocomplete($options);

		}

	}

});



I figure it out why this code work but searching data didn’t work. The problem is YII CGridView generates ajax by using:/ukmdata/index?UKMProfil[nama_ukm]=Contoh UKM&UKMProfil[alamat_ukm]=&UKMProfil

following this pattern:ModelName[field_name]=search_value … etc

so, all I need is simply change name property in CJuiAutoComplete widget. something like this:




	array(

		'name'=>'nama_ukm',

		'value'=>'CHtml::value($data,\'nama_ukm\')',

		'type'=>'raw',

		'filter'=> $this->widget('zii.widgets.jui.CJuiAutoComplete', array(

			'name'=>'UKMProfil[nama_ukm]', 

			'attribute'=>'nama_ukm',

			'source'=>$this->createUrl('ukmdata/Ukmjson'), 

			'options' => array('showAnim'=>'fold','minLength'=>'3',),

			'htmlOptions' => array(),

		), true),

	),