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
In this step, we'll install the laravel 11 application using the following command.
composer create-project laravel/laravel laravel-11-example
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');
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());
}
}
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>
Now, run the laravel 11 application using the following command.
php artisan serve
Output:
You might also like: