Real-Time Search With AJAX and CakePHP
As I type away at my 9 to 5 I get a request from an executive to create a “real-time search bar for the employees-dashboard”. A fairly typical request for a code monkey like myself and, at first glance, an easily solvable problem.
Several hours of caffeine fueled spaghetti code later, and several more hours cleaning said spaghetti code, I finally emerged with 2 things.
- A caffeine headache that put Freud’s cocaine hangovers to shame.
- A working real-time search bar, which I will be demonstrating in the tutorial below.
If you like your skull not to rattle with every heartbeat please do me a favor and let me show you how the wheel is made so you don’t spend any time trying to remake it. I’ll assume that already have a CakePHP project running and jump straight into setting up the application to work with AJAX.
Real-Time Search Setup
In order for your application to accept AJAX requests, you’ll need to load the RequestHandler
component in either your AppController or the controller you want to accept AJAX requests with.
AppController.php
public function initialize()
{
//...
$this->loadComponent('RequestHandler');
}
Now that our application can accept AJAX requests we’ll need to create a view cell. Baking a view cell is simple with cake’s command-line tool.
bin/cake bake cell Search
This should create two files:
src/View/Cell/SearchCell.php
src/Template/Cell/Search/display.ctp
Now let’s create a view cell that will handle our searches.
Real-Time Search View Cell
Open the files we previously created in the command-line and you’ll see an empty display.ctp
file and this:
SearchCell.php
<?php
namespace App\\View\\Cell;use Cake\\View\\Cell;
use Cake\\Datasource\\Paginator;class SearchCell extends Cell
{
public function display()
{
}
}
SearchCell.php
is a view cell. A view cell is a mini-controller that can invoke logic and render templates. Basically, a reusable controller meant for rendering views.
This will be the piece of logic to re-render our view every time we search. In order to make our real-time search, we may want to put some logic into it. In the display function of our view cell let’s put the following:
//...
use Cake\\View\\Cell;
use Cake\\Datasource\\Paginator;
//...
function display($name) {
$this->loadModel('Employees');
// Create a paginator
$paginator = new Paginator();
// Paginate the model
$results = $paginator->paginate(
$this->Employees->find('all'),
$this->request->getQueryParams(),
[
'conditions' => ['Employees.name like' => $name . '%']
]
); $paging = $paginator->getPagingParams() + (array)$this->request->getParam('paging');
$this->request = $this->request->withParam('paging', $paging); $this->set('employees', $results);
}
//...
That’s quite a big leap, let’s take a step back and look at what this actually does.
First we load the Employees model and create a paginator.
$this->loadModel('Employees');
$paginator = new Paginator();
Then we add our model to our paginator, pass the URL queries, and create a condition where we only show the employees who’s names are like the name passed into our view cell.
$results = $paginator->paginate(
$this->Employees->find('all'),
$this->request->getQueryParams(),
[
'conditions' => ['Employees.name like' => $name . '%']
]
);
How do we actually see the view cell in action? All we have to do is create a template that displays the relevant results! In this case that’s the employee name and position.
<table>
<tr>
<th>Name</th>
<th>Position</th>
</tr>
<?php foreach($employees as $employee): ?>
<tr>
<td><?= $employee->name ?></td>
<td><?= $employee->position ?></td>
</tr>
<?php endforeach; ?>
</table>
<?= $this->element('Layouts/pagination'); ?>
Now every time we put <?= $this->cell('Search', [$name]); ?>
in a view our view cell template will be rendered! For us that means every time we do an AJAX call we can update our table without messy calls, or view logic.
We have our view cell now let’s start searching in our main view!
Real-Time Search Controller
In our EmployeesController.php
‘s index function we will need to do two things
- Handle the initial non-AJAX call.
- Update the table when our AJAX search is used.
Here’s what that looks like in full:
EmployeesController.php
<?php
namespace App\\Controller;use Cake\\ORM\\Query;
use Cake\\Database\\Expression\\QueryExpression;
use Cake\\View\\CellTrait;
use Cake\\Datasource\\Paginator;class EmployeesController extends AppController {
use CellTrait; public function initialize() {
parent::initialize();
$this->loadComponent('Paginator');
} public function index() {
$name = $this->request->getQuery('name') ? $this->request->getQuery('name') : '';
$query = $this->Employees
->find('all', [
'conditions' => [
'Employees.name LIKE' => '%'. $name .'%'
]
]); $employees = $this->paginate($query); if ($this->request->is('ajax')) {
$name = $this->request->getQuery('name');
$this->set(compact('name'));
$this->render('index-search');
} $this->set(compact([
'name',
'employees'
]));
}?>
The first part is, hopefully, easy to understand. All we’re doing is checking if there is a name
variable in the URL, finding all employees, and paginating them. In the middle, we check to see if we are dealing with an AJAX request. If so we get the name
variable from our URL, set it as a view variable, and render a new template. Our index-search
template is a call to the view cell with the included name
view variable.
index_search.ctp
<?= $this->cell('Search', [$name]); ?>
This allows us to quickly update the employees table with our view cell template.
For our index.ctp
template file we’ll, finally, add the search bar and the AJAX call!
index.ctp
<h1>Employees</h1>
<input type="text" class="form-control" id="employee-search" placeholder="Employee name" value="<?= $name ?>">
<script type="text/javascript">
$( document ).ready(function() {
$('#employee-search').keyup(function () {
var thisHref = '/employees?name=' + $(this).val();
if (!thisHref) {
return false;
}
$('#pagination-container').fadeTo(300, 0); $('#pagination-container').load(thisHref, function() {
$(this).fadeTo(200, 1);
});
return false;
});
});
</script><div id="pagination-container" class="col-12">
<?= $this->cell('Search', [$name]); ?>
</div>
You can see that the AJAX call is simple! All we do is take the value from our search bar, call the jQuery load
function, and add-in some fancy fade effects to make the search look nicer!
The jQuery load
function takes the result from the given URL and replaces the inner HTML of the object.
Conclusion
How did you like the tutorial? Let me know in the comments below! If you liked it why not subscribe for new tutorials every Tuesday, and new bonus posts on Thursday!