Welcome to Part 3 of our tutorial on creating the simplest CRUD (Create, Read, Update, Delete) application using Laravel. In this part, we will delve into more advanced features and enhancements for our application. We will add search functionality, pagination, and a confirmation dialog for delete actions. Additionally, we’ll touch on implementing relationships between models.
Step 10: Adding Search Functionality
Search functionality allows users to find specific items quickly. We’ll add a search feature to our items list.
Updating the Controller
Open the ItemController
and update the index
method to handle search queries:
public function index(Request $request)
{
$query = Item::query();
if ($request->has('search')) {
$query->where('name', 'like', '%' . $request->search . '%')
->orWhere('description', 'like', '%' . $request->search . '%');
}
$items = $query->paginate(10);
return view('items.index', compact('items'));
}
Updating the View
Update the index.blade.php
to include a search form and display the search results:
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Items</h1>
<div class="mb-3">
<form action="{{ route('items.index') }}" method="GET">
<div class="input-group">
<input type="text" name="search" class="form-control" placeholder="Search items" value="{{ request()->query('search') }}">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="submit">Search</button>
</div>
</div>
</form>
</div>
<a href="{{ route('items.create') }}" class="btn btn-primary mb-3">Add Item</a>
@if ($message = Session::get('success'))
<div class="alert alert-success">
{{ $message }}
</div>
@endif
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach ($items as $item)
<tr>
<td>{{ $item->id }}</td>
<td>{{ $item->name }}</td>
<td>{{ $item->description }}</td>
<td>
<a href="{{ route('items.show', $item->id) }}" class="btn btn-info">View</a>
<a href="{{ route('items.edit', $item->id) }}" class="btn btn-warning">Edit</a>
<form action="{{ route('items.destroy', $item->id) }}" method="POST" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete this item?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
{{ $items->links() }}
</div>
@endsection
Step 11: Adding Pagination
Pagination helps manage large sets of data by displaying a limited number of items per page. We have already incorporated pagination in the previous step by using the paginate
method in our controller and the links
method in our view.
Step 12: Adding a Confirmation Dialog for Deleting Items
To prevent accidental deletions, we should add a confirmation dialog. We have already added an onsubmit
attribute to the delete form in the previous step. This JavaScript function will display a confirmation dialog when a user attempts to delete an item.
JavaScript Confirmation Dialog
Add the following JavaScript to the layouts/app.blade.php
file to ensure the confirmation dialog works:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple CRUD</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Simple CRUD</a>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ route('items.index') }}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('items.create') }}">Add Item</a>
</li>
</ul>
</div>
</nav>
<div class="container mt-5">
@yield('content')
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
function confirmDelete() {
return confirm('Are you sure you want to delete this item?');
}
</script>
</body>
</html>
Step 13: Implementing Relationships Between Models
To demonstrate relationships, let’s create another model called Category
and link it to our Item
model. Each item will belong to one category, and each category can have multiple items.
Creating the Category Model and Migration
Run the following command to create the model and migration:
php artisan make:model Category -m
Update the migration file for the Category
model located in the database/migrations
directory:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCategoriesTable extends Migration
{
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
Schema::table('items', function (Blueprint $table) {
$table->foreignId('category_id')->nullable()->constrained()->onDelete('cascade');
});
}
public function down()
{
Schema::table('items', function (Blueprint $table) {
$table->dropForeign(['category_id']);
$table->dropColumn('category_id');
});
Schema::dropIfExists('categories');
}
}
Run the migration to create the categories
table and update the items
table:
php artisan migrate
Defining Relationships in the Models
Update the Item
and Category
models to define the relationship.
Item.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Item extends Model
{
use HasFactory;
protected $fillable = ['name', 'description', 'category_id'];
public function category()
{
return $this->belongsTo(Category::class);
}
}
Category.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
use HasFactory;
protected $fillable = ['name'];
public function items()
{
return $this->hasMany(Item::class);
}
}
Updating the Controller
Update the ItemController
to handle categories:
namespace App\Http\Controllers;
use App\Models\Item;
use App\Models\Category;
use Illuminate\Http\Request;
class ItemController extends Controller
{
public function index(Request $request)
{
$query = Item::query();
if ($request->has('search')) {
$query->where('name', 'like', '%' . $request->search . '%')
->orWhere('description', 'like', '%' . $request->search . '%');
}
$items = $query->paginate(10);
return view('items.index', compact('items'));
}
public function create()
{
$categories = Category::all();
return view('items.create', compact('categories'));
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'description' => 'required|string',
'category_id' => 'nullable|exists:categories,id',
]);
Item::create($request->all());
return redirect()->route('items.index')
->with('success', 'Item created successfully.');
}
public function show(Item $item)
{
return view('items.show', compact('item'));
}
public function edit(Item $item)
{
$categories = Category::all();
return view('items.edit', compact('item', 'categories'));
}
public function update(Request $request, Item $item)
{
$request->validate([
'name' => 'required|string|max:255',
'description' => 'required|string',
'category_id' => 'nullable|exists:categories,id',
]);
$item->update($request->all());
return redirect()->route('items.index')
->with('success', 'Item updated successfully.');
}
public function destroy(Item $item)
{
$item->delete();
return redirect()->route('items.index')
->with('success', 'Item deleted successfully.');
}
}
Updating the Views
Update the item views to include category selection.
create.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Add New Item</h1>
<form action="{{ route('items.store') }}" method="POST">
@csrf
<div class="form-group">
<label for="name">Name</label>
<input type="text" name="name" class="form-control" required>
@error('name')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea name="description" class="form-control" required></textarea>
@error('description')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label for="category_id">Category</label>
<select name="category_id" class="form-control">
<option value="">Select Category</option>
@foreach ($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
@error('category_id')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
@endsection
edit.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Edit Item</h1>
<form action="{{ route('items.update', $item->id) }}" method="POST">
@csrf
@method('PUT')
<div class="form-group">
<label for="name">Name</label>
<input type="text" name="name" class="form-control" value="{{ $item->name }}" required>
@error('name')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea name="description" class="form-control" required>{{ $item->description }}</textarea>
@error('description')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label for="category_id">Category</label>
<select name="category_id" class="form-control">
<option value="">Select Category</option>
@foreach ($categories as $category)
<option value="{{ $category->id }}" @if($item->category_id == $category->id) selected @endif>{{ $category->name }}</option>
@endforeach
</select>
@error('category_id')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-primary">Update</button>
</form>
</div>
@endsection
show.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Item Details</h1>
<table class="table table-bordered">
<tr>
<th>ID</th>
<td>{{ $item->id }}</td>
</tr>
<tr>
<th>Name</th>
<td>{{ $item->name }}</td>
</tr>
<tr>
<th>Description</th>
<td>{{ $item->description }}</td>
</tr>
<tr>
<th>Category</th>
<td>{{ $item->category ? $item->category->name : 'None' }}</td>
</tr>
</table>
<a href="{{ route('items.index') }}" class="btn btn-primary">Back to List</a>
</div>
@endsection
Conclusion
In this third part of the tutorial, we have added search functionality, pagination, a confirmation dialog for deleting items, and implemented relationships between models. Your CRUD application is now more feature-rich and user-friendly.
Stay tuned for Part 4, where we will explore more advanced topics like authentication, authorization, and possibly deploying the application to a live server!