Search code examples
laravellaravel-livewirefilepond

Handling pre-existing images when editing a model using Livewire & Filepond


I have a Filepond component that works great for uploading images to a post.

However, I'm struggling with Filepond when needing to edit a post and its pre-existing images.

I want to load Filepond with the Post model's pre-existing images. The goal is to allow the user to upload, delete and reorder the images when editing the Post model, then update the database and the file system.

This is what I have so far:

<div
        x-data=""
        x-init="
            // Plugins
            FilePond.registerPlugin(FilePondPluginImagePreview);
            FilePond.registerPlugin(FilePondPluginImageExifOrientation);
            FilePond.registerPlugin(FilePondPluginFileValidateType);
            FilePond.registerPlugin(FilePondPluginFileValidateSize);
            FilePond.registerPlugin(FilePondPluginImageResize);

            // Set options
            FilePond.setOptions({
                allowMultiple: true,
                allowReorder: true,
                itemInsertLocation: 'after',
                server: {
                    process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
                        @this.upload('images', file, load, error, progress)
                    },
                    revert: (filename, load) => {
                        @this.removeUpload('images', filename, load)
                    },
                },
                // This allows me to reorder the images, essential resetting the $image variable in livewire component when ever th euser reorders the images in filepond
                onreorderfiles(files, origin, target){
                    @this.set('images', null);
                    files.forEach(function(file) {
                        @this.upload('images', file.file);
                    });
                },

            });

            // Create Filepond
            const pond = FilePond.create($refs.input, {
                acceptedFileTypes: ['image/png', 'image/jpeg'],
                
                @if(optional($post)->images) // If we are editing a post and if that post has images
                files: [
                    @foreach($post->images as $image)// Loop through each image for the post
                        {
                            // the server file reference
                            source: '{{ Storage::disk('post_images')->url($post->id . '/' . $image->filename.$image->extension) }}', 
                        },
                    @endforeach
                ],
                @endif
            });

            pond.on('addfile', (error, file) => {
                if (error) {
                    console.log('Oh no');
                    return;
                }
            });
        "
    >
        <div wire:ignore wire:key="images">
                <input
                    id="image-upload"
                    type="file"
                    x-ref="input"
                    multiple
                >

                @error('images.*')
                <p wire:key="error_images" class="mt-2 text-sm text-red-600" id="email-error">{{ $message }}</p>
                @enderror
        </div>
    </div>

However this is causing all of the images to re-upload every time the user views the edit page.

There is a lot online on how to upload images using Filepond and Livewire, but not really much on how to edit.

Is there a better way to manage images, other than reuploading every time the user views the edit page?


Solution

  • I was able to handle pre-existing images on a model, specifically I was able to re-order the images.

    For us to handle re-ordering of images we need a unique identifier coming from the server and with livewire's upload method it was handled for us magically.

    @this.upload('images', file, load, error, progress)
    

    This is great and is working with both processing or reverting uploads but breaks when we try to listen for filepond's onreorderfile event. Filepond knows how to handle re-ordering on the DOM level but is missing an important information, which is the unique indentifier coming from the server

    onreorderfiles(files, origin, target) {
        console.log(file.serverId); // this will be null
    }
    

    Why is that? The first time we added our images either through the filepond's files array or addFile method we processed the images using livewire's @this.upload('images', file, load, error, progress). The load callback here is what returns the serverId or the unique identifier we need so that Filepond can communicate it with the backend. The load callback from filepond is available on the server process object but not in onreorderfiles event.

    This isn't quite right but we do have a clue where to find the serverId. Diving the source code for livewire's livewire\livewire\js\UploadManager.js we will find the upload method and it does return the tmpFilename:

    this.component.on('upload:finished', (name, tmpFilenames) => this.markUploadFinished(name, tmpFilenames))
    

    The issue is clear because onreorderfiles event we aren't specifying any callbacks to filepond's load() method and when the script reaches the server process object load is undefined:

    onreorderfiles(files, origin, target){
        // omitted code
    
        @this.upload('images', file.file);
    
        // omitted code
    }
    

    Summary

    The solution is to explicitly return a success callback no matter what.

    Change the following code block:

    server: {
        process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
            @this.upload('images', file, load, error, progress)
    },
    

    to this, remember that livewire upload method returns unique filename no matter what but we need to pass it to filepond and load method accepts a response as a parameter:

    server: {
        process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
            @this.upload('images', file,
                // this is the secret method people have been looking for
                (uploadedFilename) => {
                    load(uploadedFilename);
                }, error, progress)
    },
    

    Well, it is not secret per se since it is documented at Livewire file uploads.