Laravel 12 AJAX CRUD Tutorial: Complete Guide with Vanilla JavaScript - 2025
Introduction
In this comprehensive tutorial, we will learn how to build a complete AJAX CRUD (Create, Read, Update, Delete) application in Laravel 12 using Vanilla JavaScript without jQuery. This tutorial covers all essential features including adding new records, editing existing data, deleting entries, image upload with preview, AJAX pagination, and real-time search functionality—all without page reloads.
We have chosen Vanilla JavaScript with Laravel because it aligns with current industry standards and modern web development practices. Learning Vanilla JavaScript provides a solid foundation that will benefit you when working with popular frameworks and libraries like React and Vue, as both are built on pure JavaScript concepts. This approach ensures you understand the fundamentals rather than relying solely on libraries, making you a more versatile developer.
By the end of this tutorial, you will have a fully functional, production-ready CRUD system that demonstrates modern web development techniques and best practices for 2025.
Tutorial Steps Overview
To complete this CRUD application, we will follow these systematic steps:
Step 1: Install Laravel 12
Install a fresh Laravel 12 application using Composer
Command:
composer create-project laravel/laravel student-crud
cd student-crud
Step 2: Create andLConnect DB
Now create your student_crud database and update env.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=student_crud
DB_USERNAME=root
DB_PASSWORD=
Key points:
-
Create the database before running migrations
-
Ensure MySQL server is running
-
Change from SQLite to MySQL in .env file
-
Clear config cache php artisan config:clear
Step 3: Create Model and Migration
Now generate Student model and create database migration for students table. Define table columns (id, reg_no, name, profile_image, timestamps)
Run migration to create table and model
Command:
# Create model and migration
php artisan make:model Student -m
Files created:
-
app/Models/Student.php- Eloquent model -
database/migrations/xxxx_xx_xx_create_students_table.php- Database schema
update migration file: database/migrations/xxx_create_students_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('students', function (Blueprint $table) {
$table->id();
$table->string('reg_no')->unique();
$table->string('name');
$table->string('profile_image')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('students');
}
};
Update model file : App\Models\Student.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Student extends Model
{
protected $fillable = [
'reg_no',
'name',
'profile_image'
];
}
Key points:
-
Migration file name includes timestamp
-
Model uses singular name (Student), table uses plural (students)
-
Set fillable array to prevent mass assignment vulnerabilities
After that now run migration command
Command
# Run migration
php artisan migrate
Step 4: Create Controller
-
Generate StudentController with necessary methods
-
Implement all CRUD operations (index, store, show, update, destroy)
-
Add getStudents method for AJAX data fetching
-
Handle image upload and deletion logic
-
Implement server-side validation
Command:
php artisan make:controller StudentController
File created:
-
app/Http/Controllers/StudentController.php
Controller methods to implement:
-
index()- Display main page -
getStudents()- Return paginated JSON data for AJAX and real time search -
store()- Create new student with validation -
show($id)- Return single student data for editing -
update($id)- Update existing student -
destroy($id)- Delete student and associated image conditinaly
App/Http/Controllers/StudentController.php
<?php
namespace App\Http\Controllers;
use App\Models\Student;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class StudentController extends Controller
{
// Show main page
public function index()
{
return view('students');
}
// Get students list (for AJAX)
public function getStudents(Request $request)
{
$search = $request->get('search', '');
$perPage = 3;
$students = Student::when($search, function ($query, $search) {
return $query->where('reg_no', 'like', "%{$search}%")
->orWhere('name', 'like', "%{$search}%");
})
->latest()
->paginate($perPage);
$html = view('student-list', compact('students'))->render();
return response()->json([
'success' => true,
'html' => $html,
'pagination' => [
'current_page' => $students->currentPage(),
'last_page' => $students->lastPage(),
'per_page' => $students->perPage(),
'total' => $students->total(),
'from' => $students->firstItem(),
'to' => $students->lastItem()
]
]);
}
// Store new student
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'reg_no' => 'required|unique:students,reg_no',
'name' => 'required|string|max:255',
'profile_image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048'
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors()
], 422);
}
$data = [
'reg_no' => $request->reg_no,
'name' => $request->name
];
// Handle file upload
if ($request->hasFile('profile_image')) {
$image = $request->file('profile_image');
$imageName = time() . '_' . uniqid() . '.' . $image->getClientOriginalExtension();
$image->move(public_path('uploads/students'), $imageName);
$data['profile_image'] = 'uploads/students/' . $imageName;
}
$student = Student::create($data);
return response()->json([
'success' => true,
'message' => 'Student added successfully!',
'student' => $student
]);
}
// Get single student (for edit)
public function show($id)
{
$student = Student::findOrFail($id);
return response()->json([
'success' => true,
'student' => $student
]);
}
// Update student
public function update(Request $request, $id)
{
$student = Student::findOrFail($id);
$validator = Validator::make($request->all(), [
'reg_no' => 'required|unique:students,reg_no,' . $id,
'name' => 'required|string|max:255',
'profile_image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048'
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors()
], 422);
}
$data = [
'reg_no' => $request->reg_no,
'name' => $request->name
];
// Handle file upload
if ($request->hasFile('profile_image')) {
// Delete old image
if ($student->profile_image && file_exists(public_path($student->profile_image))) {
unlink(public_path($student->profile_image));
}
$image = $request->file('profile_image');
$imageName = time() . '_' . uniqid() . '.' . $image->getClientOriginalExtension();
$image->move(public_path('uploads/students'), $imageName);
$data['profile_image'] = 'uploads/students/' . $imageName;
}
$student->update($data);
return response()->json([
'success' => true,
'message' => 'Student updated successfully!',
'student' => $student
]);
}
// Delete student
public function destroy($id)
{
$student = Student::findOrFail($id);
// Delete image if exists
if ($student->profile_image && file_exists(public_path($student->profile_image))) {
unlink(public_path($student->profile_image));
}
$student->delete();
return response()->json([
'success' => true,
'message' => 'Student deleted successfully!'
]);
}
}
Required routes to add in routes/web.php:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\StudentController;
Route::get('/students', [StudentController::class, 'index'])->name('students.index');
Route::get('/students/list', [StudentController::class, 'getStudents'])->name('students.list');
Route::post('/students', [StudentController::class, 'store'])->name('students.store');
Route::get('/students/{id}', [StudentController::class, 'show'])->name('students.show');
Route::post('/students/{id}', [StudentController::class, 'update'])->name('students.update');
Route::delete('/students/{id}', [StudentController::class, 'destroy'])->name('students.destroy');
Key points:
-
All methods return JSON for AJAX requests
-
Validation rules defined for each operation
-
Image stored in
public/uploads/students/directory -
Old images deleted when updating or deleting student
-
Use
findOrFail()to handle non-existent records
Step 5: Create View Files
-
Create main blade template with Bootstrap 5
-
Design responsive layout with search box and table
-
Create modal forms for Add and Edit operations
-
Build reusable partial for table rows
-
Add loading indicators and success/error alerts
Files to create:
-
resources/views/students.blade.php- Main page layout -
resources/views/students-js.blade.php- JavaScript file (included in main page) -
resources/views/add-student-modal.blade.php- Add form modal -
resources/views/edit-student-modal.blade.php- Edit form modal -
resources/views/student-list.blade.php- Table rows partial
CDN links to include:
-
Bootstrap 5.3.0 CSS/JS
-
Font Awesome 6.4.0 icons
View components:
-
Header with "Add Student" button
-
Search input with real-time filtering
-
Data table with pagination
-
Action buttons (Edit, Delete) for each row
-
Modals for Add/Edit forms
-
Image preview functionality
-
Success/error message containers
-
Loading spinner overlay
resources/views/students.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Student Management - AJAX CRUD</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body {
background-color: #f8f9fa;
}
.card {
box-shadow: 0 0 20px rgba(0,0,0,0.1);
}
.profile-img {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 50%;
}
.search-box {
position: relative;
}
.search-box i {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
}
.search-box input {
padding-left: 45px;
}
.image-preview {
max-width: 200px;
max-height: 200px;
margin-top: 10px;
display: none;
}
.image-preview img {
width: 100%;
height: auto;
border-radius: 8px;
border: 2px solid #dee2e6;
}
.modal-backdrop.show {
opacity: 0.5;
}
</style>
</head>
<body>
<div class="container py-5">
<!-- Header -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center">
<h2 class="mb-0">
<i class="fas fa-users text-primary"></i>
Student Management System
</h2>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addStudentModal">
<i class="fas fa-plus"></i> Add New Student
</button>
</div>
</div>
</div>
<!-- Search & Table Card -->
<div class="card">
<div class="card-header bg-white py-3">
<div class="row align-items-center">
<div class="col-md-6">
<h5 class="mb-0">Student List</h5>
</div>
<div class="col-md-6">
<div class="search-box">
<i class="fas fa-search"></i>
<input
type="text"
id="searchInput"
class="form-control"
placeholder="Search by Reg No or Name...">
</div>
</div>
</div>
</div>
<div class="card-body">
<!-- Loading Spinner -->
<div id="loadingSpinner" class="text-center py-5" style="display: none;">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2 text-muted">Loading students...</p>
</div>
<!-- Table Container -->
<div id="studentTableContainer">
<!-- Student list will be loaded here via AJAX -->
</div>
</div>
</div>
<!-- Pagination Info -->
<div class="row mt-3">
<div class="col-md-6">
<p class="text-muted" id="paginationInfo">Showing 0 to 0 of 0 entries</p>
</div>
<div class="col-md-6">
<nav>
<ul class="pagination justify-content-end mb-0" id="paginationContainer">
<!-- Pagination will be loaded here -->
</ul>
</nav>
</div>
</div>
</div>
<!-- Add Student Modal -->
@include('add-student-modal')
<!-- Edit Student Modal -->
@include('edit-student-modal')
<!-- Bootstrap 5 JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom JavaScript -->
@include('students-js')
</body>
</html>
resources/views/student-list.blade.php
<!-- Student List Table -->
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th style="width: 5%;">#</th>
<th style="width: 10%;">Image</th>
<th style="width: 20%;">Reg No</th>
<th style="width: 35%;">Name</th>
<th style="width: 15%;">Created At</th>
<th style="width: 15%;" class="text-center">Actions</th>
</tr>
</thead>
<tbody>
@forelse($students as $index => $student)
<tr>
<td>{{ $students->firstItem() + $index }}</td>
<td>
@if($student->profile_image)
<img src="{{ asset($student->profile_image) }}"
alt="{{ $student->name }}"
class="profile-img">
@else
<div class="profile-img bg-secondary d-flex align-items-center justify-content-center text-white">
<i class="fas fa-user"></i>
</div>
@endif
</td>
<td>
<span class="badge bg-primary">{{ $student->reg_no }}</span>
</td>
<td>
<strong>{{ $student->name }}</strong>
</td>
<td>
<small class="text-muted">
<i class="far fa-clock"></i>
{{ $student->created_at->format('d M, Y') }}
</small>
</td>
<td class="text-center">
<button
onclick="editStudent({{ $student->id }})"
class="btn btn-sm btn-warning"
title="Edit Student">
<i class="fas fa-edit"></i>
</button>
<button
onclick="deleteStudent({{ $student->id }})"
class="btn btn-sm btn-danger"
title="Delete Student">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center py-5">
<div class="text-muted">
<i class="fas fa-inbox fa-3x mb-3"></i>
<h5>No Students Found</h5>
<p>Start by adding a new student using the button above.</p>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
resources/views/add-student-modal.blade.php
<!-- Add Student Modal -->
<div class="modal fade" id="addStudentModal" tabindex="-1" aria-labelledby="addStudentModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="addStudentModalLabel">
<i class="fas fa-user-plus"></i> Add New Student
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="addStudentForm" enctype="multipart/form-data">
@csrf
<!-- Registration Number -->
<div class="mb-3">
<label for="addRegNo" class="form-label">
Registration Number <span class="text-danger">*</span>
</label>
<input
type="text"
class="form-control"
id="addRegNo"
name="reg_no"
placeholder="Enter registration number"
required>
</div>
<!-- Name -->
<div class="mb-3">
<label for="addName" class="form-label">
Student Name <span class="text-danger">*</span>
</label>
<input
type="text"
class="form-control"
id="addName"
name="name"
placeholder="Enter student name"
required>
</div>
<!-- Profile Image -->
<div class="mb-3">
<label for="addProfileImage" class="form-label">
Profile Image
</label>
<input
type="file"
class="form-control"
id="addProfileImage"
name="profile_image"
accept="image/*"
onchange="previewAddImage(this)">
<small class="text-muted">Allowed: JPG, PNG, GIF (Max: 2MB)</small>
<!-- Image Preview -->
<div id="addImagePreview" class="image-preview"></div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> Cancel
</button>
<button type="submit" form="addStudentForm" class="btn btn-primary" id="addStudentBtn">
<i class="fas fa-save"></i> Save Student
</button>
</div>
</div>
</div>
</div>
resources/views/edit-student-modal.blade.php
<!-- Edit Student Modal -->
<div class="modal fade" id="editStudentModal" tabindex="-1" aria-labelledby="editStudentModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title" id="editStudentModalLabel">
<i class="fas fa-user-edit"></i> Edit Student
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="editStudentForm" enctype="multipart/form-data">
@csrf
<!-- Hidden ID Field -->
<input type="hidden" id="editStudentId" name="id">
<!-- Registration Number -->
<div class="mb-3">
<label for="editRegNo" class="form-label">
Registration Number <span class="text-danger">*</span>
</label>
<input
type="text"
class="form-control"
id="editRegNo"
name="reg_no"
placeholder="Enter registration number"
required>
</div>
<!-- Name -->
<div class="mb-3">
<label for="editName" class="form-label">
Student Name <span class="text-danger">*</span>
</label>
<input
type="text"
class="form-control"
id="editName"
name="name"
placeholder="Enter student name"
required>
</div>
<!-- Profile Image -->
<div class="mb-3">
<label for="editProfileImage" class="form-label">
Profile Image
</label>
<input
type="file"
class="form-control"
id="editProfileImage"
name="profile_image"
accept="image/*"
onchange="previewEditImage(this)">
<small class="text-muted">Leave empty to keep current image. Allowed: JPG, PNG, GIF (Max: 2MB)</small>
<!-- Image Preview -->
<div id="editImagePreview" class="image-preview"></div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> Cancel
</button>
<button type="submit" form="editStudentForm" class="btn btn-warning" id="editStudentBtn">
<i class="fas fa-save"></i> Update Student
</button>
</div>
</div>
</div>
</div>
Step 6: Create JavaScript File
-
Write all AJAX functionality using Fetch API
-
Implement CRUD operations without page reload
-
Add real-time search with debouncing (500ms delay)
-
Create dynamic pagination
-
Handle image preview before upload
-
Display validation errors inline
-
Show success/error messages
File location:
-
resources/views/students-js.blade.php(included in main blade file)
JavaScript functions to implement:
Core AJAX Functions:
-
loadStudents(page)- Fetch and display student list -
editStudent(id)- Populate edit form with student data -
deleteStudent(id)- Delete student with confirmation -
updatePagination(data)- Render pagination links
Form Handlers:
-
Add form submit handler
-
Edit form submit handler
-
Search input event listener with debounce
Helper Functions:
-
previewAddImage()- Show image preview in add modal -
previewEditImage()- Show image preview in edit modal -
showLoading()- Display loading spinner -
hideLoading()- Hide loading spinner -
showAlert()- Display success/error messages -
displayErrors()- Show validation errors -
clearErrors()- Remove validation error messages
AJAX request headers:
-
X-CSRF-TOKEN- Laravel CSRF protection -
Accept: application/json- Request JSON response -
X-Requested-With: XMLHttpRequest- Identify AJAX requests
Key points:
-
Use
async/awaitfor cleaner asynchronous code -
Implement try-catch blocks for error handling
-
Use
FormDatafor file uploads -
Debounce search to reduce server requests
-
All responses return JSON format
-
No jQuery dependency - pure Vanilla JavaScript
resources/views/students-js.blade.php
<script>
// GLOBAL VARIABLES
let currentPage = 1;
let searchQuery = '';
let searchTimeout = null;
// Get CSRF Token
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
// LOAD STUDENTS FUNCTION
async function loadStudents(page = 1) {
showLoading();
currentPage = page;
try {
const response = await fetch(`{{ route('students.list') }}?page=${page}&search=${searchQuery}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
const data = await response.json();
if (data.success) {
document.getElementById('studentTableContainer').innerHTML = data.html;
updatePagination(data.pagination);
hideLoading();
}
} catch (error) {
console.error('Error loading students:', error);
hideLoading();
showAlert('Error loading students. Please try again.', 'danger');
}
}
// SEARCH FUNCTIONALITY
document.getElementById('searchInput').addEventListener('input', function(e) {
if (searchTimeout) {
clearTimeout(searchTimeout);
}
searchTimeout = setTimeout(() => {
searchQuery = e.target.value;
loadStudents(1);
}, 500);
});
// ADD STUDENT
document.getElementById('addStudentForm').addEventListener('submit', async function(e) {
e.preventDefault();
const submitBtn = document.getElementById('addStudentBtn');
const formData = new FormData(this);
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Saving...';
clearErrors('addStudentForm');
try {
const response = await fetch('{{ route("students.store") }}', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
});
const data = await response.json();
if (data.success) {
const modal = bootstrap.Modal.getInstance(document.getElementById('addStudentModal'));
modal.hide();
this.reset();
document.getElementById('addImagePreview').style.display = 'none';
showAlert(data.message, 'success');
loadStudents(currentPage);
} else {
displayErrors(data.errors, 'addStudentForm');
}
} catch (error) {
console.error('Error adding student:', error);
showAlert('Error adding student. Please try again.', 'danger');
} finally {
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-save"></i> Save Student';
}
});
// EDIT STUDENT
async function editStudent(id) {
try {
const response = await fetch(`/students/${id}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
const data = await response.json();
if (data.success) {
const student = data.student;
document.getElementById('editStudentId').value = student.id;
document.getElementById('editRegNo').value = student.reg_no;
document.getElementById('editName').value = student.name;
const previewDiv = document.getElementById('editImagePreview');
if (student.profile_image) {
previewDiv.innerHTML = `
<img src="{{ asset('') }}${student.profile_image}" alt="Current Image">
<p class="text-muted mt-2">Current Image</p>
`;
previewDiv.style.display = 'block';
} else {
previewDiv.style.display = 'none';
}
clearErrors('editStudentForm');
const modal = new bootstrap.Modal(document.getElementById('editStudentModal'));
modal.show();
}
} catch (error) {
console.error('Error fetching student:', error);
showAlert('Error loading student data. Please try again.', 'danger');
}
}
document.getElementById('editStudentForm').addEventListener('submit', async function(e) {
e.preventDefault();
const submitBtn = document.getElementById('editStudentBtn');
const studentId = document.getElementById('editStudentId').value;
const formData = new FormData(this);
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Updating...';
clearErrors('editStudentForm');
try {
const response = await fetch(`/students/${studentId}`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
});
const data = await response.json();
if (data.success) {
const modal = bootstrap.Modal.getInstance(document.getElementById('editStudentModal'));
modal.hide();
showAlert(data.message, 'success');
loadStudents(currentPage);
} else {
displayErrors(data.errors, 'editStudentForm');
}
} catch (error) {
console.error('Error updating student:', error);
showAlert('Error updating student. Please try again.', 'danger');
} finally {
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-save"></i> Update Student';
}
});
// DELETE STUDENT
async function deleteStudent(id) {
if (!confirm('Are you sure you want to delete this student?')) {
return;
}
try {
const response = await fetch(`/students/${id}`, {
method: 'DELETE',
headers: {
'X-CSRF-TOKEN': csrfToken,
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
const data = await response.json();
if (data.success) {
showAlert(data.message, 'success');
loadStudents(currentPage);
}
} catch (error) {
console.error('Error deleting student:', error);
showAlert('Error deleting student. Please try again.', 'danger');
}
}
// IMAGE PREVIEW FUNCTIONS
function previewAddImage(input) {
const preview = document.getElementById('addImagePreview');
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
preview.innerHTML = `<img src="${e.target.result}" alt="Preview">`;
preview.style.display = 'block';
};
reader.readAsDataURL(input.files[0]);
} else {
preview.style.display = 'none';
}
}
function previewEditImage(input) {
const preview = document.getElementById('editImagePreview');
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
preview.innerHTML = `<img src="${e.target.result}" alt="Preview"><p class="text-muted mt-2">New Image</p>`;
preview.style.display = 'block';
};
reader.readAsDataURL(input.files[0]);
}
}
// PAGINATION FUNCTIONS
function updatePagination(pagination) {
const container = document.getElementById('paginationContainer');
const info = document.getElementById('paginationInfo');
info.textContent = `Showing ${pagination.from || 0} to ${pagination.to || 0} of ${pagination.total} entries`;
let html = '';
html += `
<li class="page-item ${pagination.current_page === 1 ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="loadStudents(${pagination.current_page - 1}); return false;">
<i class="fas fa-chevron-left"></i>
</a>
</li>
`;
for (let i = 1; i <= pagination.last_page; i++) {
if (i === 1 || i === pagination.last_page || (i >= pagination.current_page - 2 && i <= pagination.current_page + 2)) {
html += `
<li class="page-item ${i === pagination.current_page ? 'active' : ''}">
<a class="page-link" href="#" onclick="loadStudents(${i}); return false;">${i}</a>
</li>
`;
} else if (i === pagination.current_page - 3 || i === pagination.current_page + 3) {
html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
}
}
html += `
<li class="page-item ${pagination.current_page === pagination.last_page ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="loadStudents(${pagination.current_page + 1}); return false;">
<i class="fas fa-chevron-right"></i>
</a>
</li>
`;
container.innerHTML = html;
}
// UTILITY FUNCTIONS
function showLoading() {
document.getElementById('loadingSpinner').style.display = 'block';
document.getElementById('studentTableContainer').style.opacity = '0.5';
}
function hideLoading() {
document.getElementById('loadingSpinner').style.display = 'none';
document.getElementById('studentTableContainer').style.opacity = '1';
}
function showAlert(message, type) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.querySelector('.container').insertBefore(alertDiv, document.querySelector('.row'));
setTimeout(() => {
alertDiv.remove();
}, 5000);
}
function displayErrors(errors, formId) {
for (let field in errors) {
const input = document.querySelector(`#${formId} [name="${field}"]`);
if (input) {
input.classList.add('is-invalid');
const errorDiv = document.createElement('div');
errorDiv.className = 'invalid-feedback';
errorDiv.textContent = errors[field][0];
input.parentNode.appendChild(errorDiv);
}
}
}
function clearErrors(formId) {
const form = document.getElementById(formId);
const invalidInputs = form.querySelectorAll('.is-invalid');
const errorMessages = form.querySelectorAll('.invalid-feedback');
invalidInputs.forEach(input => input.classList.remove('is-invalid'));
errorMessages.forEach(error => error.remove());
}
// INITIALIZE ON PAGE LOAD
document.addEventListener('DOMContentLoaded', function() {
loadStudents(1);
document.getElementById('addStudentModal').addEventListener('hidden.bs.modal', function() {
document.getElementById('addStudentForm').reset();
document.getElementById('addImagePreview').style.display = 'none';
clearErrors('addStudentForm');
});
document.getElementById('editStudentModal').addEventListener('hidden.bs.modal', function() {
clearErrors('editStudentForm');
});
});
</script>
Step 7: Test CRUD Operations
-
Start Laravel development server
-
Test all CRUD functionalities
-
Verify validation works correctly
-
Check image upload and preview
-
Test search and pagination
Command: open your terminal and run artisan command.
php artisan serve
```
**Access application:**
```
http://localhost:8000/students
Hope this will help you a lot. If you like this crud turorials please share these helpful resource with your friends and community.
-min.png-1766246749-6946c95db1cab.png)