Tags

, ,

Update to this post is here. (New libraries and new techniques are discussed).

This is a quick example on how to setup an autocomplete field using JQuery and CakePHP.

Let’s assume that we have a Product model and products controller. Our goal is to allow the user to type in a few characters and then to display a matching list of products from the DB by using autocomplete.

First things first, be sure that you’ve included the JQuery library. If you are using $scripts_for_layout, then in your view file you’d probably do something like:

$javascript->link('jquery/jquery.min', false);

Next, you have to include the autocomplete plugin… There are a few of them out there.
Well, this one does the trick for me: http://www.pengoworks.com/workshop/jquery/autocomplete.htm

(Please refer to the documentation at the link above for details on how the JQuery autocomplete works and for an example of what it looks like. You can also grab the relevant CSS and the loader image from there).

After you save it somewhere in your JS directory (perhaps js/jquery/plugins) you should include it in the view just like this: $javascript->link(‘jquery/plugins/autocomplete’, false);

Now you need the form field that will be used as an autocomplete field. The plugin, by default, will expect a field with an id = “autoComplete”. So, as part of your form, you’ll probably do something like

<?php
echo $form->text('Product.name', array(
    'size'=>'30',
    'id'=>'autoComplete'));
?>

(Remember that we are assuming the Product model here)

Not too bad, so far. Alright, now we have to trigger the autocomplete by creating some JavaScript that will actually attach the autocomplete action to your form element.
Make a new JS file and call it autocompleteAction.js (don’t forget to also include it in the view).

Here’s what it should look like:

$(document).ready(function(){
$("#autoComplete").autocomplete("/products/autoComplete",
{
minChars: 2,
cacheLength: 10,
onItemSelect: selectItem,
onFindValue: findValue,
formatItem: formatItem,
 autoFill: false
 });
});

function selectItem(li) {
  findValue(li);
}

function findValue(li) {
  if( li == null ) return alert("No match!");

// if coming from an AJAX call, let's use the product id as the value
  if( !!li.extra ) var sValue = li.extra[0];

  // otherwise, let's just display the value in the text box
else var sValue = li.selectValue;

alert("The value you selected was: " + sValue);
}

function formatItem(row) {
  if(row[1] == undefined) {
    return row[0];
  }
  else {
    return row[0] + " (id: " + row[1] + ")";
  }
}

Just to cover the basics, the above action will tell the autocomplete to kick-in after you’ve typed two characters into your form field (minChars: 2). The lineSeparator: “^” tells autocomplete to jump onto the next line whenever it encounters the ^ character. I will explain this a little more later. It is worth noting that the default new line character is “\n”, but I could not get it to work with CakePHP so I’ve decided to use a somewhat obscure character instead, which is unlikely to appear in a product name. This is not a very good solution by any means and someday I will come back to figuring it out. (Update: this works just fine now, so no longer such trickery as I’ve used before is required).

So what’s going on here? Well, the autocomplete needs to get the list of available items (products in our case) somewhere, and this code tells it exactly where to look: autocomplete(“/products/autoComplete”… You’ve probably guessed that in terms of CakePHP you need to have a products controller and an autoComplete action. Now is a a good time to create one.

It should look something like:

function autoComplete() {

 Configure::write('debug', 0);
 $this->layout = 'ajax';

 $products = $this->Product->find('all', array(
   'conditions'=>array('Product.name LIKE'=>$this->params['url']['q'].'%'),
   'fields'=>array('name', 'id')));

$this->set('products', $products);
}

So basically we are looking for all products who’s name matches (LIKE) the two characters that we’ve typed in the form field. We are returning a product name and product id fields from the DB. Note the: $this->params['url']['q']. The autocomplete plugin uses the GET method to pass the form data to your controller and it expects to have a q= as the query string, and that’s how you read that query string in CakePHP. Now we’ve found some products and set the resulting array for the view. The view will actually be the list that pops under the text field once you start typing. Let’s create one (auto_complete.ctp):

if(!empty($products)) {
foreach($products as $product) {
  echo $product['Product']['name']. '|' .$product['Product']['id'] . "\n";
 }
}
else {
 echo 'No results';
}

Here we are looping through the products array and displaying the name and the id, of course if the array happens to be empty we will display ‘No results’.

That’s all there is to it. With some clever copy/paste and possibly a few quick changes this thing should be working like a charm in just less than twenty minutes.

Of course you’ll probably want to come up with some better usage for it than doing a JS alert…