Skip to main content

Command Palette

Search for a command to run...

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

Updated
8 min read
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 buttons

  • OrderReturnRequestController.php - Backend logic

  • custom.js - Frontend JavaScript

  • list.blade.php - View file with styling

  • routes/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:

  • OrderReturnRequestViewDetails class for JavaScript event handling

  • btn-outline-success for green styling

  • fa fa-image icon to indicate image viewing functionality

  • data-id attribute 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 content

  • AJAX 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

  • 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

  1. Security: Always validate and sanitize user inputs

  2. Performance: Use pagination for large image sets

  3. Accessibility: Include proper alt texts and ARIA labels

  4. Error Handling: Provide meaningful error messages

  5. Mobile First: Design for mobile devices first

  6. 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! 🚀

More from this blog

B

Bhavik Bhuva’s Tech Insights: Full Stack Development, Flutter Developer, and More

7 posts

Code With Bhavik