Laravel 11 Nested Stepper Form with jQuery

Hello Laravel web developers! In this article, I’ll show you how to create a vertical nested stepper form using jQuery in Laravel 11. We’ll build a stepper form that includes the next and previous buttons to navigate between steps.

I’ll be using Bootstrap 5 and jQuery to design the form, ensuring it's simple and easy to use. Let’s dive in and create this stepper form together.

By the end of this tutorial, you'll have a functional stepper form with a clean design and smooth navigation. This form will allow users to move between steps with ease, making it perfect for multi-step processes. Let's get started with setting up Bootstrap 5 and jQuery in Laravel 11.

Laravel 11 Nested Stepper Form with jQuery

Laravel 11 Nested Stepper Form with jQuery

 

Step 1: Install Laravel 11 Application

In this step, we'll install the laravel 11 application using the following command.

composer create-project laravel/laravel laravel-11-example

 

Step 2: Define Routes

Next, define the routes into the web.php file.

routes/web.php

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TestController;


/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::get('/index', [TestController::class,'index'])->name('index');
Route::post('/store', [TestController::class, 'storeAllRatings'])->name('store');

 

Step 3: Create Controller

Then, we'll create a controller and add the following code to the controller file.

app\Http\Controllers\TestController.php

<?php 

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;

class TestController extends Controller
{
    public function index(Request $request)
    {
        $topics = [
            [
                'name' => 'Project Scope',
                'questions' => [
                    ['id' => 1, 'question' => 'What is the project objective?', 'description' => 'The project aims to develop a web-based platform for online learning, providing users with access to various courses and resources.'],
                    ['id' => 2, 'question' => 'What are the key deliverables?', 'description' => 'The deliverables include a responsive web application, a course management system, and a user dashboard for tracking progress.'],
                    ['id' => 3, 'question' => 'Who are the stakeholders?', 'description' => 'The stakeholders include project managers, developers, course content providers, and end-users.'],
                ],
            ],
            [
                'name' => 'Environmental Impact',
                'questions' => [
                    ['id' => 4, 'question' => 'What is the environmental impact of the project?', 'description' => 'The project’s server infrastructure will be hosted in a data center powered by renewable energy to minimize carbon footprint.'],
                    ['id' => 5, 'question' => 'How will waste be managed?', 'description' => 'All electronic waste generated from hardware replacements will be properly recycled in partnership with certified e-waste recyclers.'],
                    ['id' => 6, 'question' => 'What measures are in place for energy efficiency?', 'description' => 'Energy-efficient coding practices and server optimizations will be implemented to reduce power consumption.'],
                ],
            ],
            [
                'name' => 'Compliance and Disclosure',
                'questions' => [
                    ['id' => 7, 'question' => 'What information needs to be disclosed to regulators?', 'description' => 'We need to disclose the data privacy policy and user consent mechanisms to comply with GDPR regulations.'],
                    ['id' => 8, 'question' => 'Why is disclosure important for data security?', 'description' => 'Disclosure of security protocols ensures transparency with users and builds trust in how their data is protected.'],
                    ['id' => 9, 'question' => 'What are the key compliance requirements?', 'description' => 'Compliance with GDPR, CCPA, and other data protection laws is mandatory to avoid legal penalties.'],
                    ['id' => 10, 'question' => 'How are privacy concerns addressed?', 'description' => 'All user data will be encrypted, and regular audits will be conducted to ensure ongoing privacy compliance.'],
                ],
            ],
        ];
            
        return view('welcome', compact('topics'));    
    }

    public function storeAllRatings(Request $request)
    {
        info($request->all());        
    }

}

 

Step 4: Create Blade File

Then, create a welcome.blade.php blade file and add the following HTML code to that file.

resources\views\welcome.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Stepper Form Example - Techsolutionstuff</title>        
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        #topicList .list-group-item.active {
            background-color: #198754;
            color: white;
        }
        .fa-star {
            cursor: pointer;
            color: #ddd;
            font-size: 24px;
        }
        .fa-star.checked {
            color: #f39c12;
        }

        .cus-btn{
            width: 100px;
            padding: 10px;
        }
    </style>
</head>
<body>

<h3 class="text-center my-5">Stepper Form with jQuery - Techsolutionstuff</h3>
<div class="container mt-5 p-5" style="border: 1px solid #bbb;border-radius: 10px;">
    <div class="row">
        <!-- Stepper (Topics List) -->
        <div class="col-md-3">
            <ul class="list-group" id="topicList">
                @foreach($topics as $index => $topic)
                    <li class="list-group-item {{ $index === 0 ? 'active' : '' }}" data-index="{{ $index }}">{{ $topic['name'] }}</li>
                @endforeach
            </ul>
        </div>

        <!-- Content Area -->
        <div class="col-md-9">
            <!-- Navigation Buttons for Questions -->
            <div class="d-flex justify-content-between mb-5">
                <button class="btn btn-primary cus-btn" id="prevQuestionBtn" disabled>Prev</button>
                <button class="btn btn-primary cus-btn" id="nextQuestionBtn">Next</button>
            </div>

            <!-- Topic Contents -->
            @foreach($topics as $index => $topic)
                <div class="topic-content" data-index="{{ $index }}" style="{{ $index === 0 ? '' : 'display:none;' }}">
                    @foreach($topic['questions'] as $qIndex => $question)
                        <div class="question-container" data-qindex="{{ $qIndex }}" style="{{ $qIndex === 0 ? '' : 'display:none;' }}">
                            <h3>{{ $question['question'] }}</h3>
                            <p>{{ $question['description'] }}</p>
                            
                            <!-- Star Rating -->
                            <div class="rating mb-3">
                                @for($i = 1; $i <= 5; $i++)
                                    <span class="fa fa-star" data-value="{{ $i }}"></span>
                                @endfor
                            </div>
                        </div>
                    @endforeach
                </div>
            @endforeach

            <!-- Navigation Buttons for Topics -->
            <div class="d-flex justify-content-between mt-5">
                <button class="btn btn-secondary cus-btn" id="prevTopicBtn" disabled>Prev</button>
                <button class="btn btn-secondary cus-btn" id="nextTopicBtn">Next</button>
                <button class="btn btn-success" id="submitRatingsBtn" style="display: none;">Submit</button>
            </div>
        </div>
    </div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
    $(document).ready(function() {
        let currentTopicIndex = 0;
        let currentQuestionIndex = 0;
        let ratings = [];

        function showQuestion(topicIndex, questionIndex) {
            $('.topic-content').hide();
            $(`.topic-content[data-index="${topicIndex}"]`).show();
            $('.question-container').hide();
            $(`.topic-content[data-index="${topicIndex}"] .question-container[data-qindex="${questionIndex}"]`).show();
            updateButtons();
        }

        function updateButtons() {
            const questionCount = $(`.topic-content[data-index="${currentTopicIndex}"] .question-container`).length;
            const isLastTopic = currentTopicIndex === $('.topic-content').length - 1;
            const isLastQuestion = currentQuestionIndex === questionCount - 1;

            $('#prevQuestionBtn').prop('disabled', currentQuestionIndex === 0);
            $('#nextQuestionBtn').prop('disabled', isLastQuestion);
            $('#prevTopicBtn').prop('disabled', currentTopicIndex === 0);
            $('#nextTopicBtn').prop('disabled', isLastTopic && isLastQuestion);

            if (isLastTopic && isLastQuestion) {
                $('#submitRatingsBtn').show();
                $('#nextTopicBtn').hide();
            } else {
                $('#submitRatingsBtn').hide();
                $('#nextTopicBtn').show();
            }
        }

        $('#topicList').on('click', 'li', function() {
            currentTopicIndex = $(this).data('index');
            currentQuestionIndex = 0;
            showQuestion(currentTopicIndex, currentQuestionIndex);
            $('#topicList li').removeClass('active');
            $(this).addClass('active');
        });

        $('#nextQuestionBtn').click(function() {
            if (currentQuestionIndex < $(`.topic-content[data-index="${currentTopicIndex}"] .question-container`).length - 1) {
                currentQuestionIndex++;
                showQuestion(currentTopicIndex, currentQuestionIndex);
            }
        });

        $('#prevQuestionBtn').click(function() {
            if (currentQuestionIndex > 0) {
                currentQuestionIndex--;
                showQuestion(currentTopicIndex, currentQuestionIndex);
            }
        });

        $('#nextTopicBtn').click(function() {
            if (currentTopicIndex < $('.topic-content').length - 1) {
                currentTopicIndex++;
                currentQuestionIndex = 0;
                showQuestion(currentTopicIndex, currentQuestionIndex);
                $('#topicList li').removeClass('active');
                $(`#topicList li[data-index="${currentTopicIndex}"]`).addClass('active');
            }
        });

        $('#prevTopicBtn').click(function() {
            if (currentTopicIndex > 0) {
                currentTopicIndex--;
                currentQuestionIndex = 0;
                showQuestion(currentTopicIndex, currentQuestionIndex);
                $('#topicList li').removeClass('active');
                $(`#topicList li[data-index="${currentTopicIndex}"]`).addClass('active');
            }
        });

        $('.rating .fa-star').on('click', function() {
            const ratingValue = $(this).data('value');
            const topicId = currentTopicIndex + 1; // Assuming 0-indexed topics
            const questionId = currentQuestionIndex + 1; // Assuming 0-indexed questions

            $(this).siblings().removeClass('checked');
            $(this).addClass('checked').prevAll().addClass('checked');

            // Store the rating in the array
            ratings.push({
                topic_id: topicId,
                question_id: questionId,
                rating: ratingValue,
            });
        });

        $('#submitRatingsBtn').click(function() {
            $.ajax({
                url: '{{ route("store.all.ratings") }}',
                method: 'POST',
                data: {
                    _token: '{{ csrf_token() }}',
                    ratings: ratings,                    
                },
                success: function(response) {
                    alert('All ratings submitted successfully!');                    
                },
                error: function(xhr) {
                    console.log(xhr.responseText);
                }
            });
        });

        showQuestion(currentTopicIndex, currentQuestionIndex);
    });
</script>
</body>
</html>

 

Step 5: Run the Laravel 11 Application

Now, run the laravel 11 application using the following command.

php artisan serve

Output:

Stepper Form with jQuery in Laravel 11

 


You might also like:

techsolutionstuff

Techsolutionstuff | The Complete Guide

I'm a software engineer and the founder of techsolutionstuff.com. Hailing from India, I craft articles, tutorials, tricks, and tips to aid developers. Explore Laravel, PHP, MySQL, jQuery, Bootstrap, Node.js, Vue.js, and AngularJS in our tech stack.

RECOMMENDED POSTS

FEATURE POSTS