I have a view scoped bean where I create a person. A person can have a picture. This picture is uploaded the same page the person is created. The picture is not stored in a database or on disk (since the person isn't created yet). The bean has to be view scoped since a person can be created elsewhere and this uses the same bean. If the bean is session scoped and a user uploads a picture but does not save the person, the picture will be displayed next time the user tries to create a person.
I solved this by using two beans; one view scoped bean to create the person and a session scoped bean to upload the picture and to get the picture as a stream. This however causes the problem noted above.
How can I solve this in a better way?
The upload bean:
@ManagedBean(name = "uploadBean")
@SessionScoped
public class UploadBean
{
private UploadedFile uploadedFile;
public UploadedFile getUploadedFile()
{
return uploadedFile;
}
public StreamedContent getUploadedFileAsStream()
{
if (uploadedFile != null)
{
return new DefaultStreamedContent(new ByteArrayInputStream(uploadedFile.getContents()));
}
return null;
}
public void uploadFile(FileUploadEvent event)
{
uploadedFile = event.getFile();
}
}
The create-a-person bean:
@ManagedBean(name = "personBean")
@ViewScoped
public class PersonBean
{
private Person newPerson = new Person();
public Person getNewPerson()
{
return newPerson;
}
private UploadedFile getUploadedPicture()
{
FacesContext context = FacesContext.getCurrentInstance();
ELContext elContext = context.getELContext();
UploadBean uploadBean = (UploadBean) elContext.getELResolver().getValue(elContext, null, "uploadBean");
return uploadBean.getUploadedFile();
}
public void createPerson()
{
UploadedFile uploadedPicture = getUploadedPicture();
// Create person with picture;
}
}
The relevant JSF page part:
<h:form enctype="multipart/form-data">
<p:outputPanel layout="block" id="personPicture">
<p:graphicImage height="150"
value="#{uploadBean.uploadedFileAsStream}"
rendered="#{uploadBean.uploadedFileAsStream != null}" />
</p:outputPanel>
<p:fileUpload auto="true" allowTypes="/(\.|\/)(gif|jpe?g|png)$/"
fileUploadListener="#{uploadBean.uploadedFile}"
update="personPicture" />
<p:commandButton value="Save" actionListener="#{personBean.createPerson()}"/>
</h:form>
I've gone for a different approach. I initially went for displaying an uploaded image, however if the Person
isn't created yet it seemed like a better idea to keep it all client side. I found this question and created the following based on the chosen answer:
In the head I include html5shiv if the browser is IE and the version is less than 9 for compatibility:
<h:outputText value="<!--[if lt IE 9]>" escape="false" />
<h:outputScript library="js" name="html5shiv.js" />
<h:outputText value="<![endif]-->" escape="false" />
To display/upload the image I have these elements:
<p:fileUpload binding="#{upload}" mode="simple"
allowTypes="/(\.|\/)(gif|jpe?g|png)$/"
value="#{personBean.uploadedPicture}"/>
<p:graphicImage value="#" height="150" binding="#{image}" />
And some JavaScript/jQuery magic:
function readPicture(input, output)
{
if (input.files && input.files[0])
{
var reader = new FileReader();
reader.onload = function(e)
{
output.attr('src', e.target.result);
};
reader.readAsDataURL(input.files[0]);
}
}
$("[id='#{upload.clientId}']").change(
function()
{
readPicture(this, $("[id='#{image.clientId}']"));
});
The uploadedPicture
property is now a simple property:
@ManagedBean(name = "personBean")
@ViewScoped
public class PersonBean
{
private UploadedFile uploadedPicture;
public UploadedFile getUploadedPicture()
{
return uploadedPicture;
}
public void setUploadedPicture(UploadedFile uploadedPicture)
{
this.uploadedPicture = uploadedPicture;
}
}