Hi. Tired of having to write code for searches, usually autocompletes, over and over again? If you expect JSON as the outcome of your searches, like the JQuery UI’s Autocomplete does, you may find this post interesting.
In my projects, I add the following function to my AppController to provide a fast search action to all my controllers:
function search() { $this->disableCache(); $items = array(); $fields = explode(',', $this -> params['named']['fields']); $model = $this -> modelClass; $conditions = array(); if (isset($this -> params['named']['conditions'])) { $conds = explode(',', $this -> params['named']['conditions']); for($i = 0; $i < count($conds); $i += 2) { $conditions[] = array("$model.{$conds[$i]}" => $conds[$i+1]); } } $get = array(); if (isset($this -> params['named']['get'])) { $get = explode(',', $this -> params['named']['get']); } if(isset($this -> params['url']['term'])) { $id = (string) strtolower($this -> params['url']['term']); $selector = "%$id%"; if (count($fields) == 1) { $conditions[] = array("LOWER($model." . $fields[0] . ') LIKE' => $selector); } else { $or = array(); foreach($fields as $f) { $or[] = array("LOWER($model.$f) LIKE" => $selector); } $conditions[] = array('OR' => $or); } $args = array('conditions' => $conditions); if (count($get) > 0) { // the id must come automatically $get[] = "$model.id"; $args['fields'] = $get; } $this -> {$model} -> recursive = 0; $items = $this -> {$model} -> find('all', $args); } $this -> set('items', $items); $this -> autorender = false; $this->viewPath = '/elements'; $this -> render('search', 'json'); }
(The original code has been updated. Thanks to: dogmatic69 and Cauan Cabral)
Besides the search action. You also have to have a json.ctp file in your view/layouts dir with the following content:
<?php echo $content_for_layout; ?>
Now you’re ready to have it working. All you have to do to finally “enable” the feature is to place the view (search.ctp) in your views/elements dir:
<?php echo $javascript -> object($items); ?>
Using the search
Currently, the search function accepts the following three named parameters:
- fields: the (comma separated) fields which we are going to compare against the term parameter.
- conditions: (pairs of comma separated) conditions for the search
- get: which (comma separated) fields we want to receive from the search (the result)
Now suppose you have a Post model in your application. If we want to:
- search for posts with “php” (term parameter) in the title or in it’s description (fields)
- the posts must be published already and the author has the user id (author_id field) equals 4.
- we want to retrieve only the title and tags fields
To make the previous search, we can now use the following URL to receive our JSON result:
http://example.com/posts /search/fields:title,description /conditions:published,1,author_id=4 /get:title,tags /?term=php
I think you got it, right? As always, I’m waiting for your comments. See ya.








[...] This post was mentioned on Twitter by Juan Basso, Zé Ricardo. Zé Ricardo said: [Cake on Steroids] Write-once Autocomplete Search: http://www.josericardo.eti.br/2010/07/31/cake-steroids-write-once-autocomplete-search [...]
cool idea but there are some things you could change in your method to make it more “cakeish”, like not using $_REQUEST but rather $this->params['named'] etc
and you could pick up problems with using $this->modelNames as there could be more than one model loaded on the controller and the one needed may not be the [0] model that is needed. its better to use $this->modelClass
you can also use Router::parseExtentions(‘json’) with a json view class to make it more automatic, where you can just add .json to the url and the json view takes over delivering the data.
Nice tips dogmatic, I’m going to update it ASAP.
UPDATED.
I knew I could parse the JSON extension, but I’m almost sure the JQuery UI’s Autocomplete does not allow me to append the .json string to the generated URL. I’ll check it out ASAP.
well ive noticed that so long as you have a .json anywere in the url cake picks it up… i have urls like /controller/action/someparam.json /controller/action.json/param:123 /controller/action/someparam/.json
and they all work
another random bit, mysql is not case sensitive for the most part so LOWER() is not needed… there are encodings that are case sensitive though.
Nice post…
About the last step (rendering results in json format), you can use a element (named “search.ctp”) and before call $this->render(‘search’, ‘json’); you set $this->viewPath = “/elements”;
Oh yeah, this is exactly what I was looking for. Updated
Well put, sir, well put. I’ll cetiralny make note of that.
This looks like it would be incredibly useful. For someone that’s still relatively new to CakePHP and early into the learning curve, could you provide an example of how to implement this in the view with the form that the user would be typing in? It would be greatly appreciated!
Looks like I was able to get things working. Ran into a few problems with the newest version of JQuery UI Autocomplete (1.8.2). It is now looking for a ‘label’ field to show the user in the drop down list. In my case, I don’t have a field in my table called label, so I had to create a $virtualFields entry in my model.
Also, the way the data comes back from the $this->{$model}->find(‘all’,$args); doesn’t mesh well with the format the JQuery UI Autocomplete is expecting:
[ { "id": "1", "label": "European Robin", "value": "European Robin" }, { "id": "2", "label": "Rufous-Tailed Scrub Robin", "value": "Rufous-Tailed Scrub Robin" } ]
vs CakePHP’s:
[ { "Bird" : { "id": "1", "label": "European Robin", "value": "European Robin" }}, { "Bird": { "id": "2", "label": "Rufous-Tailed Scrub Robin", "value": "Rufous-Tailed Scrub Robin" }} ]
I wasn’t sure if there was a better method to do reformat the data for JQuery, but this is what I ended up using:
$items = $newItems = array();
… do all the work …
foreach($items as $item) {
array_push($newItems,$item[$model]);
}
$this->set(‘items’, $newItems);
Gabriel, I’ve only used the JQuery UI’s Autocomplete with a callback as the source, setting the label and value in javascript
IMHO, returning the whole objects gives us more flexibility.
zehzinho: I’d like to do this the right way, but am a beginner with jquery as well so am struggling through this. Would it be possible for you to provide an example of a $(‘#blah’).autocomplete({…}) that you’d use to parse through the data as its returned via your search() function above?
Gabriel, I’ll do it ASAP, ok?
[...] http://www.josericardo.eti.br/2010/07/31/cake-steroids-write-once-autocomplete-search/ [...]
cool