Automatically open the printer dialog after providing PDF download - jsf

I am currently opening a pdf file in a new tab in my browser but I need to know how to open a printer dialog to print the pdf jasper report after pressing a commandButton
This is the method that open the pdf in a new tab:
public void printJasper() {
JasperReport compiledTemplate = null;
JRExporter exporter = null;
ByteArrayOutputStream out = null;
ByteArrayInputStream input = null;
BufferedOutputStream output = null;
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
try {
List<String> sampleList = new ArrayList<String>();
sampleList.add("Fist sample string");
sampleList.add("Second sample string");
JRBeanCollectionDataSource beanCollectionDataSource = new JRBeanCollectionDataSource(sampleList);
Map<String, Object> reportValues = new HashMap<String, Object>();
reportValues.put("anyTestValue", "test value");
facesContext = FacesContext.getCurrentInstance();
externalContext = facesContext.getExternalContext();
response = (HttpServletResponse) externalContext.getResponse();
FileInputStream file = new FileInputStream("/any_dir/sample.jasper");
compiledTemplate = (JasperReport) JRLoader.loadObject(file);
out = new ByteArrayOutputStream();
JasperPrint jasperPrint = JasperFillManager.fillReport(compiledTemplate, reportValues, beanCollectionDataSource);
exporter = new JRPdfExporter();
exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, out);
exporter.exportReport();
input = new ByteArrayInputStream(out.toByteArray());
response.reset();
response.setHeader("Content-Type", "application/pdf");
response.setHeader("Content-Length", String.valueOf(out.toByteArray().length));
response.setHeader("Content-Disposition", "inline; filename=\"fileName.pdf\"");
output = new BufferedOutputStream(response.getOutputStream(), Constants.DEFAULT_BUFFER_SIZE);
byte[] buffer = new byte[Constants.DEFAULT_BUFFER_SIZE];
int length;
while ((length = input.read(buffer)) > 0) {
output.write(buffer, 0, length);
}
output.flush();
} catch (Exception exception) {
/* ... */
} finally {
try {
if (output != null) {
output.close();
}
if (input != null) {
input.close();
}
} catch (Exception exception) {
/* ... */
}
}
facesContext.responseComplete();
}
This is the button that open the pdf file:
<p:commandButton action="#{sampleBB.printJasper}"
ajax="false" onclick="this.form.target='_blank'"
value="#{msg['generate.report']}" />
What I need to do?

With JasperReports
When using JasperReports, simply add this parameter to JasperReports exporter:
exporter.setParameter(JRPdfExporterParameter.PDF_JAVASCRIPT, "this.print();");
This basically instructs Adobe Acrobat to execute the script this.print() when opening the PDF. See also page 79-80 of Adobe Acrobat Scripting Guide. Below is an extract of relevance:
Printing PDF Documents
It is possible to use Acrobat JavaScript to specify whether a PDF document is sent to a
printer or to a PostScript file. In either case, to print a PDF document, invoke the doc
object’s print method. [...]
Without JasperReports
If you don't have control over generation of PDFs and thus can't manipulate it to add the mentioned script, an alternative is to change all the Java/JSF code accordingly so that the PDF file is idempotently available (i.e. the PDF file must be available by just a GET request rather than a POST request). This allows you to embed it in an <iframe> for which it's in turn possible to print its content by JavaScript during onload (keep CORS in mind though).
Simply put, the enduser must be able to download the desired PDF file by just entering its URL in browser's address bar. You can of course make use of GET request query string to specify parameters, allowing a bit more dynamicness. If it's "very large" data, then you can always let JSF put it in the HTTP session or DB and then pass an unique identifier around as request parameter so that the servlet can in turn obtain it from the very same HTTP session or DB.
Whilst possible with some nasty hacks, a JSF backing bean is simply insuitable for the job of idempotently serving a non-JSF response. You'd better use a "plain vanilla" servlet for this. You'll end up with much simpler code. Here's a kickoff example of such a servlet:
#WebServlet("/pdf")
public class PdfServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String foo = request.getParameter("foo");
String bar = request.getParameter("bar");
// ...
// Now just use the same code as in your original bean *without* FacesContext.
// Note that the HttpServletResponse is readily available as method argument!
response.setContentType("application/pdf");
// ...
}
}
With this setup, it's available by http://localhost:8080/context/pdf?foo=abc&bar=xyz.
Once you get that part to work, then you just have to reference it in an <iframe> which uses JavaScript to print its own content window during its load event. You can do this in a JSF page, e.g. /pdf.xhtml:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
>
<h:head>
<style>html, body { height: 100%; margin: 0; overflow: hidden; }</style>
</h:head>
<h:body>
<iframe src="#{request.contextPath}/pdf?#{request.queryString}"
width="100%" height="100%" onload="this.contentWindow.print()" />
</h:body>
</html>
All you need to do in your JSF backing bean is to send a redirect to that page, if necessary with parameters in request query string (which will end up in #{request.queryString} so that the servlet can obtain them via request.getParameter(...)).
Here's a kickoff example:
<h:form target="_blank">
<h:commandButton value="Generate report" action="#{bean.printPdf}" />
</h:form>
public String printPdf() {
// Prepare params here if necessary.
String foo = "abc";
String bar = "xyz";
// ...
return "/pdf?faces-redirect=true"
+ "&foo=" + URLEncoder.encode(foo, "UTF-8")
+ "&bar=" + URLEncoder.encode(bar, "UTF-8");
}

There is a <p:printer> Primefaces's component for that purpose.
Something like this might work, not tested though.
<h:form>
<h:commandButton value="Print" type="button" icon="ui-icon-print">
<p:printer target="pdf" />
</h:commandButton>
<p:media style="display:none;" id="pdf" value="/aPDF.pdf" />
</h:form>
Note :
<p:media> does have a print button to print the displayed pdf.
Edit :
You have to embed the pdf file inside an iframe and use the JavaScript print() function on it, or you can activate the auto-print function in the PDF itself. But it is definitely possible.
See this question on SO : Can a PDF file's print dialog be opened with Javascript?
How to Use JavaScript to Print a PDF

You can't print a URL directly from JavaScript, you can only open the print dialog for the existing page - article and print API.
PDF is generated on the server and sent to the web-browser (as a separate "page") which has to decide how to process it - the user is usually asked if it wants to display or save the PDF.
To "automatically print" (i.e. open a print dialog) an HTML page you would just have something like this:
window.onload = function() {
window.print();
};
But that can't be done for a PDF since it's not an HTML page.
To "automatically print" something other than an HTML page you would need to have a web-browser plug-in to handle PDFs from your server.
Another posibility is to write a GreaseMonkey user-script that would react on *.myserver.com/**.pdf and have it printed. Note: GreaseMonkey is a Mozilla Firefox plug-in.
Heavy weight option
You could accomplish your task by adding print support to your server application. Application requirements:
It would have to be an Intranet application (self-hosted inside the user's network),
Admin user would need to register network printers accessible from the server via a JSP page,
A "print dialog" page where you would select a registered printer and on clicking the "Print" button send a "print" request, for example:
/print?printer=printer1&doc=/reports/report1
I have seen a Java web application that supported this, but as you can see, it's not an easy task.
#Sujan Sivagurunathan
I tried combining the p:printer and p:media by replacing the image on the p:printer demo page with the PDF file from the p:media demo page:
// Replace this line:
<img id="j_idt18:image" src="/showcase/images/nature1.jpg?pfdrid_c=true" alt="">
// With this one:
<object **id="j_idt18:image"** style="display:none;" type="application/pdf" data="/showcase/resources/other/guide.pdf?pfdrid_c=true">Undefined</object>
When you click the Print button you get an empty page. If you omit the style="display:none;" and leave the PDF's height="300px" width="100%" you will get a small rectangle on the page print preview.
Eidt
Thank you, BalusC and Sujan. I agree, there is an option of embedding JavaScript inside the PDF, but that's usually disabled for security reasons.
I suppose the most browser-compatible and user-friendly way is to have a dedicated Print Preview pop-up window with an iframe showing the given PDF via GET request and a Print button to invoke the IFRAME's contentWindow.print().
It is generally a bad idea to just print a document without letting the user select the printer and configure it.

Related

Update doesn't work when call sendRedirect (HttpServletResponse) [duplicate]

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.

uploading two files with JSF results in two times the same file

I have a JSF page + Bean to upload a file and preview the result (it's for images). In the bean the upload method puts the data (base64) in a list and the JSF page loops through the list to display the images.
So far it works as expected for the first image. But when I upload a second image there are two things that might go wrong:
the first image gets uploaded again instead
Or, with a little change in the code the second image does not get uploaded at all until I reselect the file again and upload it.
JSF page
<h:form enctype="multipart/form-data" id="uploadForm" prependId="false">
<!-- here I loop through the list to preview the images -->
<c:forEach var="image" items="#{bean.imageList}">
<h:graphicImage value="#{image}" />
</c:forEach>
<!-- that's the upload -->
<h:inputFile id="file" value="#{bean.uploadedFile}" />
<h:commandButton value="submit" action="#{bean.uploadFile}" />
</h:form>
bean
private Part uploadedFile; // the Part with the uploaded file
private ArrayList<String> imageList; // the list of images (base64 encoded)
public void uploadFile() throws IOException{
if(null != uploadedFile){
String imageType = "data:" + uploadedFile.getContentType() + ";base64,"; // will be something like: "data:image/png;base64,"
InputStream inputStream = uploadedFile.getInputStream(); // getting the inputStream from the Part
byte[] imageData; // array to but the image data
try(BufferedInputStream bis = new BufferedInputStream(inputStream)){ // converting the inputStream to a buffered one
imageData = new byte[bis.available()]; // initializing the byte array with the right size
bis.read(imageData); // reading / writing the image data
// HERE: if I comment out the next line, the second upload will upload the first image again.
// but if I set the Part to null, the second upload will result in nothing (page reloads, no data uploaded) and I have to upload it again
uploadedFile = null;
}
imageList.add(imageType + javax.xml.bind.DatatypeConverter.printBase64Binary(imageData)); // this adds the base64 image string to the list of images
}
}
My bean is #ViewScoped (I need it to be that way for other things).
So my guess was that the Part uploadedFile just didn't get the new image data for the second file, but as I said, when setting it to null it just skips the second upload process.
Does anyone see what I am doing wrong?
You might try the following.
byte[] imageData = new byte[uploadedFile.getSize()];
try (BufferedInputStream bis = new BufferedInputStream(inputStream)) {
bis.read(imageData);
uploadedFile.delete();
}
Using Part.getSize()
Not relying on available() which makes no guarantee that all is available
Using delete() to clear the bean
(Not tried it myself.)

p:remoteCommand returns <eval> twice in ajax response

I try to render a new page in a new window (or tab) with the link I get from a selected page object in an autoComplete component.
After trying multiple options the only chance in my opinion is to use javascript to catch the submit, trigger a remote command, that gives me a javascript call with the link attribute from the page object.
JSF-Snipplet (with reduced attributes in autoComplete)
<h:form id="autoCompleteForm">
<p:autoComplete id="autoComplete" widgetVar="autoCompleteWidget" value="#{pageBean.selectedPage}" />
<p:remoteCommand action="#{pageBean.showPage}" name="showPage" />
</h:form>
some JS:
// form submit
('#autoCompleteForm').on('submit', function(e) {
e.preventDefault();
showPage();
});
// open link
var openLink = function(pageLink) {
window.open(pageLink, '_blank');
};
Bean part for action
public void showPage() {
RequestContext context = RequestContext.getCurrentInstance();
context.execute("openLink('" + selectedPage.getLink() + ".xhtml')");
}
Everything works nice together, but the response contains the eval tag twice.
<partial-response>
<changes>
<update id="javax.faces.ViewState"><![CDATA[2851816213645347690:-2276123702509360418]]></update>
<eval><![CDATA[openLink('target.xhtml');]]></eval>
<eval><![CDATA[openLink('target.xhtml');]]></eval>
</changes>
</partial-response>
I tried different approaches with redirects or returning view names, but that all leads to no satisfying solutions (e.g. URL not changing or no new window).
Problem solved:
I had defined PrimePartialViewContextFactoryin my faces-config before:
<factory>
<partial-view-context-factory>org.primefaces.context.PrimePartialViewContextFactory</partial-view-context-factory>
</factory>
By removing it the application acts like expected.
This also solves a problem (JSON.parse: unexpected non-whitespace character after JSON data) with pagination and sorting in DataTables.

how to open page in new window NOT tab using h:commandLink?

Using Primefaces 4.0 and JSF 2.2.
I would like to send the visitor to a page in a new window (not tab). The landing page is a jsf page called grade.xhtml
I tried:
<h:commandLink value="OK" onclick="window.open('grade.html', 'newwindow','width=300, height=300'); return false;"/>
This gets to a 404. Obviously this is all happening client side so grade.html doesn't get generated from grade.xhtml. How should I do?
Note: if I put onclick="window.open('grade.xhtml', 'newwindow','width=300, height=300'); return false;" then the page does open, but it is the jsf (xhtml code) not html version that shows up.
try this:
<h:commandLink value="OK" action="grade.xhtml" target="_blank"/>
Look an example here: When i click on any link it should Open in the same New Window in JSF, Primefaces
I use javaScript for this.. Try it:
<script type="text/javascript" language="javascript">
function funcForWindowPopup()
{
var popup = null;
popup = window.open("grade.xhtml", "popup", "toolbar=no,menubar=no,scrollbars=yes,location=no,left=350,top=50,width=650, height=600");
popup.openerFormId = "formID";
popup.focus();
}
</script>
You can change parameters as you need;)
Open the URL in new window from JSF controller.
HttpServletResponse res = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
HttpServletRequest req = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
String uri = req.getRequestURI();
res.getWriter().println("<script>window.open('" + myUrl + "','_blank', 'location=yes,height=600,width=800,scrollbars=yes,status=yes'); window.parent.location.href= '"+uri+"';</script>");
FacesContext.getCurrentInstance().responseComplete();

How to show java (jsf) generated pdf directly in AdobeReader

i am using JSF 2.0 (Eclipse IDE) and i have a method in one of my beans which generates some PDF files using Apache FOP 1.0. When I click "makePDF" button on my JSF page, generated PDF opens in the SAME window where my page is (and i have to use 'back' button after i view/save/print PDF).
I can change this in a way that when "makePDF" button is pressed, standard pop-out dialog appears and asks me if i wish to save file or to open it with AdobeReader.
Is there a way that, when i click my "makePDF" button, generated PDF is directly opened in AdobeReader (and i still have my JSF page and dont need to confirm anything) ??? Also (it's not really needed, but good to know), is there a way to open it in new window or send it directly to printer?
Here is the main part of my code (i deleted some irrelevant stuff) :
public void makePDF(long contractId, int documentType) {
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
String basePath = externalContext.getRealPath("/");
try {
fopFactory.setUserConfig(new File(basePath + "/pdf_transform/config/userconfig.xml"));
fopFactory.setBaseURL(basePath);
fopFactory.getFontManager().setFontBaseURL(basePath);
} catch (Exception e) {
e.printStackTrace();
}
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
foUserAgent.setBaseURL(fopFactory.getBaseURL());
HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
response.reset();
response.setHeader("Content-Type", "application/pdf");
response.setHeader("Content-disposition", "attachment");
BufferedOutputStream output = null;
try {
output = new BufferedOutputStream(response.getOutputStream(), 10240);
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, output);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer(new StreamSource(xsltfile));
Source src = new DOMSource(makeXML(contract)); // my method
Result res = new SAXResult(fop.getDefaultHandler());
transformer.transform(src, res);
} catch (Exception e) {
e.printStackTrace();
}
facesContext.responseComplete();
}`
When i remove response.setHeader("Content-disposition", "attachment"); it opens in the same window, otherwise it asks me to save or open.
Thanks in advance.
Opening a request in a new window is not directly a JSF concern. It's more a HTML concern.
Add target="_blank" to the form
<h:form target="_blank">
and it'll submit the request into a new tab/window.
Sending to the client's printer directly is not possible without introducing some piece of code which runs at the client side as man in the middle, like an applet.
I had a similar problem and luckily found an elegant way to solve it. If someone has a better solution then please let me know.
Use case:
User enters data in a JSF generated form necessary for PDF creation (PDF is generated from a servlet). If validation fails, error messages are rendered but if everything goes fine pdf file opens in a new window.:-)
My solution:
In a template.xhtml (or the site which defines facelets) enter:
<ui:define name="head">
<h:outputScript rendered="#{myBean.pdfDownloadable}">
window.open('#{myBean.pdfUrl}',"pdfWindow","width=800,height=900,left=50,top=10")
</h:outputScript>
</ui:define>
Your command link in the JSF-form points to action method in myBean as well (e.g. myBean.showPdf())
If form data are valid and showPdf() is called you can set the pdfDownloadable field to true and the same outcome is rendered again but this time with window.open() in it.
Hope it helps somebody..
if you want to open a PDF directly, pls use
response.setHeader("Content-Disposition", "inline");

Resources