Back to Course |
Creating a Quiz System with Laravel 10 + Livewire 3: Step-by-Step

Admin: Answer Options for Questions

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:

edit question

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?

  • First, the $questionOptions public property, where all options will be added.
  • In the mount() where we check if Question exists, we add code to add all options into the $questionOptions public array property.
  • Next, two methods 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.
  • In the save() method we added the creation of the options for the questions.
  • And last, additional rules for question options.

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.

create question with question options

Now you can create questions with question options.

edit question