Unleashing User-Friendly Search: jQuery UI Autocomplete in CakePHP 4

jQuery UI library provides different types of user interface widgets that are easy to implement on the page. One of the powerful widget is autocomplete.

Autocomplete simplifies user interaction by presenting a list of suggestions as users type into an input field. You can choose to pre-load this suggestion data on the client side during initialization or fetch it dynamically using AJAX.

In this tutorial, I show how you can add jQuery UI Autocomplete in CakePHP 4 and load MySQL database data using jQuery AJAX.

Unleashing User-Friendly Search: jQuery UI Autocomplete in CakePHP 4


Table of Content

  1. Create a Table and Insert Data
  2. Update Database Configuration
  3. Creating the Model
  4. Creating the Controller
  5. Include jQuery and CSRF token
  6. Creating the Autocomplete Template
  7. Output
  8. Conclusion

1. Create a Table and Insert Data

In the example, I am using users Tables. It has the following structure and data –

CREATE TABLE `users` (
    `id` int(11) NOT NULL,
    `username` varchar(50) NOT NULL,
    `name` varchar(60) NOT NULL,
    `gender` varchar(10) NOT NULL,
    `email` varchar(60) NOT NULL,
    `city` varchar(80) NOT NULL
);

ALTER TABLE `users`
    ADD PRIMARY KEY (`id`);

ALTER TABLE `users`
    MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

INSERT INTO `users` (`id`, `username`, `name`, `gender`, `email`, `city`) VALUES
(1, 'yssyogesh', 'Yogesh singh', 'male', 'yogesh@makitweb.com', 'Bhopal'),
(2, 'bsonarika', 'Sonarika Bhadoria', 'female', 'bsonarika@makitweb.com', 'Indore'),
(3, 'sunil', 'Sunil singh', 'male', 'sunil@makitweb.com', 'Pune'),
(4, 'vishal', 'Vishal Sahu', 'male', 'vishal@makitweb.com', 'Bhopal'),
(5, 'jiten', 'jitendra singh', 'male', 'jitendra@makitweb.com', 'Delhi'),
(6, 'shreya', 'Shreya joshi', 'female', 'shreya@makitweb.com', 'Indore'),
(7, 'abhilash', 'Abhilash namdev', 'male', 'abhilash@makitweb.com', 'Pune'),
(8, 'ekta', 'Ekta patidar', 'female', 'ekta@makitweb.com', 'Bhopal'),
(9, 'deepak', 'Deepak singh', 'male', 'deepak@makitweb.com', 'Delhi'),
(10, 'rohit', 'Rohit Kumar', 'male', 'rohit@makitweb.com', 'Bhopal'),
(11, 'bhavna', 'Bhavna Mahajan', 'female', 'bhavna@makitweb.com', 'Indore'),
(12, 'ajay', 'Ajay singh', 'male', 'ajay@makitweb.com', 'Delhi'),
(13, 'mohit', 'Mohit', 'male', 'mohit@makitweb.com', 'Pune'),
(14, 'akhilesh', 'Akhilesh Sahu', 'male', 'akhilesh@makitweb.com', 'Indore'),
(15, 'ganesh', 'Ganesh', 'male', 'ganesh@makitweb.com', 'Pune'),
(16, 'vijay', 'Vijay', 'male', 'vijay@makitweb.com', 'Delhi');

2. Update Database Configuration

  • Open config/app_local.php file.
  • Specify your database configuration details in the Datasources default.
'Datasources' => [
     'default' => [
          'host' => '127.0.0.1',
          /*
          * CakePHP will use the default DB port based on the driver selected
          * MySQL on MAMP uses port 8889, MAMP users will want to uncomment
          * the following line and set the port accordingly
          */
          //'port' => 'non_standard_port_number',

          'username' => 'root',
          'password' => 'root',

          'database' => 'cakephp4',
          /*
          * If not using the default 'public' schema with the PostgreSQL driver
          * set it here.
          */
          //'schema' => 'myapp',

          /*
          * You can use a DSN string to set the entire configuration
          */
          'url' => env('DATABASE_URL', null),
     ],

     /*
     * The test connection is used during the test suite.
     */
     'test' => [
          'host' => 'localhost',
          //'port' => 'non_standard_port_number',
          'username' => 'my_app',
          'password' => 'secret',
          'database' => 'test_myapp',
          //'schema' => 'myapp',
          'url' => env('DATABASE_TEST_URL', 'sqlite://127.0.0.1/tests.sqlite'),
     ],
],

3. Creating the Model

  • To create the Users model, open your command line or terminal and navigate to your CakePHP application directory. Then, run the following command:
bin/cake bake model Users
  • Executing this command will result in the creation of two files:
src/Model/Entity/User.php
src/Model/Table/UsersTable.php

src/Model/Entity/User.php

This Entity class is where we define the fields that can be inserted or updated in the database. You have the flexibility to specify which fields are accessible for these operations. You can also choose to exclude certain fields by either removing them or setting them to ‘false’.

<?php
declare(strict_types=1);

namespace App\Model\Entity;

use Cake\ORM\Entity;
class User extends Entity
{
     protected $_accessible = [
          'username' => true,
          'name' => true,
          'gender' => true,
          'email' => true,
          'city' => true,
     ];
}

src/Model/Table/UsersTable.php

The Table class is crucial as it informs the ORM (Object-Relational Mapping) which database table to interact with and also defines validation rules for the fields.

<?php
declare(strict_types=1);

namespace App\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class UsersTable extends Table
{

      public function initialize(array $config): void
      {
            parent::initialize($config);

            $this->setTable('users');
            $this->setDisplayField('name');
            $this->setPrimaryKey('id');
      }

      public function validationDefault(Validator $validator): Validator
      {
            $validator
                ->scalar('username')
                ->maxLength('username', 50)
                ->requirePresence('username', 'create')
                ->notEmptyString('username');

            $validator
                ->scalar('name')
                ->maxLength('name', 60)
                ->requirePresence('name', 'create')
                ->notEmptyString('name');

            $validator
                ->scalar('gender')
                ->maxLength('gender', 10)
                ->requirePresence('gender', 'create')
                ->notEmptyString('gender');

            $validator
                ->email('email')
                ->requirePresence('email', 'create')
                ->notEmptyString('email');

            $validator
                ->scalar('city')
                ->maxLength('city', 80)
                ->requirePresence('city', 'create')
                ->notEmptyString('city');

            return $validator;
      }

      public function buildRules(RulesChecker $rules): RulesChecker
      {
            $rules->add($rules->isUnique(['username']), ['errorField' => 'username']);
            $rules->add($rules->isUnique(['email']), ['errorField' => 'email']);

            return $rules;
      }
}

4. Creating the Controller

  • Create a AutcompleteController.php file in src/Controller/ folder.
  • Create AutocompleteController Class that extends AppController.

Create 2 method –

  • index() 
  • getUserSuggestions() –

Managing jQuery UI AJAX requests and generating the autocomplete suggestion list using this method.

Extracting the search value from the POST request and assigning it to the $search variable. By utilizing $this->getTableLocator()->get('Users'), an instance of the Users Table is created. If $search is not empty, a condition is set to search the name field using the LIKE operator.

It’s worth noting that a limit of 5 entries has been defined, although this value can be tailored to suit the specific requirements of your project.

To facilitate the response, an array named $data_arr is established. The records retrieved from the query are looped through, and for each user, their ID is associated with the value key, while their name is linked to the label key within the $data_arr array.

Return the formatted $data_arr array in the JSON format.

<?php
declare(strict_types=1);

namespace App\Controller;

class AutocompleteController extends AppController
{

      public function index(){

      }

      // Get Autocomplete Data
      public function getUserSuggestions(){

           ## POST value
           $search = trim($this->request->getData()['search']);

           ## Fetch users
           $USERS = $this->getTableLocator()->get('Users');

           $query = $USERS->find('all');

           // Search value
           if(!empty($search)){
                $query->where(['name LIKE' => "%".$search."%"]);
           }
           $query->order(['name' => 'ASC']);
           $query->limit(5);
           $usersList = $query->toArray();

           $data_arr = array();
           foreach($usersList as $user){
                $data_arr[] = array(
                      'value' => $user['id'],
                      'label' => $user['name']
                );
           }

           echo json_encode($data_arr);
           die;
      }

}

5. Include jQuery and CSRF token

I am including JS libraries and CSRF token on templates/layout/default.php file.

Stored CSRF token in <meta > tag –

<?= $this->Html->meta('csrfToken', $this->request->getAttribute('csrfToken')); ?>

Include jQuery and jQuery UI library in <head > section –

<!-- jQuery UI CSS -->
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/themes/smoothness/jquery-ui.css">

<!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>

<!-- jQuery UI JS -->
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js"></script>

Full Code

<?php
$cakeDescription = 'CakePHP: the rapid development php framework';
?>
<!DOCTYPE html>
<html>
<head>
     <?= $this->Html->charset() ?>
     <meta name="viewport" content="width=device-width, initial-scale=1">

     <!-- CSRF Token -->
     <?= $this->Html->meta('csrfToken', $this->request->getAttribute('csrfToken')); ?>
     <title>
           <?= $cakeDescription ?>:
           <?= $this->fetch('title') ?>
     </title>
     <?= $this->Html->meta('icon') ?>

     <link href="https://fonts.googleapis.com/css?family=Raleway:400,700" rel="stylesheet">

     <?= $this->Html->css(['normalize.min', 'milligram.min', 'cake']) ?>
     <!-- jQuery UI CSS -->
     <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/themes/smoothness/jquery-ui.css">

     <!-- jQuery -->
     <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>

     <!-- jQuery UI JS -->
     <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js"></script>

     <?= $this->fetch('meta') ?>
     <?= $this->fetch('css') ?>
     <?= $this->fetch('script') ?>
</head>
<body>
     <nav class="top-nav">
          <div class="top-nav-title">
               <a href="<?= $this->Url->build('/') ?>"><span>Cake</span>PHP</a>
          </div>
          <div class="top-nav-links">
               <a target="_blank" rel="noopener" href="https://book.cakephp.org/4/">Documentation</a>
               <a target="_blank" rel="noopener" href="https://api.cakephp.org/">API</a>
          </div>
     </nav>
     <main class="main">
          <div class="container">
               <?= $this->Flash->render() ?>
               <?= $this->fetch('content') ?>
          </div>
     </main>
     <footer>
     </footer>
</body>
</html>

6. Creating the Autocomplete Template

Create a directory named Autocomplete within the templates/ location. Inside this directory, generate a file named index.php at templates/Autocomplete/index.php.

Create 2 input elements  –

  1. The first input serves as the initializer for jQuery UI’s autocomplete functionality.
  2. The second input is designated to exhibit the value of the selected item, where in this case, the user ID is being showcased.

jQuery

  1. Extract the CSRF token from the <meta> tag and assign it to the variable csrfToken.
  2. Initialize the autocomplete on #searchuser and configure three options:

These options are as follows:

  • “source”: This option handles the data loading process.
    • An AJAX POST request is send to the URL defined by $this->Url->build(['controller' => 'Autocomplete','action' => 'getUserSuggestions']).
    • The “dataType” is set to ‘json’, and the typed input value is transmitted as data – { search: request.term }.
    • The CSRF token is also included in the request headers – { 'X-CSRF-Token': csrfToken }.
  • “select”: This event is triggered when an item is chosen from the suggestions list.
    • The selected item’s label is stored in the #searchuser element.
    • The associated value (user ID) is stored in the #userid element.
  • “focus”: This event activates when navigating through the list using keyboard arrow keys.
    • Similar to “select”, the label and value of the chosen item are stored in the respective input elements.
<div class="row">
     <div class="col-6">

          <!-- -->
          <?php 
          echo $this->Form->control('searchuser',['id'=>'searchuser','label'=>'Search User','class' => 'form-control']);
          echo $this->Form->control('userid',['id'=>'userid','label' => 'Selected User ID','class' => 'form-control']);
          ?>
          <!-- -->

     </div>
</div>

<!-- Script -->
<script type="text/javascript">
// Read CSRF Token
var csrfToken = $('meta[name="csrfToken"]').attr('content');
$(document).ready(function(){

     // Initialize
     $( "#searchuser" ).autocomplete({

          source: function( request, response ) {

               // Fetch data
               $.ajax({
                    url: "<?= $this->Url->build(['controller' => 'Autocomplete','action' => 'getUserSuggestions']) ?>",
                    type: 'post',
                    dataType: "json",
                    data: {
                         search: request.term
                    },
                    headers:{
                         'X-CSRF-Token': csrfToken
                    },
                    success: function( data ) {
                         response( data );
                    }
               });
          },
          select: function (event, ui) {
               // Set selection
               $('#searchuser').val(ui.item.label); // display the selected text
               $('#userid').val(ui.item.value); // save selected id to input
               return false;
          },
          focus: function(event, ui){
               $( "#searchuser" ).val( ui.item.label );
               $( "#userid" ).val( ui.item.value );
               return false;
          },
     });

});
</script>

7. Output

View Output


8. Conclusion

jQuery UI autocomplete can greatly enhance the search functionality of your web pages it makes them more user-friendly and efficient. Users can easily find what they are looking for by simply typing a few characters, and the real-time search suggestions can significantly improve their experience.

I hope this tutorial has been helpful in getting you started.

You can also checkout this tutorial if you want to know auto-populate dropdown using jQuery AJAX in CakePHP 4.

If you found this tutorial helpful then don't forget to share.

Leave a Comment