I am using PrimeFaces.Monitordownload function to monitor the progress of file download. First the file is created then downloaded. If anything goes wrong, a error file is created and downloaded with streamed content instead of real file. This is fine and works well. The problem occurs when I want to add a error message along with the error file. I have no idea how to update my growl.
When using PrimeFaces.monitorDownload, I need to set ajax=false. Could this be part of my problem?
I have tried multiple things, like setting update="growl" on the p:commandButton, autoUpdate=true on the growl itself, but nothing. I might be doing something wrong in my backing bean, but don't think so.
Here is my relevant Java code:
boolean fileCreated = processor.start();
if(fileCreated){
File xlsfile = new File(filePath);
InputStream stream = new FileInputStream(xlsfile);
file = new DefaultStreamedContent(stream, "file/xlsx", fileName);
return file;
}else{
fileName = fileName.replace(".xlsx", ".txt");
filePath = rootPath + fileName;
File xlsfile = new File(filePath);
InputStream stream = new FileInputStream(xlsfile);
file = new DefaultStreamedContent(stream, "file/txt", fileName);
message = new FacesMessage(FacesMessage.SEVERITY_WARN, "Something went wrong!", "Please send an email to servicedesk and alert the administrators of this page!");
FacesContext.addMessage(null, message);
return file;
}
And my xhtml:
<p:dialog modal="true" widgetVar="statusDialog" header="Status"
footer="Creating file. It might take up to 30 minutes, depending on the file size."
draggable="true" closable="false" resizable="false">
<p:graphicImage name="/bigLoader.gif"
style="margin-left :auto; margin-right:3.7cm;" />
</p:dialog>
<p:commandButton id="submitBtn" value="submit" ajax="false"
disabled="#{controllerBean.selectedBrands.size() lt 1}"
styleClass="btn btn-default"
onclick="PrimeFaces.monitorDownload(start, stop);">
<p:fileDownload value="#{controllerBean.file}" />
</p:commandButton>
<p:commandButton value="Add brand" styleClass="btn btn-default"
action="#{controllerBean.addBrand()}"
update=":first:brands: :first:tableGroup: :first:submitBtn:">
</p:commandButton>
I'm using Mojarra 2.2, Primefaces 5.3.
Any help much appreciated!
Filedownload is a non-ajax request, so the update attribute won't do anything.
I think you don't need to download a fake/blank file in the case of an error. You should just present the user an error msg.
I recommend that you create 2 steps to download, as your file generation process seems too slow:
First step (ajax) generates the file and saves it on the backing bean (if successful). If the generation fails, then present error message.
If the file was successfully generated (fileCreated=true), enable the download button, which will just download the generated file (ajax=false).
Remember to nullify the file variable after download to free memory.
Additionally, if you have the time, you could put a progressBar for the generation process...
Related
This question already has an answer here:
How to include file from external local disk file system folder in JSF
(1 answer)
Closed 6 years ago.
I am letting the user upload a file and am storing it as so
List<Meetingsattachment> mal= new ArrayList<Meetingsattachment>();
meeting.setMeetingsattachments(mal);
Random rand=new Random();
int num=Math.abs(rand.nextInt());
String dirname="/path/to/files/"+meeting.getDescription()+"-"+num;
File dir= new File(dirname);
System.out.println(dir.getAbsolutePath());
if(! dir.exists())
dir.mkdir();
for(UploadedFile uf:ufl){
Meetingsattachment ma=new Meetingsattachment();
ma.setAttachment(dirname+"/"+uf.getFileName());
File file = new File(dirname+"/"+uf.getFileName());
try (FileOutputStream fop = new FileOutputStream(file)) {
// if file doesn't exists, then create it
if (!file.exists()) {
file.createNewFile();
}
// get the content in bytes
fop.write(IOUtils.toByteArray(uf.getInputstream()));
fop.flush();
fop.close();
meeting.addMeetingsattachment(ma);
System.out.println("Done");
} catch (IOException e) {
e.printStackTrace();
}
}
int meetingid=mrm.addMeeting(meeting);
return "meetings?faces-redirect=true";
What I am basically doing is taking the file storing it at /path/to/files/ and then storing the location in my database, so value in db is as so
/path/to/files/t2-672409104/1.pdf
I am able to download the file as so
Meetingsattachment ma=(Meetingsattachment) actionEvent.getComponent().getAttributes().get("attachment");
//System.out.println("here "+form.getRqid());
try{
InputStream stream = new FileInputStream(new File(ma.getAttachment()));
String fl=ma.getAttachment();
System.out.println(fl);
String filename=fl.split("/")[5];
//System.out.println(ra.getRaid());
setFile(new DefaultStreamedContent(stream,"application/pdf",filename));
in the jsf
<ui:repeat var="meetattachment" value="#{meet.meetingsattachments}">
<h:outputText value="#{meetattachment.attachment}" />
<p:commandButton icon="ui-icon-arrowthickstop-1-s"
onclick="PrimeFaces.monitorDownload(showStatus, hideStatus)"
actionListener="#{meetBean.buttonAction}">
<f:attribute name="attachment" value="#{meetattachment}"></f:attribute>
<p:fileDownload value="#{meetBean.file}" />
</p:commandButton>
But when I try to display the file in an iframe I just get an iframe showing resource not found
<ui:repeat var="meetattachment" value="#{meet.meetingsattachments}">
<h:outputText value="#{meetattachment.attachment}" />
<p:lightBox iframe="true">
<h:outputLink value="#{meetattachment.getAttachment()}">
preview2
</h:outputLink>
</p:lightBox>
<br />
</ui:repeat>
The value that the h:outputlink is getting is /path/to/files/t2-672409104/1.pdf
In the developer webconsole I can see the following error
GET
http://localhost:8080/path/to/files/t2-672409104/1.pdf not found
so I think the problem is the path it is trying to use to find the file, from my understanding if I store the file using the relative path /path/to/files the actual path for the file is C:\path\to\files
I tried hardcoding the value as such just to test
<h:outputLink value="C:\path\to\files\t2-672409104\1.pdf">
I get a message saying it cannot understand the protocol C
So my question basically is how do I provide the path to these files to open them in a frame
The way jsf work out the links is different. That is the reason you get the file not found error. If you want to open the file inside an iframe then you need to do some extra works. There is pdfjs library which you can download and use it to show the pdf inside an iframe. In primefaces there is a component to do that. You can use the primefaces source to see how the same is accomplished. If you want more info or complete hand holding please do let me know.
I have a button which opens a new tab with a generated pdf-file.
However, after I click on the button, I want to navigate to another page.
That means, after clicking on the button i want to open a new tab with the pdf and navigate to another page on the initial tab. I am using primefaces p:commandButton and tried with onclick="window.location.href='www.google.de'" but it does not work. However onclick="window.lalert('www.google.de')" does work.
This is my code:
<h:form id="transForm" target="_blank">
<p:commandButton value="Zertifikat erstellen" ajax="false"
label="Speichert die Anmeldung und erstellt ein Zertifikat im PDF-Format"
action="#{transportErfassen.generatePDFZertifikat()}"/>
</h:form>
generatePDFZertifikat() does create a pdf-File with following code, I think here is the issue:
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
externalContext.setResponseContentType("application/pdf" );
externalContext.setResponseHeader("Expires", "0");
externalContext.setResponseHeader("Cache-Control","must-revalidate, post-check=0, pre-check=0");
externalContext.setResponseHeader("Pragma", "public");
externalContext.setResponseHeader("Content-disposition", "inline; filename=\"" + fileName +"\"");
externalContext.setResponseContentLength(out.length);
externalContext.addResponseCookie(Constants.DOWNLOAD_COOKIE, "true", new HashMap<String, Object>());
//setze explizit auf OK
externalContext.setResponseStatus(200);
OutputStream os = externalContext.getResponseOutputStream();
os.write(out, 0, out.length);
os.flush();
facesContext.responseComplete();
facesContext.renderResponse();
You're basically trying to send 2 responses back to 1 request. This is not ever going to work in HTTP. If you want to send 2 responses back, you've got to let the client fire 2 requests somehow. You were already looking in the right direction for the solution, with little help of JavaScript it's possible to fire multiple requests on a single event (click). Your attempt in onclick is however not valid, the change of window.location on click of the submit button, right before the form submit, completely aborts the original action of the button, submitting the form.
Your best bet is to directly navigate to the result page which in turn invokes JavaScript window.open() on page load, pointing to the URL of the PDF file which you'd like to open. It's namely not possible to send some HTML/JS code along with the PDF file instructing a navigation (as that would obviously corrupt the PDF file). This also means, that you can't return the PDF directly to the form submit request. The code has to be redesigned in such way that the PDF can be retrieved by a subsequent GET request. The best way is to use a simple servlet. You could store the generated PDF temporarily on disk or in session, associated with an unique key, and pass that unique key as request pathinfo or parameter to the servlet in window.open() URL.
Here's a kickoff example:
Initial form:
<h:form>
...
<p:commandButton ... action="#{bean.submit}" />
</h:form>
Bean:
public String submit() {
File file = File.createTempFile("zertifikat", ".pdf", "/path/to/pdfs");
this.filename = file.getName();
// Write content to it.
return "targetview";
}
Target view:
<h:outputScript rendered="#{not empty bean.filename}">
window.open('#{request.contextPath}/pdfservlet/#{bean.filename}');
</h:outputScript>
PDF servlet (nullchecks etc omitted for brevity; Java 7 assumed for Files#copy()):
#WebServlet("/pdfservlet/*")
public class PdfServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
File file = new File("/path/to/pdfs", request.getPathInfo().substring(1));
response.setHeader("Content-Type", "application/pdf");
response.setHeader("Content-Length", String.valueOf(file.length()));
response.setHeader("Content-Disposition", "inline; filename=\"zertifikat.pdf\"");
Files.copy(file.toPath(), response.getOutputStream());
}
}
As BalusC said, Refresh/navigate current page and opening downloading file are two different responses, there must be two resquests. I encountered a similar problem. I solved it with jsf ajax successfully.
Here's part of my code:
XHTML:
<h:commandButton id="download-button" class="download-button"
value="download">
<f:ajax event="click" execute="#form" render=":msg-area"
listener="#{myController.checkForDownload}" onevent="checkCallBack" />
</h:commandButton>
<h:commandButton id="download-button2" class="download-button2"
value="download" style="display: none;"
action="#{myController.download}">
</h:commandButton>
Javascript:
function checkCallBack(data) {
var ajaxStatus = data.status;
switch (ajaxStatus) {
case "begin":
break;
case "complete":
break;
case "success":
document.getElementById('download-form:download-button2').click();
break;
}
}
download-button renders a message area on page and download-button2 triggers a download method. they are two different requests. When the first request completed, the second request will be triggered.
I need to upload an image into the server.
I'm using primefaces, here is my code:
deposit.xhtml
<h:form>
<p:fileUpload mode="simple"
allowTypes="/(\.|\/)(gif|jpe?g|png)$/"
value="#{imageHandler.uploadedPicture}" />
<p:commandButton action="#{imageHandler.savefile(imageHandler.uploadedPicture)}"
value="Déposer" ajax="false" />
</h:form>
ImageHandler:
#ManagedBean (name = "imageHandler")
#RequestScoped
public class ImageHandler {
private UploadedFile uploadedPicture; // +getter+setter
public void savefile(UploadedFile uploadedPicture)
{
try {
InputStream input = uploadedPicture.getInputstream();
File folder = new File("C:\\Users\\Clyde\\Documents\\NetBeansProjects\\DSJEE\\web\\resources\\Items");
String filename = FilenameUtils.getBaseName(uploadedPicture.getFileName());
String extension = FilenameUtils.getExtension(uploadedPicture.getFileName());
File file = File.createTempFile(filename + "-", "." + extension, folder);
Files.copy(input, file.toPath());
FacesContext.getCurrentInstance().getExternalContext().redirect("index2.xhtml");
} catch (IOException ex) {
Logger.getLogger(ImageHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Concerning the trace here is the error I get, those are 3 lines that I have picked from the trace:
javax.faces.el.EvaluationException: java.lang.NullPointerException
Caused by: java.lang.NullPointerException
at ImageHandler.savefile(ImageHandler.java:43)
In other words, it's coming from here:
InputStream input = uploadedPicture.getInputstream();
I have tried many things to get rid of that error. I used savefile() without parameters, changed the scope, etc... Still can't go on. How is this caused and how can I solve it?
It will be null in the action method when the browser is unable to send the file contents along with the request body, or when the server is unable to grab the file contents from the request body.
In order to let the broswer send the file contents (and thus not only the name), you need to make sure that the request body encoding type is set to multipart/form-data. This is to be achieved by setting the enctype attribute of the form as below:
<h:form enctype="multipart/form-data">
Unrelated to the concrete problem, the below doesn't make sense:
<p:commandButton action="#{imageHandler.savefile(imageHandler.uploadedPicture)}">
You don't need to pass a bean property forth and back to the very same bean. Just let the action method access it directly.
<p:commandButton action="#{imageHandler.savefile}">
Also, the attempt to save the uploaded file in IDE project folder is a bad idea. Don't do that. Store it elsewhere. See also a.o. Uploaded image only available after refreshing the page.
I'm using <p:fileUpload> which is restricted to PDF only. However, the invalidFileMessage shows inside the <p:fileUpload> component. How can I show it in <p:growl> instead?
<p:fileUpload allowTypes="/(\.|\/)(pdf)$/"
invalidFileMessage="File is Invalid. Only PDF files are allowed" />
You can't handle this server side. The file type is validated at client side without hitting any code in server side. So, any suggestions which suggest to manually create FacesMessage and/or explicitly add <p:message(s)> are unthoughtful and untested.
You should use jQuery. It solves everything.
Based on the fileupload.js source code, your best bet is to listen on the fictional show event of the message container and then move the messages container to end of the form.
First extend $.show() to actually trigger the show event.
(function($) {
var originalShowFunction = $.fn.show;
$.fn.show = function() {
this.trigger("show");
return originalShowFunction.apply(this, arguments);
};
})(jQuery);
Then simply create a listener on show event which basically runs when file upload messages appear and then parse every single message and use the renderMessage() function of the <p:growl> JS API. The below example assumes that you've a <p:growl widgetVar="growl"> somewhere in the same page.
$(document).on("show", ".ui-fileupload-content>.ui-messages", function() {
$(this).children().hide().find("li").each(function(index, message) {
var $message = $(message);
PF("growl").renderMessage({
summary: $(".ui-messages-error-summary", $message).text(),
detail: $(".ui-messages-error-detail", $message).text()
});
});
});
Well add an message tag in your page something like:
<p:messages id="test" autoUpdate="true" />
And in fileupload update="#this,test" and your message will be displayed in p:messages. You can change easly in growl works the same.
Look in the primefaces showcase for more examples
Looked up an example in Primefaces showcase and found this. The actual page:
<p:fileUpload fileUploadListener="#{fileUploadController.handleFileUpload}"
mode="advanced"
update="messages"
allowTypes="/(\.|\/)(pdf)$/"/>
<p:growl id="messages" showDetail="true"/>
And the file uploader controller class:
public void handleFileUpload(FileUploadEvent event) {
FacesMessage msg = new FacesMessage("Succesful", event.getFile().getFileName() + " is uploaded.");
FacesContext.getCurrentInstance().addMessage(null, msg);
}
Maybe something to keep in mind on how to display messages in Primefaces
I am wondering if it possible, by using the primefaces upload in advance mode to limit the user uploading one file only, currently i have :
<p:fileUpload fileUploadListener="#{fileUploadController.handleFileUpload}"
mode="advanced"
multiple="false"
update="messages"
sizeLimit="100000000"
allowTypes="/(\.|\/)(gif|jpe?g|png|doc|docx|txt|pdf)$/"
auto="false"/>
<p:growl id="messages" showDetail="true"/>
as you can see i have muliple ="false" but a user is still able to upload multiple files, any tips ?
EDIT :
<p:fileUpload widgetVar="upload" fileUploadListener="#{fileUploadController.handleFileUpload}"
mode="advanced"
multiple="false"
update="messages"
label="Select File"
sizeLimit="100000000"
allowTypes="/(\.|\/)(gif|jpe?g|png|doc|docx|txt|pdf|html)$/"
auto="false"/>
<p:growl id="messages" showDetail="true"/>
have added the widgetVar above
and in my js
<script type="text/javascript">
function Naviagtion()
{
//alert("Sent to the printing holding queue, you may close this app now, your work will still print out ");
window.setTimeout(afterDelay, 500);
location.href = 'FilesUploaded.xhtml';
}
upload.buttonBar.find('input[type=file]').change(function() {
if (this.value) {
var files = upload.uploadContent.find('.files tr');
if (files.length > 1) {
files.get(0).remove();
}
}
});
</script>
but i am still able to multi upload, am i going about this in the right direction
Although better behavior to solve it should be as #BalusC suggested, but in primefaces 4.0 I am seeing the attribute
fileLimit="1"
which you can set to 1 to disallow multiple file additions using "Choose" button. When user adds more file then it simply says
"Maximum number of files exceeded"
The multiple="false" only tells the webbrowser to disable multiple file selection in the browser-specific Browse dialog. However, it indeed doesn't prevent the enduser from clicking multiple times on the Choose button of the PrimeFaces file upload section to browse and add a single file multiple times.
Your best bet is to bring in some JS/jQuery to remove all previously selected files when a new file is selected. Provided that you have given your <p:fileUpload> a widgetVar="upload", then this should do:
$(document).ready(function() {
upload.buttonBar.find('input[type=file]').change(function() {
if (this.value) {
var files = upload.uploadContent.find('.files tr');
if (files.length > 1) {
files.get(0).remove();
}
}
});
});
Works for me on PrimeFaces 3.5.
If you have file limit set to 1 and some error happens while file is loading - you have to refresh the page to get file upload work again. If you don't refresh the page you get out of limit error message.
I used solution with JS, like in accepted answer,but have to change selectors, because wigetWar did not work for me.
In my view i have :
<p:fileUpload id="objectUpload"... />
In my portlet theme, table with files has a css class of "ui-fileupload-files".
$(document).ready(function() {
$("div[id*='objectUpload']").find('input[type=file]').change(function() {
if (this.value) {
var files = $("div[id*='objectUpload']").find('.uifileupload-files tr');
if (files.length > 1) {
files.get(0).remove();
}
}
});
});
Hope it helps.I used PrimeFaces 6.0