Autocompletion – the easy way

Published on June 06, 2006 and tagged with ajax  cakephp  component

Nate has published a nice component (update 2009-05-27: for CakePHP 1.2, please use the version from GitHub which contains the patch from Mariano Guezuraga) which makes the creation of an input field with Ajax autocompletion very easy. In fact, it is (almost) as easy as creating a simple input field.

What do you have to do? Put the component to your app/controllers/components directory. In your controller you have to include the component with:

var $components = array('Autocomplete');

Also make sure that you have defined the Ajax helper in your controller. I usually add it to the helpers array in my AppController (app/app_controller.php):

var $helpers = array('Html', 'Javascript', 'Ajax');

After that, you can add the autocompleter to your view.

<?php echo $ajax->autocomplete('Company.name'); ?>

One condition must be fullfilled to make this code snippet work: the controller which renders the view must have access to the appropriate model, in our example to the Company model.

That’s it :)

Thanks to Nate for the hint.

55 comments baked

  • jf June 09, 2006 at 23:00

    Moi j’obtiens l’erreur suivante :

    Erreur : Ajax.Autocompleter is not a constructor
    Fichier source : http://localhost/cake/index.php/types/
    Ligne : 60

    Ma ligne 60 :

    new Ajax.Autocompleter(’Type_name’, ‘Type_name_autoComplete’, ‘/cake/index.php//types/’, {asynchronous:true, evalScripts:true});

    D’ou pourrait vernir le problème ?

    Merci

  • cakebaker June 10, 2006 at 10:10

    @jf: Hm, I have no idea what could be the problem. My code looks almost identical…

    I am sorry, but maybe you will have more luck in the CakePHP google group: http://groups.google.com/group/cake-php

  • jf June 16, 2006 at 21:56

    Solution :
    You have to include the prototype and script.aculo.us libraries in the
    header of your page.

  • new cake July 01, 2006 at 21:00

    how can I give access of one model to another for this script.

  • cakebaker July 03, 2006 at 07:53

    @new cake: Sorry, I don’t understand what you try to accomplish.

  • Ritesh Agrawal July 20, 2006 at 14:48

    hi,

    I am trying to use the autocomplete component but not sure where I am making a mistake

    ==== Here is my controller that access author model ====

    ===== This is the view /view/authors/auto.thtml
    link(’prototype/prototype’);
    echo $javascript->link(’scriptaculous/scriptaculous’);
    ?>

    Last Name
    autocomplete(’Author/last_name’); ?>
    submit(’View Post’)?>

    ==== Here is the generated form

    Last Name

    new Ajax.Autocompleter(’AuthorLastName’, ‘AuthorLastName_autoComplete’, ‘/cake/authors/auto’, {});

  • Ritesh Agrawal July 20, 2006 at 14:51

    hi,

    I am trying to use the autocomplete component but not sure where I am making a mistake

    ==== Here is my controller that access author model ====
    class AuthorsController extends AppController
    {
    var $name = ‘Authors’;
    var $scaffold;

    var $components = array(’Autocomplete’);
    var $helpers = array(’Html’, ‘Javascript’, ‘Ajax’);

    function auto(){

    }

    }//endofclass

    ===== This is the view /view/authors/auto.thtml

    php echo $javascript->link(’prototype/prototype’);
    php echo $javascript->link(’scriptaculous/scriptaculous’);

    form action=”/authors/auto” method=”POST”
    Last Name:
    php echo $ajax->autocomplete(’Author/last_name’);
    php echo $html->submit(’View Post’)

    ==== Here is the generated form
    form action=”/authors/auto” method=”POST”
    label>Last Name

    div id=”AuthorLastName_autoComplete” class=”auto_complete” /> new Ajax.Autocompleter(’AuthorLastName’, ‘AuthorLastName_autoComplete’, ‘/cake/authors/auto’, {});

  • cakebaker July 20, 2006 at 17:55

    @Ritesh Agrawal: Hm, there was a bug in the latest release of CakePHP, so maybe that is the cause for the problem. See https://trac.cakephp.org/ticket/1154 . If that does not fix the problem, please post the code in the google group (it is easier than posting code here *g*).

  • nickr July 27, 2006 at 00:16

    For me, what made the difference was adding in a link to the controls.js file, so that my app\views\layout\default.html included these four lines:

    charset(’UTF-8′) ?>
    link(’prototype’) ?>
    link(’controls’) ?>
    link(’scriptaculous.js?load=effects’) ?>

    Refreshed the page and it all worked perfectly. I just need a little CSS to power the dropdown, and I’ll be set! Thanks!

  • nickr July 27, 2006 at 00:19

    Oops. Am hoping I can use code tags. The four lines should be:

  • Jefferson Wellington da Cunha August 07, 2006 at 06:56

    How I do to access all attributtes of other model for I to use your component autocompleting a attribute that not yourself.

    Example:
    Model A, hasMany B
    Model B, belongsTo A (Table b have a_id attribute)

    I need autocomplete a attribute different of ‘a_id’ at Controller of Model B.

    How I do?

    So Thanks,
    JWCunha

  • mirceade November 27, 2006 at 10:39

    var $uses = array(’[Model name]‘);
    You MUST include this in your controller code for the ajax autocompletion to work.

  • alwhorley February 09, 2007 at 05:33

    I am a total noob, and beg some help. I am trying to use this as described, I have the html, javascript, and ajax helpers in my controller. Actually I have them in my controller and in app_controller.php. I have a form in my view that contains only the setup and completion code for the form, a submit, and the snippet from here. The component is installed in the controllers/components dir. When I call the view, I get the form, but it fails silently. when I check the javascript console, I get an Ajax is not defined error. any clues that will help with my ignorance would be very helpful.
    Al

  • cakebaker February 09, 2007 at 17:30

    @alwhorley: Did you include the prototype and scriptaculous libraries in the header of your page?

  • alwhorley February 09, 2007 at 19:19

    yes I have them in in the head section of my layouts/default.thtml. like this:

    charsetTag(’UTF-8′); ?>
    link(’lib/prototype’); ?>
    link(’src/scriptaculous.js?load=effects’); ?>
    link(’src/controls’); ?>

    CakePHP :
    webroot . ‘favicon.ico’;?>” type=”image/x-icon” />
    webroot . ‘favicon.ico’;?>” type=”image/x-icon” />
    css(’cake.generic’);?>

  • alwhorley February 09, 2007 at 22:03

    Oh, and thanks for your response!

  • cakebaker February 10, 2007 at 11:40

    @alwhorley: Hm, to me it sounds like the ajax libraries are not loaded. If you look at the source of the generated page, does it look correct?

  • alwhorley February 10, 2007 at 19:50

    D’OH, the echo command was missing in my link statements! thanks for the heads up.

  • cakebaker February 13, 2007 at 10:18

    @alwhorley: Cool to hear you could solve the problem :)

  • AbhinavZone - Your Source for fun and knowledge May 24, 2007 at 14:22

    Hey thanx..

    I found your script very useful.
    I am new to cake php.. can u please tell me where to find good tutorial to learn developing components for cake php…

    I think you are perfect baker ;)

  • cakebaker May 24, 2007 at 16:51

    @Abhinav: Have a look at the manual, there is a chapter about writing components: http://manual.cakephp.org/chapter/components

    Btw: I am by no means a perfect baker, I make errors as everyone else ;-)

  • mazoo June 03, 2007 at 09:50

    I can’t register myself on bakery.cakephp.org (white screen after “/user/add”) so i make my comment to the autocomplete component here.

    I changed line 109 of version 0.4 from

    e(”\t”.$element.”\n”);

    to

    e(”\t”.preg_replace (”/” . preg_quote($data[$model][$field]) . “/i”, “<strong>” . $data[$model][$field] . “</strong>”, $element).”\n”);

    i think it’s even a bit cooler like that :)
    Cheers,
    mazoo

  • cakebaker June 06, 2007 at 17:59

    @mazoo: Did you open a ticket for that issue on https://trac.cakephp.org ?

  • mazoo June 06, 2007 at 19:55

    After i saw in the cake php google group that someone else has the same problem with similar setup, i checked it again and it was fixed…

    btw: in my earlier comment about autocompletion i forgot to encode the strong tags around the second $data[][].

  • cakebaker June 07, 2007 at 17:24

    @mazoo: Ok, I fixed your comment.

  • dswingle June 08, 2007 at 22:30

    I’m trying to make a Live Search work for a phone list and the AutoComplete component looks like it does sort of what I want so I figure this is the place to get some help.

    I’ve got the following view code in one of my view classes:

    ‘view’,
    ‘url’ => ‘/usefulnumbers/search’,
    ‘frequency’ => 1,
    ‘loading’ => “Element.hide(’view’)”,
    ‘complete’ => “Effect.Appear(’view’)”
    );
    echo $ajax->observeField(’UsefulnumberSearch’,
    $options);
    ?>

    Above that I have my form with the text input element ‘UsefulnumberSearch’.

    What I want to happen whenever I type something into that input element is for my controller to call it’s search function and render my search view form inside the ‘view’ div element.

    Now whenever I type anything into the input field, the div appears like it should but I get a SQL_Error because for some reason my code is creating a query that’s something like this:

    SELECT * FROM usefulnumbers AS Usefulnumber WHERE ‘Usefulnumber’.’search’ LIKE %%

    The problem is that search is not a column in the usefulnumbers table and I have no idea why it’s being added to the query.

    The input element that ajax observes has the name data['Usefulnumber']['search'] because I made it with the html helper

    input(’Usefulnumber/search’,
    array(’size’ => ‘40′));
    ?>

    Could that possibly be causing my problem?

    Sorry about the length of this comment but I’ve been beating my head against the wall for a while now and any help you could give me would be GREATLY appreciated.

    Thanks,

    dswingle

  • dswingle June 08, 2007 at 22:32

    crap, that first block of code should start with:

    $options = array(
    ‘update’ => ‘view’,

  • cakebaker June 09, 2007 at 11:34

    @dswingle: I think the AutoComplete component expects that your input field is named like the column in which the data are for the autocomplete functionality, i.e. your input field should look like:

    input('Usefulnumber/column_name');
    

    HTH

  • dswingle June 11, 2007 at 15:18

    Cakebaker, you rock almost as much as Cake itself. Thank you SO much for the help, it definitely put me in the right direction, and, with a little bit of tinkering, I’ve got livesearch working perfectly!

    Again, THANKS!

  • cakebaker June 12, 2007 at 18:57

    @dswingle: Thanks, I am glad I could help :)

  • vasileios September 07, 2007 at 09:08

    Hi,

    thanks for the really nice component! However, I noticed a small problem when it is used in conjunction with the $ajax->observeField. The component seems to handle all ajax requests so $ajax->observeField doesnt return any valid results. Is it possible to make the component handle only the autocomplete requests ?

    Thanks!

    Vasilis

  • cakebaker September 10, 2007 at 18:40

    @Vasilis: I am not sure if it helps in your situation, but you can disable the component by setting $enabled to false.

    HTH

  • Georgi Momchilov September 20, 2007 at 18:39

    Hey there,
    very cool component :)
    just have a few remarks :
    I tried to use it on a big database – good some pretty bad performance hits because of findAll() and LIKE%% .
    My solution:
    autocomplete_component.php:
    line 88: $base = array($model.’.’.$field => ‘LIKE ‘.$data[$model][$field].’%'); ( remove % in front of LIKE as people are expected to enter the first chars of the desired entry. this also makes the queries faster )
    line 95: $controller->{$model}->recursion = false;
    $results = $controller->{$model}->findAll($conditions, $field, $field.’ ASC’, 15);
    remove recursion as we actually need very little data from that model and get only the desired field. also, limit the returned records ( to 15 in my case ) – otherwise, we can get veeeery long lists.
    hope that helped someone’s frustration
    thanks,
    Georgi

  • cakebaker September 22, 2007 at 10:42

    @Georgi: Thanks for this tip!

  • Josh September 30, 2007 at 16:50

    If anyone is interested, here is the CSS Code I found and modified a little so it works with this:
    div.auto_complete {
    position:absolute;
    width:250px;
    background-color:white;
    border:1px solid #888;
    margin:0px;
    padding:0px;
    }
    div.auto_complete ul {
    list-style-type:none;
    margin:0px;
    padding:0px;
    }
    div.auto_complete ul li.selected { background-color: #ffb;}
    div.auto_complete ul li {
    list-style-type:none;
    display:block;
    margin:0;
    padding:2px;
    height:32px;
    cursor:pointer;
    }

  • cakebaker October 01, 2007 at 15:57

    @Josh: Thanks for sharing this CSS code!

  • angrys0ul October 05, 2007 at 10:48

    i cant get it to work!
    the code the view generates is

    new Ajax.Autocompleter('ApplicationCity', 'ApplicationCity_autoComplete', '/applications/search', {});

    i have checked common things like including JS files, access to model, nothing wrong there

    and why does the generated html code have autocomplete=”off” ??

    btw should i name the component autocomplete or autoComplete ??

  • cakebaker October 06, 2007 at 17:04

    @angrys0ul: Hm, difficult to say what’s wrong. Do you get any errors?

  • angrys0ul October 06, 2007 at 17:20

    the full code is not visible in the comment, may be its a security feature
    i dont get any kind of errors. the form renders normally but just doesnt autocomplete when something is typed

  • cakebaker October 06, 2007 at 17:51

    @angrys0ul: You can paste the code to the bin (http://bin.cakephp.org). Do you get a javascript error? (if you are using Firefox then Firebug is a good tool to see such errors)

  • JoeyK January 25, 2008 at 11:50

    I’ve got the same problem as angrys0ul, I’m using cake 1.2… There’s a “Ajax is not defined” error, and autocomplete=off. However if you firebug the autocomplete field to “on” everything works perfectly…

  • cakebaker January 26, 2008 at 12:10

    @JoeyK: Hm, did you include the prototype and scriptaculous libraries? At least here it works fine with the latest version of Cake 1.2, and autocomplete=off is set, too.

  • Reza Muhammad March 13, 2008 at 20:07

    @angrys0ul & JoeyK:

    I had the same problem with you guys, and I found out that I loaded a echo $javascript->link(’scriptaculous.js?load=effects’);

    I don’t think you should use “load=effects”. I took that part out of my code, and the autocompletion worked fine.

  • cakebaker March 14, 2008 at 19:53

    @Reza: Thanks for the hint!

  • Frederick Ricaforte April 22, 2008 at 01:57

    Thanks for the post!

    Model A, hasMany B
    Model B, belongsTo A (Table b have a_id attribute)
    where A is autocomplete form to extract from B.

    In relation to post above. If you’re going to extract data from your member model and you cannot use $uses to link to your current model, you can use this patch below:

    replace:
    if (!is_array($data[$model]) || count($data[$model]) != 1 || !is_object($controller->{$controller->modelClass}->{$model}) || !is_object($controller->{$model})) {
    return false;
    }
    with:
    if (!is_array($data[$model]) || count($data[$model]) != 1 || !is_object($controller->{$controller->modelClass}->{$model}) ) {
    if(!is_object($controller->{$model})) {
    return false;
    }
    }
    if(is_object($controller->{$controller->modelClass}->{$model})) {
    $modelObject = $controller->{$controller->modelClass}->{$model};
    }
    else {
    $modelObject = $controller->{$model};
    }

    replace:
    $results = $controller->{$model}->findAll($conditions);
    with:
    $results = $modelObject->findAll($conditions);

  • cakebaker April 22, 2008 at 19:14

    @Frederick: Thanks for your addition, it may be helpful for others!

  • Sam September 19, 2008 at 09:14

    To make autosuggest faster when using with database, we might,

    1. Limit the resultset with the “limit” clause in the query.
    2. Index the column that is searched.

    Is there any other technique apart from these two?

  • cakebaker September 19, 2008 at 16:53

    @Sam: Apart from your points, the patterns Predictive Fetch and Submission Throttling might help.

  • Mariano Guezuraga October 31, 2008 at 17:44

    I made it work with 1.2 rc3:

    in the component, change:
    $base = array($model.’.’.$field => ‘LIKE %’.$data[$model][$field].’%');
    to:
    $base = array($model.’.’.$field.’ LIKE’ => ‘%’.$data[$model][$field].’%');

    and:
    $results = $controller->{$model}->findAll($conditions);
    to:
    $results = $controller->{$model}->find(’all’, array(’conditions’ => $conditions));
    (this last one not necessary, but I guess findAll methods will be deprecated)

  • sbeer April 26, 2009 at 03:15

    Hi! It seems to be a very nice component, but I can’t make it work. I’m using 1.2 and changed the lines as Mariano suggested. I also changed autocomplete(’Event/title’); ?> to autocomplete(’Event.title’); ?>. I’m quite new to cake, so are there some other things I need to change to make it work? I see the input-form, but when I type something in it, nothing happens. Thanks for the help!

  • cakebaker May 01, 2009 at 08:54

    @sbeer: Hm, did you add the prototype/script.aculo.us libraries to “app/webroot/js”?

  • KK Kow May 22, 2009 at 12:20

    After reading through all the comments, I still can’t seem to get the autocomplete to work. No error messages is given. It just doesn’t work. Anyway, I’ll put the generated code below, please let me know is it suppose to be like this… Thanks.

    <input name="data[Member][Member/full_name]" type="text" id="Member/fullName" autocomplete="off" value="" /> 
    <div id="Member/fullName_autoComplete" class="auto_complete"></div> 
    <script type="text/javascript"> 
    //<![CDATA[ 
    new Ajax.Autocompleter('Member/fullName', 'Member/fullName_autoComplete', '/members/add', {}); 
    //]]>
  • cakebaker May 24, 2009 at 11:41

    @KK Kow: Yes, the output looks correct, though I would use autocomplete(’Member.fullName’); instead of autocomplete(’Member/fullName’);

    Did you apply the changes to the component mentioned by Mariano? And did you load the prototype/scriptaculous libraries?

    Hope that helps!

  • KK Kow May 24, 2009 at 19:26

    Hi, I somehow manage to make it work. I read on some other article saying that we need to set debug to 0 in order for the xhtml to work, so what I do is just added the following line on the first line of the startup method.

    Configure::write('debug', '0');
    

    Of course I also change the line to ‘Member.fullName’.
    Another question though, how do I add label for the my input box, I notice there is not label for the input box.

  • cakebaker May 27, 2009 at 16:21

    @KK Kow: To add a label you have to use the label() method of the FormHelper:

    echo $form->label('Member.fullName', 'Name');
    

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License