zen of coding

CakePHP and jQuery auto-complete revisited

CakePHP 2.3
jQuery 1.10.2
jQuery UI 1.10.3

I’ve realized that my old post about jQuery auto-complete and cake is still pretty popular, but hopelessly outdated.

Therefore, I figured it would be a good time to revisit that old post and give it an update.
We’ve come so far! A lot has changed in the jQuery world
since then.

For this tutorial we will create a single auto-complete field using jQuery and jQuery UI.
Although to show off some features of CakePHP we will also create a model a controller a view and a JSON response (more on that later).

The goal is simple, we’ll have a field where we’ll type some name of a car maker. If at least one character was entered, we’ll show suggestions using jQuery UI’s auto-complete widget.

First we’ll start with the schema and some data.

Let’s create our cars table.

CREATE  TABLE `test`.`cars` (
  `name` VARCHAR(45) NULL ,
  `created` VARCHAR(45) NULL ,
  `modified` VARCHAR(45) NULL ,
  PRIMARY KEY (`id`) )
COLLATE = utf8_unicode_ci;

Now let’s populate it with some popular brands:

(name, created, modified)
( 'Aston Martin', now(), now() ),
( 'Acura', now(), now() ),
( 'Audi', now(), now() ),
( 'Bentley', now(), now() ),
( 'Bmw', now(), now()),
( 'Bugatti', now(), now() ),
( 'Buick', now(), now() ),
( 'Cadillac', now(), now() ),
( 'Chevrolet', now(), now() ),
( 'Chrysler', now(), now() ),
( 'Dodge', now(), now() ),
( 'Ferrari', now(), now() ),
( 'Ford', now(), now() ),
( 'Gmc', now(), now()),
( 'Honda', now(), now() ),
( 'Hyundai', now(), now() ),
( 'Infiniti', now(), now() ),
( 'Jaguar', now(), now() ),
( 'Jeep', now(), now() ),
( 'Lamborghini', now(), now() ),
( 'Lexus', now(), now() ),
( 'Lincoln', now(), now() ),
( 'Maserati', now(), now() ),
( 'Mazda', now(), now() ),
( 'Mercedes-Benz', now(), now() ),
( 'Mitsubishi', now(), now() ),
( 'Tesla', now(), now() ),
( 'Nissan', now(), now() ),
( 'Porsche', now(), now() ),
( 'Rolls Royce', now(), now() ),
( 'Subaru', now(), now() ),
( 'Tesla', now(), now() ),
( 'Toyota', now(), now() ),
( 'Volkswagen', now(), now() ),
( 'Volvo', now(), now() )

Let’s go ahead and create a new layout for this application. It will be pretty simple, let’s do something like this (this will be a new file in View/Layout/basic.ctp):

<!DOCTYPE html>
<html lang="en">
    <meta charset="utf-8">
    <title>Sample App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="">
    <meta name="author" content="">
      echo $this->Html->css('https://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css');

  <?php echo $content_for_layout; ?>

  <!-- our scripts will be here -->
  <?php echo $scripts_for_layout; ?>

To give some style to the auto-complete field and the “suggest” drop-down, we’ll add the CSS file form jQuery’s built-in themes.

Next we’ll create a simple controller in Controllers/CarsController.php:

  class CarsController extends AppController {

    public $layout = 'basic';

    public function index() {


The only thing we do differently from our standard controller setup, is specifying the layout… which matches the file name above (minus the .ctp part).
We are leaving the index() action empty for now.

And finally let’s take a look at the view in View/Cars/index.ctp:

  //let's load jquery libs from google
  $this->Html->script('https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js', array('inline' => false));
  $this->Html->script('https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js', array('inline' => false));

  //load file for this view to work on 'autocomplete' field
  $this->Html->script('View/Cars/index', array('inline' => false));

  //form with autocomplete class field
  echo $this->Form->create();
  echo $this->Form->input('name', array('class' => 'ui-autocomplete',
               'id' => 'autocomplete'));
  echo $this->Form->end();

First, we load our jQuery libs from Google. Next, notice $this->Html->script(‘View/Cars/index’, array(‘inline’ => false)); this tells CakePHP that we need to load a JavaScript file from our webroot/js/View/Cars/index.js. I recommend keeping your .js files in a similar directory structure as your .ctp files.

Because we have array(‘inline’ => false) as a second argument, our script will be included in place of the $scripts_for_layout.

This pretty much completes our CakePHP setup. We now need some code to retrieve data from our DB and some JavaScript code to act on our “#autocomplete” field.
As you’ve probably guessed, this JS code will be located in webroot/js/View/Cars/index.js:

(function($) {
        source: "/cars/index.json"

That’s it… One thing to note here is the path “/cars/index.json”. By adding the .json extension to our request URL we’ll utilize cake’s built-in JSON View and format the response as JSON (or JSONP).

Let’s take a look at that now. We will need to beef up our Controller just a little:

  class CarsController extends AppController {

    public $layout = 'basic';

    public $components = array('RequestHandler');

    public function index() {
      if ($this->request->is('ajax')) {
        $term = $this->request->query('term');
        $carNames = $this->Car->getCarNames($term);
        $this->set('_serialize', 'carNames');

One thing you’ll notice is that we’ve added a RequsetHandler component. This is the magic in CakePHP that will properly handle our request from jQuery and allow us to set our response as a JSON object. You can find out more details about how ‘_serialize’ and RequestHandler work by reading up in the manual.

It is important to note that in your routes file you’ll need to enable the parsing of extensions. (i.e. index.json).
Simply edit app/Config/routes.php and add the following line to the file:


Next you see that I am getting the list of model names from our Car model in the method called getCarNames().
This is because I’m trying to follow the golden rule of MVC: “fat models, skinny controllers”.
Although it’s easy to leave all the car-name-finding logic in the controller (and not have to create a model at all!), we’ll presume good architecture here and create a model to handle our data finding needs.

Here we go (app/Model/Car.php):

  class Car extends AppModel {

    public function getCarNames ($term = null) {
      if(!empty($term)) {
        $cars = $this->find('list', array(
          'conditions' => array(
            'name LIKE' => trim($term) . '%'
        return $cars;
      return false;

I use a standard find(‘list’) method of CakePHP to get car names from our table above. The data is returned in an array formatted in a way so that becomes very easy to return as a JSON object back to our jQuery. You can see that in the controller above.
First we set a variable for the view (as you’d do for any view) and then you “serialize” it to become a JSON object.

(By creating a Car model cake automatically associated it with “cars” table. Even if I didn’t actually crate a Car model file, cake would still be able to execute basic model methods as all of our methods extend the built-in core Model. This topic is a bit more advanced and you can find out more about by studying he API or checking up on our friendly IRC channel).

In conclusion, we have everything we need to have a fully functional auto-complete using CakePHP and jQuery/jQuery UI.
If you were to type-in “f” in the input field, you’d get a list with “Ferrari” and “Ford”.

  • fly2279

    Thanks for updating this article. I’m curious as to why you didn’t just find ‘list’ to get a list of car names matching the search term instead of using the Hash lib?

    • teknoid_cakephp

      Awesome point. I kind of realized that way after I wrote the whole thing. I was debating to leave Hash in there, just to demo it… but that would totally distract from the article. So thanks for inspiring me to make it more simple.

  • euromark

    App::uses(‘Lib’, ‘Hash’); should be App::uses(‘Hash’, ‘Utility’);

    and that method should better always return an array :)

    • teknoid_cakephp

      Thanks for pointing that out, but I’ve decided to really simplify the whole thing with find(‘list’) ;)

      What’s wrong with returning a bool?

      • Daniel Hofstetter

        At least for me the “problem” is that I would expect this method to return all car names if I don’t specify any term.

      • teknoid

        Makes sense… but I’m not sure if this is the right expectation in this case. We are passing an argument to the method and act on it. Therefore, IMO, if nothing is found containing the given term, then nothing should be returned.

  • ian

    not working brother

    • teknoid

      seems to work ok ;)

    • teknoid_cakephp

      seems to work alright :)

    • spacepil

      Works great indeed. Thank you so much for sharing your code!
      I only had to modify the source (“cars/index.json”) in index.js to make it work because my test app was not at the web root.
      It was obvious by looking at the http request from the console:
      GET http://localhost/cars/index.json (404)
      instead of now
      GET http://localhost/cakephp/ajaxboxes/cars/index.json (200)

  • Nicolas Ducrotoy

    Hello, I adapted this code for city but I have a problem

    The accent return a null value …

    Can you help me ;) ?

    Sincerely yours

  • Lokeshjain2008

    Nice article. your post helped me. I have one question how do you bake views for pages controller(for static pages)?

    • teknoid

      there are typically no actions in PagesController. and your static pages are typically just HTML and maybe some basic PHP…

  • Praveen

    Thanks a lot for the detailed notes… the only thing did not work is the name of the input field.. it should be ‘term’ to match with the entire tutorial. Isn’t it?

    echo $this->Form->input(‘term’, array(‘class’ => ‘ui-autocomplete’,

    ‘id’ => ‘autocomplete’));

    • teknoid_cakephp

      if that makes it more clear, then sure… $terms is really the search string (query).. while name represents the actual name … so $term could be “Au” while name would be “Audi”.

      don’t make much difference, as far as logic goes :) good naming is too difficult anyways.

  • Praveen

    Awesome article.. Thanks a ton for the details !!

    The only misleading portion is the ‘name’ of the field in index.ctp. Shouldn’t it be ‘term’ to match with other portion of the tutorial?

  • Chris

    I have setup auto-complete in CakePhp 2.5.4. It works nicely. I had to change the name “CarController.php” to “CarsController.php” but otherwise all was good.
    I would like to use it as an Element but I’m not getting very far. Any suggestions?

    • http://zenofcoding.com vladko

      nice catch. thanks. i’ve updated the post. you should jump on cakephp irc channel. they are always ready to help.


  • http://www.vucogpsy.nl/staff-members/daniel-schreij/ DaniĆ«l Schreij

    This example only worked for me when I passed everything to the a $(document).ready() function. So the contents of my /View/Cars/index.js became

    source: “/cars/index.json”,

  • http://arielis.com David Spelts

    Very nice. I always also include some cache busting on controller functions made by ajax calls, especially when the data changes often. It can prevent some ‘bug’ reports.

    $this->header(‘Pragma: no-cache’);
    $this->header(‘Cache-control: no-cache’);
    $this->header(“Expires: Mon, 26 Jul 1997 05:00:00 GMT”);

  • Raul Magdalena Catala


    i’m trying to use this with cakephp 3.0 without exit
    The important change between versions (if i’m not wrong) is how to enable the parsing of extensions
    in both cases it is done in the ruotes.php file
    in 2.3 by Router::parseExtensions()
    in 3.0 by Router::extensions(‘json’)

    anyway, when i write into the input, nothing happens
    i’ve checked with the browser debugging tool and no call to http://localhost:8765/cars/index.json occurs.
    even if i write the url http://localhost:8765/cars/index.json?term=fo in the browser, i do not receive a json response, in fact no response or error message

    any idea about what i’m doing wrong?

%d bloggers like this: