Real-Time Search With AJAX and CakePHP

Christian Gonzalez | cakephp.blog
4 min readNov 18, 2020

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.

  1. A caffeine headache that put Freud’s cocaine hangovers to shame.
  2. 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:

  1. src/View/Cell/SearchCell.php
  2. 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

  1. Handle the initial non-AJAX call.
  2. 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!

--

--