How many of us has wondered how to create an autocomplete that will display the names of a related models but do require the id of that selected name to be submitted for model creation/update?
I was looking around wiki and found that was no approach as the one I did so I guessed this is worth to write.
For our example, I want to be able to:
To setup the autocomplete was a very straight forward operation, but I couldn't figure out how to get values from a custom JSON response and then fill the correspondent hidden fields.
CAutoComplete does have a way to do it, but I wanted to use CJuiAutoComplete to get all the cool features of JQuery UI and by looking at his code there was no method chain, something that is required to work with custom JSON responses as we need to override some methods.
After doing some research I decided to:
Very simple, we are going to add a methodChain property and modify the run function to include it (zii is not a major concern to Yii, but main developers should think about this minor change).
class myAutoComplete extends CJuiAutoComplete { /** * @var string the chain of method calls that would be appended at the end of the autocomplete constructor. * For example, ".result(function(...){})" would cause the specified js function to execute * when the user selects an option. */ public $methodChain; /** * Run this widget. * This method registers necessary javascript and renders the needed HTML code. */ public function run() { list($name,$id)=$this->resolveNameID(); if(isset($this->htmlOptions['id'])) $id=$this->htmlOptions['id']; else $this->htmlOptions['id']=$id; if(isset($this->htmlOptions['name'])) $name=$this->htmlOptions['name']; if($this->hasModel()) echo CHtml::activeTextField($this->model,$this->attribute,$this->htmlOptions); else echo CHtml::textField($name,$this->value,$this->htmlOptions); if($this->sourceUrl!==null) $this->options['source']=CHtml::normalizeUrl($this->sourceUrl); else $this->options['source']=$this->source; $options=CJavaScript::encode($this->options); $js = "jQuery('#{$id}').autocomplete($options){$this->methodChain};"; $cs = Yii::app()->getClientScript(); $cs->registerScript(__CLASS__.'#'.$id, $js); } }
Now that we have our beautiful widget that handles method chain in our Autocomplete, let's assume a couple of things:
// This function will echo a JSON object // of this format: // [{id:id, name: 'name'}] public function actionAutocomplete(){ $res = array(); $term = Yii::app()->getRequest()->getParam('term', false); if ($term) { // test table is for the sake of this example $sql = 'SELECT id, name FROM {{test}} where LCASE(name) LIKE :name'; $cmd = Yii::app()->db->createCommand($sql); $cmd->bindValue(":name","%".strtolower($term)."%", PDO::PARAM_STR); $res = $cmd->queryAll(); } echo CJSON::encode($res); Yii::app()->end(); }
We have everything, let's use our widget in our view:
// REMEMBER, we have a hidden // input HTML element with model's attribute_id <?php echo $form->hiddenField($model, 'attribute_id'); <?php // ext is a shortcut for application.extensions $this->widget('ext.myAutoComplete', array( 'name' => 'test_autocomplete', 'source' => $this->createUrl('test/autocomplete'), // attribute_value is a custom property that returns the // name of our related object -ie return $model->related_model->name 'value' => $model->isNewRecord ? '': $model->attribute_value, 'options' => array( 'minLength'=>3, 'autoFill'=>false, 'focus'=> 'js:function( event, ui ) { $( "#test_autocomplete" ).val( ui.item.name ); return false; }', 'select'=>'js:function( event, ui ) { $("#'.CHtml::activeId($model,'attribute_id').'") .val(ui.item.id); return false; }' ), 'htmlOptions'=>array('class'=>'input-1', 'autocomplete'=>'off'), 'methodChain'=>'.data( "autocomplete" )._renderItem = function( ul, item ) { return $( "<li></li>" ) .data( "item.autocomplete", item ) .append( "<a>" + item.name + "</a>" ) .appendTo( ul ); };' ));
Done! Just make sure that when you do submit your form, you get the value from the hidden field instead of the autocomplete element :)
I do not know if there are other ways of doing the same thing (apart from pure Javascript) to have the same results. If you know, with CJuiAutoComplete widget, let us know here.
Hope you find this wiki useful.
Cheers
Total 5 comments
is there a way to implement this into cgridview? I'm trying but it's not working, but I'm only a beginner. can you please help? thanks
i hade to add this before the custom class declaration to get it work
the other ways without method chain can't do a custom render of the list
http://jqueryui.com/demos/autocomplete/#custom-data
I just saw to late your post, I spend few hours reading Yii source code, and I come with similar solution:
while data(json) from 'customer/autocomplete' must be in next format:
I used 'id' as key for id, but I think you should get same results using value for key.
A yii'er told me another way to do it without method chain:
Source: http://www.eha.ee/labs/yiiplay/index.php/en/site/widget?view=autocomplete
For this solution to be part of the core... there would need to be a way to handle the hidden field automatically... any idea for this ?
Leave a comment
Please login to leave your comment.