I am trying to add a multiple file upload using h:inputFile
. I had a quick look through the source code and it appears that it does not have the option to render multiple="multiple"
. Is there a way around this without writing a custom component?
If not, is there a suggested custom JSF2.2 component available that can handle multiple Ajax file uploads?
Update:
I have passed the multiple="multiple"
using passthrough
tag, but when I debugged the FileRenderer
the relevant piece of code overwrites the first file with the second:
for (Part cur : parts) {
if (clientId.equals(cur.getName())) {
component.setTransient(true);
setSubmittedValue(component, cur);
}
}
As you can see, since there are two Part
s with the same clientId
, it always use the last instead of passing a list.
Please recommend an alternative if there is one.
ASP.NET FileUpload control provides AllowMultiple property to upload multiple files to the server. This property takes either true or false value. The <asp:FileUpload> tag is used to create a browse button that allows us to upload file.
This is not natively supported by <h:inputFile>
until Faces version 4.0. It's introduced in Faces 4.0 as per spec issue 1555 (by yours truly):
<html ... xmlns:h="jakarta.faces.html">
...
<h:form enctype="multipart/form-data">
<h:inputFile value="#{bean.files}" multiple="true" />
<h:commandButton value="submit" action="#{bean.submit}" />
</h:form>
private List<Part> files;
public void submit() {
for (Part file : files) {
String name = Paths.get(part.getSubmittedFileName()).getFileName().toString();
long size = part.getSize();
// ...
}
}
In case you're not on Faces 4.0 yet, then there are 2 alternative options:
Set the multiple
attribute as a passthrough attributes (browser support is currently quite broad).
<html ... xmlns:a="http://xmlns.jcp.org/jsf/passthrough">
...
<h:inputFile ... a:multiple="true" />
However, the <h:inputFile>
component itself doesn't support grabbing multiple Part
s from the request and setting it as an array or Collection
bean property. It would only set the last part matching the input field name. Basically, to support multiple parts, a custom renderer needs to be created (and you should immediately take the opportunity to just support multiple
attribute right away without resorting to passthrough attributes).
For the sake of having a "workaround" without creating a whole renderer, you could however manually grab all the parts via HttpServletRequest
with help of below little utility method:
public static Collection<Part> getAllParts(Part part) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
return request.getParts().stream().filter(p -> part.getName().equals(p.getName())).collect(Collectors.toList());
}
So, the below construct should work with above utility method:
<h:inputFile value="#{bean.part}" a:multiple="true" />
<h:commandButton ... action="#{bean.submit}" />
private Part file;
public void submit() throws ServletException, IOException {
for (Part part : getAllParts(file)) {
String fileName = part.getSubmittedFileName();
InputStream fileContent = part.getInputStream();
// ...
// Do your thing with it.
// E.g. https://stackoverflow.com/q/14211843/157882
}
}
public Part getFile() {
return null; // Important!
}
public void setFile(Part file) {
this.file = file;
}
Do note that the getter can for safety and clarity better always return null
. Actually, the entire getter method should have been unnecessary, but it is what it is.
Or, use the JSF utility library OmniFaces. Since OmniFaces version 2.5 the <o:inputFile>
is offered which should make multiple and directory selection less tedious.
<o:inputFile value="#{bean.files}" multiple="true" />
<o:inputFile value="#{bean.files}" directory="true" />
The value can be bound to a List<Part>
.
private List<Part> files; // +getter+setter
This component was the base for the new Faces 4.0 feature.
I think it is possible to use multiple file upload using the standard JSF 2.2 using the passthrough tag.
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:pt="http://xmlns.jcp.org/jsf/passthrough">
...
<h:form id="form" enctype="multipart/form-data">
<h:inputFile id="file" value="#{fileUploadBean.uploadedFile}" pt:multiple="multiple" />
...
The JSF renderer class FileRenderer for the javax.faces.File
type of the javax.faces.Input
family of components doesn't handle this case correctly.
Instead, as it iterates through the parts of the form, it just overwrites the preceding part with each file in the uploaded collection.
I think a better strategy is to always have the value of the component be a List<Part>
instead of just Part
as suggested here and implemented here.
The last thing to make it work is to configure such modified multiple file renderer class in faces-config.xml
adding the following to the <faces-config>
root element:
<render-kit>
<renderer>
<description>Multiple File Renderer</description>
<component-family>javax.faces.Input</component-family>
<renderer-type>javax.faces.File</renderer-type>
<renderer-class>com.example.MultipleFileRenderer</renderer-class>
</renderer>
</render-kit>
Since the question was asked a very long time ago, I would like to give an update here. If you are working with the new Jakarta EE Faces 4.0 specification it becomes quite simple to support multiple file upload:
As already mentioned the h:from
has to be extended with the enctype "multipart/form-data"
. And the h:inputFile
needs the passthrough attribute multiple=true
:
<ui:composition template="/WEB-INF/templates/layout.xhtml"
xmlns:faces="jakarta.faces" xmlns:f="jakarta.faces.core"
xmlns:h="jakarta.faces.html" xmlns:ui="jakarta.faces.facelets"
xmlns:pt="jakarta.faces.passthrough">
<f:view>
<h:form id="models_form_id" enctype="multipart/form-data">
.....
<h:inputFile id="file" value="#{myBean.files}" pt:multiple="true"/>
....
<h:commandButton id="submit" value="Submit" action="#{myBean.submit}" />
</h:form>
</f:view>
</ui:composition>
Your bean code just need to support the 'files' property as a List of jakarta.servlet.http.Part
elements:
@Named
@RequestScoped
public class MyBean implements Serializable {
private List<Part> files;
public List<Part> getFiles() {
return files;
}
public void setFiles(List<Part> files) {
this.files = files;
}
public void submit() throws IOException {
if (files != null) {
System.out.println(" uploading " + files.size() + " files");
for (Part file : files) {
System.out.println("name: " + file.getSubmittedFileName());
System.out.println("type: " + file.getContentType());
System.out.println("size: " + file.getSize());
InputStream content = file.getInputStream();
// Write content to disk or DB.
}
}
}
}
....
That's it. Now you can process uploaded files as any other data in your form.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With