i. Development Workflow
i. Development Workflow
Now that you're now acquainted with the technologies, functions, and components within the application, we can now combine all of these to create a CRUD operation within the application.
In this demonstration we will create a simple CRUD operation for managing job titles for employees.
1. Create a Route
Route::get('/mgt/jobs', [JobController::class, 'index'])->name('job');
2. Create Table
app/Livewire/Pages/Job
<?php
namespace App\Livewire\Pages\Job;
use App\Contracts\IBaseRepository;
use Livewire\Component;
class Table extends Component
{
public $jobs;
private $baseRepository;
public function boot()
{
$this->baseRepository = app(IBaseRepository::class);
}
public function getJobs()
{
$this->jobs = $this->baseRepository->getAllJobTitle();
$this->dispatch('fetchedJobs');
}
public function render()
{
return view('modules.hr.job.table');
}
}
resources/modules/hr/job/table.blade.php
<div wire:init="getJobs">
<header class="mb-3">
<h2 class="tw-text-base tw-tracking-wider tw-font-black tw-uppercase tw-mb-0 tw-flex tw-items-center">
<span class="tw-w-2 tw-h-2 tw-bg-teal-500 tw-rounded-full tw-inline-block tw-me-2"></span>
Job Titles
</h2>
</header>
<div class="tw-relative">
<x-alert theme="secondary">
Select a job title to view more details.
</x-alert>
<div tooltip-parent class="tw-z-10 tw-w-full" wire:ignore>
<table id="jobsTbl" class="table table-sm tw-w-full">
<thead>
<tr class="tw-border-b-2">
<th class="tw-whitespace-nowrap tw-transition-[width]">Title</th>
</tr>
</thead>
<tbody>
<x-row-skeleton rowNumber="5" colNumber="1" />
</tbody>
</table>
<x-tooltip message="Select to view details" />
</div>
</div>
</div>
@script
<script>
$(function() {
const $table = $('#jobsTbl');
let table = $table.DataTable(window.baseDtConfig);
Livewire.on('fetchedJobs', () => {
// Destroy the existing DataTable
if ($.fn.DataTable.isDataTable('#jobsTbl')) {
table.destroy();
}
let tableData = @this.jobs.map(function(item) {
return {
job_id: item.job_id,
job_title: item.job_title
};
});
table = $table.DataTable({
dom: window.dtPagination,
pageLength: 25,
scrollX: true,
lengthMenu: window.dtLengthMenuShort,
language: window.dtLanguage,
data: tableData,
order: [],
columns: [{
data: 'job_title',
className: 'hover-scale'
}],
createdRow: function(row, data, dataIndex) {
window.initTooltip($table);
window.dtClickSelectHighlightLev0(row);
},
});
window.adjustTableColumns(table);
// On row click, emit the event to fetch the assignment information
$table.on('click', 'tr', function() {
const data = table.row(this).data();
@this.dispatch('rowSelected', {
id: data.job_id,
});
});
});
});
</script>
@endscript
3. Create a Sidebar Off Canvas (SOC)
On rowSelected
, an event will trigger for SOC to open.
app/Livewire/Pages/Job
<?php
namespace App\Livewire\Pages\Job;
use App\Contracts\IBaseRepository;
use App\Helpers\CRUD;
use App\Models\Job;
use Illuminate\Support\Facades\Session;
use Livewire\Component;
class SidebarOCJob extends Component
{
public $job;
public $jobId,
$jobTitle;
public $open = false, $editing = true;
// Used for dropdowns
private $baseRepository, $jobInstance;
protected $listeners = [
'rowSelected' => 'getJob',
'deleteJob',
'close'
];
protected $rules = [
'jobId' => 'nullable|numeric|digits_between:1,4',
'jobTitle' => 'required|max:300',
];
public function boot()
{
$this->baseRepository = app(IBaseRepository::class);
$this->jobInstance = app(Job::class);
}
public function updated()
{
$this->resetErrorBag();
}
public function getJob($id)
{
$this->open = true;
$this->job = $this->baseRepository->getJobTitle($id);
if ($this->job) {
$this->jobId = $this->job->job_id;
$this->jobTitle = $this->job->job_title;
$this->editing = false;
} else {
$this->editing = true;
}
$this->dispatch('fetchedJob', $this->job);
}
public function createJob()
{
CRUD::handle([
'component' => $this,
'operate' => function () {
$this->validate();
// The operation logic to create a new job
return $this->jobInstance->store(new Job([
'job_id' => null,
'job_title' => $this->jobTitle
]));
},
'onSuccess' => function ($result) {
Session::flash('success', "Job: {$this->jobTitle} has been created successfully!");
return redirect(request()->header('Referer'));
},
'onError' => function ($error) {
$this->open = true;
$this->dispatch('errorProcJob', $error);
}
]);
}
public function updateJob()
{
CRUD::handle([
'component' => $this,
'operate' => function () {
$this->validate();
// The operation logic to create a new job
return $this->jobInstance->modify(new Job([
'job_id' => $this->jobId,
'job_title' => $this->jobTitle
]));
},
'onSuccess' => function ($result) {
Session::flash('success', "Job: {$this->jobTitle} has been updated successfully!");
return redirect(request()->header('Referer'));
},
'onError' => function ($error) {
$this->open = true;
$this->dispatch('errorProcJob', $error);
}
]);
}
public function deleteJob()
{
CRUD::handle([
'component' => $this,
'operate' => function () {
$this->validate();
// The operation logic to create a new job
return $this->jobInstance->purge($this->jobId);
},
'onSuccess' => function ($result) {
Session::flash('success', "Job: {$this->jobTitle} has been deleted successfully!");
return redirect(request()->header('Referer'));
},
'onError' => function ($error) {
$this->open = true;
$this->dispatch('errorProcJob', $error);
}
]);
}
public function clear()
{
$this->reset([
'editing',
'job',
'jobId',
'jobTitle'
]);
$this->dispatch('clearedJob');
}
public function close()
{
$this->open = false;
$this->clear();
$this->dispatch('closedSidebar');
}
public function render()
{
return view('modules.hr.job.sidebar-oc-content');
}
}
resources/modules/hr/job/sidebar-oc-content.blade.php
<x-sidebar-off-canvas id="sidebarJob" :open="$open">
<div class="tw-absolute tw-left-0 tw-top-0 tw-p-5 tw-w-full">
<div class="tw-sticky tw-top-0 tw-bg-neutral-50 tw-z-10003">
<header class="tw-flex tw-justify-between">
<section>
@if (collect($job)->isEmpty())
<h1 class="tw-mb-0 tw-text-3xl tw-font-black">Create Job</h1>
<p class="tw-mb-0">This will insert a new office record within the University.</p>
@else
<h1 class="tw-mb-0 tw-text-3xl tw-font-black">Update Job</h1>
<p class="tw-mb-0">Update the necessary information of the selected office
<b>{{ $jobTitle }}</b>.
</p>
@endif
</section>
</header>
<hr />
</div>
<form x-data="{ editing: @entangle('editing') }" wire:submit.prevent="{{ collect($job)->isNotEmpty() ? 'updateJob' : 'createJob' }}">
<div class="card">
<div class="border card-body border-1 tw-rounded-md">
<header class="tw-flex tw-justify-between">
<div class="tw-w-max">
<h2
class="tw-text-base tw-tracking-wider tw-font-black tw-uppercase tw-mb-0 tw-flex tw-items-center">
<span
class="tw-w-2 tw-h-2 tw-bg-teal-500 tw-rounded-full tw-inline-block tw-me-2"></span>
Job Information
</h2>
<p>Fill in the necessary information to create a new job.</p>
</div>
@accessview('attmonitorstaff', 'issofficer', 'recordsmgtspec')
<div class="tw-ms-auto tw-w-max">
@if (collect($job)->isNotEmpty())
<button type="button" class="btn text-danger waves-effect waves-danger tw-px-5"
data-bs-toggle="modal" data-bs-target="#confirmMDeleteJB"
x-on:click="$dispatch('confirmMDeleteJB')" x-show="editing" x-transition>
<i class="ri-delete-bin-7-line"></i>
Delete
</button>
@endif
<button type="submit" class="btn btn-primary tw-px-5 waves-effect waves-light"
wire:loading.attr="disabled" x-bind:disabled="!editing"
wire:target="createJob, updateJob">{{ collect($job)->isNotEmpty() ? 'Update' : 'Create' }}
Job
</button>
</div>
@endaccessview
</header>
@accessview('attmonitorstaff', 'issofficer', 'recordsmgtspec')
<div class="row gx-2">
<div class="mb-3 col-lg-12">
<div class="form-check form-switch tw-w-max">
<label class="form-check-label" for="jobEditingModeCB">
Editing Mode
</label>
<input class="border form-check-input border-1 tw-border-slate-400" type="checkbox"
role="switch" id="jobEditingModeCB" x-on:click="editing = !editing"
x-bind:checked="editing">
</div>
</div>
</div>
@endaccessview
<div class="row gx-2">
<div class="mb-3 col-lg-12">
<div class="form-floating">
<input type="text" autocomplete="off"
class="form-control @error('jobTitle') is-invalid @enderror" id="jobTitle"
wire:model.live="jobTitle" x-bind:disabled="!editing">
<label for="jobTitle">Job Title
<x-indicator />
</label>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</x-sidebar-off-canvas>
Since this just a simple sidebar with no datatables within it, there's no need for an
info-card
component.
4. Create a Modal for Confirming Deletion
<?php
namespace App\Livewire\Pages\Job;
use Livewire\Component;
class ModalDeleteJob extends Component
{
public function confirmDeletion()
{
$this->dispatch('deleteJob');
}
public function render()
{
return view('modules.hr.job.modal-delete-jb');
}
}
resources/modules/hr/job/modal-delete-jb.blade.php
<x-modal id="confirmMDeleteJB" wire:ignore.self>
<div class="modal-header">
<span class="tw-w-2 tw-h-2 tw-bg-red-300 tw-inline-block rounded-circle tw-me-2"></span>
<h5 class="modal-title" id="modalTitle">Confirm Job Deletion?</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>
Are you sure you want to delete this job title? This action cannot be undone.
</p>
<div class="tw-flex tw-gap-1 tw-w-max tw-ms-auto">
<button type="button" class="btn btn-light-danger text-danger waves-effect waves-light tw-px-5"
data-bs-dismiss="modal">
Cancel
</button>
<button type="button" class="btn btn-danger waves-effect waves-light tw-px-5" wire:click="confirmDeletion"
wire:loading.attr="disabled" data-bs-dismiss="modal">
<i class="ri-delete-bin-7-line"></i>
Yes, Proceed
</button>
</div>
</div>
</x-modal>
➡️ Next up!