In web applications, auto populating dropdowns are commonly used to simplify user input and ensure data consistency. When a user selects a value from one dropdown, another dropdown with related data is dynamically populated.
Consider this example. Assume we have a global online store and want to allow our customers to filter products by country and city. There could be two dropdowns: one for countries and one for cities. The city dropdown will dynamically change depending on the country selected by the user, displaying only cities in that country. This feature will make it easier for users to select the correct filter, reducing confusion and providing a better user experience.
In this tutorial, I show how you can create a dynamic dependent dropdown with MySQL database data using jQuery AJAX in CodeIgniter 4.
Table of Content
- Database configuration
- Enable CSRF
- Create Tables using Migration
- Create Models
- Create Routes
- Create Controller
- Create View
- Demo
- Conclusion
1. Database configuration
- Open
.env
file which is available at the project root.
NOTE – If dot (.) not added at the start then rename the file to .env.
- Remove # from start of
database.default.hostname
,database.default.database
,database.default.username
,database.default.password
,database.default.DBDriver
,database.default.DBPrefix
, anddatabase.default.port
. - Update the configuration and save it.
database.default.hostname = 127.0.0.1 database.default.database = codeigniterdb database.default.username = root database.default.password = root database.default.DBDriver = MySQLi database.default.DBPrefix = database.default.port = 3306
2. Enable CSRF
- Again open
.env
file. - Remove # from the start of the
security.tokenName
,security.headerName
,security.cookieName
,security.expires
,andsecurity.regenerate
. - I update the
security.tokenName
value with'csrf_hash_name'
. With this name read CSRF hash. You can update it with any other value. - If you don’t want to regenerate CSRF hash after each request then set
security.regenerate = false
.
security.tokenName = 'csrf_hash_name' security.headerName = 'X-CSRF-TOKEN' security.cookieName = 'csrf_cookie_name' security.expires = 7200 security.regenerate = true
- Open
app/Config/Filters.php
file. - Uncomment
'csrf'
in'before'
if commented.
// Always applied before every request public $globals = [ 'before' => [ // 'honeypot', 'csrf', // 'invalidchars', ], 'after' => [ 'toolbar', // 'honeypot', // 'secureheaders', ], ];
3. Create Tables using Migration
Create 3 new tables using migration –
- countries – Store countries name.
- states – Store country states name.
- cities – Store state cities name.
countries Table –
- Create
countries
table.
php spark migrate:create create_countries_table
- Now, navigate to
app/Database/Migrations/
folder from the project root. - Find a PHP file that ends with
CreateCountriesTable
open it. - Define the table structure in the
up()
method. - Using the
down()
method deletecountries
table that calls when undoing migration.
<?php namespace App\Database\Migrations; use CodeIgniter\Database\Migration; class CreateCountriesTable extends Migration { public function up(){ $this->forge->addField([ 'id' => [ 'type' => 'INT', 'constraint' => 10, 'unsigned' => true, 'auto_increment' => true, ], 'name' => [ 'type' => 'VARCHAR', 'constraint' => '60', ] ]); $this->forge->addKey('id', true); $this->forge->createTable('countries'); } public function down(){ $this->forge->dropTable('countries'); } }
states Table –
- Create
states
table.
php spark migrate:create create_states_table
- Navigate to
app/Database/Migrations/
folder and find PHP file that ends withCreateStatesTable
. - Define table structure in
up()
method. - Here, I added a foreign key to
country_id
field. - Using the
down()
method deletestates
table that calls when undoing migration.
<?php namespace App\Database\Migrations; use CodeIgniter\Database\Migration; class CreateStatesTable extends Migration { public function up(){ $this->forge->addField([ 'id' => [ 'type' => 'INT', 'constraint' => 10, 'unsigned' => true, 'auto_increment' => true, ], 'country_id' => [ 'type' => 'INT', 'constraint' => 10, 'unsigned' => true, ], 'name' => [ 'type' => 'VARCHAR', 'constraint' => '60', ] ]); $this->forge->addKey('id', true); $this->forge->addForeignKey('country_id', 'countries', 'id', 'CASCADE', 'CASCADE'); $this->forge->createTable('states'); } public function down(){ $this->forge->dropTable('states'); } }
cities Table –
- Create
cities
table.
php spark migrate:create create_cities_table
- Navigate to
app/Database/Migrations/
folder and find PHP file that ends withCreateCitiesTable
. - Define table structure in
up()
method. - Here, I added a foreign key to
state_id
field. - Using the
down()
method deletecities
table that calls when undoing migration.
<?php namespace App\Database\Migrations; use CodeIgniter\Database\Migration; class CreateCitiesTable extends Migration { public function up(){ $this->forge->addField([ 'id' => [ 'type' => 'INT', 'constraint' => 10, 'unsigned' => true, 'auto_increment' => true, ], 'state_id' => [ 'type' => 'INT', 'constraint' => 10, 'unsigned' => true, ], 'name' => [ 'type' => 'VARCHAR', 'constraint' => '60', ] ]); $this->forge->addKey('id', true); $this->forge->addForeignKey('state_id', 'states', 'id', 'CASCADE', 'CASCADE'); $this->forge->createTable('cities'); } public function down(){ $this->forge->dropTable('cities'); } }
- Run the migration –
php spark migrate
I added some records to the table.
4. Create Models
Create 3 Models –
- Countries
- States
- Cities
Countries Model –
- Create
Countries
Model.
php spark make:model Countries
- Open
app/Models/Countries.php
file. - In
$allowedFields
Array specify field names –['name']
that can be set during insert and update.
Completed Code
<?php namespace App\Models; use CodeIgniter\Model; class Countries extends Model { protected $DBGroup = 'default'; protected $table = 'countries'; protected $primaryKey = 'id'; protected $useAutoIncrement = true; protected $insertID = 0; protected $returnType = 'array'; protected $useSoftDeletes = false; protected $protectFields = true; protected $allowedFields = ['name']; // Dates protected $useTimestamps = false; protected $dateFormat = 'datetime'; protected $createdField = 'created_at'; protected $updatedField = 'updated_at'; protected $deletedField = 'deleted_at'; // Validation protected $validationRules = []; protected $validationMessages = []; protected $skipValidation = false; protected $cleanValidationRules = true; // Callbacks protected $allowCallbacks = true; protected $beforeInsert = []; protected $afterInsert = []; protected $beforeUpdate = []; protected $afterUpdate = []; protected $beforeFind = []; protected $afterFind = []; protected $beforeDelete = []; protected $afterDelete = []; }
States Model –
- Create
States
Model.
php spark make:model States
- Open
app/Models/States.php
file. - In
$allowedFields
Array specify field names –['country_id','name']
.
Completed Code
<?php namespace App\Models; use CodeIgniter\Model; class States extends Model { protected $DBGroup = 'default'; protected $table = 'states'; protected $primaryKey = 'id'; protected $useAutoIncrement = true; protected $insertID = 0; protected $returnType = 'array'; protected $useSoftDeletes = false; protected $protectFields = true; protected $allowedFields = ['country_id','name']; // Dates protected $useTimestamps = false; protected $dateFormat = 'datetime'; protected $createdField = 'created_at'; protected $updatedField = 'updated_at'; protected $deletedField = 'deleted_at'; // Validation protected $validationRules = []; protected $validationMessages = []; protected $skipValidation = false; protected $cleanValidationRules = true; // Callbacks protected $allowCallbacks = true; protected $beforeInsert = []; protected $afterInsert = []; protected $beforeUpdate = []; protected $afterUpdate = []; protected $beforeFind = []; protected $afterFind = []; protected $beforeDelete = []; protected $afterDelete = []; }
Cities Model –
- Create
Cities
Model.
php spark make:model Cities
- Open
app/Models/Cities.php
file. - In
$allowedFields
Array specify field names –['state_id','name']
.
Completed Code
<?php namespace App\Models; use CodeIgniter\Model; class Cities extends Model { protected $DBGroup = 'default'; protected $table = 'cities'; protected $primaryKey = 'id'; protected $useAutoIncrement = true; protected $insertID = 0; protected $returnType = 'array'; protected $useSoftDeletes = false; protected $protectFields = true; protected $allowedFields = ['state_id','name']; // Dates protected $useTimestamps = false; protected $dateFormat = 'datetime'; protected $createdField = 'created_at'; protected $updatedField = 'updated_at'; protected $deletedField = 'deleted_at'; // Validation protected $validationRules = []; protected $validationMessages = []; protected $skipValidation = false; protected $cleanValidationRules = true; // Callbacks protected $allowCallbacks = true; protected $beforeInsert = []; protected $afterInsert = []; protected $beforeUpdate = []; protected $afterUpdate = []; protected $beforeFind = []; protected $afterFind = []; protected $beforeDelete = []; protected $afterDelete = []; }
5. Create Routes
- Open
app/Config/Routes.php
file. - Define 3 routes –
- /
- page/fetchCountryStates– Fetch all states of the selected country.
- page/fetchStateCities – Fetch all cities of the selected state.
$routes->get('/', 'PagesController::index'); $routes->post('page/fetchCountryStates', 'PagesController::fetchCountryStates'); $routes->post('page/fetchStateCities', 'PagesController::fetchStateCities');
6. Create Controller
- Create
PagesController
Controller –
php spark make:controller PagesController
- Include
Countries
,States
, andCities
Models.
Create 3 methods –
- index() – Fetch all records from the
countries
table and assign it to$data['countries']
. Loadindex
view and pass$data
Array. - fetchCountryStates() – This method is use to handle AJAX requests and return states list by country_id.
Read POST country_id
value and assign it to $country_id
variable. Fetch records from the states
table where country_id = $country_id
. Assign fetched records to $response['states']
Array.
In the $response
Array also store a new CSRF hash to the token
key.
Return $response
Array in JSON format.
- fetchStateCities() – This method is also use to handle AJAX requests and return cities list by state_id.
Read POST state_id
value and assign it to $state_id
variable. Fetch records from the cities
table where state_id = $state_id
. Assign fetched records to $response['cities']
. Store new CSRF hash to $response['token']
.
Return $response
Array in JSON format.
Completed Code
<?php namespace App\Controllers; use App\Controllers\BaseController; use App\Models\Countries; use App\Models\States; use App\Models\Cities; class PagesController extends BaseController { public function index(){ ## Fetch all Countries $countryObj = new Countries(); $countriesList = $countryObj->select('*')->findAll(); $data['countries'] = $countriesList; return view('index',$data); } // Return country states list public function fetchCountryStates(){ ## Read POST data $request = service('request'); $postData = $request->getPost(); $country_id = $postData['country_id']; ## Fetch states list by country_id $stateObj = new States(); $statesList = $stateObj->select('id,name') ->where('country_id',$country_id) ->find(); $response['states'] = $statesList; ## New CSRF token $response['token'] = csrf_hash(); return $this->response->setJSON($response); } // Return state cities list public function fetchStateCities(){ ## Read POST data $request = service('request'); $postData = $request->getPost(); $state_id = $postData['state_id']; ## Fetch cities list by state_id $cityObj = new Cities(); $citiesList = $cityObj->select('id,name') ->where('state_id',$state_id) ->find(); $response['cities'] = $citiesList; ## New CSRF token $response['token'] = csrf_hash(); return $this->response->setJSON($response); } }
7. Create View
- Create
index.php
file inapp/Views/
folder.
HTML
- Create a hidden field to store CSRF hash in
value
attribute and CSRF name in thename
attribute. - Create 3
<select >
elements –<select id="sel_country" >
is for country selection. Loop on$countries
Array to add country<option >
.- Empty
<select id="sel_state" >
element for state selection. - Empty
<select id="sel_city" >
element for city selection.
jQuery Script
Country selection changed (Load country states) –
- Define
change
event on<select id="sel_country" >
. - Empty the
<select id="sel_state">
and<select id="sel_city">
elements, read the selected value, and send an AJAX POST request to<?=site_url('page/fetchCountryStates')?>
if it is not empty. SetdataType
tojson
and pass the CSRF token and country id as data. - On successful callback update CSRF token in hidden element with
response.token
. - Loop on the states list and add new
<option >
to<select id="sel_state">
.
State selection changed (Load state cities) –
- Define
change
event on<select id="sel_state" >
. - Empty the
<select id="sel_city">
, read the selected value, and send an AJAX POST request to<?=site_url('page/fetchStateCities')?>
if it is not empty. SetdataType
tojson
and pass the CSRF token and state id as data. - On successful callback update CSRF token in hidden element with
response.token
. - Loop on the citites list and add new
<option >
to<select id="sel_city">
.
Completed Code
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>How to Create Dynamic Dependent Dropdowns with AJAX in CodeIgniter 4</title> <style type="text/css"> body{ font-size: 18px; } select{ width: 200px; padding: 5px; font-size: 18px; } </style> </head> <body> <!-- CSRF token --> <input type="hidden" class="txt_csrfname" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" /> Country: <br> <select id="sel_country" class="form-control"> <option value="">-- Select Country --</option> <?php foreach($countries as $country){ ?> <option value="<?= $country['id'] ?>"><?= $country['name'] ?></option> <?php } ?> </select> <br><br> State: <br> <select id="sel_state" class="form-control"> <option value="">-- Select State --</option> </select> <br><br> City: <br> <select id="sel_city" class="form-control"> <option value="">-- Select City --</option> </select> <!-- Script --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function(){ // Country selection change $('#sel_country').change(function(){ var country_id = $(this).val(); // Empty state and city dropdown $('#sel_state').find('option').not(':first').remove(); $('#sel_city').find('option').not(':first').remove(); if(country_id != ''){ // CSRF Hash var csrfName = $('.txt_csrfname').attr('name'); // CSRF Token name var csrfHash = $('.txt_csrfname').val(); // CSRF hash // AJAX request $.ajax({ url:"<?=site_url('page/fetchCountryStates')?>", type: 'post', data: {[csrfName]: csrfHash,country_id: country_id}, dataType: 'json', success: function(response){ // Update CSRF Token $('.txt_csrfname').val(response.token); // Read state list var len = response.states.length; // Add data to state dropdown for( var i = 0; i<len; i++){ var id = response.states[i]['id']; var name = response.states[i]['name']; $("#sel_state").append("<option value='"+id+"'>"+name+"</option>"); } } }); } }); // State selection change $('#sel_state').change(function(){ var state_id = $(this).val(); // Empty city dropdown $('#sel_city').find('option').not(':first').remove(); if(state_id != ''){ // CSRF Hash var csrfName = $('.txt_csrfname').attr('name'); // CSRF Token name var csrfHash = $('.txt_csrfname').val(); // CSRF hash // AJAX request $.ajax({ url:"<?=site_url('page/fetchStateCities')?>", type: 'post', data: {[csrfName]: csrfHash,state_id: state_id}, dataType: 'json', success: function(response){ // Update CSRF Token $('.txt_csrfname').val(response.token); // Read city list var len = response.cities.length; // Add data to city dropdown for( var i = 0; i<len; i++){ var id = response.cities[i]['id']; var name = response.cities[i]['name']; $("#sel_city").append("<option value='"+id+"'>"+name+"</option>"); } } }); } }); }); </script> </body> </html>
8. Demo
9. Conclusion
Auto populating dropdowns using AJAX is a valuable feature that improves the user experience in web applications and reduces the risk of incorrect data input. By using AJAX, you can dynamically update the contents of a dropdown.
By following the tutorial, you can easily create multiple dynamic dependent dropdowns on your page.
Remove the CSRF token code if it is not enabled in your project.
If data is not loading in the dropdown after selection then debug it using the browser console and network tab, and recheck the code and SQL queries.
View more CodeIgniter 4 tutorials.
If you found this tutorial helpful then don't forget to share.