Custom Autocomplete Display and Value Submission

  1. Introduction
  2. Requirements
  3. Making the right choice
  4. My Solution
  5. Final Notes

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

12 0
18 followers
Viewed: 61 775 times
Version: 1.1
Category: How-tos
Written by: Antonio Ramirez
Last updated by: SebK
Created on: Jul 14, 2011
Last updated: 12 years ago
Update Article

Revisions

View all history

Related Articles