How to handle pre-existing file uploads?

I’ve successfully implemented file uploads with FilePond, and the submit/store works fine. However, I want to try and make it so when someone edits an existing post, FilePond pre-fills with the already existing files linked to that post.

I already managed to get FilePond showing the files via Javascript, but it doesn’t stay sync’d with the property that wire:model uses.

I tried adding files manually, but that didn’t work. I tried creating a new TemporaryUploadedFile and that didn’t work either because as soon as it gets converted to an array (since Livewire can’t bind to any weird types), it wipes all file info and just leaves you with; ['disk' => 'local']

This makes it quite hard to do any custom functions with it.

Hopefully someone can explain cuz i cant rly find any sort of information on this anywhere.

Hey @Tasty_Arrival,

i had the same problem yesterday and tried to gather information how to achieve this using FilePond. Turns out FilePond is capable of doing this, but it was kind of hard to find a working way, which integrates with Livewires implementation.

That being said, i managed to get it working, even removing the image using only the normal FilePond functionality.

First of all, this is how my FilePond component looks like:

@props([
    'baseurl' => false,
    'filename' => false,
])

<div
    wire:ignore
    x-data
    x-init="
        FilePond.create($refs.input, {
            allowMultiple: {{ isset($attributes['multiple']) ? 'true' : 'false' }},
            server: {
                process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
                    @this.upload('{{ $attributes['wire:model'] }}', file, load, error, progress)
                },
                revert: (filename, load) => {
                    @this.removeUpload('{{ $attributes['wire:model'] }}', filename, load)
                },
                load: (uniqueFileId, load, error, progress, abort, headers) => {
                    fetch(`{{ route('media.restore', ['course', $filename]) }}`).then((res) => {
                        return res.blob();
                     }).then(load);
                },
            },
            @if($baseurl && $filename)
                files: [
                    {
                        source: '{{ $filename }}',

                        options: {
                            type: 'local',
                        },
                    }
                ],

                onremovefile: (error, file) => {
                    @this.set('{{ $attributes['wire:model'] }}', null);
                }
            @endif
        });
    "
>
    <input type="file" {{ $attributes->only('accept') }} x-ref="input">

    @error($attributes['name'])
    <div class="text-sm text-red-600 mt-2">{{ $message }}</div>
    @enderror
</div>

I modified it quite a bit to what looks like when you followed the Screencasts or looked into the surge repo.

The important parts are:

  1. The FilePond options have been moved to the second parameter of the “create” method. Otherwise the Options would be globally set and it wouldn’t be possible to have more than one component per page.

  2. As per the documentation of FilePond the “load” callback is used to load already existing files. So i created an endpoint which will return the requested file, in the way FilePond wants it (File Object, with Content-Disposition: inline header set). I simply fetch the file using the route and return the file blob.

  3. Only when the BaseURL and the Filename props are set, i add the “files” configuration to the FilePond Object. To be able to remove an image using the preview i also use the “onremovefile” callback, which is called when you click on the “x” in the upper left corner of the FilePond “component”. When it’s clicked i simply set the wired property in my Livewire component to “null”. This signals me to delete the file on save.

Here is my Endpoint for loading the image (the route is protected using the auth guard)
You shouldn’t use the Controller as is, as even though it is protected through the auth middleware, it provides security vulnerabilities! You could pass in any Disk and Filename and the file would be returned! This is only for testing purpose and for the “sake of simplicity”!

class RestoreMediaController extends Controller
{
    public function __invoke(string $disk, string $fileName)
    {
        return response()->file(Storage::disk($disk)->path($fileName), [
            'Content-Disposition' =>  "inline; filename={$fileName}"
        ]);
    }
}

Now when i save the Model i simply check, whether the original value is not equal to the current one and if so, i delete the old image and set the the attribute to either the new value or to null:

if ($this->course->image !== $this->image) {
    Storage::disk('course')->delete($this->course->image);

    $this->course->image = is_null($this->image) ? null : $this->image->store('/', 'course');
}

In my tests this worked seamlessly. I’m not sure whether there is some pitfall i can’t see at the moment or maybe there is a way better approach. But at least it works as it should :smiley:

Hope this helps anyone with the same problem. When someone needs help, please feel free to reach out :wink: