ক্লাস নং-২০(পার্ট-১৪)-লারাভেল LMS Project Quiz section create and functionality add
Table of contents
আজকের ক্লাসে আমরা মূলত আমাদের লারাভেল প্রজেক্টের কুইজ সেকশন নিয়ে কাজ করব।এবং এখানে teacher কুইজ তৈরি করে সেটার লিংক students এর সাথে share করতে পারবে।এবং students সেই লিংকে গিয়ে তাদের নিজের কোর্সের অবস্থা সম্পর্কে জানতে পারবে।
তো এর জন্য লাগবে প্রশ্ন যেটা থেকে শিক্ষক কুইজ তৈরি করবেন। তো প্রথমেই আমাদের question model make করব।এবং সেই সাথে migration and controller
php artisan make:model Question -mc
এবং এর পর আমরা আমাদের quiz এর জন্য তৈরি করব।
php artisan make:model Quiz -mc
এবার আমাদের question এর migration টা ready করে ফেলি।
$table->id();
$table->string('name');
$table->string('answer_a');
$table->string('answer_b');
$table->string('answer_c');
$table->string('answer_d');
$table->string('correct_answer');
$table->timestamps();
এবং আমাদের কুইজ migration এ মেইন টেবিল এ শুধুমাত্র নাম থাকবে ।এবং এর নিচে আমাদের পিভট টেবিল লাগবে সেখানে আমাদের question and quize থাকবে।
Schema::create('quizzes', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
Schema::create('quiz_question', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('quiz_id');
$table->unsignedBigInteger('question_id');
$table->timestamps();
$table->unique(['quiz_id', 'question_id']);
$table->foreign('quiz_id')->references('id')->on('quizzes')->onDelete('cascade');
$table->foreign('question_id')->references('id')->on('questions')->onDelete('cascade');
});
এবার আমরা আমাদের web.php file এ রাউট গুলো ডিফাইন করে দিব।
Route::resource('question', QuestionController::class);
Route::resource('quiz', QuizController::class);
এবার আমরা আমাদের যে navigation file আছে সেখানে আমাদের দুইটা মেনু নিব।
<x-nav-link :href="route('question.index')" :active="request()->routeIs('question.index')">
{{ __('Questions') }}
</x-nav-link>
<x-nav-link :href="route('quiz.index')" :active="request()->routeIs('quiz.index')">
{{ __('Quizes') }}
</x-nav-link>
এবার আমাদের questions and quize dynamic করার জন্য আমরা livewire template make করব।
php artisan livewire:make QuestionIndex
php artisan livewire:make QuestionCreate
php artisan livewire:make QuestionEdit
edit টা যেহেতু আমাদের টাস্ক দেওয়া থাকবে তাই আমরা এটাকে আগেই তৈরি করে নিয়েছি। এবার আমাদের যে course folder টা আছে সেটাকে কপি করব এবং এর মধ্যে যে curiculum folder টা আছে সেটাকে ডিলেট করে দিব। এবং আমাদের question index file টা কে এডিট করে নি।
এবং এবার আমাদের questionController এর মধ্যে আমাদের function গুলো লিখে ফেলি।
public function index()
{
return view('question.index');
}
public function create()
{
return view('question.create');
}
public function edit($id)
{
return view("question.edit", [
'question_id' => $id
]);
}
এবং এবার যদি আমরা ভিউ করি তাহলে আমরা সুন্দর ভাবে সব গুলো কে শো করাতে পারব। আমাদের ফাইল গুলো যেহেতু কপি করা তাই আমরা question related সেগুলো কে ঠিক করে নিব।এবং প্রথমেই আমরা create এ তৈরি করব। তো আমরা এবার livewire এর create file এ আমাদের form টা লিখব।
<form wire:submit.prevent="formSubmit">
<div class="mb-4">
@include('components.form-field', [
'name' => 'name',
'label' => 'Name',
'type' => 'text',
'placeholder' => 'Question name',
'required' => 'required',
])
</div>
@foreach($answers as $answer)
<div class="mb-4">
@include('components.form-field', [
'name' => 'answer_' . $answer,
'label' => 'Answer '. ucfirst($answer),
'type' => 'text',
'placeholder' => 'Type answer ' . ucfirst($answer),
'required' => 'required',
])
</div>
@endforeach
<div class="mb-4">
<label class="lms-label" for="correct_answer">Correct answer</label>
<select class="lms-input" wire:model.prevent="correct_answer" id="correct_answer">
@foreach($answers as $answer)
<option value="{{ $answer }}">{{ ucfirst($answer) }}</option>
@endforeach
</select>
</div>
@include('components.wire-loading-btn')
</form>
যেহেতু এই ফর্ম এর ফিল্ড গুলো একের অধিক যায়গায় লেগেছে তাই আমরা এগুলো কে dynamic করে নিয়েছি।এবং প্রয়োজন অনুযায়ী বসিয়ে দিয়েছি,।এবং আমাদের question এর answer গুলো কে আমরা dynamic ভাবে এখানে শো করাব এর জন্য আমাদের livewire এর যে questioncreate file আছে সেখানে গিয়ে আমরা আমাদের সেগুলো লিখে ফেলব।এছাড়াও আমাদের question make করব।
public $answers = ['a', 'b', 'c', 'd'];
public $name;
public $answer_a;
public $answer_b;
public $answer_c;
public $answer_d;
public $correct_answer = 'a';
public function render()
{
return view('livewire.question-create');
}
protected $rules = [
'name' => 'required',
'answer_a' => 'required',
'answer_b' => 'required',
'answer_c' => 'required',
'answer_d' => 'required',
'correct_answer' => 'required',
];
public function formSubmit()
{
$this->validate();
Question::create([
'name' => $this->name,
'answer_a' => $this->answer_a,
'answer_b' => $this->answer_b,
'answer_c' => $this->answer_c,
'answer_d' => $this->answer_d,
'correct_answer' => $this->correct_answer,
]);
flash()->addSuccess('Question created successfully!');
return redirect()->route('question.index');
}
এবার আমরা একবার migration চালাব ।এবং সেই সাথে আমরা question make করব।
এবং আমাদের index টা যেহেতু টাস্ক দেওয়া ছিল সেগুলো আমরা এখান থেকেই complete করব।এরজ্যন আমাদের livewire এর questionindex এ গিয়ে নিচের কোড গুলো লিখব।
<div>
<table class="w-full table-auto">
<tr>
<th class="border px-4 py-2 text-left">SL.</th>
<th class="border px-4 py-2 text-left">Question</th>
<th class="border px-4 py-2 text-left">Answer A</th>
<th class="border px-4 py-2 text-left">Answer B</th>
<th class="border px-4 py-2 text-left">Answer C</th>
<th class="border px-4 py-2 text-left">Answer D</th>
<th class="border px-4 py-2 text-left">Correct Answer</th>
<th class="border px-4 py-2">Actions</th>
</tr>
@foreach($questions as $question)
<tr>
<td class="border px-4 py-2">{{$loop->iteration}}</td>
<td class="border px-4 py-2">{{$question->name}}</td>
<td class="border px-4 py-2">{{$question->answer_a}}</td>
<td class="border px-4 py-2">{{$question->answer_b}}</td>
<td class="border px-4 py-2">{{$question->answer_c}}</td>
<td class="border px-4 py-2">{{$question->answer_d}}</td>
<td class="border px-4 py-2">{{$question->correct_answer}}</td>
<td class="border px-4 py-2 text-center">
<div class="flex items-center justify-center">
<a class="mr-1" href="{{route('question.edit', $question->id)}}">
@include('components.icons.edit')
</a>
<form class="ml-1" onsubmit="return confirm('Are you sure?');"
wire:submit.prevent="questionDelete({{$question->id}})">
<button type="submit">
@include('components.icons.trash')
</button>
</form>
</div>
</td>
</tr>
@endforeach
</table>
<div class="mt-4">
{{$questions->links()}}
</div>
</div>
এবং আমাদের livewire component এর মাধ্যমে data পাঠাব।questionIndex থেকে।
public function render()
{
$questions = Question::paginate(15);
return view('livewire.question-index', [
'questions' => $questions
]);
}
public function questionDelete($id)
{
$question = Question::findOrFail($id);
$question->delete();
flash()->addSuccess('Question deleted successfully!');
}
এবার আমরা আমাদের qustion গুলো কে ভিউ করতে পারব।
এবার আসি questionedit এ এবং এখান থেকে আমরা dataগুলো database এ save করে update করে দেব।
public $question_id;
public $answers = ['a', 'b', 'c', 'd'];
public $name;
public $answer_a;
public $answer_b;
public $answer_c;
public $answer_d;
public $correct_answer;
public function mount()
{
$question = Question::where('id', $this->question_id)->first();
$this->name = $question->name;
$this->answer_a = $question->answer_a;
$this->answer_b = $question->answer_b;
$this->answer_c = $question->answer_c;
$this->answer_d = $question->answer_d;
$this->correct_answer = $question->correct_answer;
}
protected $rules = [
'name' => 'required',
'answer_a' => 'required',
'answer_b' => 'required',
'answer_c' => 'required',
'answer_d' => 'required',
'correct_answer' => 'required',
];
public function render()
{
return view('livewire.question-edit', [
'answers' => $this->answers
]);
}
public function questionUpdate()
{
$this->validate();
$question = Question::where('id', $this->question_id)->first();
$question->update([
'name' => $this->name,
'answer_a' => $this->answer_a,
'answer_b' => $this->answer_b,
'answer_c' => $this->answer_c,
'answer_d' => $this->answer_d,
'correct_answer' => $this->correct_answer,
]);
flash()->addSuccess('Question updated successfully!');
return redirect()->route('question.index');
}
এবং edit এর ভিউ তে গিয়ে আমরা নিচের কোড গুলো লিখব।
<form wire:submit.prevent="questionUpdate">
<div class="mb-4">
@include('components.form-field', [
'name' => 'name',
'label' => 'Name',
'type' => 'text',
'placeholder' => 'Question name',
'required' => 'required',
])
</div>
@foreach($answers as $answer)
<div class="mb-4">
@include('components.form-field', [
'name' => 'answer_' . $answer,
'label' => 'Answer '. ucfirst($answer),
'type' => 'text',
'placeholder' => 'Type answer ' . ucfirst($answer),
'required' => 'required',
])
</div>
@endforeach
<div class="mb-4">
<label class="lms-label" for="correct_answer">Correct answer</label>
<select class="lms-input" wire:model.prevent="correct_answer" id="correct_answer">
@foreach($answers as $answer)
<option value="{{ $answer }}">{{ ucfirst($answer) }}</option>
@endforeach
</select>
</div>
@include('components.wire-loading-btn')
</form>
এবং সেই সাথে আমাদের delete function টাও লিখেছি আমাদের questionindex এ সুতরাং আমাদের এটাও dynamic. তাহলে আমাদের question section complete
-question create
-question Index
-question edit
-question delete
এবার আসি আমাদের quize section এ।
এবং আমাদের যে question folder টা আছে সেটাকে আমরা কপি করে সেটার নাম দেই quize এবং আমাদের question এর data গুলো কে এডিট করে আমরা সেটা কুইজের ডেটা গুলো দিয়ে দিব।এবং এখানে যেহেতু আমাদের create file টা লাগবে না তাই আমরা সেটাকে ডিলেট করে দিব।
index, edit, show এই ফাইলগুলো তে।আমাদের কুইজের ডেটা গুলো লিখবো।
এবার আসি index file এ।আমরা আমাদের create file এ যেহেতু livewire এর টা দিয়ে ডায়নামিক করছি না তাই আমরা এখানে normally করে সেটা livewire এর index এ পাঠিয়ে দিব।
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Quiz') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<form action="{{route('quiz.store')}}" method="post"> @csrf
<div class="mb-4">
<label for="name" class="lms-label">Name</label>
<input type="text" name="name" id="name" class="lms-input">
</div>
<button type="submit" class="lms-btn">Add a quiz</button>
<livewire:quiz-index />
</form>
</div>
</div>
</div>
</div>
</x-app-layout>
এবং আমাদের quizecontroller এর মধ্যে আমাদের store function লাগবে ।তো এখানে আমাদের অন্যান্য function যেগুলো আমাদের কুইজের সাথে লাগবে সেগুলো এখানে দিয়ে দি।
public function index() {
return view('quiz.index');
}
public function create() {
return view('quiz.create');
}
public function store(Request $request) {
$quiz = $request->validate([
'name' => 'required',
]);
$created = Quiz::create($quiz);
flash()->addSuccess('Quiz created successfully');
return redirect()->route('quiz.index',$created->id);
}
public function show(Quiz $quiz) {
return view('quiz.show', [
'quiz' => $quiz,
]);
}
public function edit(Quiz $quiz)
{
return view('quiz.edit', compact('quiz'));
}
public function quizShow($id) {
$quiz = Quiz::findOrFail($id);
return view('quiz.quiz-show', [
'quiz' => $quiz,
]);
}
এবং আমাদের quize model এ গিয়ে সেখানে fillable property দিয়ে দিব। এবং সেখানে একটা রিলেশন তৈরি করব যদি এটা আমাদের পরে করলেও হত।
protected $fillable = [
'name',
];
public function questions() {
return $this->belongsToMany(Question::class, 'quiz_question', 'quiz_id', 'question_id');
}
এবার আমরা আমাদের livewire components গুলো make করতে পারি।
php artisan livewire:make QuizCreate
php artisan livewire:make QuizIndex
php artisan livewire:make QuizEdit
php artisan livewire:make QuizShow
এবার আমাদের কুইজ livewire-edit file এ আমাদের যে টাস্ক গুলো দেওয়া হয়েছে সেই অনুযায়ী করব এবং যেভাবে দেখেনো হয়েছে সেভাবে।
<div>
<form class="p-4" wire:submit.prevent="editQuiz">
<div class="mb-4">
@include('components.form-field', [
'name' => 'name',
'label' => 'Name',
'type' => 'text',
'placeholder' => 'Question name',
'required' => 'required',
])
</div>
<button type="submit" class="lms-btn">Submit </button>
</form>
@if (count($questions)>0)
<form class="p-4" wire:submit.prevent="addQuestion">
<div class="min-w-max ml-3">
<label for="question" >Add Question</label>
<select wire:model="question" id="question" class= "mb-4">
@foreach($questions as $question)
<option value="{{$question->id}}">{{$question->name}}</option>
@endforeach
</select>
@error('correct_answer')
<p class="mt-2 text-sm text-red-600 dark:text-red-500">{{$message}}</p>
@enderror
</div>
<button type="submit" class="lms-btn">Add </button>
</form>
@else
<h3 class="my-4 text-gray-600 text-lg p-4 ml-3">Add Question</h3>
<p class="text-red-500 px-4 ml-3">Not Found Any Question!</p>
@endif
<div class="p-4">
<h3 class="my-4 text-gray-600 text-lg ml-3">Question List</h3>
<div class="table w-full p-2">
<table class="w-full border">
<thead>
<tr class="bg-gray-50 border-b">
<th class="p-2 border-r cursor-pointer text-sm font-thin">
<div class="flex items-center justify-center"> Name</div>
</th>
<th class="p-2 border-r cursor-pointer text-sm font-thin">
<div class="flex items-center justify-center">Action</div>
</th>
</tr>
</thead>
<tbody>
@foreach($quiz->questions as $question)
<tr class="bg-gray-100 text-center border-b text-sm text-gray-600">
<td class="p-2 border-r text-left px-4">{{$question->name}}</td>
<td class="flex items-center justify-center">
<form wire:submit.prevent="removeQuiz({{$question->id}})" class="bg-red-500 p-2 inline-block text-white hover:shadow-lg text-xs font-thin">
<button onclick="return confirm('Are you Sure')" type="submit">Remove</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
এবং livewire এর quizedit এ আমরা নিচের কোড গুলো লিখবো,।
public $quiz;
public $name;
public $question;
public $questions;
protected $rules = [
'name' => 'required',
];
public function mount(){
$this->name = $this->quiz->name;
$alreadyAddQuestion = $this->quiz->questions->pluck('id')->toArray();
$this->questions = Question::select(['id', 'name'])->whereNotIn('id',$alreadyAddQuestion)->get();
if (count($this->questions)>0){
$this->question =$this->questions[0]->id;
}
}
public function render(){
return view('livewire.quiz-edit');
}
public function addQuestion(){
$this->validate([
'question' => 'required',
]);
$quiz = Quiz::findOrFail($this->quiz->id);
$quiz->questions()->attach($this->question);
flash()->addSuccess('Question added successfully');
return redirect()->route('quiz.edit',$this->quiz->id);
}
public function editQuiz(){
$this->validate();
$quiz = Quiz::findOrFail($this->quiz->id);
$quiz->name = $this->name;
$quiz->save();
flash()->addSuccess('Quiz edit successfully');
}
এবং এই quize গুলো show করানোর জন্য আমরা আমাদের index file create করব।
quizeIndex component
public function render() {
$quizzes = Quiz::paginate(10);
return view('livewire.quiz-index',[
'quizzes' => $quizzes]);
}
এবং ভিউ ফাইলে।
<div>
<div class="table w-full p-2">
<table class="w-full border">
<thead>
<tr class="bg-gray-50 border-b">
<th class="p-2 border-r cursor-pointer" > Name</div>
</th>
<th class="p-2 border-r cursor-pointer ">
<div class="flex items-center justify-center"> Action</div>
</th>
</tr>
</thead>
<tbody>
@foreach($quizzes as $quiz)
<tr class="bg-gray-100 text-center border-b ">
<td class="p-2 border-r text-left px-4">{{$quiz->name}}</td>
<td class="flex items-center justify-center">
<a href="{{route('quiz.edit',$quiz->id)}}" class="ml-2 pt-2">@include('components.icons.edit')</a>
<a href="{{route('quiz.show',$quiz->id)}}" class="ml-2 pt-2">@include('components.icons.eye')</a>
<form class="ml-2 pt-3" wire:submit.prevent="deleteQuiz({{$quiz->id}})" ><button onclick="return confirm('Are you sure?');" type="submit" > @include('components.icons.trash')
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="mt-4">
{{$quizzes->links()}}
</div>
</div>
আমরা কুইজ টা কে ভিউ করতে চাইলে আমাদের প্রথমে livewire component QuizShow file এ কোড গুলো লিখে ফেলি।
public $quiz;
public $answerOpitons = [
'answer_a',
'answer_b',
'answer_c',
'answer_d',
];
public $answer;
public $answer_id;
public $count_correct_answer = 0;
public $count_incorrect_answer = 0;
public $correct_answers = [];
public function render()
{
return view('livewire.quiz-show');
}
public function answerUpdate($id){
$this->answer_id = $id;
}
public function result(){
$question = Question::select('correct_answer')->findOrFail($this->answer_id);
if($question->correct_answer === $this->answer[$this->answer_id]){
flash()->addSuccess('Answer is correct');
$this->correct_answers[$this->answer_id] = true;
$this->count_correct_answer++;
}else{
flash()->addWarning('Answer is incorrect');
$this->correct_answers[$this->answer_id] = false;
$this->count_incorrect_answer++;
}
}
এবং আমাদের livewire এর blade file এ।
<div>
<h1 class="text-center text-2xl py-2">{{$quiz->name}}</h1>
@php
$i=1
@endphp
<div class="flex items-center gap-4 py-4">
<p class="flex items-center gap-2">Total <span class=" text-sm radius-full text-white flex justify-center items-center w-8 h-8">{{count($quiz->questions)}}</span></p>
<p class="flex items-center gap-2">Correct <span class=" text-sm radius-full text-white flex justify-center items-center w-8 h-8">{{$count_correct_answer}}</span></p>
<p class="flex items-center gap-2">Wrong <span class=" text-sm radius-full text-white flex justify-center items-center w-8 h-8">{{$count_incorrect_answer}}</span></p>
</div>
@foreach($quiz->questions as $question)
<div class="border border-gray-300 mb-4 p-4 @if(array_key_exists($question->id,$correct_answers)) {{$correct_answers[$question->id] ? 'bg-green-100': 'bg-red-100'}} @endif}}">
<h3 class="text-gray-600"> {{$i++}}.{{$question->name}}</h3>
<div class="flex gap-4">
@forEach($answerOpitons as $option)
<div class="flex items-center pl-4 rounded">
<input wire:click="answerUpdate({{$question->id}})" @if(array_key_exists($question->id,$correct_answers)) disabled @endif wire:change="result" wire:model="answer.{{$question->id}}" id="answer-{{$option}}-{{$question->id}}" type="radio" value="{{explode('_',$option)[1]}}" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500">
<label for="answer-{{$option}}-{{$question->id}}" class="w-full py-4 cursor-pointer ml-2 text-sm font-medium text-gray-900">{{$question->$option}}</label>
</div>
@endforeach
</div>
</div>
@endforeach
</div>
আপাতত আমাদের quize section রেডি এর কাজ গুলো আমরা বাকি টিউটোরিয়ালে complete করব।
discord link: discord.gg/9qWUUbQ3
facebook group :web.facebook.com/groups/1661735757554627
Happy Learning
Rakibul Islam