Store order (in session?) in custom Repeater

Hi, I've tried pretty much everything I can think of (Actions, “:wire:end.stop=”'mountFormComponentAction('' . $statePath . '', 'reorder', { items: $event.target.sortable.toArray() })'”, ...), but I can't find a solution. I am building a TabRepeater that displays the repeater content in tabs. I am now using x-sortable to be able to reorder the tabs. Unfortunately, the sort order is reset as soon as I add an entry. Can anyone give me a nudge in the right direction?
Solution:
Little update: The solution above was not the one I was looking for. Instead, I needed '$wire.mountFormComponentAction()'. ```HTML...
Jump to solution
3 Replies
Jomatom
JomatomOP2mo ago
<?php
// TabRepeater.php
namespace App\Filament\Forms\Components;
use Filament\Forms\Components\Repeater;

class TabRepeater extends Repeater
{
protected string $view = 'filament.components.tab-repeater';
protected string $singularTitle = "item";
protected string $pluralTitle = "items";

protected function setUp(): void
{
parent::setUp();
}

public function getSingularTitle()
{
return $this->singularTitle;
}

public function getPluralTitle()
{
return $this->pluralTitle;
}

public function singularTitle($title)
{
$this->singularTitle = $title;

return $this;
}

public function pluralTitle($title)
{
$this->pluralTitle = $title;

return $this;
}

// also tried to call this method from the blade view, but can't get it to work
public function updateItems($oldIndex, $newIndex)
{
$items = $this->getState();
[$items[$oldIndex], $items[$newIndex]] = [$items[$newIndex], $items[$oldIndex]];
$this->state($items);
$this->callAfterStateUpdated();
}
}
<?php
// TabRepeater.php
namespace App\Filament\Forms\Components;
use Filament\Forms\Components\Repeater;

class TabRepeater extends Repeater
{
protected string $view = 'filament.components.tab-repeater';
protected string $singularTitle = "item";
protected string $pluralTitle = "items";

protected function setUp(): void
{
parent::setUp();
}

public function getSingularTitle()
{
return $this->singularTitle;
}

public function getPluralTitle()
{
return $this->pluralTitle;
}

public function singularTitle($title)
{
$this->singularTitle = $title;

return $this;
}

public function pluralTitle($title)
{
$this->pluralTitle = $title;

return $this;
}

// also tried to call this method from the blade view, but can't get it to work
public function updateItems($oldIndex, $newIndex)
{
$items = $this->getState();
[$items[$oldIndex], $items[$newIndex]] = [$items[$newIndex], $items[$oldIndex]];
$this->state($items);
$this->callAfterStateUpdated();
}
}
// tab-repeater.blade.php
<x-filament::tabs x-sortable x-on:update="console.log($event.target.sortable.toArray())"
x-on:update="handleSort" @class(['flex-wrap gap-y-4 ml-0'])
>
@php
$items = getNestedValue($this->data, str_replace('data.', '', $statePath)) ?? [];
@endphp
@foreach ($items as $key => $item)
@php
$tabId = 'tab-' . $loop->index;
$panelId = 'panel-' . $loop->index;
@endphp
<x-filament::tabs.item :wire:key="$tabId" :tab="$tabId" x-sortable-item="{{ $tabId }}"
x-on:click="activeTab = '{{ $tabId }}'" alpine-active="activeTab === '{{ $tabId }}'"
x-bind:class="{ 'active': activeTab === '{{ $tabId }}' }">
<span x-sortable-handle x-on:click.stop>:white_circle:</span>
{{ $item['name'] ?? $getSingularTitle() . ' ' . ($loop->index + 1) }}
</x-filament::tabs.item>
@endforeach

@if ($isAddable && $addAction->isVisible())
<div @class([
'flex',
match ($getAddActionAlignment()) {
Alignment::Start, Alignment::Left => 'justify-start',
Alignment::Center, null => 'justify-center',
Alignment::End, Alignment::Right => 'justify-end',
default => $alignment,
},
])>
{{ $addAction }}
</div>
@endif
</x-filament::tabs>

[...]
// tab-repeater.blade.php
<x-filament::tabs x-sortable x-on:update="console.log($event.target.sortable.toArray())"
x-on:update="handleSort" @class(['flex-wrap gap-y-4 ml-0'])
>
@php
$items = getNestedValue($this->data, str_replace('data.', '', $statePath)) ?? [];
@endphp
@foreach ($items as $key => $item)
@php
$tabId = 'tab-' . $loop->index;
$panelId = 'panel-' . $loop->index;
@endphp
<x-filament::tabs.item :wire:key="$tabId" :tab="$tabId" x-sortable-item="{{ $tabId }}"
x-on:click="activeTab = '{{ $tabId }}'" alpine-active="activeTab === '{{ $tabId }}'"
x-bind:class="{ 'active': activeTab === '{{ $tabId }}' }">
<span x-sortable-handle x-on:click.stop>:white_circle:</span>
{{ $item['name'] ?? $getSingularTitle() . ' ' . ($loop->index + 1) }}
</x-filament::tabs.item>
@endforeach

@if ($isAddable && $addAction->isVisible())
<div @class([
'flex',
match ($getAddActionAlignment()) {
Alignment::Start, Alignment::Left => 'justify-start',
Alignment::Center, null => 'justify-center',
Alignment::End, Alignment::Right => 'justify-end',
default => $alignment,
},
])>
{{ $addAction }}
</div>
@endif
</x-filament::tabs>

[...]
Ok, I guess, I'm on the right track with 'registerListeners'.
class TabRepeater extends Repeater
{

protected string $view = 'filament.components.tab-repeater';
protected string $singularTitle = "item";
protected string $pluralTitle = "items";

protected function setUp(): void
{
parent::setUp();

$this->registerListeners([
'tabrepeater::order-updated' => [
function ($component, $statePath, $oldIndex, $newIndex) {
$items = $component-
// ...
class TabRepeater extends Repeater
{

protected string $view = 'filament.components.tab-repeater';
protected string $singularTitle = "item";
protected string $pluralTitle = "items";

protected function setUp(): void
{
parent::setUp();

$this->registerListeners([
'tabrepeater::order-updated' => [
function ($component, $statePath, $oldIndex, $newIndex) {
$items = $component-
// ...
in the Blade-View, I can call it like:
x-data="{ activeTab: 'tab-0', handleSort: ({ oldIndex, newIndex }) => { $wire.dispatchFormEvent('tabrepeater::order-updated', '{{ $getStatePath() }}', oldIndex, newIndex) } }"
x-data="{ activeTab: 'tab-0', handleSort: ({ oldIndex, newIndex }) => { $wire.dispatchFormEvent('tabrepeater::order-updated', '{{ $getStatePath() }}', oldIndex, newIndex) } }"
Solution
Jomatom
Jomatom2mo ago
Little update: The solution above was not the one I was looking for. Instead, I needed '$wire.mountFormComponentAction()'.
<div x-data="{
activeTab: 'tab-0',
handleSort: (event) => {
const { oldIndex, newIndex } = event;
$wire.mountFormComponentAction('{{ $getStatePath() }}', 'handleOrder', { oldIndex, newIndex });
}
}"
<div x-data="{
activeTab: 'tab-0',
handleSort: (event) => {
const { oldIndex, newIndex } = event;
$wire.mountFormComponentAction('{{ $getStatePath() }}', 'handleOrder', { oldIndex, newIndex });
}
}"
public function orderAction(?Closure $callback): static
{
$this->modifyReorderActionUsing = $callback;

return $this;
}

public function getOrderActionName(): string
{
return 'handleOrder';
}

public function getOrderAction(): Action
{
$action = Action::make($this->getOrderActionName())
-> ...
->action(function (array $arguments, Repeater $component): void {
$items = $component->getState();

['oldIndex' => $oldIndex, 'newIndex' => $newIndex] = $arguments;

$keys = array_keys($items);
$values = array_values($items);

[$values[$oldIndex], $values[$newIndex]] = [$values[$newIndex], $values[$oldIndex]];
$newItems = array_combine($keys, $values);

$component->state($newItems);
$component->callAfterStateUpdated();
}
// ...
}
public function orderAction(?Closure $callback): static
{
$this->modifyReorderActionUsing = $callback;

return $this;
}

public function getOrderActionName(): string
{
return 'handleOrder';
}

public function getOrderAction(): Action
{
$action = Action::make($this->getOrderActionName())
-> ...
->action(function (array $arguments, Repeater $component): void {
$items = $component->getState();

['oldIndex' => $oldIndex, 'newIndex' => $newIndex] = $arguments;

$keys = array_keys($items);
$values = array_values($items);

[$values[$oldIndex], $values[$newIndex]] = [$values[$newIndex], $values[$oldIndex]];
$newItems = array_combine($keys, $values);

$component->state($newItems);
$component->callAfterStateUpdated();
}
// ...
}
Grégoire
Grégoire2mo ago
Thanks for sharing your solution, I was looking for something similar and wanted to say that posting solutions is helpful!
Want results from more Discord servers?
Add your server