<script> doesn't run on refresh after first upload to file input

I’m working on an avatar uploader for my project. Everything has been so far so good, and this morning I had no issues. A little while later, BOOM. Death and destruction. Very sadness.

When I first choose a file, it pops up the crop tool immediately, and works fine. If I attempt to upload a different file, the crop tool disappears and not even a preview of the image is presented.

Here’s the form I’m working on. It’s also the component that’s refreshed every time that Livewire sees a new file in the input.

<label for="image-upload">
    <div class="w-full mt-4 button"><i class="far fa-fw fa-upload"></i> Drag and drop, or <b>browse</b></div>
</label>
<input id="image-upload" type="file" wire:model="image" class="hidden" accept="image/png, image/gif, image/jpeg">

@if($image)
    <div id="avatar-preview" class="w-full" style="height: 300px;"></div>

    <script>
        new Croppie(document.querySelector('#avatar-preview'), {
            viewport: { width: 200, height: 200, type: 'circle' },
            enforceBoundary: true,
        }).bind('{!! $image->temporaryUrl() !!}');
    </script>

    <button type="submit" class="w-full mt-16 button is-green">Submit new avatar</button>
@endif

I’m using the Croppie JS package for the crop tool. It requires that I either pass it an img element it’ll attach to, or a container to fit into. I picked a container so I could control the size of the crop tool. I attempt to create a new Croppie instance, attach it to my preview container, and bind the temporary image to it.

This works on the first file upload! Then, when I attempt to change the file (as a user might) the entire instance disappears. I attempted to check and see if it was an issue instantiating Croppie again, since in my mind if the component is refreshed, creating a new instance of the tool shouldn’t be an issue.

<script>
    if (typeof crop === 'undefined') {
        console.log('crop undefined');
        let crop = new Croppie(document.querySelector('#avatar-preview'), {
            viewport: { width: 200, height: 200, type: 'circle' },
            enforceBoundary: true,
        }).bind('{!! $image->temporaryUrl() !!}');
    } else {
        console.log('crop defined');
        crop.bind('{!! $image->temporaryUrl() !!}');
    }

    console.log('crop finished');
</script>

And the ‘undefined’ and ‘finished’ logs come in on the first upload, but nothing happens on the refresh.

So I also tested just doing this…

@if($image)
    <img src="{{ $image->temporaryUrl() }}">
@endif

To ensure at the very least that the preview was working correctly, and lo and behold it does!

The big problem is that even when the Croppie doesn’t refresh, no errors or warnings occur. It just doesn’t seem to execute what’s in the <script> tag at all.

Any tips or advice?

Hey @splashsky,

it’s absolutly correct that your script gets only executed once. You inject it when the image variable is set, when you change the file, the variable is still set, so the script is still in the DOM, so why would the Browser execute it again :wink:

You should check out AlpineJS for this integration, as it’s way easier to listen for this kind of changes and updating Croppie.

As an alternative you could dispatch a browser Event from Livewire and listen for it on the frontend to then reinit Croppie.

1 Like

Thanks very much @maxeckel! The solution to have Livewire emit an event was the best course of action. My component class now does $this->emit('avatar_preview_updated', $this->image->temporaryUrl()); in the updated() function, which allows me to do the following…

livewire.on('avatar_preview_updated', image => {
    new Croppie(document.querySelector('#avatar-preview'), {
        url: image,
        viewport: { width: 200, height: 200, type: 'circle' },
        boundary: { height: 300 },
        enforceBoundary: true,
    });
});

You are really welcome! Glad i could help!