Reputation: 795
My need: I'd like to add an "upload from clipboard" functionality into a Vaadin 23 application so that the user can paste a screenshot into an Upload
field.
Known pieces of the puzzle: I know that there is a paste
event (see here https://stackoverflow.com/a/51586232/10318272 or here https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event ) and there's the Vaadin Upload
component ( https://vaadin.com/docs/latest/components/upload ).
Question: How can I transfer the pasted data into the Upload
field?
Upvotes: 0
Views: 480
Reputation: 795
Why initially intended solution does not work: It seems that uploading a screenshot via an Upload
field is not feasible because the FileList (= model of a file input field) does not allow to add/append a new File
object.
(Working) Workaround: So my workaround is a TextArea
with a paste
-EventListener that does a remote procedure call of a @ClientCallable
method at the server.
Left component is the TextArea
, right component is a preview Image
.
Code:
import com.vaadin.flow.component.ClientCallable;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.server.StreamResource;
import java.io.ByteArrayInputStream;
import java.util.Base64;
public class PasteScreenshotField extends HorizontalLayout {
private final Image previewImg;
public PasteScreenshotField() {
// This could be any focusable type of component, I guess.
TextArea textField = new TextArea();
textField.setWidth("50px");
textField.setHeight("50px");
this.add(textField);
String pasteFunction = "for(const item of event.clipboardData.items) {"
+ " if(item.type.startsWith(\"image/\")) {"
+ " var blob = item.getAsFile();"
+ " var reader = new FileReader();"
+ " reader.onload = function(onloadevent) {$1.$server.upload(onloadevent.target.result);};"
+ " reader.readAsDataURL(blob);"
+ " }"
+ "}";
this.getElement().executeJs("$0.addEventListener(\"paste\", event => {"+pasteFunction+"})", textField.getElement(), this);
// Optional: Preview of the uploaded screenshot
previewImg = new Image();
// TODO: Fixed size of 50px x 50px stretches the image. Could be better.
previewImg.setWidth("50px");
previewImg.setHeight("50px");
this.add(previewImg);
}
@ClientCallable()
private void upload(String dataUrl) {
System.out.println("DataUrl: "+dataUrl);
if (dataUrl.startsWith("data:")) {
byte[] imgBytes = Base64.getDecoder().decode(dataUrl.substring(dataUrl.indexOf(',') + 1));
// Showing a preview is just one of the possible scenarios.
// TODO: check filename extension. Maybe it's not a png.
previewImg.setSrc(new StreamResource("preview.png", () -> new ByteArrayInputStream(imgBytes)));
}
}
}
Extendability: Instead of previewImg.setSrc
you could do whatever you want with the uploaded file. The preview is just the proof that the screenshot goes to the server (and could go back to the client again).
Possible connection to Upload
component:
If you've got an Upload
component and want to extend it with this paste functionality, you can register the paste
listener at the Upload
component (or at some other component) and instead of previewImg.setSrc
you just call this (whereas the onSucceededRunner
is a BiConsumer<String, String>
in my case that runs the onSucceeded stuff (updating thumbnails, setting attributes at the bound bean, ...)):
String filename = "screenshot.png";
String mimeType = "image/png";
OutputStream outputStream = uploadField.getReceiver().receiveUpload(filename, mimeType);
outputStream.write(imgBytes);
outputStream.flush();
outputStream.close();
onSucceededRunner.accept(filename, mimeTypeString);
Final result:
This is what my custom upload field looks like in the end (assembled from the code above plus an Upload
field plus a TextField
for the file name and a thumbnail preview). The user now has to click somewhere at that field (=focus it, because there could be more than one in a form), press Ctrl+V and then a screenshot gets uploaded (if there is any) from clipboard to Vaadin application at the server.
Upvotes: 1