ux icon indicating copy to clipboard operation
ux copied to clipboard

[LiveComponent][RFC] File uploads in forms

Open Lustmored opened this issue 3 years ago • 4 comments

I think last the missing piece of cake before LiveComponent form support can be considered full is functional file upload. I wasn't tracking changes for the last few weeks, so if there is functioning support already - let me know and I will celebrate and close the issue :wink:

I have already experimented with file uploads within live component with forms and other logic in 2.0.x (in particular - this form has multiple steps in UI and that's where LiveComponent really shines). But to achieve functioning file upload I had to follow those steps:

  1. I've created separate LiveComponent to handle just the <input type="file"> and track its changes.
  2. Within this component I have Stimulus controller that tracks file changes, sends them to the server (that stores it somewhere and returns path), saves the path to hidden field and notifies live controller about changes.

This way once the file is uploaded it is not discarded during re-renders and I can work with it in submit action.

I think similar could be possible out of the box with LiveComponent and would love to work on it. But before I dive into it I'd love some feedback and suggestions. My proposed solution is inspired by how EasyAdmin handles image fields, but with major changes due to different nature of the problem.

EasyAdmin way:

  • custom form type for file upload
  • model transformer that handles file saving and translates values to File instances and back

It is simple, efficient, easily extendable and - most importantly - working well enough.

LiveComponent differences:

  • The file should be uploaded as soon as possible (not only on submit) to allow partial validation
  • Upon successful upload file should be transformed into serialized value that can be shared between back-end and front-end

Those are my rough implementation ideas, please help with them :smile:

Front-end:

  • uploaded files details should be serializable into dataValue or a new parameter, eg. filesValue,
  • _makeRequest needs to be able to send files along with other data

Back-end:

  • ComponentWithFormTrait should be able to handle/validate files (there is a chance it does out of the box)
  • Upon successful file validation the file should be moved out of the system temp dir (to var/cache/ as a default maybe?) and return UploadedFile-like serializable object pointing to that file. I think it could be private trait method so that components can overwrite it with own logic. Also - exceptions raised within that logic should be handled with grace
  • Whenever form is recreated information about previously uploaded files should be also recreated and passed to form data

How does it sound? As LiveController is pretty complex already I'm unsure as to where exactly what part of this logic fits, so help with that would be also appreciated.

Lustmored avatar Feb 25 '22 10:02 Lustmored

Hey @Lustmored!

Sorry for the slow reply - a LOT happening in the world suddenly :/. But, this is a happy escape!

For the most part, I'm definitely following your thinking here - it's very solid. I'd also look at Livewire, as they have solved this problem already - https://laravel-livewire.com/docs/2.x/file-uploads#basic-upload - they have bullet points that talk about how the upload works behind-the-scenes.

Upon successful upload file should be transformed into serialized value that can be shared between back-end and front-end

This may be feasible for small files, but not large ones. If we DID do it this way, we would also need an alternative way of handling this - something similar to what Livewire does.

And, also, this should probably work even without the form component (and that may help things actually, as it's simpler just to think of a having a normal HTML form that binds to a few public properties on your component class). Again, Livewire has a nice example here where $photo is a normal property that is connected to an <input type="file" on the frontend.

Let me know what you think :)

weaverryan avatar Mar 04 '22 00:03 weaverryan

After chatting with @kbond, we may have another, smaller idea - which is more "in line" with what you were proposing.

Basically, we would make the JavaScript "smart enough" so that it does NOT send the file upload on a re-render. And then, we also make sure to NOT re-render that <input type="file"> field (inside of morphdom). So basically, the file upload wouldn’t be sent via Ajax for any "re-render". But it would be sent if you triggered a custom action (there probably would need to be a way to opt into sending the file for an action, as you may not want it for some actions). Though, even better than using an action would be to allow the form to submit normally (not through your live component).

This is just an idea: the goal is to try to provide upload support that works without needing to create a giant system. However, it doesn't mean that we can't, afterward, create some super cool system where the file uploads as soon as you select a file.

weaverryan avatar Mar 04 '22 15:03 weaverryan

I think this is the idea I can start work on :smile: I have just a little concern about submitting full form - that may have weird side effects if component does not span entire form but is rendering just a part of it. But on the other hand everything might work out of the box :) But I think we also need to make sure file upload works perfectly fine without being attached to any form.

I will probably come back with draft pull request concerning this issue when I wrap my head around it :+1:

Lustmored avatar Mar 11 '22 12:03 Lustmored

@weaverryan Any chance you could take a look at the PR? I have made a few choices there that I'd love to get reviewed before delving into tests and documentation. Thanks in advance! :smile:

Lustmored avatar Mar 25 '22 12:03 Lustmored