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:

1 Like

@maxeckel

Thanks for the in depth reply it was very helpful.

Out of curiosity how would you go about implementing an upload that supports multiple instead of just 1 upload? I assume the @this.set for multiple isn’t as easy as just setting the value to null like you can with single upload :sweat_smile:

I am also unsure how to implement this with multiple uploads. I have a post that has multiple images and would like to prepopulate already uploaded images. Has anyone achieved this yet with FilePond?

what about this?

    let x = FilePond.create($refs.input);
    @foreach($attachments as $attachment)
        x.addFiles('/storage/{{$attachment->path}}');
    @endforeach

it’s only annoying because as far as i see it first downloads the media and then reuploads them again, but looks like it’s working this way.