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 $helpersnew 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:

class TestController extends AppController {
...
var $helpers = array('Html', 'Form', 'Ajax', 'Javascript', 'StrictAutocomplete');
...
}

Then in the View you should add following line:

<?php echo $strictAutocomplete->autoComplete('autocomplete', 'ajax_autocomplete', array('label'=>'Demo Autocomplete Strict', 'strict'=>true));?>

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:

$options['strict'] = true; //boolean

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.

Tags: , , , , , , ,


Add to: del.icio.us:Extended Autocomplete Helper digg:Extended Autocomplete Helper spurl:Extended Autocomplete Helper wists:Extended Autocomplete Helper simpy:Extended Autocomplete Helper newsvine:Extended Autocomplete Helper blinklist:Extended Autocomplete Helper furl:Extended Autocomplete Helper reddit:Extended Autocomplete Helper fark:Extended Autocomplete Helper blogmarks:Extended Autocomplete Helper Y!:Extended Autocomplete Helper smarking:Extended Autocomplete Helper magnolia:Extended Autocomplete Helper segnalo:Extended Autocomplete Helper gifttagging:Extended Autocomplete Helper

16 Comments to “Extended Autocomplete Helper”

  1. 10 reasons to choose CakePHP as Framework | MT-Soft Website Development | October 18th, 2007 at 2:57 am

    [...] 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 [...]

  2. ?°???­ Pizzro’s Blog » 10 reasons to choose CakePHP as Framework(from other) | October 21st, 2007 at 10:00 am

    [...] 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 [...]

  3. TimNo Gravatar | February 15th, 2008 at 8:59 pm

    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)

  4. Nik ChankovNo Gravatar | February 15th, 2008 at 9:43 pm

    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:

    var $helpersnew Array('Form');

    which should solve this fatal error.

    Hope you working with CakePHP 1.2, because this helper is written for that version of Cake. :)

  5. huzzNo Gravatar | February 18th, 2008 at 10:23 pm

    thanks for sharing…
    however i still confuse where & how to put the ID (keys for displayed autocomplete items)? Am I miss something?

  6. Nik ChankovNo Gravatar | February 18th, 2008 at 11:27 pm

    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:

    <ul>
       <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.

  7. CoryNo Gravatar | March 5th, 2008 at 10:18 pm

    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

  8. Nik ChankovNo Gravatar | March 6th, 2008 at 11:16 am

    #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.

  9. helloworldNo Gravatar | March 6th, 2008 at 12:14 pm

    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

  10. Nik ChankovNo Gravatar | March 6th, 2008 at 1:10 pm

    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:

    function ajax_autocomplete(){
            $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…

  11. helloninioNo Gravatar | March 7th, 2008 at 8:21 am

    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 ?

  12. Nik ChankovNo Gravatar | March 10th, 2008 at 11:34 am

    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.

  13. Javi Sanromán web log » Componentes y helpers en cakephp | May 28th, 2008 at 10:00 pm

    [...] 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 [...]

  14. Nils EllingsenNo Gravatar | June 30th, 2008 at 2:59 pm

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

  15. Nik ChankovNo Gravatar | June 30th, 2008 at 4:44 pm

    I don’t have such error. But I checked my code and look like I’ve added these helpers in my controller class ;)

  16. bedrijfskledingNo Gravatar | July 21st, 2008 at 5:29 pm

    works great

Leave a Comment

*
To prove that you're not a bot, enter this code
Anti-Spam Image