Tutorial Membuat CRUD CodeIgniter 4 Ajax dan Validasi

irfan-photo irfan · 2 bulan lalu

Tutorial Membuat CRUD CodeIgniter 4 Ajax dan Validasi

belajar dasar CodeIgniter 4 sudah dilakukan, sekarang saatnya untuk mengetes kemampuan dengan membuat proses CRUD

CRUD itu singkatan dari create, read, update, delete

jadi nantinya aplikasi CRUD ini akan menjadi sebuah aplikasi daftar-produk yang memiliki proses seperti diatas

untuk lebih jelasnya seperti apa CRUD yang akan dibuat berikut spesifikasinya

  • Menggunakan Ajax
  • Dengan Validasi Data (client dan server)
  • Menghandle File Upload
  • Menggunakan Plugin jQuery, Bootstrap 5, bootbox.js dan bootstrap-table (CDN)
  • Membuat 2 Halaman yaitu homepage dan product
  • CRUD Single Page

Persiapan

sebelum membuat aplikasi CRUD ini pastikan kalian sudah melakukan hal berikut

Pastikan kalian juga punya koneksi internet karena tutorial ini akan menggunakan script CDN yang dimana untuk mengaksesnya membutuhkan internet

Membuat Database dan Table

kita gunakan spark untuk membuat database dan tablenya menggunakan migration

Langkah 1 (Konfigurasi Database)

buka file '.env' dan edit bagian databasenya

#--------------------------------------------------------------------
# DATABASE
#--------------------------------------------------------------------

database.default.hostname = localhost
database.default.database = belajar_ci4
database.default.username = root
database.default.password = 
database.default.DBDriver = MySQLi
database.default.DBPrefix =

Langkah 2 (Buat Database)

ketikan perintah spark

php spark db:create belajar_ci4

Langkah 3 (Buat Migration)

ketikan perintah spark

php spark make:migration product

buka file migration yang telah dibuat terletak di 'app/Database/Migrations/DATETIME_Product.php'

Ganti isi Kodenya dengan kode dibawah ini

<?php
 
namespace App\Database\Migrations;
 
use CodeIgniter\Database\Migration;
 
class Product extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id'          => [
                'type'           => 'INT',
                'constraint'     => 100,
                'unsigned'       => true,
                'auto_increment' => true,
            ],
            'name'       => [
                'type'       => 'VARCHAR',
                'constraint' => '150',
            ],
            'category' => [
                'type' => 'VARCHAR',
                'constraint' => '100',
            ],
            'price' => [
                'type' => 'INT',
                'default' => 0,
            ],
            'photo' => [
                'type' => 'VARCHAR',
                'constraint' => '200',
            ],
            'created_at' => [
                'type' => 'DATETIME',
            ],
            'updated_at' => [
                'type' => 'DATETIME',
            ]
            ]);
        $this->forge->addKey('id', true);
        $this->forge->createTable('product');
    }
 
    public function down()
    {
        $this->forge->dropTable('product');
    }
}

Langkah 4 (Jalankan Proses Migrasi)

ketikan perintah spark dibawah ini untuk mnejalankan migrasi yang telah dibuat

php spark migrate

Membuat Controller

kita gunakan spark untuk membuat controllernya dengan mengetikan perintah

php spark make:controller product
php spark make:controller homepage

dilanjut dengan mengedit controllernya

buka file controller homepage terletak di 'app/Controllers/Homepage.php'

edit kodenya menjadi seperti dibawah ini

<?php

namespace App\Controllers;

use App\Controllers\BaseController;

class Homepage extends BaseController
{
    public function index()
    {

        $data['title'] = 'Homepage';

        $productModel = new \App\Models\Product();
        $data['products'] = $productModel->getData();

        return view('homepage', $data);
    }
}

edit kode controller product menjadi seperti dibawah ini

<?php

namespace App\Controllers;

use App\Controllers\BaseController;

class Product extends BaseController
{
    public function index()
    {

        $data['title'] = 'Product';

        return view('product', $data);
    }

    public function read()
    {
        $productModel = new \App\Models\Product();
        $products = $productModel->getDataForBootstrapTable($this->request);        
        return $this->response->setJSON($products);
    }

    private function _post_product($is_update = false)
    {

        // validate input text
        $validationRule = [
            'name' => [
                'rules' => 'required'
            ],
            'category' => [
                'rules' => 'required'
            ],
            'price' => [
                'rules' => 'required'
            ]            
        ];

        if (!$this->validate($validationRule)) {
            $error = $this->validator->getErrors();
            $error_val = array_values($error);
            die(json_encode([
                'status' => false,
                'response' => $error_val[0]
            ])); 
        }           

        $data['name'] = $this->request->getPost('name');
        $data['category'] = $this->request->getPost('category');        
        $data['price'] = $this->request->getPost('price');    

        // ======== photo handle

        // if create and not have photo
        if (!$is_update AND !$this->request->getFile('photo')->isValid()) {
            die(json_encode([
                'status' => false,
                'response' => 'photo required'
            ])); 
        }        

        // check new photo exist
        $photo = $this->request->getFile('photo');      
        if ($photo->isValid()) {

            // validate input file
            $validationRule = [
                'photo' => [
                    'rules' => 'uploaded[photo]'
                    . '|is_image[photo]'
                    . '|mime_in[photo,image/jpg,image/jpeg,image/gif,image/png,image/webp]'
                    . '|max_size[photo,100]'
                    . '|max_dims[photo,1024,768]'
                ]                
            ];        

            if (!$this->validate($validationRule)) {
                $error = $this->validator->getErrors();
                $error_val = array_values($error);
                die(json_encode([
                    'status' => false,
                    'response' => $error_val[0]
                ])); 
            }             

            $file_name = $data['name'].'.'.$photo->getClientExtension();
            $dir_upload = './uploads/';
            $file_des = $dir_upload.$file_name;

            // if update
            if ($is_update) {
                // delete previous photo
                $prev_photo = $this->request->getPost('previous_photo');
                if (file_exists($dir_upload.$prev_photo)) {
                    unlink($dir_upload.$prev_photo);
                }
            }

            // then upload
            $photo->move('./uploads/', $file_name);
            $data['photo'] = $file_name;    
        }   

        // ======== photo handle

        return $data;        
    }

    public function create()
    {

        $data = $this->_post_product();

        $productModel = new \App\Models\Product();
        $productModel->insert($data);

        return $this->response->setJSON([
            'status' => true,
            'response' => 'Success create data '.$data['name']
        ]);
    }

    public function edit()
    {

        $id = $this->request->getPost('hash');

        $productModel = new \App\Models\Product();
        $product = $productModel->select('name,category,price,photo')->find($id);

        // build hash
        $product['hash'] = $id;

        return $this->response->setJSON([
            'status' => true,
            'response' => $product
        ]);
    }

    public function update()
    {

        $id = $this->request->getPost('hash');

        $data = $this->_post_product($id);

        $productModel = new \App\Models\Product();
        $productModel->update($id, $data);

        return $this->response->setJSON([
            'status' => true,
            'response' => 'Success update data '.$data['name']
        ]);
    }        

    public function delete()
    {

        $id = $this->request->getPost('hash');

        $productModel = new \App\Models\Product();
        // read first
        $product = $productModel->select('photo')->find($id);
        // then delete
        if ($productModel->delete($id) AND file_exists('./uploads/'.$product['photo'])) {
            // delete photo
            unlink('./uploads/'.$product['photo']);
        }

        return $this->response->setJSON([
            'status' => true,
            'response' => 'Success delete data '.$id
        ]);
    }

    public function delete_batch()
    {
        $ids = $this->request->getPost('ids');
        $ids_array = explode("','", $ids);
        $count = count($ids_array);

        $productModel = new \App\Models\Product();
        // read first
        $products = $productModel->select('id,photo')->find($ids_array);

        // then delete one by one
        foreach ($products as $product) {
            if ($productModel->delete($product['id']) AND file_exists('./uploads/'.$product['photo'])) {
                // delete photo
                unlink('./uploads/'.$product['photo']);
            }
        }

        return $this->response->setJSON([
            'status' => true,
            'response' => 'Success '. $count .' delete data '
        ]);
    }
}

Mengatur Route

buka file routes terletak di 'app/Config/Routes.php'

ganti default controllernya ke homepage

$routes->setDefaultController('Homepage');

ganti kode default Route Definitions dengan kode dibawah ini

// route homepage
$routes->get('/', 'homepage::index');

// route product
$routes->get('/product', 'product::index');
$routes->get('/product/read', 'product::read');
$routes->post('/product/create', 'product::create');
$routes->post('/product/edit', 'product::edit');
$routes->post('/product/update', 'product::update');
$routes->post('/product/delete', 'product::delete');
$routes->post('/product/delete_batch', 'product::delete_batch');

Membuat Model

ketikan perintah spark

php spark make:model product

edit model product dengan kode dibawah ini, file terletak di 'app/Models/Product.php'

<?php

namespace App\Models;

use CodeIgniter\Model;

class Product extends Model
{
    protected $table            = 'product';
    protected $primaryKey       = 'id';
    protected $useAutoIncrement = true;
    protected $returnType       = 'array';
    protected $protectFields    = false;

    // Dates
    protected $useTimestamps = true;
    protected $dateFormat    = 'datetime';
    protected $createdField  = 'created_at';
    protected $updatedField  = 'updated_at';

    public function getData()
    {

        $products = $this->orderBy('id','DESC')->findAll();

        // load helper
        helper('number');        

        // build data
        $data = [];
        foreach ($products as $product) {
            $data[] = array(
                'hash' => $product['id'],
                'id' => $product['id'],
                'name' => $product['name'],
                'category' => $product['category'],
                'price' => number_to_currency($product['price'], "IDR", "id", 0),
                'photo' => base_url('uploads/'.$product['photo'])
            );
        }   

        return $data;     
    }

    public function getDataForBootstrapTable($request)
    {

        $builder =  $this->select('id, name, price');       

        // search query
        $builder->like('name', $request->getGet('search'));

        // sort query
        $builder->orderBy($request->getGet('sort'), $request->getGet('order'));  

        // paging query
        $builder->limit($request->getGet('limit'), $request->getGet('offset'));  

        $total = $builder->countAllResults(false); // set false for not reset query
        $products = $builder->get()->getResultArray();
        $total_filter = count($products);

        // load helper
        helper('number');

        // build data
        $data = [];
        foreach ($products as $product) {
            $data[] = array(
                'hash' => $product['id'],
                'id' => $product['id'],
                'name' => $product['name'],
                'price' => number_to_currency($product['price'], "IDR", "id", 0),
            );
        }

        return [
            'total' => $total,
            'totalNotFiltered' => $total_filter,
            'rows' => $data
        ];   
    }


}

Membuat View

silahkan buat folder dan file seperti struktur dibawah ini

.
└── app/
    └── Views/
        ├── _layout/
        │   ├── css.php
        │   ├── footer.php
        │   ├── js.php
        │   └── nav.php
        ├── _layout.php
        ├── homepage.php
        └── product.php

dilanjut dengan mengis kode disetiap file view yang telah dibuat

edit file view _layout.php dengan kode dibawah ini

<!DOCTYPE html>
<html class="h-100">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title><?= (!empty($title) ? $title : 'No Title') ?></title>

    <?= $this->include('_layout/css') ?>
</head>
<body class="d-flex flex-column h-100">
    
    <?= $this->include('_layout/nav') ?>

    <?= $this->renderSection('content') ?>

    <?= $this->include('_layout/footer') ?>
    <?= $this->include('_layout/js') ?>
</body>
</html>

edit file view homepage.php dengan kode dibawah ini

<?= $this->extend('_layout') ?>

<?= $this->section('content') ?>

<main class="py-5 my-auto">
    <div class="container">

        <?php if (!$products): ?>
            <div class="text-center fs-3">
                There are no products to display yet
            </div>
        <?php endif ?>

        <div class="row row-cols-2 row-cols-sm-3 row-cols-md-4 g-3">
            
            <?php foreach ($products as $product): ?>
                <div class="col">
                    <div class="card shadow-sm">

                        <img class="bd-placeholder-img card-img-top" src="<?= $product['photo']  ?>">

                        <div class="card-body">

                            <h5 class="card-title">
                                <a class="text-decoration-none" href="#">
                                    <?= $product['name'] ?>
                                </a>
                            </h5>

                            <div class="d-flex justify-content-between align-items-center">
                                <small class="text-muted"><?= $product['price'] ?></small>
                                <span class="badge bg-dark">
                                    <?= $product['category'] ?>
                                </span>
                            </div>

                        </div>
                    </div>
                </div>                
            <?php endforeach ?>
        </div>  
    </div>
</main>

<?= $this->endSection() ?>

edit file view product.php dengan kode dibawah ini

<?= $this->extend('_layout') ?>

<?= $this->section('css') ?>

<!-- bootstrap-table -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.20.2/bootstrap-table.min.css" integrity="sha512-HIPiLbxNKmx+x+VFnDHICgl1nbRzW3tzBPvoeX0mq9dWP9H1ZGMRPXfYsHhcJS1na58bzUpbbfpQ/n4SIL7Tlw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<?= $this->endSection() ?>

<?= $this->section('content') ?>

<main class="py-5">
    <div class="container">

        <div class="card">
            <div class="card-header bg-light">
                <div class="row">
                    <div class="col-auto">
                        <h1 class="fs-4">
                            Data Product
                        </h1>
                    </div>

                    <div class="col">                   
                        <button id="create-product" class="btn btn-outline-primary btn-sm">
                            <i class="bi bi-plus"></i> Create
                        </button>
                    </div>

                </div>
            </div>

            <div class="card-body">

                <div id="toolbar" class="btn-group">
                    <button class="btn btn-danger btn-md" id="delete" disabled><i class="bi bi-trash"></i></button>
                </div>

                <table  
                id="table" 

                data-toolbar="#toolbar"       

                data-search="true"        
                data-show-refresh="true"
                data-show-columns="false"     
                data-minimum-count-columns="2"
                data-show-pagination-switch="false"

                data-detail-view="false"
                data-detail-formatter="detailFormatter"   

                data-pagination="true"
                data-page-list="[10, 25, 50, 100, all]"

                data-click-to-select="false"
                data-id-field="id"    

                data-height="auto"
                data-url="<?= base_url('product/read')  ?>"
                data-side-pagination="server"
                data-response-handler="responseHandler"       
                data-sort-name="id" 
                data-sort-order="desc"
                ></table>

            </div>
        </div>
    </div>
</main>

<?= $this->endSection() ?>

<?= $this->section('js') ?>

<!-- bootstrap-table -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.20.2/bootstrap-table.min.js" integrity="sha512-9KY1w0S1bRPqvsNIxj3XovwFzZ7bOFG8u0K1LByeMVrzYhLu3v7sFRAlwfhBVFsHRiuMc6shv1lRLCTInwqxNg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<script>
    let $table = $('#table'),
    $delete = $('#delete'),
    selections = []; 

    $table.bootstrapTable('destroy').bootstrapTable({
        responseHandler: function(res) {
            $.each(res.rows, function (i, row) {
                row.state = $.inArray(row.identity, selections) !== -1
            })
            return res
        },
        detailFormatter: function(index, row) {
            var html = []
            $.each(row, function (key, value) {
                html.push('<p><b>' + key + ':</b> ' + value + '</p>')
            })
            return html.join('')
        },
        columns: [
        {
            field: 'state',
            checkbox: true,
            align: 'center',
            valign: 'middle'
        },                 
        {
            title: 'Name',
            field: 'name',
            align: 'left',
            valign: 'middle',
            sortable: true,
        }, 
        {
            title: 'Price',
            field: 'price',
            align: 'left',
            valign: 'middle',
            sortable: true,
        },       
        {
            title: 'Action',
            align: 'right',
            valign: 'middle',
            width: 100,
            formatter: function(value, row, index) {
                var html = '';

                html += `<button data-hash="${row.hash}" class='btn btn-primary btn-sm me-1 action-edit'>
                <i class='bi bi-pencil'></i>
                </button>`;

                html += `<button data-name="${row.name}" data-hash="${row.hash}" class='btn btn-danger btn-sm action-delete'>
                <i class='bi bi-trash'></i>
                </button>`;

                return html;
            }
        }
        ]
    });

    $table.on('load-success.bs.table', function(){
        
        /**
        * Edit Product
        */
        $(".action-edit").on('click', function(){

            const hash = $(this).data('hash');

            $.post(base_url + `/product/edit`, {hash : hash})
            .done(function(data){
                if (data.status) {

                    // call bootbox
                    let form_html = '';
                    form_html += `
                    <form id="form-product" enctype="multipart/form-data">

                    <div class="mb-3">
                    <label class="form-label text-capitalize">name</label>
                    <input value="${data.response.name}" name="name" type="text" class="form-control">
                    </div>

                    <div class="mb-3">
                    <label class="form-label text-capitalize">category</label>
                    <input value="${data.response.category}" name="category" type="text" class="form-control">
                    </div>

                    <div class="mb-3">
                    <label class="form-label text-capitalize">price</label>
                    <input value="${data.response.price}" name="price" type="number" class="form-control">
                    </div>

                    <div class="mb-3">
                    <label class="form-label text-capitalize">new photo</label>
                    <input value="${data.response.photo}" name="previous_photo" type="hidden" class="form-control">
                    <input name="photo" type="file" class="form-control">
                    </div>

                    <input value="${data.response.hash}" name="hash" type="hidden" class="form-control">
                    <button type="submit" class="btn btn-outline-primary btn-submit">Submit</button>

                    </form>
                    `;

                    var dialog = bootbox.dialog({
                        title: `Edit Product ${data.response.name}`,
                        message: form_html,
                        centerVertical: true,
                        closeButton: true,
                        size: 'medium',
                        onShown : function(){

                            $("input[name=name]",$("#form-product")).focus();

                            formProduct(dialog, 'update');
                        }
                    });

                }else{
                    alert(data.response);
                }
            }).fail(function(xhr, statusText, errorThrown) {   
                alert(xhr.responseText);
            });    
        })

        /**
        * Delete Product
        */
        $(".action-delete").on('click', function(){

            const hash = $(this).data('hash'),
            name = $(this).data('name');

            // call bootbox
            let dialog = bootbox.confirm({
                centerVertical: true,
                closeButton: false,
                title: 'Confirm Delete',
                message: `Are you sure want to delete data ${name}`,
                buttons: {
                    confirm: {
                        label: '<i class="bi bi-check"></i> Yes',
                        className: 'btn-primary'
                    },
                    cancel: {
                        label: '<i class="bi bi-x"></i> No',
                        className: 'btn-danger'
                    }
                },
                callback: function (result) {    

                    if (result) {                       

                        // animation
                        $(".bootbox-accept, .bootbox-cancel").prop("disabled",true);    
                        $(".bootbox-accept").html($(".bootbox-accept").html() + xsetting.spinner);  
                        let buttonspinner = $(".button-spinner");         

                        $.post(base_url + `/product/delete`, { hash: hash }, function(data) {}, 'json')
                        .done(function(data){
                            
                            // animation
                            $(".bootbox-accept, .bootbox-cancel").prop("disabled",false);    
                            buttonspinner.remove();

                            if (data.status) {                            
                                $table.bootstrapTable('refresh'); 
                                dialog.modal('hide'); // hide modal after get success response
                            }
                        })
                        .fail(function(xhr, statusText, errorThrown) {
                            alert(xhr.responseText);

                            // animation
                            $(".bootbox-accept, .bootbox-cancel").prop("disabled",false);    
                            buttonspinner.remove();
                        }); 

                        // prevent hide modal
                        return false;                                
                    }
                }
            });
        });   

        // for checkbox
        $table.on('check.bs.table uncheck.bs.table ' + 'check-all.bs.table uncheck-all.bs.table', function () {
            $delete.prop('disabled', !$table.bootstrapTable('getSelections').length);
        });     
    })  

    /**
     * Delete Multiple
     */
     $delete.on('click', function(){

        var ids = $.map($table.bootstrapTable('getSelections'), function (row) {
            return row.id
        });

        // validate
        if (ids.length < 1) {return false;}

        // convert 
        ids = ids.join("','");

        bootbox.confirm({
            centerVertical: true,
            closeButton: false,
            title: `Confirm Batch Delete`,
            message: `Are you sure want to delete selected data`,
            buttons: {
                confirm: {
                    label: '<i class="bi bi-check"></i> Yes',
                    className: 'btn-primary'
                },
                cancel: {
                    label: '<i class="bi bi-x"></i> No',
                    className: 'btn-danger'
                }
            },
            callback: function (result) {                
                if (result) {            
                    $.post(base_url + `/product/delete_batch`, { ids:ids }, function(data) {}, 'json')
                    .done(function(data){
                        if (data.status) {
                            $table.bootstrapTable('refresh'); 
                        }
                    })
                    .fail(function(xhr, statusText, errorThrown) {
                        alert(xhr.responseText);
                    });      
                }
            }
        });
    }); 
</script>

<script>

/**
* Create Product
*/
$("#create-product").on("click",function(e){

    const button = $(this);

    let form_html = '';
    form_html += `
    <form id="form-product" enctype="multipart/form-data">

    <div class="mb-3">
    <label class="form-label text-capitalize">name</label>
    <input name="name" type="text" class="form-control">
    </div>

    <div class="mb-3">
    <label class="form-label text-capitalize">category</label>
    <input name="category" type="text" class="form-control">
    </div>

    <div class="mb-3">
    <label class="form-label text-capitalize">price</label>
    <input name="price" type="number" class="form-control">
    </div>

    <div class="mb-3">
    <label class="form-label text-capitalize">photo</label>
    <input name="photo" type="file" class="form-control">
    </div>

    <button type="submit" class="btn btn-outline-primary btn-submit">Submit</button>

    </form>
    `;

    var dialog = bootbox.dialog({
        title: `Create New Product`,
        message: form_html,
        centerVertical: true,
        closeButton: true,
        size: 'medium',
        onShown : function(){

            $("input[name=name]",$("#form-product")).focus();

            formProduct(dialog, 'create');
        }
    });
});

function formProduct(dialog,action) {
    let form = $("#form-product");
    form.on("submit",function(e){

        e.preventDefault();

        // animation
        $("input", form).prop("readonly",true); 
        $(".btn-submit").prop("disabled",true); 
        $(".btn-submit").html($(".btn-submit").html() + xsetting.spinner);
        $(".bootbox-close-button").hide();
        let buttonspinner = $(".button-spinner");             

        $.ajax({
            url: base_url + `/product/` + action,
            type: "POST",
            data: new FormData($(this)[0]),
            dataType: "json", 
            mimeTypes:"multipart/form-data",
            contentType: false,
            cache: false,
            processData: false,
            success: function(data){

                // animation
                $("input", form).prop("readonly",false);    
                $(".btn-submit").prop("disabled",false);
                $(".bootbox-close-button").show();    
                buttonspinner.remove(); 

                if (data.status) {
                    dialog.modal('hide');
                    $table.bootstrapTable('refresh'); 
                }else{
                    alert(data.response);
                }               
            },error: function(xhr, statusText, errorThrown) {
                alert(xhr.responseText);

                // animation
                $("input", form).prop("readonly",false);    
                $(".btn-submit").prop("disabled",false);
                $(".bootbox-close-button").show();    
                buttonspinner.remove();
            }
        });     
    });     
}
</script>

<?= $this->endSection() ?>

edit file view _layout/css.php dengan kode dibawah ini

<!-- bootstrap -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0-beta1/css/bootstrap.min.css" integrity="sha512-o/MhoRPVLExxZjCFVBsm17Pkztkzmh7Dp8k7/3JrtNCHh0AQ489kwpfA3dPSHzKDe8YCuEhxXq3Y71eb/o6amg==" crossorigin="anonymous" referrerpolicy="no-referrer" />

<!-- bootstrap-icon -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.8.1/font/bootstrap-icons.min.css" integrity="sha512-Oy+sz5W86PK0ZIkawrG0iv7XwWhYecM3exvUtMKNJMekGFJtVAhibhRPTpmyTj8+lJCkmWfnpxKgT2OopquBHA==" crossorigin="anonymous" referrerpolicy="no-referrer" />

<?= $this->renderSection('css') ?>

edit file view _layout/footer.php dengan kode dibawah ini

<footer class="footer mt-auto py-3 bg-light">
  <div class="container">
    <span class="text-muted">&copy; <?= date('Y')  ?> Kurteyki - Belajar CRUD CI 4</span>
  </div>
</footer>

edit file view _layout/js.php dengan kode dibawah ini

<script>
    let base_url = '<?= base_url()  ?>',
    current_url = '<?= current_url()  ?>';

    // setting
    xsetting = {
        spinner : ` <div class="spinner-border spinner-border-sm button-spinner" role="status"></div>`
    }   
</script>

<!-- bootsrap -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0-beta1/js/bootstrap.bundle.min.js" integrity="sha512-ndrrR94PW3ckaAvvWrAzRi5JWjF71/Pw7TlSo6judANOFCmz0d+0YE+qIGamRRSnVzSvIyGs4BTtyFMm3MT/cg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<!-- jquery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<!-- bootbox -->
<style type="text/css">
/* bootbox clos-button fix */
.bootbox-close-button{box-sizing:content-box;color:#000!important;opacity:initial!important;border:0;border-radius:0.25rem;background:none;font-size:24px!important;font-weight:bold;padding:0;margin:0;float:initial!important}    
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.5.2/bootbox.min.js" integrity="sha512-RdSPYh1WA6BF0RhpisYJVYkOyTzK4HwofJ3Q7ivt/jkpW6Vc8AurL1R+4AUcvn9IwEKAPm/fk7qFZW3OuiUDeg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<?= $this->renderSection('js') ?>

edit file view _layout/nav.php dengan kode dibawah ini

<nav class="navbar navbar-expand-md mb-4">
  <div class="container">
    <span class="navbar-brand">Store</span>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarCollapse">
      <ul class="navbar-nav me-auto mb-2 mb-md-0">
        <li class="nav-item">
          <a class="nav-link" aria-current="page" href="<?= base_url()  ?>">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="<?= base_url('product')  ?>">Product</a>
        </li>
      </ul>
    </div>
  </div>
</nav>

Membuat Folder Uploads

silahkan buat folder uploads didalam folder public, jadinya akan seperti ini

.
└── public/
    ├── uploads/
    ├── .htacces(s)
    ├── favicon.ico
    ├── index.php
    └── robots.txt

Uji Coba Aplikasi

jalankan web server dengan spark

php spark serve

buka url http://localhost:8080 dibrowser kalian

dan berikut hasilnya

Download Source Code

Terakhir diupdate 19 Jun 2022 06:23 WIB
Tips
  • tutorial crud ini dibuat menggunakan CI 4.21 pastikan kalian juga menggunakan versi yang sama agar tidak terjadi error saat mempraktekannya
Daftar Konten Belajar CodeIgniter 4