0

Consider a form with a fileUpload component. The user selects some files using the fileUpload component, then submits the form, reasonably thinking that the selected files are implicitly submitted with the form but they are not. The Primefaces fileUpload component requires the user to upload the files as an explicit action before submit and provide a convenient "Upload" button for this purpose. However, if the user neglects to do this, the form is submitted with no file(s) and no indication that they were excluded from the submit. In some use cases, the files may not later be attached to the object created by the form submit. By contrast the JSF and Omnifaces inputFile components and the HTML5 <input type="file"> on which they are based upload the files on submit which seems more usable to me.

Here is some example code. First the presentation:

    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"
        xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:p="http://primefaces.org/ui">
    <h:head></h:head>
    <h:body>
        <f:view>
            <h:form id="form" enctype="multipart/form-data" method="POST">
                <p:panelGrid id="grid" columns="2" cellpadding="15">
                    <p:outputLabel>HTML5 input type="file"</p:outputLabel>
                    <input type="file" id="fileupload1" name="fileupload1" multiple="multiple" />
                    <p:outputLabel>Primefaces fileUpload</p:outputLabel>
                    <p:fileUpload id="fileupload2" fileUploadListener="#{myBean.handleFileUpload}" multiple="true" />
                    <p:outputLabel>JSF Input File</p:outputLabel>
                    <h:inputFile id="fileupload3" value="#{myBean.uploadedFile}" />
                </p:panelGrid>
                <p:commandButton id="submit" value="Submit" actionListener="#{myBean.submit}" process="@form" ajax="false" />
            </h:form>
        </f:view>
    </h:body>
    </html>

And the backing bean:

@ManagedBean
@SessionScoped
public class MyBean {

    private Map<String, Path> uploadedFiles = new HashMap<String, Path>();
    Part uploadedFile;

    public void handleFileUpload(FileUploadEvent event) {
        System.out.println("Uploaded file: " + event.getFile().getFileName());
    }

    public void submit(ActionEvent actionEvent) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
        for (Part part : request.getParts()) {
            if (part != null && part.getSize() > 0 && part.getSubmittedFileName() != null) {
                String fileName = part.getSubmittedFileName();
                String contentType = part.getContentType();
                System.out.println("File in request: " + fileName + "; contentType " + contentType + "; size: " + part.getSize());
            }
        }
        System.out.println("Primefaces uploaded Files (" + (uploadedFiles == null ? "0" : uploadedFiles.size()) + "):");
        if (uploadedFiles != null) {
            for (String name : uploadedFiles.keySet()) {
                System.out.println("     Name: " + name + "; Path " + uploadedFiles.get(name));
            }
        }
        System.out.println("JSF uploaded file: " + (uploadedFile == null ? "null" : uploadedFile.getSubmittedFileName() + ", size " + uploadedFile.getSize()));
    }

    public Part getUploadedFile() {
        System.out.println("get uploaded File Part: " + (uploadedFile == null ? "none" : uploadedFile.getSubmittedFileName()));
        return uploadedFile;
    }

    public void setUploadedFile(Part uploadedFile) {
        System.out.println("set uploaded File Part: " + (uploadedFile == null ? "none" : uploadedFile.getSubmittedFileName()));
        this.uploadedFile = uploadedFile;
    }
}

If you submit this form after selecting files in all three file upload components, the Primefaces one is the only one that does not upload the files on submit. Granted, you can set "auto" to true to cause it to upload the files on selection, but then there is no way to back out if the user decides not to upload a file. You can set it to required, but in the general case the user may not elect to upload any file. So it seems that the Primefaces component is a step backward in this regard, although it is very easy to use and way more full featured.

Is there a Primefaces way to upload multiple files at submit time that I am missing?

Kukeltje
  • 12,223
  • 4
  • 24
  • 47
snakedog
  • 357
  • 1
  • 2
  • 13
  • Where did you read _"The JSF tag, the Primefaces tag, and the Omnifaces tag all require the user to upload the files as an explicit action before submit and provide a convenient "Upload" button for this purpose."_ ? This is not true from my understanding – Kukeltje Sep 24 '18 at 20:11
  • `p:fileUpload`, `h:inputFile` and `o:inputFile` all render an `input type="file"` btw, so it is fully HTML5, and they do not all have upload buttons... So I am very curious how you came to your conclusion. They also support ajax upload btw, so it is automatically uploaded when selected. – Kukeltje Sep 24 '18 at 20:22
  • I cannot create a test now since i just have my mobile phone. Do you have an [mcve] that requires an explicit upload button – Kukeltje Sep 24 '18 at 20:56
  • 1
    I don't understand your core problem. Are you trying to ask how to make the input field required? If so, why don't you just set `required="true"` attribute as usual? Or, are you trying to ask how to distinguish an uploaded file from a non-uploaded file in business action? If so, why don't you just do a `if(file!=null)` check? You seem to be pretty qualified to do either, that's why this question is so confusing. – BalusC Sep 25 '18 at 10:31
  • Thank you for taking the time to respond and I apologize for the lack of clarity and errors in my original post. My understanding of the Omnifaces component was wrong. I've updated the post with an ugly but MCV example. The core problem is really usability around the additional "Upload" action required by the PF component. I didn't think of marking the PF fileupload widget as required, which would work, except that in my current use case, submission of a support request ticket, the user generally won't submit any files. – snakedog Sep 26 '18 at 21:35
  • Why do you do `` for PF and `` for plain JSF? Why not use a more similar `` Is there a reason for this? – Kukeltje Sep 26 '18 at 21:37
  • I did try having the submit button click the PF "Upload" button onstart, which would seem to work, but the upload action is apparently async so the submit completes before the upload happens. – snakedog Sep 26 '18 at 21:38
  • @Kukeltje - `advanced` defaults to true, and the PF docs say "FileUploadListener is the way to access the uploaded files in this mode." I find your suggestion more readable and familiar. However, I think without ajax and `auto="true"` the operation is the same. – snakedog Sep 26 '18 at 21:49
  • Can't you set mode to simple explicitly? Mabe some small change can be made in PF to support `multiple="true"` with mode="simple" but I cannot look into that within the coming week, sorry. And did you try with `` (even the docs state differently)? And also explicitly set the context-param to native. Just to be sure. – Kukeltje Sep 26 '18 at 21:52
  • That works! And retains multiple file capability. mode="simple" effectively turns the PF component into a basic JSF component but with multiple capability and the sexy PF theme. Not nearly as sexy as the "advanced" mode but more usable IMHO. So I think there is a functional tradeoff with the PF component: either "simple" with upload on submit capability or "advanced" with explicit upload, not both. Unfortunately, with "simple" and "multiple" it displays the filename of only the first of the selected files, which looks like a bug, but it does upload them all on submit. Thanks! – snakedog Sep 26 '18 at 22:35

1 Answers1

0

Thanks to @Kukeltje for helping me find an answer.

The "simple" file upload widgets like HTML5 <input type="file">, JSF's <h:inputFile>, and Omnifaces <o:inputFile> allow the user to select one or more files to be uploaded at submit time.

Some more sophisticated fileUpload widgets like the Primefaces one (with mode="advanced", the default mode, and multiple="true") allow the user to upload multiple files in one click BEFORE submit time, but if the user neglects to do this the selected files are not uploaded at submit time. The PF widget can be used with mode="simple" if the designer wants to avoid the usability issue where the user assumes that selected file(s) are uploaded at submit time and they are not. The benefits of using the PF widget in this way are aesthetic and the ability to select multiple files.

As a footnote, unrelated to the original question, there is an issue with the PF widget in particular, and perhaps other similar ones in similar frameworks. In simple mode, if the user selects multiple files, only the first one selected is shown by the widget.

I see another issue where after the user has selected multiple files in ANY of these widgets that allow multiple, the files cannot be unselected. After experimenting with JS to list the files selected, I found that I cannot in JS change the list due to browser security, see BalusC's answer here. I have ended up with some simple JS that, on submit, checks to see if the PF upload widget has any selected files at all, and if it does, prevents the submit and asks the user to upload the files prior to submit. I think this is the most usable solution.

<p:commandButton value="Submit" actionListener="#{bean.submit}" onclick="return checkAttachments();" ajax="false" />

<script>
function checkAttachments() {
    var attachmentRows = $(".ui-fileupload-row");
    console.log("rows " + attachmentRows.length);
    if (attachmentRows.length == 0 ) {
        return true;
    }
    PF('uploadAttachmentsWarningDialog').show();
    return false;
}
</script>
snakedog
  • 357
  • 1
  • 2
  • 13