What seems to be the problem:
I’ve got two problems arising from trying to create an edit form for a data model with a relationship.
I’ve got two Models called Modules
and Products
and I’m using a pivot table called product_module
to relate them. Then I’m passing a collection of Products to my livewire blade component to produce a table of editable fields with each product in a separate row.
What I’m trying to achieve is a create/edit form for each Product which has an html select list containing all Modules (there’s only 10 of them) as well as being pre-populated with the currently referenced modules in the case of an edit.
The first problem is that when my blade template outputs the I can see that the ‘selected’ property is being set on the correct items but something about AlpineJS (I think) is then cancelling them out so nothing looks selected. I’ve found the same issue here (https://github.com/livewire/livewire/issues/85) but none of the workarounds makes any difference on my code.
The second problem is that I need to populate the Modules select list to contain ALL modules as I’m going to want to assigne modules to my Products which aren’t already assigned; so I’ve passed in a separate Modules model which contains just that. In my livewire blade template I’m comparing the current module id
from my foreach loop, with the Products.models and seeing selected if it’s true. For the initial page render this works just fine except for the first issue as above. BUT as soon as I click on a modle to select it, the callback triggers the following exception:
Call to a member function contains() on array (View: /home/vagrant/code/tts_laravel/resources/views/livewire/products/manage.blade.php)
It seems that since the initial render, my Products Model object has changed from being an instance of an Eloquent Collection into a regular PHP Array(). Is this a bug or a feature ?
Steps to Reproduce:
Create two simple models and relate them through a pivot table
Create a Livewire component to manage editing of Model A and use eager-loading to obtain the Model B related content e.g. ModelA::with('ModelB')->all()
Add a public property to Model A’s component called ModelB that gets populated with a collection of all ModelB instances.
Use a blade template to write each instance of Model A into a table row, with input boxes for its properties. Include a cell containing an html select element and use foreach to add all ModelBs as options.
Check if the current Model B id
is contained in the Model B relation of the current instance of Model As - If yes then add the selected
property.
Are you using the latest version of Livewire:
Yes - I’m working on Laravel 8 with the latest Jetstream and livewire pulled down using composer.
Do you have any screenshots or code examples:
app/Http/Livewire/Products/Manage.php
namespace App\Http\Livewire\Products;
use Livewire\Component;
use App\Models\Products;
use App\Models\Modules;
class Manage extends Component
{
public $products;
public $modules;
protected $listeners = ['saved'];
# public $modules;
protected $rules = [
'products.*.title' => 'required|string|min:6',
'products.*.price' => 'required', #|regex:/^\d+(\.\d{1,2})?$/',
'products.*.modules' => 'required', #|regex:/^\d+(\.\d{1,2})?$/',
];
public function render()
{
# $list = Products::find(14); #::with('modules')->get(); #find($id); #::with('modules');
return view('livewire.products.manage')
->layout('layouts.app', [
'header' => "Product List",
'products' => $this->products,
'modules' => $this->modules
]);
}
public function mount() {
$this->products = Products::with('modules')->get(); #::with('modules');
$this->modules = Modules::all();
}
public function add() {
$prod = Products::create([
'type' => 'MODULE',
'title' => '',
'price'=> 0.00,
'created_at' => now(),
'updated_at' => now(),
]);
$this->products[] = $prod;
}
public function saved() {
$this->render();
}
public function save()
{
$this->validate();
foreach ($this->products as $product) {
$product->save();
}
$this->emit('saved');
}
resources/views/livewire/products/manage.blade.php
<h2>MANAGE</h2>
<!--form wire:submit.prevent="save"-->
<form wire:submit.prevent="save">
@foreach ($products as $index => $product)
<div wire:key="products-field-{{ $product->id }}">
<label for="title">Title of product {{ $product->id }}</label>
<input type="text" wire:model="products.{{ $index }}.title">
<input type="number" step="0.01" wire:model="products.{{ $index }}.price">
<select multiple wire:model="products.{{ $index }}.modules">
@foreach ($modules as $module)
<option
@if ($product->modules->contains($module->id)) selected @endif
value="{{$module->id}}">{{$module->title}}</option>
@endforeach
</select>
</div>
@endforeach
<button type="submit" wire:click.prevent="add">Add</button>
<button type="submit" wire:click.prevent="save">Save</button>
</form>
</div>