In this part, we will create a Blade component for Select2 and will use it for selecting questions for a quiz.
So, first let's create the component and add scripts.
php artisan make:component SelectList
With this component, we also create a class that will accept the $options
parameter.
app/View/Components/SelectList.php:
class SelectList extends Component{ /** * Create a new component instance. */ public function __construct(public mixed $options, public mixed $selectedOptions) { // } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { return view('components.select-list'); }}
And the blade component:
resources/views/components/select-list.blade.php:
<div> <div wire:ignore class="w-full"> <select class="select2 w-full" data-placeholder="Select your option" {{ $attributes }}> @if(!isset($attributes['multiple'])) <option></option> @endif @foreach($options as $key => $value) <option value="{{ $key }}" @selected(in_array($key, $selectedOptions))>{{ $value }}</option> @endforeach </select> </div></div> @push('scripts') <script> document.addEventListener('livewire:init', () => { let el = $('#{{ $attributes['id'] }}') function initSelect() { el.select2({ placeholder: 'Select your option', allowClear: !el.attr('required') }) } initSelect() Livewire.hook('message.processed', (message, component) => { initSelect() }); el.on('change', function (e) { let data = $(this).select2("val") if (data === "") { data = null } @this.set('{{ $attributes['wire:model'] }}', data) }); }); </script>@endpush
But before using this component we need to set up Select2 using CDN. Also, in the blade component we are using the @push
blade directive to render JS code in the layout file. For this, to work we need to add @stack('scripts')
into the main layout file.
resources/views/layouts/app.blade.php:
// ... <!-- Scripts --> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" /> @vite(['resources/css/app.css', 'resources/js/app.js']) @livewireStyles </head> // ... <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> @livewireScripts @stack('scripts') </body></html>
Now we need to load all the questions in the QuizForm
Livewire component.
app/Livewire/Quiz/QuizForm.php:
use App\Models\Question; class QuizForm extends Component{ public ?Quiz $quiz = null; public string $title = ''; public string $slug = ''; public string|null $description = ''; public bool $published = false; public bool $public = false; public bool $editing = false; public array $listsForFields = []; public function mount(Quiz $quiz): void { $this->initListsForFields(); if ($quiz->exists) { $this->quiz = $quiz; $this->editing = true; $this->title = $quiz->title; $this->slug = $quiz->slug; $this->description = $quiz->description; $this->published = $quiz->published; $this->public = $quiz->public; } else { $this->published = false; $this->public = false; } } // ... protected function initListsForFields(): void { $this->listsForFields['questions'] = Question::pluck('question_text', 'id')->toArray(); } }
In the form, we will add this field under the description.
resources/views/livewire/quiz/quiz-form.blade.php:
// ...<div class="mt-4"> <x-input-label for="questions" value="Questions" /> <x-select-list class="w-full" id="questions" name="questions" :options="$this->listsForFields['questions']" :selectedOptions="$questions" wire:model="questions" multiple /> <x-input-error :messages="$errors->get('questions')" class="mt-2" /></div>// ...
Now in the create form after selecting questions input you should see the all questions list.
As you can see we bind this question's input into the questions
property. So let's add that to the QuizForm
component.
app/Livewire/Quiz/QuizForm.php:
class QuizForm extends Component{ public ?Quiz $quiz = null; public string $title = ''; public string $slug = ''; public string|null $description = ''; public bool $published = false; public bool $public = false; public bool $editing = false; public array $questions = []; public array $listsForFields; // ...}
Next, before saving we need to create a Many to Many relationship for the Quiz model with the Question.
php artisan make:migration "create question quiz table"
database/migrations/xxxx_create_question_quiz_table.php:
return new class extends Migration{ public function up(): void { Schema::create('question_quiz', function (Blueprint $table) { $table->foreignId('question_id')->constrained()->cascadeOnDelete(); $table->foreignId('quiz_id')->constrained()->cascadeOnDelete(); }); }};
app/Models/Quiz.php:
class Quiz extends Model{ // ... public function questions(): BelongsToMany { return $this->belongsToMany(Question::class); }}
app/Models/Question.php:
class Question extends Model{ // ... public function quizzes(): BelongsToMany { return $this->belongsToMany(Quiz::class); }}
Now we can sync Quiz with the Questions.
app/Livewire/Quiz/QuizForm.php:
class QuizForm extends Component{ // ... public function save(): Redirector|RedirectResponse { $this->validate(); if (empty($this->quiz)) { $this->quiz = Quiz::create($this->only(['title', 'slug', 'description', 'published', 'public'])); } else { $this->quiz->update($this->only(['title', 'slug', 'description', 'published', 'public'])); } $this->quiz->questions()->sync($this->questions); return to_route('quizzes'); } // ...}
The only thing we have left to do in the form is to load questions when editing the quiz.
app/Livewire/Quiz/QuizForm.php:
class QuizForm extends Component{ // ... public function mount(Quiz $quiz): void { $this->initListsForFields(); if ($quiz->exists) { $this->quiz = $quiz; $this->editing = true; $this->title = $quiz->title; $this->slug = $quiz->slug; $this->description = $quiz->description; $this->published = $quiz->published; $this->public = $quiz->public; $this->questions = $quiz->questions()->pluck('id')->toArray(); } else { $this->published = false; $this->public = false; } } // ...}
If you want to have a style for the select2 as it is in the screen above, add below CSS code to the resources/css/app.css
and compile assets.
/* Select 2 */.select2 { @apply w-full border-0 placeholder-gray-300 text-gray-600 bg-white rounded text-sm shadow focus:outline-none focus:ring !important;} .select2-dropdown { @apply absolute block w-auto box-border bg-white shadow-lg border-blue-200 z-50 float-left;} .select2-container--default .select2-selection--single { @apply border-0 h-11 flex items-center text-sm} .select2-container--default .select2-selection--multiple { @apply border-0 text-sm mr-1} .select2-container--default.select2-container--focus .select2-selection--single,.select2-container--default.select2-container--focus .select2-selection--multiple { @apply border-0 outline-none ring ring-indigo-500} .select2-container--default .select2-selection--single .select2-selection__arrow { top: 9px;} .select2-container .select2-selection--single .select2-selection__rendered { @apply px-3 py-3 text-blue-600} .select2-container .select2-selection--multiple .select2-selection__rendered { @apply px-3 py-2 text-blue-600} .select2-container--default .select2-selection--single .select2-selection__rendered { line-height: inherit;} .select2-selection__choice { @apply text-xs font-semibold inline-block py-1 px-2 rounded bg-gray-800 border-0 !important;} .select2-selection__choice span { @apply text-white !important;} .select2-search__field:focus { outline: none;} .select2-container--default .select2-selection--single .select2-selection__clear { @apply text-rose-500 ml-1 !important} .select2-container--default .select2-selection--multiple .select2-selection__choice__remove { @apply text-gray-300 mr-1 static !important} .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover { @apply bg-gray-600 !important} .select-all, .deselect-all { @apply cursor-pointer; font-size: 0.5rem !important;}
One last thing in this lesson. Let's show the count of questions in the quizzes list. I will add it after the description.
resources/views/livewire/quiz/quiz-list.blade.php:
// ...<th class="bg-gray-50 px-6 py-3 text-left"> <span class="text-xs font-medium uppercase leading-4 tracking-wider text-gray-500">Description</span></th><th class="bg-gray-50 px-6 py-3 text-left"> {{-- [tl! add:start --}} <span class="text-xs font-medium uppercase leading-4 tracking-wider text-gray-500">Questions count</span></th> {{-- [tl! add:end --}} // ... <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> {{ $quiz->description }}</td><td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> {{-- [tl! add:start --}} {{ $quiz->questions_count }}</td> {{-- [tl! add:end --}}// ...
Don't forget to eager load so that you won't get an N+1 problem!
app/Livewire/Quiz/QuizList.php:
class QuizList extends Component{ public function render(): View { $quizzes = Quiz::latest()->paginate(); $quizzes = Quiz::withCount('questions')->latest()->paginate(); return view('livewire.quiz.quiz-list', compact('quizzes')); } // ...}
Hello I am having an erro "trim(): Argument #1 ($string) must be of type string, array given"
select-list-blade file
quiz-form.blade file
what can i do to fix this error?
select-list-blade file - https://prnt.sc/oSt058CovRWc
quiz-form.blade file - https://prnt.sc/GcAHvqV9pN19
@PedroSMaia it's a very wierd error that only some people get. How does your
app/View/Components/SelectList.php
look? But adding props options to the component should fix error.@Nerijus my app/View/Components/SelectList.php print is on the link bellow https://prnt.sc/t9u0HSGJP8-4
Sorry wrong file, I meant
app/Http/View/Components/SelectList.php
. But have you tried adding props for options to the blade file?Hi again, my file app/Http/View/Components/SelectList.php is in the printscreen bellow https://prnt.sc/FGPNaXeOQyJt
i don't understand what do you say about props
Your
SelectList.php
is different from the in this tutorial.@PedroSMaia, you are missing parameters inside construct method of Selectlist.php
i have the same error as @PedroSMaia, i added this inside my SelectList.php:
public function __construct(public mixed $options)
and now a different error occurs:
"Unresolvable dependency resolving Parameter #0 mixed $options in class App\View\Components\SelectList"
help anyone?
i just solved my problem. i ran the following command:
php artisan optimize:clear
seems like its a caching issue
If you are working with Livewire 3 must replace the event
livewire:load
bylivewire:init