Using jQuery UI sortable you can enable HTML element dragging on your page.
Initialize it on the element that you want to allow the users to reorder.
In this tutorial, I show how you can reorder images using jQuery UI and save them to the MySQL database in CodeIgniter 4.

Contents
1. Database configuration
- Open
.envfile 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
.envfile. - Remove # from the start of the
security.tokenName,security.headerName,security.cookieName,security.expires,andsecurity.regenerate. - I update the
security.tokenNamevalue 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.phpfile. - Uncomment
'csrf'in'before'if commented.
// Always applied before every request
public $globals = [
'before' => [
//'honeypot'
'csrf',
],
'after' => [
'toolbar',
//'honeypot'
],
];
3. Create Table
- Create a new table
imagesusing migration.
php spark migrate:create create_images_table
- Now, navigate to
app/Database/Migrations/folder from the project root. - Find a PHP file that ends with
CreateImagesTableand open it. - Define the table structure in the
up()method.- filename – This field is used to store file name.
- path – This field is used to store the upload file path.
- sort – Store position.
- Using the
down()method deleteimagestable that calls when undoing migration.
<?php namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateImagesTable extends Migration
{
public function up() {
$this->forge->addField([
'id' => [
'type' => 'INT',
'constraint' => 10,
'unsigned' => true,
'auto_increment' => true,
],
'filename' => [
'type' => 'VARCHAR',
'constraint' => '100',
],
'path' => [
'type' => 'VARCHAR',
'constraint' => '255',
],
'sort' => [
'type' => 'INT',
'constraint' => '5',
],
]);
$this->forge->addKey('id', true);
$this->forge->createTable('images');
}
//--------------------------------------------------------------------
public function down() {
$this->forge->dropTable('images');
}
}
- Run the migration –
php spark migrate
I added some records to the table.
4. Model
- Create
ImagesModel –
php spark make:model Images
- Open
app/Models/Images.phpfile. - In
$allowedFieldsArray specify field names –['filename','path','sort']that can be set during insert and update.
Completed Code
<?php
namespace App\Models;
use CodeIgniter\Model;
class Images extends Model
{
protected $DBGroup = 'default';
protected $table = 'images';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
protected $insertID = 0;
protected $returnType = 'array';
protected $useSoftDeletes = false;
protected $protectFields = true;
protected $allowedFields = ['filename','path','sort'];
// 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. Route
- Open
app/Config/Routes.phpfile. - Define 2 routes –
- / – Display images list in view.
- images/updatePosition – Update file position.
Completed Code
$routes->get('/', 'ImagesController::index');
$routes->post('images/updatePosition', 'ImagesController::updatePosition');
6. Controller
- Create
ImagesControllerController –
php spark make:controller ImagesController
- Open
app/Controllers/ImagesController.phpfile. - Import
ImagesModel. - Create 2 methods –
- index() – Fetch all records from
imagestable and order bysortfield. Assign fetched records to$data['images'].
- index() – Fetch all records from
Load images view and pass $data Array.
-
- updatePosition() – Using this method update file position.
Read POST data and check imageids is POST or not. If POST then assign $postData['imageids'] to $imageids_arr Array.
Loop on $imageids_arr Array to read update id. Update sort field with new $position value. Assign 1 to $status.
Store new token hash to $data['token'] and $status to $data['status']. Return $data Array in JSON format.
Completed Code
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use App\Models\Images;
class ImagesController extends BaseController
{
public function index(){
// Fetch data
$images = new Images();
$data['images'] = $images->select('*')
->orderBy('sort','asc')
->findAll();
return view('images',$data);
}
// Update position
public function updatePosition(){
$request = service('request');
$postData = $request->getPost();
$status = 0;
$imageids_arr = array();
if(isset($postData['imageids'])){
$imageids_arr = $postData['imageids'];
if(count($imageids_arr) > 0){
// Update sort position of images
$position = 1;
foreach($imageids_arr as $id){
$image = new Images();
$image->set('sort', $position);
$image->where('id', $id);
$image->update();
$position ++;
}
}
$status = 1;
}
// New CSRF token
$data['token'] = csrf_hash();
$data['status'] = $status;
return $this->response->setJSON($data);
}
}
7. View
HTML
- Create
images.phpfile inapp/Views/. - Include jQuery, jQuery UI, and jQuery touch library.
<!-- 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.1/jquery.min.js"></script> <!-- jQuery UI JS --> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js"></script> <!-- jQuery touch --> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.touch/1.1.0/jquery.touch.min.js"></script>
NOTE – Using
jquery.touch.min.jsto enable touch event on the mobile devices.
- Create a hidden element to store the token name and hash.
- Create
<ul id="sortable" >to list images and initialize sortable. - Loop on
$imagesto add<li >elements. - In the
<li >addclass="ui-state-default"and store image id indata-idattribute. - Add
<img >element. Pass$pathinsrcattribute. - Create a button to update the position of the images using jQuery AJAX.
Script
- Initialize
sortable()on<ul id="sortable" >. - On button
clickread CSRF token name and hash. - Create
imageids_arrArray to store image ids for updating records. - Loop on
<ul id="sortable" ><li>and readdata-idattribute value and push toimageids_arr. - Send AJAX POST request to
site_url('images/updatePosition'). - Here, pass CSRF token and
imageids_arrArray. - On successful callback update CSRF token hash and alert a success message.
Completed Code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Reorder images and save to MySQL database - CodeIgniter 4</title>
<!-- 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.1/jquery.min.js"></script>
<!-- jQuery UI JS -->
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js"></script>
<!-- jQuery touch -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.touch/1.1.0/jquery.touch.min.js"></script>
<style>
#sortable {
list-style-type: none;
margin: 0;
padding: 0;
width: 90%;
}
#sortable li {
margin: 3px 3px 3px 0;
padding: 1px;
float: left;
border: 0;
background: none;
}
#sortable li img{
width: 180px;
height: 140px;
}
</style>
</head>
<body>
<!-- CSRF Token -->
<input type="hidden" class="txt_csrfname" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />
<div style='width: 100%;'>
<!-- List Images -->
<ul id="sortable" >
<?php
foreach($images as $image){
$id = $image['id'];
$filename = $image['filename'];
$path = $image['path'];
echo '<li class="ui-state-default" data-id="'.$id.'" >
<img src="'.$path.'" title="'.$filename.'" ><br>
'.$filename.'
</li>';
}
?>
</ul>
</div>
<div style="clear: both; margin-top: 20px;">
<input type='button' value='Save' id='btnsave'>
</div>
<!-- Script -->
<script type="text/javascript">
$(document).ready(function(){
// Initialize sortable
$( "#sortable" ).sortable();
// Save order
$('#btnsave').click(function(){
// CSRF Token name
var csrfName = $('.txt_csrfname').attr('name');
var csrfHash = $('.txt_csrfname').val(); // CSRF hash
var imageids_arr = [];
// Get image ids order
$('#sortable li').each(function(){
var id = $(this).data('id');
imageids_arr.push(id);
});
// AJAX request
$.ajax({
url: "<?=site_url('images/updatePosition')?>",
type: 'post',
data: {[csrfName]: csrfHash,imageids: imageids_arr},
success: function(response){
// Update token
$('.txt_csrfname').val(response.token);
if(response.status == 1)
alert('Save successfully.');
}
});
});
});
</script>
</body>
</html>
8. Output
9. Conclusion
You can use the same concept to save other dragged element positions.
In the example, I am calling AJAX request to update the position when button gets clicked but if you want to send request when any image position gets changed then use sortable sortchange event.
Learn more about sortchange event from here.