CakePHP Livesearch (CakePHP 1.1)

This is now an outdated tutorial. It has been replaced by a more polished version designed for CakePHP 1.2. Whilst the tutorial you’re looking at now still works in 1.1, you’d be best of upgrading to CakePHP 1.2. It’s much cooler.

Want to get a AJAX-ified live search working in CakePHP? So did I, but struggled to track down the relevant tutorials in English. There are two prominent tutorials out there; one by Marcus Jaschen, and another by Nio. Unless you’re well versed in German or Chinese (respectively) you might struggle to decipher these. I’ve adapted ideas seen in both tutorials and present one in plain (ish) English.

If you want to see what this looks like before getting started, try the demo.

Demo is offline at the moment, waiting to move it over to the new host, sorry!

Livesearch?

If you’re not too sure what Livesearching is, you probably don’t need to be reading this tutorial. A quick overview of a livesearch would describe how search results are presented to you before you’ve finished typing – no need to click submit, just type and watch the results appear. Livesearch is part of the Web 2.0 thing, and has come around thanks to the popularisation of AJAX. To see an example of a livesearch in action head on over to Google Suggest.

Setting up a CakePHP environment

To demonstrate the Cake livesearch we’ll need to set up a very basic environment; it doesn’t really matter what project you use, it’s only one model/controller and a few views.

For demonstrative purposes I’m going to livesearch a table of “users” (original, I know) and have the names of the users returned back. Below is the SQL for the table I’m using:

CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name_first` varchar(20) DEFAULT NULL,
`name_second` varchar(20) DEFAULT NULL,
PRIMARY KEY  (`id`)
);

And here are a few names to populate the table – they’re British Prime Ministers if you didn’t know:

INSERT INTO `users` (`id`,`name_first`,`name_second`) VALUES ('7','James','Callaghan');
INSERT INTO `users` (`id`,`name_first`,`name_second`) VALUES ('6','Margaret','Thatcher');
INSERT INTO `users` (`id`,`name_first`,`name_second`) VALUES ('1','Tony','Blair');
INSERT INTO `users` (`id`,`name_first`,`name_second`) VALUES ('5','John','Major');
INSERT INTO `users` (`id`,`name_first`,`name_second`) VALUES ('8','Edward','Heath');
INSERT INTO `users` (`id`,`name_first`,`name_second`) VALUES ('9','Harold','Wilson');
INSERT INTO `users` (`id`,`name_first`,`name_second`) VALUES ('10','Alec','Douglas-Home');
INSERT INTO `users` (`id`,`name_first`,`name_second`) VALUES ('11','Harold','Macmillan');
INSERT INTO `users` (`id`,`name_first`,`name_second`) VALUES ('12','Anthony','Eden');
INSERT INTO `users` (`id`,`name_first`,`name_second`) VALUES ('13','Clement','Attlee');
INSERT INTO `users` (`id`,`name_first`,`name_second`) VALUES ('14','Winston','Churchill');

The next step is to set up the model, this is very, very basic:
/app/models/user.php

 

Setting up views & layouts

The first thing you’ll need to do is download the prototype and scriptaculous libraries – you can get them from here. You’ll need to extract the scriptaculous.js and prototype.js to /app/webroot/js. So that these can be used, you will need to update the default layout to include these files. In the head tag of your layout, put the following:

link('prototype'));
e ($javascript->link('scriptaculous'));
?>

This will ensure the needed Javascript files are included.

The first step here is to create an empty layout, this will just display the content passed to it (which will be the search results) – no >html> tags or anything, just the content:
/app/views/layouts/results.thtml

 

The next step is to create the search page – this will need a form with an input element as well as some CakePHP/Javascript code to establish the livesearch functionality:
/app/views/users/index.thtml

<h1>Livesearch</h1>
<form enctype="application/x-www-form-urlencoded" method="get">
<input id="livesearch" name="livesearch" type="text" />
 
&lt; ?php echo $html -&gt; image("spinner.gif") ?&gt;
 
 'view',
'url'    =&gt; '/users/search',
'frequency' =&gt; 1,
'loading' =&gt; "Element.hide('view');Element.show('loading')",
'complete' =&gt; "Element.hide('loading');Effect.Appear('view')"
);
 
print $ajax -&gt; observeField('livesearch', $options);
?&gt;
</form>
<!-- Results will load here -->

Here we have a form text input called livesearch, a series of hidden div tags for showing/hiding the search spinner and the search results. The $options array outlines a series of settings for our Javascript observer function; specifically what element needs to be updated in the html document (view), the url to submit to (we will create this in the controller on the next page), the frequency in seconds to check the text input and query the controller, and what to do during the loading phase and when the results are passed back. The spinner can be found here – I’m not sure who initially created this or what the license is, but I don’t think you need to worry about using it.

The following view will have the results passed to it by our controller and is responsible for displaying the search results:
/app/views/users/search.thtml (naming must match controller action)

 0) {
foreach ($result as $user) {
print '
 
';
print $user['users']['name_first'] . ' ' . $user['users']['name_second'];
print '
 
';
}
}
 
else {
print '
 
No results found
 
';
}
?&gt;

You should probably know what this does – it just loops through our results (assuming there are some) and prints out the first and second names for said users. If there are no results, print out a polite message.

Setting up the livesearch controller

If you’re reading this you probably have some knowledge of Cake architecture already, and I don’t need to delve into the workings of a controller.

Where we stand thus far is:

  • We have a model linking to our user table
  • We have all the views/layouts ready to go
  • The default view for our controller, index.html, is needing a controller action to submit to, and get results back from

Because we named the search results page search.thtml the controller action needs to be called search – feel free to change this, but don’t forget to update the url value in the $options array in index.html. Let’s take a look at the controller action:
/app/controllers/users_controller.php

 

This shows the very start of our controller, set up for scaffolding (just for defaults’ sake) and opting to use lots of helpers – we don’t need them all, but it’s generally a safe bet to keep them. We are, of course, most interested in the ajax helper.

Next we need to add an index function to prevent scaffolding taking over:
/app/controllers/users_controller.php

/* .. snip .. */
FUNCTION INDEX()
{
$this-&gt;set('data',$this-&gt;User-&gt;findAll());
}
/* .. snip .. */

The final step is to add a search function which will populate the search.thtml view created earlier.

This function will be called by the form observer in index.thtml – it’s not as complicated as you might think:
/app/controllers/users_controller.php

/* .. snip .. */
FUNCTION search()
{
$this -&gt; RequestHandler -&gt; setAjax($this);
IF (!empty($this-&gt;params['form']['livesearch']))
{
$word   = $this-&gt;params['form']['livesearch'];
$result = $this -&gt; User -&gt; query("SELECT * FROM users WHERE name_first LIKE '%".$word."%' OR name_second LIKE '%".$word."%'");
$this-&gt;set('result', $result);
}
}
/* .. snip .. */

What’s happening here, then? Well, firstly we check to make sure the livesearch field is not blank – if it is CakePHP will knows to use the blank Ajax layout.

Moving on, we do a very basic query of the User model (table users created right at the beginning) – we’re querying for first name or second name which is LIKE the input text.

When we’ve got the results we pass them to a result array variable which will be accessed by the search.thtml view. Remember that CakePHP has a built in layout for dealing with displaying the results of Ajax requests, so we don’t need to worry about that.

Next steps

This has been a very rudimentary demonstration of how to accomplish a livesearch feature in CakePHP – it’s limited and probably not a good idea to use this version without any tweaks. Here’s a few things you might want to address:

  • There’s no differentiation between no results and an empty search box, it would be good to clear all messages if the search input is empty.
  • The MySQL query is very basic – if you type a complete name in “Firstname Secondname” the query won’t pick it up – there needs to be a few more LIKEs in there. Try looking into the CONCAT() MySQL function.
  • It would be great if we could detect when the user has finished typing by setting some form of delay reset – this will prevent the function searching needlessly
  • The search string is not checked before it’s sent to the query, opening up endless possibilities of SQL injection attacks.
  • Highlight the search string in the results

Hope this has been useful for you – I’d appreciate a quick comment if it has, or if you feel there is room for improvement with this tutorial, in addition to typos and factual errors!

Revisions

13th March
* Modified tutorial to be single page

28nd November
+ Added a demo
* Modified the controller to use RequestHandler component
* Simplified the controller thanks to Daniel’s feedback.

22nd November
+ Updated to include javascript library instructions, thanks Daniel.
* Modified search.thtml to check for variable setting. Again, thanks Daniel.