Pendahuluan
Schema database dari aplikasi yang telah kita buat akan menjadian acuan dalam proses development kedepannya, meskipun perlu saya tekankan bahwa schema tersebut masih bisa mengalami perubahan. Namun, garis besarnya telah mewakili inti dari aplikasi yang akan kita buat.
Saya akan mengajak teman-teman menganalisa, bagian mana yang harus diselesaikan terlebih dahulu berdasarkan schema database yang dimiliki? Pertama, pisahkan terlebih dahulu table yang tidak membutuhkan table lainnya untuk penyimpanan datanya. Maka, yang akan kita dapatkan adalah table: categories
danusers
. Mengapa demikian? Karena categories dan users dapat menyimpan data tanpa perlu mengambil data dari table lainnya. Lantas, mungkin ada yang bertanya, password_resets
bagaiamana? Kita kesampingkan terlebih dahulu table tersebut karena hanya bersifat opsional yang berguna sebagai table bantuan dalam membuat fitur reset password nantinya.
Berangkat dari dua table yang telah kita miliki, mana yang perlu didahulukan? Bebas saja!, Tapi pada sesi ini kita akan mengerjakan table categories
terlebih dahulu. Kenapa? Sesuai judul, yang akan dibuat adalah fitur manajemen kategori.
Baca Juga: Membuat Aplikasi Pos (Point of Sales) Laravel 5.6 - Schema Database
Membuat Master Layouts
Oops, jangan terburu-buru. Ada hal yang perlu dipersiapkan terlebih dahulu yaitu layouts yang akan menjadi acuan kita dalam membuat view untuk inputan data categories
.
Buat file master.blade.php di dalam folder resources/views/layouts
. Jika folder layouts
tidak ditemukan di dalam folder views
, maka silahkan terlebih dahulu. Strukturnya akan tampak seperti ini
Kita akan menggunakan template AdminLTE, template sejuta ummat katanya. Apabila kita extract, maka di dalamnya terlihat seperti ini
Pindahkan ke-2 folder: dist, plugins ke dalam folder public
project kamu, kemudian buka file master.blade.php
lalu masukkan kerangka berikut:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@yield('title')
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ asset('plugins/font-awesome/css/font-awesome.min.css') }}">
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<link rel="stylesheet" href="{{ asset('dist/css/adminlte.min.css') }}">
<link rel="stylesheet" href="{{ asset('plugins/iCheck/flat/blue.css') }}">
<link rel="stylesheet" href="{{ asset('plugins/morris/morris.css') }}">
<link rel="stylesheet" href="{{ asset('plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}">
<link rel="stylesheet" href="{{ asset('plugins/datepicker/datepicker3.css') }}">
<link rel="stylesheet" href="{{ asset('plugins/daterangepicker/daterangepicker-bs3.css') }}">
<link rel="stylesheet" href="{{ asset('plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css') }}">
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
</head>
<body class="hold-transition sidebar-mini">
<div class="wrapper">
<nav class="main-header navbar navbar-expand bg-white navbar-light border-bottom">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" data-widget="pushmenu" href="#"><i class="fa fa-bars"></i></a>
</li>
<li class="nav-item d-none d-sm-inline-block">
<a href="index3.html" class="nav-link">Home</a>
</li>
<li class="nav-item d-none d-sm-inline-block">
<a href="#" class="nav-link">Contact</a>
</li>
</ul>
<!-- SEARCH FORM -->
<form class="form-inline ml-3">
<div class="input-group input-group-sm">
<input class="form-control form-control-navbar" type="search" placeholder="Search" aria-label="Search">
<div class="input-group-append">
<button class="btn btn-navbar" type="submit">
<i class="fa fa-search"></i>
</button>
</div>
</div>
</form>
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown">
<a class="nav-link" data-toggle="dropdown" href="#">
<i class="fa fa-comments-o"></i>
<span class="badge badge-danger navbar-badge">3</span>
</a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
<a href="#" class="dropdown-item">
<div class="media">
<img src="dist/img/user1-128x128.jpg" alt="User Avatar" class="img-size-50 mr-3 img-circle">
<div class="media-body">
<h3 class="dropdown-item-title">
Brad Diesel
<span class="float-right text-sm text-danger"><i class="fa fa-star"></i></span>
</h3>
<p class="text-sm">Call me whenever you can...</p>
<p class="text-sm text-muted"><i class="fa fa-clock-o mr-1"></i> 4 Hours Ago</p>
</div>
</div>
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item dropdown-footer">See All Messages</a>
</div>
</li>
</ul>
</nav>
@include('layouts.module.sidebar')
@yield('content')
@include('layouts.module.footer')
</div>
<!-- jQuery -->
<script src="{{ asset('plugins/jquery/jquery.min.js') }}"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script>
$.widget.bridge('uibutton', $.ui.button)
</script>
<script src="{{ asset('plugins/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js') }}"></script>
<script src="{{ asset('plugins/morris/morris.min.js') }}"></script>
<script src="{{ asset('plugins/sparkline/jquery.sparkline.min.js') }}"></script>
<script src="{{ asset('plugins/jvectormap/jquery-jvectormap-1.2.2.min.js') }}"></script>
<script src="{{ asset('plugins/jvectormap/jquery-jvectormap-world-mill-en.js') }}"></script>
<script src="{{ asset('plugins/knob/jquery.knob.js') }}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.2/moment.min.js') }}"></script>
<script src="{{ asset('plugins/daterangepicker/daterangepicker.js') }}"></script>
<script src="{{ asset('plugins/datepicker/bootstrap-datepicker.js') }}"></script>
<script src="{{ asset('plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}"></script>
<script src="{{ asset('plugins/slimScroll/jquery.slimscroll.min.js') }}"></script>
<script src="{{ asset('plugins/fastclick/fastclick.js') }}"></script>
<script src="{{ asset('dist/js/adminlte.js') }}"></script>
<script src="{{ asset('dist/js/pages/dashboard.js') }}"></script>
<script src="{{ asset('dist/js/demo.js') }}"></script>
</body>
</html>
Penjelasan: Ada 3 bagian yang perlu diperhatikan, pertama: @yield('title')
yang nantinya akan di-replace dengan tag <title></title>
yang dikirimkan dari child views. Jadi skemanya, file master.blade.php ini akan menjadi parent layouts yang sifatnya tidak berubah-ubah. Kedua, @yield('content')
akan di-replace dengan content yang akan ditampilkan. Terakhir, terdapat code untuk memanggil file lainnya (baca: @include
), yakni sidebar
dan footer
. Sengaja dipisahkan agar lebih mudah menemukannya, karena nantinya kita akan sering mengubah sidebar.blade.php
untuk menambahkan menu.
Buat filter sidebar.blade.php
di dalam folder resources/views/layouts/module
:
<aside class="main-sidebar sidebar-dark-primary elevation-4">
<!-- Brand Logo -->
<a href="index3.html" class="brand-link">
<img src="{{ asset('dist/img/AdminLTELogo.png') }}" alt="POS" class="brand-image img-circle elevation-3"
style="opacity: .8">
<span class="brand-text font-weight-light">POS</span>
</a>
<!-- Sidebar -->
<div class="sidebar">
<!-- Sidebar user panel (optional) -->
<div class="user-panel mt-3 pb-3 mb-3 d-flex">
<div class="image">
<img src="{{ asset('dist/img/user2-160x160.jpg') }}" class="img-circle elevation-2" alt="User Image">
</div>
<div class="info">
<a href="#" class="d-block">Daengweb.id</a>
</div>
</div>
<nav class="mt-2">
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
<li class="nav-item has-treeview menu-open">
<a href="#" class="nav-link active">
<i class="nav-icon fa fa-dashboard"></i>
<p>
Dashboard
<i class="right fa fa-angle-left"></i>
</p>
</a>
</li>
<li class="nav-item has-treeview">
<a href="#" class="nav-link">
<i class="nav-icon fa fa-server"></i>
<p>
Manajemen Produk
<i class="right fa fa-angle-left"></i>
</p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="{{ route('kategori.index') }}" class="nav-link">
<i class="fa fa-circle-o nav-icon"></i>
<p>Kategori</p>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link">
<i class="fa fa-circle-o nav-icon"></i>
<p>Produk</p>
</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</aside>
Terakhir, buat file footer.blade.php
di dalam folder resources/views/layouts/module
:
<footer class="main-footer">
<strong>Copyright © 2014-2018 <a href="http://adminlte.io">AdminLTE.io</a>.</strong>
All rights reserved.
<div class="float-right d-none d-sm-inline-block">
<b>Version</b> 3.0.0-alpha
</div>
</footer>
Data Kategori
Sampai pada tahap terakhir yang dilakukan, kita belum mendapatkan apa-apa yang dapat ditampilkan. Maka, sekarang saatnya untuk membuat proses pencatatan data kategori, buat controller terlebih dahulu dengan command:
php artisan make:controller CategoryController
Buka file app/Http/Controllers/CategoryController.php
kemudian tambahkan method index:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Category;
class CategoryController extends Controller
{
public function index()
{
$categories = Category::orderBy('created_at', 'DESC')->paginate(10);
return view('categories.index', compact('categories'));
}
}
Penjelasan: Pada line-6, menggunakan use statement untuk memanggil model Category. Line-12 melakukan query ke table categories
dengan ketentuan order by created_at
yang berarti data yang terakhir kali di-input akan berada pada posisi paling atas. Kemudian ditutup dengan method paginate(10)
yang berarti membatasi hanya me-load 10 data, lalu data selanjutnya dikelompokkan. Line-13, memanggil view
yang terletak di resources/views/categories/index.blade.php
, lalu diakhiri dengan mem-passing variable $categories
ke view
dengan menggunakan method compact
.
Kemudian buat file index.blade.php
di dalam folder resources/views/categories
, kemudian masukkan code berikut:
@extends('layouts.master')
@section('title')
<title>Manajemen Kategori</title>
@endsection
@section('content')
<div class="content-wrapper">
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0 text-dark">Manajemen Kategori</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="#">Home</a></li>
<li class="breadcrumb-item active">Kategori</li>
</ol>
</div>
</div>
</div>
</div>
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-4">
@card
@slot('title')
Tambah
@endslot
@if (session('error'))
@alert(['type' => 'danger'])
{!! session('error') !!}
@endalert
@endif
<form role="form" action="{{ route('kategori.store') }}" method="POST">
@csrf
<div class="form-group">
<label for="name">Kategori</label>
<input type="text"
name="name"
class="form-control {{ $errors->has('name') ? 'is-invalid':'' }}" id="name" required>
</div>
<div class="form-group">
<label for="description">Deskripsi</label>
<textarea name="description" id="description" cols="5" rows="5" class="form-control {{ $errors->has('description') ? 'is-invalid':'' }}"></textarea>
</div>
@slot('footer')
<div class="card-footer">
<button class="btn btn-primary">Simpan</button>
</div>
</form>
@endslot
@endcard
</div>
<div class="col-md-8">
@card
@slot('title')
List Kategori
@endslot
@if (session('success'))
@alert(['type' => 'success'])
{!! session('success') !!}
@endalert
@endif
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<td>#</td>
<td>Kategori</td>
<td>Deskripsi</td>
<td>Aksi</td>
</tr>
</thead>
<tbody>
@php $no = 1; @endphp
@forelse ($categories as $row)
<tr>
<td>{{ $no++ }}</td>
<td>{{ $row->name }}</td>
<td>{{ $row->description }}</td>
<td>
<form action="{{ route('kategori.destroy', $row->id) }}" method="POST">
@csrf
<input type="hidden" name="_method" value="DELETE">
<a href="{{ route('kategori.edit', $row->id) }}" class="btn btn-warning btn-sm"><i class="fa fa-edit"></i></a>
<button class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center">Tidak ada data</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@slot('footer')
@endslot
@endcard
</div>
</div>
</div>
</section>
</div>
@endsection
Penjelasan:
-
@extends('layouts.master')
: Menggunakan layouts master yang telah dibuat sebelumnya -
@section('title') ...... @endsection
: Apapun yang diapit, maka akan me-replace ke@yield('title')
yang terdapat di master.blade.php, karena tujuannya untuk memberikan title halaman, maka kita masukkan tag<title></title>
. -
@section('content') .... @endsection
: Akan me-replace@yield('content')
yang terdapat di master.blade.php , maka di dalamnya membuat dua buah grid, grid pertama adalah form untuk menginput data dan grid kedua adalah table untuk menampilkan data. -
@card .... @endcard
dan@alert .... @endalert
: adalah custom component. Untuk cara membuatnya silahkan baca artikel: Reusable with blade component Laravel.
Adapun isi dari card component ini adalah:
<div class="card">
<div class="card-header with-border">
<h3 class="card-title">{{ $title }}</h3>
</div>
<div class="card-body">
{{ $slot }}
</div>
{{ $footer }}
</div>
Dan alert component:
<div class="alert alert-{{ $type }} alert-dismissible">
{{ $slot }}
</div>
Note: Saya tidak akan menjelaskan code diatas diletakkan pada file apa, silahkan baca artikel yang sudah saya referensikan karena sudah dibahas secara detail.
Buka file resources/views/layouts/module/sidebar.blade.php
, kemudian modifikasi pada menu kategori. Kita akan menambahkan route agar bisa diklik nantinya.
...
<a href="{{ route('kategori.index') }}" class="nav-link">
<i class="fa fa-circle-o nav-icon"></i>
<p>Kategori</p>
</a>
...
Penjelasan: Yang ditambahkan hanya bagian {{ route('categories.index') }}
, dengan begini menu kategori sudah dapat diklik berdasarkan url yang akan didefinisikan kemudian.
Buka file routes/web.php
, kemudian tambahkan code berikut:
Route::resource('/kategori', 'CategoryController')->except([
'create', 'show'
]);
Penjelasan: Menggunakan resource
berarti semua route akan didefinisikan, diantaranya: index, create, store, edit, show, update, destroy. Akan tetapi terdapat tambahan method except()
yang berarti akan mengecualikan create
dan show
.
Apabila kita menjalankan command:
php artisan route:list
Maka kamu melihat list dari route yang telah didefinisikan
Note: Perhatikan kolom Name, name tersebut yang dipanggil ketika menggunakan helper route()
.
Buka url : http://localhost/namaproject/public/kategori (note: ganti namaproject
dengan nama folder project kamu), atau jika kamu menggunakan built in server dengan command php artisan serve
, maka buka url: http://localhost:8000/kategori. Tampilannya kurang lebih seperti ini
Store Kategori
Apabila tombol simpan ditekan maka akan terjadi error karena method yang meng-handle route kategori.store
belum dibuat. Buka file CategoryControler.php
kemudian tambahkan method berikut:
...
public function store(Request $request)
{
//validasi form
$this->validate($request, [
'name' => 'required|string|max:50',
'description' => 'nullable|string'
]);
try {
$categories = Category::firstOrCreate([
'name' => $request->name
], [
'description' => $request->description
]);
return redirect()->back()->with(['success' => 'Kategori: ' . $categories->name . ' Ditambahkan']);
} catch (\Exception $e) {
return redirect()->back()->with(['error' => $e->getMessage()]);
}
}
...
Penjelasan: Line 19-22 adalah validasi data yang diterima dari form. Line 25-29 berfungsi untuk menyimpan data ke table categories
menggunakan model Category dengan method firstOrCreate
yang berarti, apabila nama kategori yang sama sudah ada, maka records tersebut hanya akan di-select, apabila tidak, maka kategori tersebut akan ditambahkan. Line-30 akan melakukan redirect dengan mengirimkan flash message success (baca: untuk flash message silahkan baca disini: Flash Message Laravel). Line-32 juga sama, hanya saja pesan yang dikirim berupa error dengan isi pesan yang diambil dari exception.
Sampai pada tahap ini, data yang diterima belum dapat disimpan ke database karena field name
dan description
belum diizinkan untuk menerima data. So, buka model app/Category.php
kemudian tambahkan:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
protected $fillable = ['name', 'description'];
}
Penjelasan: $fillable
berarti mengizinkan field yang disebutkan untuk menyimpan data ke table terkait.
Apabila berhasil menyimpan, maka akan tampak seperti ini:
Destroy Kategori
Untuk menghapus data, dibutuhkan unique key sebagai pemicunya, maka dalam hal ini kita akan menggunakan id. Apabila kita lihat file categories/index.blade.php
, terdapat tag <form action="{{ route('kategori.destroy', $row->id) }}" method="POST">
, dimana kita telah mengirimkan id
ke route kategori.destroy
. So, buka file CategoryController.php
, kemudian tambahkan method berikut:
public function destroy($id)
{
$categories = Category::findOrFail($id);
$categories->delete();
return redirect()->back()->with(['success' => 'Kategori: ' . $categories->name . ' Telah Dihapus']);
}
Penjelasan: Line-3 berfungsi melakukan query select ke table categories
dengan id yang diterima dari form. Line-4 akan menghapus record yang telah di-select. Lalu line selanjutnya akan melakukan redirect sekaligus membuat flash message success.
Edit & Update Kategori
Skema dari editing dan updating data adalah menampilkan data terlebih dahulu ke dalam form yang kemudian hasil dari editing tersebut yang akan di-update. Maka ada dua hal yang perlu kita lakukan pada sub topik kali ini. Pertama, menampilkan data. Buka file CategoryController.php
kemudian tambahkan method berikut:
public function edit($id)
{
$categories = Category::findOrFail($id);
return view('categories.edit', compact('categories'));
}
Penjelasan: Karena url untuk mengedit kategori adalah /kategori/{id}
, dimana terdapat id di url, ID ini kita gunakan sebagai kondisi query select yang bisa kita perhatikan pada line-3 . Line-4 memanggil view edit
yang ada di dalam folder categories. Kemudian ditutup dengan passing variable categories
ke view.
Karena membutuhkan file edit.blade.php, maka buat terlebih dahulu didalam folder categories
Kemudian masukkan code berikut:
@extends('layouts.master')
@section('title')
<title>Edit Kategori</title>
@endsection
@section('content')
<div class="content-wrapper">
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0 text-dark">Edit Kategori</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="#">Home</a></li>
<li class="breadcrumb-item"><a href="{{ route('kategori.index') }}">Kategori</a></li>
<li class="breadcrumb-item active">Edit</li>
</ol>
</div>
</div>
</div>
</div>
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-6">
@card
@slot('title')
Edit
@endslot
@if (session('error'))
@alert(['type' => 'danger'])
{!! session('error') !!}
@endalert
@endif
<form role="form" action="{{ route('kategori.update', $categories->id) }}" method="POST">
@csrf
<input type="hidden" name="_method" value="PUT">
<div class="form-group">
<label for="name">Kategori</label>
<input type="text"
name="name"
value="{{ $categories->name }}"
class="form-control {{ $errors->has('name') ? 'is-invalid':'' }}" id="name" required>
</div>
<div class="form-group">
<label for="description">Deskripsi</label>
<textarea name="description" id="description" cols="5" rows="5" class="form-control {{ $errors->has('description') ? 'is-invalid':'' }}">{{ $categories->description }}</textarea>
</div>
@slot('footer')
<div class="card-footer">
<button class="btn btn-info">Update</button>
</div>
</form>
@endslot
@endcard
</div>
</div>
</div>
</section>
</div>
@endsection
Penjelasan: Hampir serupa dengan tambah data, hanya saja di form kita tambahkan property value untuk menampilkan data yang akan di-edit.
Buka kembali file CategoryController.php
, kemudian tambahkan method berikut:
public function update(Request $request, $id)
{
//validasi form
$this->validate($request, [
'name' => 'required|string|max:50',
'description' => 'nullable|string'
]);
try {
//select data berdasarkan id
$categories = Category::findOrFail($id);
//update data
$categories->update([
'name' => $request->name,
'description' => $request->description
]);
//redirect ke route kategori.index
return redirect(route('kategori.index'))->with(['success' => 'Kategori: ' . $categories->name . ' Ditambahkan']);
} catch (\Exception $e) {
//jika gagal, redirect ke form yang sama lalu membuat flash message error
return redirect()->back()->with(['error' => $e->getMessage()]);
}
}
Baca Juga: Simple Validation With VueJS
Kesimpulan
Sepanjang tutorial yang sangat panjang ini, ada hal yang perlu diperhatikan dengan seksama, yakni: CRUD, gabungan dari sub topic: Data Kategori, Store Kategori, Destroy Kategori, Edit & Update Kategori. Sebab ke empat hal ini, konsepnya akan sering dilakukan, yaitu: proses menyimpan, menampilkan, mengedit dan menghapus data. Hanya saja terkadang skema yang diinginkan yang berubah. Tapi secara konsep, sama!.
Ohya, untuk dokumentasi source code-nya dapat kamu intip di Github.
Comments