Is there a way to trigger the same functionality provided by live_component_upload
, but from JavaScript?
The question is about darg & dropping and copy/pasting files into a form.
With drag and drop you end up with (pseudocode):
window.addEventListener('drop', function(e){
// e.dataTransfer.files contains all dropped file metadata
// so, for each file:
const reader = new FileReader();
reader.onload = (function (theFile) {
return function (e) {
console.log(reader.result)
};
})(file);
// you can read in the file as any of the following
reader.readAsDataURL(file); // reader.result will be data:image/jpeg;base64,/9j/4A...
reader.readAsBinaryString(file); // binary
reader.readAsArrayBuffer(file); // etc.
})
Once we have this data, is there a way to trigger sending this data?
Or the only solutions are, basically:
-
Trigger LiveView to generate a form for these files. Wait until the form is on the page (via a hook?). Assign files to the file inputs in the form. Trigger the upload
-
Just send an event to LiveView with the file data (will lose any progress info)
?
1 Like
Why not have a form beforehand? Instead of generating it on the fly.
1 Like
I don’t know how many files I’ll have beforehand: I may drag in a bunch at once, or just one. I’m also thinking of having multiple dropzones for files on the page.
However, your suggestion did give me an idea: pre-generate the form(s), have it/them on page (but hidden) and assign files to them as needed (and unhiding the relevant parts). Should work wonders with auto_upload
. I’ll have to check this tomorrow 
And here’s a solution. Thanks to @olivermt for steering me in this direction.
Do prerender your live upload form:
In your controller or live component:
def mount(socket) do
{:ok,
socket
|> assign(:uploaded_files, [])
|> allow_upload(
:files,
accept: ~w(.jpg .jpeg .png),
max_entries: 10,
auto_upload: true,
progress: &handle_progress/3
)}
end
(see auto_upload
and progress
in the docs)
In your live template (this code is straight out of docs):
<%= for entry <- @uploads.files.entries do %>
<%= entry.client_name %> - <%= entry.progress %>%
<% end %>
<form phx-change="validate" phx-target="<%= @myself %>">
<%= live_file_input @uploads.files %>
</form>
And then in the code that handles your file drops:
window.addEventListener('drop', function(e){
var dt = e.dataTransfer;
var files = dt.files;
// let's find our file input and populate it with dropped files
const fileInput = el.querySelector('input[type="file"]');
fileInput.files = files;
// now we have to trigger a change event on the input so that LiveView correctly picks it up
// NOTE: When tested in Chrome, Chrome required this. However, the even was triggered automatically
// in Safari
var event = document.createEvent("UIEvents");
event.initUIEvent("change", true, true);
fileInput.dispatchEvent(event);
}
Voilà
This will work with multiple forms as well.
For my case I made a hook on a div that wraps the upload form. I attached the drag/drop events to that div (to show overlays and whatnot). When the file is dropped, I attach the files and send the event to the form inside that particular div. So now I can have however many file dropzones as I need.
1 Like
Well, in Chrome you have to trigger the event manually. In Safari it gets triggered once you set files on the input.