Reputation: 57
I'm currently developping a project with Livewire and AlpineJS.
I have to use a div with contenteditable set to "true" to access and modify my data model.
What I want
I would like to share the state between Livewire And Alpine using @entangle.
My code
My code is in two parts. The first-part rely on a full-Page component where I simply register an array of $fruits. The second, is a component containing a "form" allowing me to access and modify the data through @entangle and a div with contenteditable set to true.
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class ListFruit extends Component
{
protected $listeners = ['addFruit'];
public array $fruits = [];
public function mount()
{
$this->addFruit();
}
public function addFruit()
{
$this->fruits[] = $this->makeBlankFruit();
}
public function makeBlankFruit(): array
{
return [
'type' => '',
'color' => ''
];
}
public function render()
{
return view('livewire.list-fruit');
}
}
The views/livewire/list-fruit.blade.php file
<div class="w-full container mx-auto">
<h3 class="text-2xl font-sembibold">
Fruits
</h3>
<div id="fruits">
@foreach($fruits as $fruitIndex=> $fruit)
<livewire:fruit :fruitIndex="$fruitIndex" :fruit="fruit" :wire:key="$fruitIndex" >
@endforeach
</div>
</div>
Fruit Component
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class Fruit extends Component
{
public array $fruit = [];
public string $fruitIndex;
public function render()
{
return view('livewire.fruit');
}
}
views/livewire/fruit.blade.php file
<div x-data="{type: @entangle('fruit.type'), color: @entangle('fruit.color')}"
x-init="console.log(type)"
>
<div>
<div x-on:blur="type = $event.target.innerHTML" contenteditable="true">{{ $fruit['type'] }}</div>
<div x-on:blur="color = $event.target.innerHTML" contenteditable="true">{{ $fruit['color'] }}</div>
</div>
<div>
$type @ Livewire: {{ $fruit['type'] }}
$color @ Livewire: {{ $fruit['color'] }}
</div>
<div>
type @ AlpineJS: <span x-text="type"></span>
color @ AlpineJS: <span x-text="color"></span>
</div>
</div>
If I had only one array with some properties in my main component, let's say :
public array $fruit = [
'type' => '',
'color' => '',
];
I would be able to access them with my fruit.blade.php
I also tried to do something like this.
{type: @entangle('fruits.'. $fruitIndex.'.color'), color: @entangle('fruits.'. $fruitIndex.'.type')}
What I obtained
In my AlpineJs component I read an [object Object] proxy and when modifying my fruit.color or fruit.type property the livewire part isn't updated.
I don't know why. In my final attempt, I tried to separate my fruits into multiple sub-components allowing me to work on a single array.
I'm a looking at a dead end, so thank you in advance for your help. Tanuki
Upvotes: 0
Views: 3321
Reputation: 3467
I think you should simplify your example. Although your real-world code is likely to be more complex than your (no doubt contrived) fruits example, it seems to me unnecessary to nest Livewire components here. It's far simpler to keep one PHP/Livewire class and one Blade file. Then instead of using Blade to loop through your data with @foreach
, use Alpine's x-for
. Contrary to current trends, I don't always believe that it's desirable to keep file-sizes small, if the cost is to nest layer upon layer of (increasingly difficult to debug) blade-templates (or Livewire components).
Anyway, that aside, let's go back to the principle that you're trying to establish here, which is how one entangles a PHP/Livewire array with a Javascript/Alpine variable.
The answer is surprisingly simple: you can entangle an arbitrarily complicated PHP data-structure (aka array) to a single JavaScript variable. As you would expect if you had built the framework yourself, the PHP array will simply be mapped to a JSON object.
First, set up your complicated back-end data-structure:
<?php
namespace App\Business\Tbd;
use Livewire\Component;
class StartLw extends Component
{
public array $fruits = [];
public function mount() {
$fruit_template = ['apple', 'banana', 'garlic'];
$fruit_colours = ['green', 'yellow', 'pink'];
$health_benefit = [
'keeps doctor away',
'the bend keeps you flexible',
'None. It\'s not really a fruit, though there is that vampire thing'
];
foreach ($fruit_template as $id => $fruit) {
$this->fruits[$fruit] = [
'colour' => $fruit_colours[$id],
'benefit' => $health_benefit[$id],
'bought_by' => array_slice(['Jim', 'John', 'Sue'], 0, $id+1),
];
}
}
public function render()
{
return view('my.livewire.template');
}
}
Then the following Blade template will tell you all you need to know about the contents of the JavaScript/JSON object (which was originally created in PHP).
<div class="container-fluid">
<div class="row" x-data="{ fruits: @entangle('fruits') }">
<div class="col-11" id="main">
<p>Type of fruit: <span x-text="typeof fruits"></span></p>
<template x-for="(fruit_info, fruit) in fruits" :key="fruit">
<code>
Fruit type: <span x-text="fruit"></span><br />
Colour: <span x-text="fruit_info.colour"></span><br />
Health Superstitions: <span x-text="fruit_info.benefit"></span><br />
Bought by:
<template x-for="(pers, id) in fruit_info.bought_by" :key="id">
<span>
<span x-text="id"></span> => <span x-text="pers"></span>,
</span>
</template>
<br />
<br />
</code>
</template>
</div>
</div>
</div>
As you see, if you build your Blade templates with enough flexibility, they can become agnostic to the underlying data-structures, which (in a modular application) means that they can be used over and over again.
Upvotes: 2