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.
How can I upload multiple files with JSF 2.2
You can indeed achieve this with another JSF 2.2 feature: passthrough attributes. Set the multiple
attribute as a passthrough attribute (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.
On the more modern browsers you can even select whole folders. This only requires a yet newer directory
attribute. This is supported since Firefox 46 (already since 42, but needs to be explicitly enabled in about:config). Webkit based browsers (Chrome 11+, Safari 4+ and Edge) support this via the proprietary webkitdirectory
attribute. So if you specify both attributes, you're generally safe.
<h:inputFile ... a:multiple="true" a:directory="true" a:webkitdirectory="true" />
Do note that this does not send physical folders, but only files contained in those folders.
Update: if you happen to use JSF utility library OmniFaces, since 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