Extended Autocomplete Helper
Ajax, CakePHP, Development, Frameworks, General, PHP September 3rd, 2007
When I started with CakePHP, my first project includes also Autocomplete functionality for some fields. Of course I start using default Ajax Helper provided from the library. The main problem was that in most of the cases I needed the ID of the string which I searched. The best example is the Country list which is very easy to be accessed with Autocomplete, but instead to messing up with strings as usual developer I wanted to have the ID of the specified country. I used some kind of hack by adding callback function onComplete, which checks the selection in the Autocomplete field, and by this selection set hidden ID field, but as you can imagine this is not the best choice, first of all because it’s a hack - you rely on a sting which already sound as a hack /imagine if you are searching in a list there are some duplicated entries - examples are too many/ and second if you rely on callback this mean that you need another second or two for the second responce. Anyway, by giving this examples just wanted to convince you once again that Autocomplete helper with Key and Value related are really necessary in the projects /or at least that’s what I am thinking./
Well so far the default Autocomplete helper doesn’t provide such functionality. That’s why I created this helper which solves this problem partly. Why partly? You will see the answer of this in Strict Autocomplete with Scriptaculous (Part II)
Ok, let’s taste the my recipe.
Here is the helper:
* Autocomplete Helper
*
* @author Nik Chankov
* @website http://nik.chankov.net
* @version 1.0.0
*/
class StrictAutocompleteHelper extends AjaxHelper {
var $helpers = new Array('Form');
/**
* The Main Function for autocomplete
*
* @param string $field Name of the database field. Possible usage with Model.
* @param string $url Url of the Ajax Request. Possible null. Then the Ajax will get the current URL.
* @param array $options Optional Array. Potions are the same as in the usual text input field.
* Additional option is to determine autocomplete as ID or not - boolean $options['strict'].
*/
function autoComplete($field, $url = null, $options = array()){
$var = '';
$idField = '';
$checkField = '';
$strict = true;
//Get Ajax.Autocomplete in a var /if the variable name is set/
if (isset($options['var'])) {
$var = 'var ' . $options['var'] . ' = ';
unset($options['var']);
}
if (isset($options['strict'])) {
$strict = $options['strict'];
unset($options['strict']);
}
//Equalize the URL if empty to current URL
if($url === null){
$url = $this->Html->params['url']['url'];
}
//Camelize the ID
if (!isset($options['id'])) {
$options['id'] = Inflector::camelize(r("/", "_", $field));
}
//Add id and check fields ids
$options_id = Inflector::camelize(r("/", "_", $field.'_id'));
$options_check = Inflector::camelize(r("/", "_", $field.'_check'));
$divOptions = array('id' => $options['id'] . "_autoComplete", 'class' => isset($options['class']) ? $options['class'] : 'auto_complete');
if (isset($options['div_id'])) {
$divOptions['id'] = $options['div_id'];
unset($options['div_id']);
}
$htmlOptions = $this->__getHtmlOptions($options);
$htmlOptions['autocomplete'] = "off";
if($strict == true){
$options['afterUpdateElement'] = "function(text, li){\$('".$options_id."').value = li.id;\$('".$options_check."').value = text.value;}";
}
foreach ($this->autoCompleteOptions as $opt) {
unset($htmlOptions[$opt]);
}
if (isset($options['tokens'])) {
if (is_array($options['tokens'])) {
$options['tokens'] = $this->Javascript->object($options['tokens']);
} else {
$options['tokens'] = '"' . $options['tokens'] . '"';
}
}
$options = $this->_optionsToString($options, array('paramName', 'indicator'));
$options = $this->_buildOptions($options, $this->autoCompleteOptions);
//preparing Fields
if($strict == true){
$idField = $this->Form->input($field.'_id', array('label'=>'field id', 'id'=>$options_id, 'type'=>'hidden'));
$checkField = $this->Form->input($field.'_check', array('label'=>'field check', 'id'=>$options_check, 'type'=>'hidden'));
}
$oriField = $this->Form->input($field, $htmlOptions);
//prepare the output
$return = "";
$return .=
$oriField.
$idField.
$checkField.
$this->Html->div(null, '', $divOptions) . "\n" .
$this->Javascript->codeBlock("{$var}new Ajax.Autocompleter('" . $htmlOptions['id']
. "', '" . $divOptions['id'] . "', '" . $this->Html->url($url) . "', " .
$options . ");"). "\n";
if($strict == true){
$return .= $this->Javascript->codeBlock("
Event.observe(\"".$htmlOptions['id']."\", \"blur\", function (event){
var element = Event.element(event);
if($(element.id).value != $('".$options_check."').value){
$(element.id).value = '';
$('".$options_id."').value = '';
$('".$options_check."').value = '';
}
}
);
//Clean the ID and Check if there is change in the Autocomplete Field
Event.observe(\"".$htmlOptions['id']."\", \"keyup\", function (event){
var element = Event.element(event);
if($(element.id).value != $('".$options_check."').value){
//$(element.id).value = '';
$('".$options_id."').value = '';
$('".$options_check."').value = '';
}
}
);
");
}
return $return;
}
}
Here is how to use it:
In the controller you should add this helper in $helpers class variable:
...
var $helpers = array('Html', 'Form', 'Ajax', 'Javascript', 'StrictAutocomplete');
...
}
Then in the View you should add following line:
Where:
- ‘autocomplete’ is name of the field. The format could be also Model/field instead just field.
- ‘ajax_autocomplete’ is the url of the Ajax Response. In that Example the url is in the same controller. It’s also possible to leave this variable null. Then it will take the current url as Ajax Response url.
- third parameter is the options tag, which is the same as normal Form->input(field, options) field.
The only difference between them is the extra parameter:
which determine the field as well known behaviour of the Autocomplete helper /false/, or the “Strict” usage when also ID is needed /true/.
So far this is not complete Ajax replica of the Select HTML tag, because there are few know issues. If you want to know more you should take a look on Strict Autocomplete with Scriptaculous (Part II) article in my blog where I explained what are the bugs and the ways to escape them.
You can see that Helper in action, slightly modified so you can see the control fields, while in the real helper they are hidden - here
Hope this helps someobody.
Add to:


[...] ways - form submit through Ajax, Event observer or build in Autocomplete. I have an article about Autocomplete with ID=>Value relation which you can take a look. CakePHP uses Ajax with Prototype and [...]
[...] ways - form submit through Ajax, Event observer or build in Autocomplete. I have an article about Autocomplete with ID=>Value relation which you can take a look. CakePHP uses Ajax with Prototype and [...]
Hey Guy,nice little thing…..okay,it looks like its nice,but it actually doesnt work.
I always get these two errors:
Notice: Undefined property: StrictAutocompleteHelper::$Form in /Users/timbuchwaldt/Sites/cake/app/views/helpers/strict_autocomplete.php on line 73
Fatal error: Call to a member function input() on a non-object in /Users/timbuchwaldt/Sites/cake/app/views/helpers/strict_autocomplete.php on line 73
when i use it. Maybe i did something wrong,so here is what i did:
added the line with the helpers-array, added this
autoComplete(’autocomplete_actor’, ‘Movie/autocomplete’, array(’label’=>’Demo Autocomplete Strict’, ’strict’=>true))?>
to my index view , im having the autocomplete function(i use the one from cakephp.org)
Hi Tim,
this error could be caused only from one thing: Form helper is not attached in the class. I already modified the helper by adding:
which should solve this fatal error.
Hope you working with CakePHP 1.2, because this helper is written for that version of Cake.
thanks for sharing…
however i still confuse where & how to put the ID (keys for displayed autocomplete items)? Am I miss something?
Huzz,
helper just set the field from the first parameter of the function to be hidden and visible autocomplete field is just for visualization, and should not be used in the processing part in the controller.
The important part is the second parameter of the function /which is the url which should be called when the user start typing in the field/ and it should point to an action with ajax layout with format like this:
<li id="1">Value 1</li>
<li id="2">Value 2</li>
<li id="3">Value 3</li>
...
<li id="n">Value N</li>
</ul>
I leave to your imagination how to create it with controller and view
of course you can see the example:
http://nik.chankov.net/examples/playground/test/autocomplete
Bear in mind that this is working with modified version of scriptaculous, so it’s not 100% compliant to the last version of scriptaculous.
Hope this make more sense.
I am a noob to Cake and I have gotten this to work. But is there a way to post exact code used to generate your:
http://nik.chankov.net/examples/playground/test/autocomplete
page? I would like to duplicate this page exactly and I am having some problems
#Cory - could you tell me where you experiencing problems with this? Bear in mind that Scriptaculous js is a modified version and is not 100% the same as latest version on the Script.aculo.us site and I guess that the problem is this.
Another hint I give in my previous comment - where I explained what should return the url specified in the second parameter.
newbie here..
Error:
Fatal error: Call to a member function div() on a non-object in APP\views\helpers\strict_autocomplete.php on line 95
i’m lost. what should i do?
about the second parameter.
could you please elaborate more on that.
what files do i have to create, and where should i put them?
thanks in anticipation
So Cory, your error show me that you working on CakePHP1.1.x.x while this and all articles related to CakePHP in this blog are for CakePHP 1.2
About the second parameter:
if you see this url:
http://nik.chankov.net/examples/playground/test/ajax_autocomplete
this is the second parameter’s page.
The code generated this page is similar to this:
$this->layout = 'ajax';
$this->cleaner = new Sanitize();
$this->data = $this->cleaner->clean($this->data);
$countries = $this->Country->findAll(array("name like '".$this->data[0]['autocomplete_auto']."%'"));
$this->set('countries', $countries);
}
and then make one view which will do ul and li tags.
Hope now is more clear…
thanks for the ajax_autocomplete…
so i tried adding the code to my TestController and creating one view that contains ul and li tags..
but, i still have the error:
Fatal error: Call to a member function div() on a non-object in APP\views\helpers\strict_autocomplete.php on line 95
what is causing this ?
what should i do to resolve this problem ?
As I told you - you using CakePHP v 1.1 while I am using CakePHP 1.2.
The error coming, because in 1.1 there is no function div() in the html helper.
if you just start using CakePHP I strongly recommend you to switch to 1.2 because it’s more reach and this is the future of the framework. 1.1 just support old apps which already used it.
[...] igual que los componentes también podemos desarrollar nuestros propios helpers, aqui tenemos un buen ejemplo donde se crea un helper para autocompletar un input text mediante ajax [...]
Got PHP errors with CakePHP v1.2.0.7125 RC1, changed line 12:
var $helpers = new Array(’Form’);
change to:
var $helpers = Array(’Form’,'Html’,'Javascript’);
I don’t have such error. But I checked my code and look like I’ve added these helpers in my controller class
works great