In this tutorial, we will add the ability to add the possible answer options to the question. This is the first time in this course where Livewire will actually shine:
Let's start by creating a Model with Migration and adding relationships.
php artisan make:model QuestionOption -m
database/migrations/xxxx_create_question_options_table.php:
return new class extends Migration{ public function up(): void { Schema::create('question_options', function (Blueprint $table) { $table->id(); $table->string('option'); $table->boolean('correct')->default(0)->nullable(); $table->foreignId('question_id')->nullable()->constrained(); $table->timestamps(); $table->softDeletes(); }); }};
app/Models/QuestionOption.php:
class QuestionOption extends Model{ use HasFactory; use SoftDeletes; protected $fillable = [ 'option', 'correct', 'question_id', ]; protected $casts = [ 'correct' => 'boolean', ]; public function question(): BelongsTo { return $this->belongsTo(Question::class); }}
To the QuestionOption
Model we also add a Cast to the correct
field, so that it would always return a boolean.
class Question extends Model{ use SoftDeletes; protected $fillable = [ 'question_text', 'code_snippet', 'answer_explanation', 'more_info_link', ]; public function questionOptions(): HasMany { return $this->hasMany(QuestionOption::class)->inRandomOrder(); } }
Next, we need to extend the Livewire component for the questions form that we created in an earlier lesson.
app/Livewire/Questions/QuestionForm.php:
class QuestionForm extends Component{ public Question $question; public bool $editing = false; public array $questionOptions = []; public function mount(Question $question): void { $this->question = $question; if ($this->question->exists) { $this->editing = true; foreach ($question->questionOptions as $option) { $this->questionOptions[] = [ 'id' => $option->id, 'option' => $option->option, 'correct' => $option->correct, ]; } } } public function addQuestionsOption(): void { $this->questionOptions[] = [ 'option' => '', 'correct' => false ]; } public function removeQuestionsOption(int $index): void { unset($this->questionOptions[$index]); $this->questionOptions = array_values(($this->questionOptions)); } public function save(): Redirector { $this->validate(); if (empty($this->question)) { $this->question = Question::create($this->only(['question_text', 'code_snippet', 'answer_explanation', 'more_info_link'])); } else { $this->question->update($this->only(['question_text', 'code_snippet', 'answer_explanation', 'more_info_link'])); } $this->question->questionOptions()->delete(); foreach ($this->questionOptions as $option) { $this->question->questionOptions()->create($option); } return to_route('questions'); } public function render(): View { return view('livewire.questions.form'); } protected function rules(): array { return [ 'question_text' => [ 'string', 'required', ], 'code_snippet' => [ 'string', 'nullable', ], 'answer_explanation' => [ 'string', 'nullable', ], 'more_info_link' => [ 'url', 'nullable', ], 'questionOptions' => [ 'required', 'array', ], 'questionOptions.*.option' => [ 'required', 'string', ], ]; }}
Now, what have we added here?
$questionOptions
public property, where all options will be added.mount()
where we check if Question
exists, we add code to add all options into the $questionOptions
public array property.addQuestionsOption()
and removeQuestionsOption()
. Both the name says what they do. First, adds a new question option to the array, and second, removes and indexes an array.save()
method we added the creation of the options for the questions.Now, let's show question options in the form. We will add it right after the question text.
resources/views/livewire/questions/form.blade.php:
// ...<div class="mt-4"> <x-input-label for="question_options" value="Question options"/> @foreach($questionOptions as $index => $questionOption) <div class="flex mt-2"> <x-text-input type="text" wire:model="questionOptions.{{ $index }}.option" class="w-full" name="questions_options_{{ $index }}" id="questions_options_{{ $index }}" autocomplete="off"/> <div class="flex items-center"> <input type="checkbox" class="mr-1 ml-4" wire:model.defer="questionOptions.{{ $index }}.correct"> Correct <button wire:click="removeQuestionsOption({{ $index }})" type="button" class="ml-4 rounded-md border border-transparent bg-red-200 px-4 py-2 text-xs uppercase text-red-500 hover:bg-red-300 hover:text-red-700"> Delete </button> </div> </div> <x-input-error :messages="$errors->get('questionOptions.' . $index . '.option')" class="mt-2" /> @endforeach <x-input-error :messages="$errors->get('questionOptions')" class="mt-2" /> <x-primary-button wire:click="addQuestionsOption" type="button" class="mt-2"> Add </x-primary-button></div> // ...
After visiting create the form you should see a button ADD
, and after pressing it you should see appearing input for adding an option to a question.
Now you can create questions with question options.