How to Add a Modal Button to Display Customer Images and Reasons in Laravel Bootstrap Tables

In this tutorial, we'll implement a feature that allows administrators to view customer-uploaded images and reasons for return requests through a beautiful modal popup. This is perfect for e-commerce applications, support systems, or any application where customers submit requests with supporting images.
🎯 What We'll Build
A third action button in a Bootstrap table
A smooth modal popup displaying customer images in a gallery format
Customer reasons displayed in a styled card
Clickable images that open in full size
Responsive design that works on all devices
Proper error handling and loading states
📋 Prerequisites
Laravel 8+ application
Bootstrap Table implementation
SweetAlert2 for modals
Basic knowledge of PHP, JavaScript, and Laravel
🏗️ Project Structure
We'll work with these key files:
BootstrapTableService.php- For generating action buttonsOrderReturnRequestController.php- Backend logiccustom.js- Frontend JavaScriptlist.blade.php- View file with stylingroutes/web.php- API routes
Step 1: Database Model Setup
First, ensure your model has the necessary fields and accessors:
<?php
// app/Models/OrderReturnRequest.php
namespace App\\\\Models;
use Illuminate\\\\Database\\\\Eloquent\\\\Model;
use Illuminate\\\\Support\\\\Facades\\\\Storage;
class OrderReturnRequest extends Model
{
protected $fillable = [
'order_id',
'order_item_id',
'type',
'reason',
'customer_image', // JSON array of image paths
'admin_image',
'status',
'requested_at',
'status_at',
'admin_receipt',
'admin_notes'
];
protected $casts = [
'customer_image' => 'array', // Important: Cast to array
'admin_image' => 'array',
'requested_at' => 'datetime',
'status_at' => 'datetime',
];
// Accessor to return full URLs for customer images
public function getCustomerImageAttribute($value)
{
if ($value) {
$images = json_decode($value, true);
if (is_array($images)) {
return array_map(function ($path) {
return url(Storage::url($path));
}, $images);
}
}
return [];
}
public function order()
{
return $this->belongsTo(Order::class);
}
public function orderItem()
{
return $this->belongsTo(OrderItem::class);
}
}
Step 2: Create the Button Service Method
Add a new method to your BootstrapTableService to generate the view button:
<?php
// app/Services/BootstrapTableService.php
public static function orderReturnRequestViewButton($orderReturnRequestId, $permission = null)
{
$customClass = [
"OrderReturnRequestViewDetails",
"btn-outline-success",
"btn-xs",
"btn-rounded",
"btn-icon"
];
$customAttributes = [
"title" => trans("View Images & Reason"),
"data-id" => $orderReturnRequestId
];
$iconClass = "fa fa-image"; // FontAwesome image icon
return self::button($iconClass, "#", $customClass, $customAttributes, $permission);
}
Key Points:
OrderReturnRequestViewDetailsclass for JavaScript event handlingbtn-outline-successfor green stylingfa fa-imageicon to indicate image viewing functionalitydata-idattribute to pass the request ID to JavaScript
Step 3: Update the Controller
Modify your controller to include the new button and create an API endpoint:
<?php
// app/Http/Controllers/OrderReturnRequestController.php
public function show()
{
try {
// ... existing code for pagination and filtering ...
$bulkData = [
'total' => $total,
'rows' => $rows->map(function ($row) {
// Generate action buttons
$operate = BootstrapTableService::orderReturnRequestStatusButton(
$row->id,
$row->status,
$row->admin_receipt,
$row->admin_image
);
// Add our new view button
$operate .= BootstrapTableService::orderReturnRequestViewButton($row->id);
// Add details button
$operate .= BootstrapTableService::detailsButton(
$row->order_id,
'order/details',
'order-view'
);
return [
'id' => $row->id,
'order_id' => $row->order_id,
'reason' => $row->reason,
'status' => ucfirst($row->status),
'status_badge' => $row->status,
'customer_image' => $row->customer_image, // Uses accessor
'operate' => $operate,
// ... other fields ...
];
})
];
return response()->json($bulkData);
} catch (\\\\Throwable $th) {
$this->logError($th, 'show');
return response()->json([
'error' => true,
'message' => trans('anErrorOccurred')
]);
}
}
// New API endpoint to fetch return request details
public function getReturnRequestDetails($id)
{
try {
$returnRequest = OrderReturnRequest::findOrFail($id);
return response()->json([
'success' => true,
'data' => [
'id' => $returnRequest->id,
'order_id' => $returnRequest->order_id,
'reason' => $returnRequest->reason,
'customer_image' => $returnRequest->customer_image, // Uses accessor
'status' => $returnRequest->status,
'requested_at' => $returnRequest->requested_at?->format('Y-m-d H:i:s'),
]
]);
} catch (\\\\Throwable $th) {
$this->logError($th, 'getReturnRequestDetails');
return response()->json([
'success' => false,
'message' => trans('anErrorOccurred')
]);
}
}
Step 4: Add the Route
Add the new API route to your routes/web.php:
<?php
// routes/web.php
Route::group(['middleware' => ['auth']], function () {
// ... existing routes ...
Route::get('order-return-requests', [OrderReturnRequestController::class, 'index'])
->name('order-return-requests');
Route::get('order-return-request-list', [OrderReturnRequestController::class, 'show'])
->name('order-return-request-list');
Route::post('change-order-return-request-status', [OrderReturnRequestController::class, 'order_return_request_status_change'])
->name('change-order-return-request-status');
// Our new route
Route::get('get-return-request-details/{id}', [OrderReturnRequestController::class, 'getReturnRequestDetails'])
->name('get-return-request-details');
});
Step 5: JavaScript Implementation
Add the JavaScript event handler to your custom.js file:
// public/assets/js/custom/custom.js
// Handle View Images & Reason button click for Order Return Requests
$(document).on("click", ".OrderReturnRequestViewDetails", function () {
var request_id = $(this).attr("data-id");
// Fetch return request details via AJAX
$.ajax({
url: baseUrl + "/get-return-request-details/" + request_id,
type: "GET",
dataType: "json",
success: function (response) {
if (response.success) {
var data = response.data;
var imagesHtml = '';
// Build images gallery
if (data.customer_image && data.customer_image.length > 0) {
imagesHtml = '<div class="images-gallery" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin: 15px 0;">';
data.customer_image.forEach(function (imageUrl, index) {
imagesHtml += `
<div class="image-item" style="text-align: center;">
<img src="${imageUrl}"
alt="Customer Image ${index + 1}"
style="width: 100%; height: 120px; object-fit: cover; border-radius: 8px; cursor: pointer; border: 2px solid #ddd;"
onclick="window.open('${imageUrl}', '_blank')">
<small style="display: block; margin-top: 5px; color: #666;">
Image ${index + 1}
</small>
</div>`;
});
imagesHtml += '</div>';
} else {
imagesHtml = '<p style="text-align: center; color: #999; font-style: italic; margin: 15px 0;">No images uploaded by customer</p>';
}
// Show the modal with details
Swal.fire({
title: 'Return Request Details',
html: `
<div style="text-align: left; max-width: 600px; margin: 0 auto;">
<div style="margin-bottom: 20px;">
<h4 style="color: #333; margin-bottom: 10px; border-bottom: 2px solid #007bff; padding-bottom: 5px;">
<i class="fa fa-comment-o" style="margin-right: 8px;"></i>Customer Reason
</h4>
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #007bff;">
<p style="margin: 0; line-height: 1.5; color: #555;">
${data.reason || 'No reason provided'}
</p>
</div>
</div>
<div>
<h4 style="color: #333; margin-bottom: 15px; border-bottom: 2px solid #28a745; padding-bottom: 5px;">
<i class="fa fa-image" style="margin-right: 8px;"></i>Customer Images
</h4>
${imagesHtml}
</div>
</div>
`,
width: '700px',
showConfirmButton: true,
confirmButtonText: 'Close',
confirmButtonColor: '#007bff',
customClass: {
popup: 'return-request-details-modal'
}
});
} else {
Swal.fire({
icon: 'error',
title: 'Error',
text: response.message || 'Failed to fetch return request details'
});
}
},
error: function (xhr) {
Swal.fire({
icon: 'error',
title: 'Error',
text: xhr.responseJSON?.message || 'An error occurred while fetching details'
});
}
});
});
Key JavaScript Features:
Event Delegation: Uses
$(document).on()for dynamically loaded contentAJAX Request: Fetches data without page reload
Dynamic HTML Generation: Builds image gallery dynamically
Error Handling: Proper error messages for failed requests
Image Gallery: CSS Grid layout for responsive images
Click to View: Images open in new tab when clicked
Step 6: Add CSS Styling
Add these styles to your Blade view file:
{{-- resources/views/order_return_request/list.blade.php --}}
@section('style')
<link href="<https://cdnjs.cloudflare.com/ajax/libs/ekko-lightbox/5.3.0/ekko-lightbox.css>" rel="stylesheet">
<style>
/* Return Request Details Modal Styling */
.return-request-details-modal .images-gallery .image-item img:hover {
border-color: #007bff !important;
transform: scale(1.05);
transition: all 0.3s ease;
}
.return-request-details-modal .images-gallery .image-item {
transition: transform 0.2s ease;
}
.return-request-details-modal .images-gallery .image-item:hover {
transform: translateY(-2px);
}
/* Custom scrollbar for modal content */
.return-request-details-modal .swal2-popup {
max-height: 90vh;
overflow-y: auto;
}
/* Responsive grid for images */
@media (max-width: 768px) {
.return-request-details-modal .images-gallery {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)) !important;
}
}
</style>
@endsection
Step 7: HTML Table Structure
Your Bootstrap table should include the actions column:
{{-- resources/views/order_return_request/list.blade.php --}}
<table aria-describedby="mydesc" class='table' id='table_list'
data-toggle="table"
data-url="{{ route('order-return-request-list') }}"
data-click-to-select="true"
data-side-pagination="server"
data-pagination="true"
data-page-list="[5, 10, 20, 50, 100, 200,All]"
data-search="true"
data-show-columns="true"
data-show-refresh="true"
data-sort-name="id"
data-sort-order="desc"
data-query-params="OrderReturnRequestQueryParams">
<thead>
<tr>
<th scope="col" data-field="id" data-sortable="true" data-visible="false">
{{ __('id') }}
</th>
<th scope="col" data-field="order_id" data-sortable="false">
{{ __('order_id') }}
</th>
<th scope="col" data-field="reason" data-sortable="false">
{{ __('reason') }}
</th>
<th scope="col" data-field="status_badge" data-sortable="false"
data-formatter="orderReturnRequestStatusFormatter" data-escape="false">
{{ __('status') }}
</th>
<th data-escape="false" scope="col" data-field="operate" data-sortable="false">
{{ __('actions') }}
</th>
</tr>
</thead>
</table>
🎨 UI/UX Features
Responsive Image Gallery
CSS Grid: Automatically adjusts columns based on screen size
Mobile Optimization: Smaller images on mobile devices
Hover Effects: Smooth animations when hovering over images
Professional Modal Design
Structured Layout: Clear sections for reason and images
Color Coding: Blue for reason, green for images
Typography: Proper spacing and font sizes
Icons: FontAwesome icons for visual clarity
User Experience
Smooth Animations: No harsh popup transitions
Loading States: Button feedback during AJAX requests
Error Handling: User-friendly error messages
Accessibility: Proper alt texts and ARIA labels
🔧 Troubleshooting Common Issues
1. Images Not Loading
// Check your storage configuration
// config/filesystems.php
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],
2. AJAX 404 Errors
// Make sure baseUrl is defined
var baseUrl = "{{ url('/') }}";
// Or use Laravel's route helper
url: "{{ route('get-return-request-details', ':id') }}".replace(':id', request_id)
3. Modal Not Appearing
// Ensure SweetAlert2 is loaded
if (typeof Swal === 'undefined') {
console.error('SweetAlert2 is not loaded');
}
🚀 Advanced Enhancements
1. Image Lightbox
// Add lightbox functionality
imagesHtml += `<img src="${imageUrl}"
data-toggle="lightbox"
data-gallery="return-gallery">`;
2. Image Zoom
.image-item img:hover {
cursor: zoom-in;
}
3. Loading Spinner
beforeSend: function() {
$('.OrderReturnRequestViewDetails[data-id="' + request_id + '"]')
.html('<i class="fa fa-spinner fa-spin"></i>');
}
📝 Best Practices
Security: Always validate and sanitize user inputs
Performance: Use pagination for large image sets
Accessibility: Include proper alt texts and ARIA labels
Error Handling: Provide meaningful error messages
Mobile First: Design for mobile devices first
Caching: Consider caching frequently accessed data
🎯 Conclusion
You now have a complete implementation of a modal system for viewing customer images and reasons. This solution provides:
Professional UI: Clean, modern design that matches your application
Responsive Design: Works perfectly on all devices
Smooth Animations: No jarring transitions
Error Handling: Graceful error management
Scalable Architecture: Easy to extend and maintain
The implementation follows Laravel best practices and provides a solid foundation for similar features in your application.
📚 Additional Resources
Happy coding! 🚀

