Yii 1.1: Custom Autocomplete Display and Value Submission

20 followers

Introduction

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.

Requirements

For our example, I want to be able to:

  1. Have an autocomplete field in our form
  2. Once user selects an item in the dropdown list and fill a hidden box with the id of the selected item for submission

Making the right choice

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.

My Solution

After doing some research I decided to:

  • extend from CJuiAutoComplete
  • include the required property for method chain and modify its 'run' function
  • then initialize the newly created property with the javascript functions that handle my custom JSON

Extending from CJuiAutoComplete and make required modifications

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);
    }
}

Using our widget

Now that we have our beautiful widget that handles method chain in our Autocomplete, let's assume a couple of things:

  • We saved our class in our application folder - ie protected/extensions
  • We have a hidden INPUT HTML element with model's attribute_id
  • We have created an action on our testController named autocomplete that returns a JSON object of the following format:
// 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 :)

Final Notes

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

#11648 report it
cappadochian at 2013/01/24 04:08pm
cgridview

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

#6250 report it
manuel-84 at 2011/12/21 04:45pm
custom render

i hade to add this before the custom class declaration to get it work

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

the other ways without method chain can't do a custom render of the list

http://jqueryui.com/demos/autocomplete/#custom-data

#4571 report it
Ivica at 2011/07/21 08:02am
More about another way

I just saw to late your post, I spend few hours reading Yii source code, and I come with similar solution:

$this->widget('zii.widgets.jui.CJuiAutoComplete', array(
        'name'=>'customer_name',
        'sourceUrl' => Yii::app()->createUrl('customer/autocomplete'),
        'options'=>array(
            'minLength'=>'2',
            'select'=>'js:function( event, ui ) {
                $("#'.CHtml::activeId($model,'customer_id').'").val(ui.item.id);
                return false;
            }',
        ),
 
        'htmlOptions'=>array(
            'class'=>'input-1'
        ),
    ));

while data(json) from 'customer/autocomplete' must be in next format:

var customers = [
{label:'customer1', id: '1'},
{label:'customer2', id: '4'},
{label:'customer3', id: '5'},
]

I used 'id' as key for id, but I think you should get same results using value for key.

#4528 report it
Antonio Ramirez at 2011/07/17 07:30am
Other ways to do it

A yii'er told me another way to do it without method chain:

$this->widget('zii.widgets.jui.CJuiAutoComplete', array(
    //'model'=>$model,
    //'attribute'=>'name',
    'id'=>'country-chain',
    'name'=>'country_chain',
    'source'=>$this->createUrl('request/suggestCountry'),
    'options'=>array(
        'delay'=>300,
        'minLength'=>2,
        'showAnim'=>'fold',
        'select'=>"js:function(event, ui) {
            $('#label').val(ui.item.label);
            $('#code').val(ui.item.code);
            $('#call_code').val(ui.item.call_code);
        }"
    ),
    'htmlOptions'=>array(
        'size'=>'40'
    ),
));

Source: http://www.eha.ee/labs/yiiplay/index.php/en/site/widget?view=autocomplete

#4512 report it
Maurizio Domba Cerin at 2011/07/15 05:15am
hidden field

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 to leave your comment.

Write new article