live() deletes my cropper instance, custom field

I made a form field that incorporates Cropper.js to select part of an image and give back the coordinates to four other input form fields. This works perfectly so far, but after the very first update to the state, the Cropper instance seems to completely disappear... When removing live(), this doesn't happen, but obviously the other four form fields are no longer being updated. Does anyone know what the issue is here? Thank you! image 1: without live() image 2: with live(), cropper instance dissappeared Field:
ImageCropper::make('coordinates')
->image(fn (CreateMapping $livewire) => Storage::disk('public')->url($livewire->getMapType()->preview))
->columnSpan(3)
->default('0, 0, 0, 0')
->afterStateUpdated(function(Set $set, string $state)
{
[$x, $y, $width, $height] = explode(', ',$state);
$set('x', $x);
$set('y', $y);
$set('width', $width);
$set('height', $height);
})
->live()
ImageCropper::make('coordinates')
->image(fn (CreateMapping $livewire) => Storage::disk('public')->url($livewire->getMapType()->preview))
->columnSpan(3)
->default('0, 0, 0, 0')
->afterStateUpdated(function(Set $set, string $state)
{
[$x, $y, $width, $height] = explode(', ',$state);
$set('x', $x);
$set('y', $y);
$set('width', $width);
$set('height', $height);
})
->live()
Class:
<?php

namespace App\Forms\Components;

use Filament\Forms\Components\Field;
use Filament\Forms\Get;
use Illuminate\Support\Facades\Storage;

class ImageCropper extends Field
{
protected string $view = 'forms.components.image-cropper';

public $image;


public function image(string|\Closure|null $image) :static
{
$this->image = $image;
return $this;
}

public function getImageUrl(): ?string
{
if($result = $this->evaluate($this->image))
{
return $result;
}
return null;
}
}
<?php

namespace App\Forms\Components;

use Filament\Forms\Components\Field;
use Filament\Forms\Get;
use Illuminate\Support\Facades\Storage;

class ImageCropper extends Field
{
protected string $view = 'forms.components.image-cropper';

public $image;


public function image(string|\Closure|null $image) :static
{
$this->image = $image;
return $this;
}

public function getImageUrl(): ?string
{
if($result = $this->evaluate($this->image))
{
return $result;
}
return null;
}
}
Blade:
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
<div ax-load
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('image-cropper-cropperjs') }}"
x-data="imageCropper({
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$getStatePath()}')") }},
})">
<!-- Interact with the `state` property in Alpine.js -->

<input x-ref="arrayState" class="text-black" x-model='state'/>

<img id="image" src="{{ $getImageUrl() }}" alt="Picture">

</div>
</x-dynamic-component>
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
<div ax-load
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('image-cropper-cropperjs') }}"
x-data="imageCropper({
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$getStatePath()}')") }},
})">
<!-- Interact with the `state` property in Alpine.js -->

<input x-ref="arrayState" class="text-black" x-model='state'/>

<img id="image" src="{{ $getImageUrl() }}" alt="Picture">

</div>
</x-dynamic-component>
Script:
import Cropper from 'cropperjs';

export default function imageCropper({
state,
}) {
let component = null;
let cropper = null;
let image = null;
let canvas = null;
let selection = null;
return {
state,

init: function () {
component = this;
cropper = new Cropper('#image');
canvas = cropper.getCropperCanvas();
image = cropper.getCropperImage();
selection = cropper.getCropperSelection();
selection.zoomable = false;
image.scalable = false;


canvas.classList.add("aspect-square");

// Function to handle Cropper.js event
function handleSelectionChange(event) {
component.state = selection.x + ', ' + selection.y + ', ' + selection.width + ', ' + selection.height;
}

selection.addEventListener('change', handleSelectionChange);

console.log(state);
console.log(cropper);
console.log(canvas);
console.log(selection);
console.log('done!');

},
}
}
import Cropper from 'cropperjs';

export default function imageCropper({
state,
}) {
let component = null;
let cropper = null;
let image = null;
let canvas = null;
let selection = null;
return {
state,

init: function () {
component = this;
cropper = new Cropper('#image');
canvas = cropper.getCropperCanvas();
image = cropper.getCropperImage();
selection = cropper.getCropperSelection();
selection.zoomable = false;
image.scalable = false;


canvas.classList.add("aspect-square");

// Function to handle Cropper.js event
function handleSelectionChange(event) {
component.state = selection.x + ', ' + selection.y + ', ' + selection.width + ', ' + selection.height;
}

selection.addEventListener('change', handleSelectionChange);

console.log(state);
console.log(cropper);
console.log(canvas);
console.log(selection);
console.log('done!');

},
}
}
No description
No description
Solution:
Okay, adding
wire:key="{{rand()}}"
wire:key="{{rand()}}"
to the x-dynamic-component seems to work in reinitializing the script! Now I just need to set it to the correct position 😄
Jump to solution
2 Replies
TimeglitchD
TimeglitchD10mo ago
Ah, I can see now in the docs, live() basically rerenders the HTML, so I can only guess this is without the scripts... That's at least part of the solution.
Solution
TimeglitchD
TimeglitchD10mo ago
Okay, adding
wire:key="{{rand()}}"
wire:key="{{rand()}}"
to the x-dynamic-component seems to work in reinitializing the script! Now I just need to set it to the correct position 😄