In this lesson, we will see how to create multiple select input. We will change a category select dropdown from a single select to a multiple one.
Before making changes to the Livewire component, we need to make changes to the DB. First, we need a BelongsToMany
relationship table.
database/migrations/xxxx_create_category_product_table.php:
Schema::create('category_product', function (Blueprint $table) { $table->foreignId('category_id')->constrained()->cascadeOnDelete(; $table->foreignId('product_id')->constrained()->cascadeOnDelete();});
app/Models/Product.php:
use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Product extends Model{ protected $fillable = [ 'name', 'description', 'category_id', 'color', 'in_stock', ]; const COLOR_LIST = [ 'red' => 'Red', 'green' => 'Green', 'blue' => 'Blue', ]; public function category(): BelongsTo { return $this->belongsTo(Category::class); } public function categories(): BelongsToMany { return $this->belongsToMany(Category::class); } }
Earlier, we added
category_id
to theProducts
table. You need to delete that migration. Otherwise, when creating a product, you will get an error. Don't forget to change theProductFactory
accordingly.
Also, I have changed the seeder to add two categories for each product.
$categories = collect(Category::pluck('id'));Product::factory(50) ->create() ->each(function (Product $product) use ($categories) { $product->categories()->sync($categories->random(2)); });
Now let's change the Products
Livewire component to show multiple companies and fix the search.
app/Livewire/Products.php:
use Illuminate\Database\Eloquent\Builder; class Products extends Component{ // ... public function render(): View { $products = Product::with('category') $products = Product::with('categories') ->when($this->searchQuery !== '', fn(Builder $query) => $query->where('name', 'like', '%'. $this->searchQuery .'%')) ->when($this->searchCategory > 0, fn(Builder $query) => $query->where('category_id', $this->searchCategory)) ->when($this->searchCategory > 0, function (Builder $query) { $query->whereHas('categories', function (Builder $query2) { $query2->where('id', $this->searchCategory); }); }) ->paginate(10); return view('livewire.products', [ 'products' => $products, ]); }}
resources/views/livewire/products.blade.php:
<div class="space-y-6"> // ... <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> @foreach($product->categories as $category) <div>{{ $category->name }}</div> @endforeach {{ $product->category->name }} </td> // ...</div>
First, add multiselect
to the select input and change wire:model
bind from form.category_id
to form.productCategories
. This new productCategories
public property has to be an array that will be passed into a sync
method.
resources/views/livewire/products-create.blade.php:
<form method="POST" wire:submit="save"> // ... <div class="mt-4"> <label for="category">Category</label> <select wire:model="form.category_id" name="category" id="category" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm"> <select multiple wire:model="form.productCategories" name="category" id="category" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm"> <option value="0">-- CHOOSE CATEGORY --</option> @foreach($categories as $id => $category) <option value="{{ $id }}">{{ $category }}</option> @endforeach </select> @error('form.category_id') @error('form.productCategories') <span class="mt-2 text-sm text-red-600">{{ $message }}</span> @enderror </div> // ...</form>
app/Livewire/Forms/ProductsForm.php:
class ProductsForm extends Form{ public ?Product $product; #[Validate('required|min:3')] public string $name = ''; #[Validate('required|min:3')] public string $description = ''; #[Validate('required|exists:categories,id', as: 'category')] public int $category_id; #[Validate('required|string')] public string $color = ''; #[Validate('boolean')] public bool $in_stock = true; #[Validate('required|array', as: 'category')] public array $productCategories = []; // ...}
After selecting categories, they will be put into the productCategories
array.
All that is left to do is sync categories with the product when it is created or updated, and when setting the product in the setProduct
method, add all product categories to the productCategories
property.
app/Livewire/Forms/ProductsForm.php:
class ProductsForm extends Form{ // ... public function setProduct(Product $product): void { $this->product = $product; $this->name = $product->name; $this->description = $product->description; $this->category_id = $product->category_id; $this->color = $product->color; $this->in_stock = $product->in_stock; $this->productCategories = $product->categories()->pluck('id')->toArray(); } public function save(): void { $this->validate(); $product = Product::create($this->all()); $product->categories()->sync($this->productCategories); } public function update(): void { $this->validate(); $this->product->update($this->all()); $this->product->categories()->sync($this->productCategories); }}
And that's it. Now when you select multiple categories, they will be attached to the product.