I found this very nice example of file upload using JSF 2.2. Is it possible to add progress bar with percent of file upload or total uploaded bytes?
<script type="text/javascript">
function progressBar(data) {
if (data.status === "begin") {
document.getElementById("uploadMsgId").innerHTML="";
document.getElementById("progressBarId").setAttribute("src", "./resources/progress_bar.gif");
}
if (data.status === "complete") {
document.getElementById("progressBarId").removeAttribute("src");
}
}
</script>
<h:messages id="uploadMsgId" globalOnly="true" showDetail="false" showSummary="true" style="color:red"/>
<h:form id="uploadFormId" enctype="multipart/form-data">
<h:inputFile id="fileToUpload" required="true" requiredMessage="No file selected ..." value="#{uploadBean.file}"/>
<h:message showDetail="false" showSummary="true" for="fileToUpload" style="color:red"/>
<h:commandButton value="Upload" action="#{uploadBean.upload()}">
<f:ajax execute="fileToUpload" onevent="progressBar" render=":uploadMsgId @form"/>
</h:commandButton>
</h:form>
<div>
<img id="progressBarId" width="250px;" height="23"/>
</div>
Bean:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Named;
import javax.servlet.http.Part;
@Named
@RequestScoped
public class UploadBean {
private static final Logger logger = Logger.getLogger(UploadBean.class.getName());
private Part file;
public Part getFile() {
return file;
}
public void setFile(Part file) {
this.file = file;
}
public void upload() {
if (file != null) {
logger.info("File Details:");
logger.log(Level.INFO, "File name:{0}", file.getName());
logger.log(Level.INFO, "Content type:{0}", file.getContentType());
logger.log(Level.INFO, "Submitted file name:{0}", file.getSubmittedFileName());
logger.log(Level.INFO, "File size:{0}", file.getSize());
try (InputStream inputStream = file.getInputStream(); FileOutputStream outputStream = new FileOutputStream("C:" + File.separator + "jsf_files_test_for_delete" + File.separator +file.getSubmittedFileName())) {
int bytesRead = 0;
final byte[] chunck = new byte[1024];
while ((bytesRead = inputStream.read(chunck)) != -1) {
outputStream.write(chunck, 0, bytesRead);
}
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Upload successfully ended!"));
} catch (IOException e) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Upload failed!"));
}
}
}
}
Is this possible without additional JavaScript code? Only with JSF?
I found that the Malsup Form plugin for jQuery is fairly simple and has good documentation and demo code (therefore fairly easy to use to Ajaxify a progress bar) if you are prepared to go the jQuery (Javascript) route. (Of course, other plugins also exist, like the BlueImp file uploader plugin which has a lot more possibilities, but may not be quite that easy to use.)
For a "JSF-only" solution, BalusC recommends using a JSF component library like Primefaces - which is probably a better option - it is recommended to read his comments and links he provides which explain reasoning behind the preference for one technology over the other.
=== Added example ===
Here is a very basic example, using the Malsup Form plugin and jQuery, that demonstrates the progress bar. (It also handles other fields on the form, if one wants that, but do read up on the pros&cons of the different enctype
settings in the <form>
element.) Note that a <div>
with a progress bar and a text label indicating progress percentage is shown, and another <div>
showing some text on completion of the process - any of these elements may be omitted or otherwise customized. These <div>
s are styled via CSS and updated by various event handlers in the Javascript. No work is done in the Java backing bean.
Note:
I hope this is obvious, but the *.js files are saved in the directory <my-eclipse-project>/WebContent/resources/js/
for the <h:outputScript>
tags to work correctly.
1. XHTML view, including CSS and Javascript
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
>
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Demo File upload with progress</title>
<style>
.progress {
position: relative;
width: 400px;
border: 1px solid #ddd;
padding: 1px;
border-radius: 3px;
}
.bar {
background-color: #B4F5B4;
width: 0%;
height: 20px;
border-radius: 3px;
}
.percent {
position: absolute;
display: inline-block;
top: 3px;
left: 48%;
}
</style>
<h:outputScript target="head" library="js" name="jquery.js" />
<h:outputScript target="head" library="js" name="jquery.form.js" /><!-- http://jquery.malsup.com/form/ -->
<h:outputScript target="body">
//<![CDATA[
jQuery(document).ready(function() {
var bar = jQuery('.bar');
var percent = jQuery('.percent');
var status = jQuery('#status');
jQuery('#formid').ajaxForm({
beforeSend: function() {
status.empty();
var percentVal = '0%';
bar.width(percentVal)
percent.html(percentVal);
},
uploadProgress: function(event, position, total, percentComplete) {
var percentVal = percentComplete + '%';
bar.width(percentVal)
percent.html(percentVal);
},
success: function() {
var percentVal = '100%';
bar.width(percentVal)
percent.html(percentVal);
},
complete: function(xhr) {
status.html(xhr.statusText);
}
});
});
//]]>
</h:outputScript>
</h:head>
<h:body>
<h:form id="formid" enctype="multipart/form-data" method="post">
<h1>Demo File upload with progress</h1>
<h:messages globalOnly="true" tooltip="true" />
<h:inputFile id="fileupload" name="fileupload" value="#{uploadBean.file}" />
<div class="progress">
<div class="bar"></div>
<div class="percent">0%</div>
</div>
<div id="status"></div>
<br />
<h:inputText value="#{uploadBean.field}"></h:inputText>
<br />
<h:commandButton id="submit" action="#{uploadBean.submit}" value="Submit" />
</h:form>
</h:body>
</html>
2. Backing bean
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.servlet.http.Part;
@ManagedBean
@ViewScoped
public class UploadBean implements Serializable {
private static final long serialVersionUID = 1L;
private String field;
private Part file;
/** Constructor */
public UploadBean() {}
/** Action handler */
public String submit() {
// the file is already uploaded at this point
// TODO whatever you need to do with the file and other form values
return ""; // ... or another view
}
// TODO getters and setters for fields
}