I am delivering a jasper report as a PDF for download. But when I do it I become unable to make the page to reload or redirect.
The page that causes the download to take place uses a form submission to start the file download. The answer provided by Govinda Sakhare was posted before I made this clarification. However his answer could be implemented with little work as the form only has one choice (big or small).
The function that handles the server response is below:
private void generateReportPDF(JasperReport jasperReport, List<? extends Object> data, Map<String, Object> parameters, HttpServletResponse resp) throws Exception {
byte[] bytes = null;
if( data == null ) {
bytes = JasperRunManager.runReportToPdf(jasperReport, parameters, reportDao.getConnection());
}
else {
bytes = JasperRunManager.runReportToPdf(jasperReport, parameters, new JRBeanCollectionDataSource(data));
}
resp.reset();
resp.resetBuffer();
resp.setContentType("application/pdf");
resp.addHeader("Content-Disposition", "attachment; filename=" + jasperReport.getName() + ".pdf");
resp.setContentLength(bytes.length);
ServletOutputStream ouputStream = resp.getOutputStream();
ouputStream.write(bytes, 0, bytes.length);
ouputStream.flush();
ouputStream.close();
return;
}
Because of this function, my controller is unable to redirect the page and I get the following error when I try to.
SEVERE: Servlet.service() for servlet [myapp-dispatcher] in context with path [/myapp] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed] with root cause
java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed
I want to reload the page. This is on a label printing screen and after the file download, I want the page to reload/redirect to itself so that labels which were printed are displayed in their new spot. Since the controller is unable to redirect, I have to modify the original process which is handling the response or implement JavaScript to occur after the file has been dealt with.
This question asks about catching downloads and responding to them, but does not involve spring so the answers would require more changes than I want to make.
Using one of the answers from the question I managed to make a workaround but its not satisfactory to me.
$(document).ready(function() {
$('#printBtn').click(function() {
if(confirm('<spring:message code="print.alert.confirm"/>') ? true : false)
{
window.addEventListener('focus', window_focus, false);
function window_focus(){
//remove buttons
var elem1 = document.getElementById('printBtn');
elem1.parentNode.removeChild(elem1);
//elem1.parentNode.replaceChild(newbutton);
var elem2 = document.getElementById('clearBtn');
elem2.parentNode.removeChild(elem2);
var elem3 = document.getElementById('restoreBtn');
elem3.parentNode.removeChild(elem3);
document.getElementById('button_spot').innerHTML = "The page will reload after you get the file.";
//watch for page to lose focus due to download dialog box
window.addEventListener('focusout', pageNoFocus);
function pageNoFocus(){
//watch for page to resume focus
window.removeEventListener('focusout', pageNoFocus);
window.addEventListener('focus', pageFocus);
function pageFocus(){
window.removeEventListener('focus', pageFocus);
location.reload();
}
}
}
return true;
}
return false;
});
$('#clearBtn').click(function() {
return confirm('<spring:message code="print.alert.clear"/>') ? true : false;
});
$('#restoreBtn').click(function() {
return confirm('<spring:message code="print.alert.restore"/>') ? true : false;
});
});
You can use the following snippet to download the file and redirect to some URL.
Download
<a href="downloadfileURL" target="_blank" id="downloadFile" />
<!-- change href with Spring mapping which will download the file -->
On click of Download link, it will click anchor which points to the actual
URL.
The file will be downloaded, followed by that it will redirect to the window.location.href
$("#download").click(function () {
$("#downloadFile")[0].click();
window.location.href = "/abc.html"; // change to the desired URL
});
If you download the file via form submission use below snippet.
$("#formId").click(function (e) {
e.preventDefault();
$("#downloadFile")[0].click();
window.location.href = "/abc.html"; // change to the desired URL
});
Related
I have a script main.js that consists of the following code:
function showListing() {
var url = $("#url")[0].value
var listingID = extractListingID(url)
if (listingID) {
alert(`ListingID is ${listingID}`)
location.href = `/listing/${listingID}`
}
}
function extractListingID(url) {
var found = url.match(/\d{9,}/)
if (found)
return found[0]
}
I expect it to redirect to /listing/XXX, where XXX is the value returned from extractListingID(). However, it redirects to /? (e.g., 127.0.0.1:5000/? on the debug server).
Here's the network log from the devtools:
The server is a locally run python/flask application that successfully returns a page when I open it directly at https://127.0.0.1:5000/listing/XXX.
Any idea why that happens?
It turns out the script was called from a button that didn't have a type set. So, after pressing this button, the showListing() function started along with the standard submit process that interfered with it.
After setting the button to type="button" everything works fine.
Even though the file is downloading, I am not able to get pop up message or even label lblDownload text not changing inside 'if condition' if it goes for 'else' condition pop up message coming.
protected void btn1_Click(object sender, EventArgs e)
{
string filePath = txt1.Text;
if (filePath != "")
{
lblDownloadS1.Text = "File downloaded successfully please check in downloads";
Response.Write("<script>alert('File downloaded succesfully')</script>");
Response.ContentType = ContentType;
Response.AppendHeader("Content-Disposition", "attachment; filename=" + Path.GetFileName(filePath));
Response.WriteFile(filePath);
Response.End();
}
else
{
Response.Write("<script>alert(' Specified file not exist')</script>");
}
}
Currently you are popup before Downloading start
Put Response.write (popup script)
Before
Response.end()
Try it
You cannot. The code for the file download must end with Response.End() like you did, and cannot write to the response after that. Similarly, although you changed lblDownloadS1.Text before Response.End(), it still won't show up because the download effectively cancelled it. In other words, you cannot do anything on a download page other than setting the headers. The body of the page must be the downloaded file and nothing else.
The only way to do it is to make the page that downloads the file a popup. So you cannot use ASP.Net button click event. Instead, make it an HTML button (or link) that calls a JavaScript function. The JavaScript function opens the download page as a popup, and then it display the alert. However, this will display the alert shorty after the download, and there is no way to wait for the download to finish. For that reason, it is better to use a <div> popup instead of alert. The JavaScript function can also change the text of label and anything else you want to do on the page.
string filePath = txt1.Text;
if (filePath != "")
{
lblDownloadS1.Text = "File downloaded successfully please check in downloads";
Response.Write("<script>alert('File downloaded succesfully')</script>");
Response.ContentType = ContentType;
Response.AppendHeader("Content-Disposition", "attachment; filename=" + Path.GetFileName(filePath));
Response.WriteFile(filePath);
// try this --- Response.Write("<script>window.open('lblDownloadS1.Text'-blank');</script>");
Response.End();
}
else
{
Response.Write("<script>alert(' Specified file not exist')</script>");
}
I'm trying to somewhat replicate what I saw in this question, particularly in this answer, but not quite the same.
My intent is, if the zip has no files (it can happen because the folder could be empty) I want to return an alert just so the user is warned that is not possible to obtain the file at the time.
But I'm missing on the redirection point, I don't want the alert to redirect the user to a blank page refering the Action, I want it to stay in the page, also due to some filters.
Is this possible? I couldn't find anything that would stop the redirection from happening.
Here is my the Action Controller code:
public ActionResult DownloadZip(List<int> things)
{
// Create zip with files
if (!zip.Any())
{
return Content(#"<script language='javascript' type='text/javascript'>
alert('Message');
</script>
");
}
// Return zip
}
Here is the call from the view:
$("#btnExportToZip").on("click", function (e) {
var grid = $("#gridThings").data("kendoGrid");
var items = grid.dataSource.data();
var lstIds = [];
$.each(items, function (index, elem) {
if (elem.Checked) {
lstIds.push(elem.Id);
}
});
if (lstIds.length > 0) {
var params = lstIds.join("&listAmostras=")
var url = '/Search/DownloadZip?listAmostras=' + params;
window.location.href = url;
}
});
If you do a redirect as you're doing here, it's too late to take it back once you've determined the zip file is empty. Your best bet here is probably to do an AJAX file download. Bear in mind, though, that this will require that the browser supports the HTML5 File API, so IE 9 and under are out.
$.ajax({
url: url,
async: false,
xhrFields: {
responseType: 'blob'
},
success: function (data) {
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = 'myfile.pdf';
a.click();
window.URL.revokeObjectURL(url);
}
});
Essentially what this does is request the zip file via AJAX. Once the file data has been received, an anchor link is added to the DOM (not visible) and dynamically "clicked" to approximate the behavior of user click a link to a static file. In other words, a download prompt will pop as soon as the AJAX request completes successfully. However, this code only removes the need to redirect. You still need to conditionally pop the download only if the zip file has something in. There's two ways you can accomplish that.
In the success callback of the AJAX, you would wrap the code there in a conditional that checks that data.size > 0. However, that might not actually work. I've never looked at an empty zip file, but it's entirely possible that there's file headers in the binary that would cause the blob to actually have a size greater than zero, even though it's "empty".
The better approach is to return an error response in your zip action when the zip file is empty. Off the top of my head, I'm not sure what the most appropriate error response code would be, but anything in 400-500 range will work for triggering the appropriate AJAX callback. Then, you just need to add and error handler to this AJAX. In that handler, you could then notify the user however you like that there's no download because the zip would be empty.
As per my understanding, alert is redirect the user to the blank page because in the javascript you have the line window.location.href = url; which might be redirect to the same action again which shows the alert.
So try to give the different url to the window.location.href
for ex:window.location.href = '../somecontroller/someaction';
thanks
Karthik
I am working in Spring 3, Java, JSP, javascript, and jquery, using Ajax occasionally. I have server functions that generate a PDF; I have a new requirement to show a "preview" of a PDF document.
I have code that generates the PDF document, that works fine. I can show it by hacking my source to display it in the place we normally show the un-watermarked completed document, so I know the generation of the PDF is working.
What I now want to do is display that PDF in its own tab as the result of clicking on a button (or link) on our web page. There are a few restrictions:
I have a bunch of data to pass up to the controller from the web page, data that it needs to generate the PDF. We have code that does this through a POST method, and use Ajax to post the necessary data.
It would be inconvenient for the PDF to show up in the same window as the button clicked to show the PDF; a popup asking if the user wants to download or view elsewhere is fine. The users aren't sophisticated enough to depend on theiri knowledge of the 'back' button here. So we want the PDF to show up elsewhere, preferably on another tab in the window but another entire window would be ok.
I have the following in my controller at the moment:
response.setContentLength(pdfGenerated.length);
response.setContentType("application/pdf");
response.setHeader("Expires", "0");
response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
response.setHeader("Pragma", "public");
response.setHeader("Content-Disposition", "attachment;filename=\"Preview.pdf\"");
ServletOutputStream out = response.getOutputStream();
out.write(pdfGenerated); // (encodedPdf);
out.flush();
out.close();
The ajax call looks like this:
$("#generatePDFPreview").live("click", function() {
var gridData = getCorrectedGridData();
var valid = validateContractContent(gridData);
if (valid) {
// the call below saves the contract data and then generates its PDF
$.ajax({
url: getModelObject("generatePDFPreviewURL")
,type:'POST'
,data: {'editedContents':JSON.stringify(gridData)}
,datatype: "application/pdf"
,async: false
,success: function(data) {
if(data != null && data.length>0 && data != "Error") {
//data must be contract id...use it to build the complete URL.
//window.location.href = getModelObject("deliveryScreenURL") + data;
window.open("data:application/pdf;base64, " + data);
} else {
alert("PDF preview not generated...Data returned is not ok. Please try again or contact Sales Support.");
}
}
});
}
});
I have tried different things here; I have left off the 'success' function entirely; I have tried encoding the data (base64) and returning that, and using data:application/pdf, etc., but that failed -- I have some evidence that the PDF data was too long for this, but am not sure (it was 85k-90k, the URL string stopped at something like 32784).
I am not worried about whether my user has the PDF reader installed. They must have it installed to use this and other parts of the application.
It is frustrating to be so close; all evidence is that we have most of the pieces in place, it's should just be a matter of telling the browser that we want it to use the PDF Reader to handle these bytes.
Can someone point us to a method, or point out what's wrong with what we've got now?
I have a button on my asp.net page that does a postback, creates an Excel file, clears the response stream and writes the file. The user can then open or save the file user the brower's standard dialog.
This works great, I based the code on this:
http://www.adventuresindevelopment.com/2009/05/27/how-to-export-data-to-excel-in-aspnet/
As the file to be created takes quite a long time I have created a loading panel, just a hidden DIV, and set this to visible when the button is clicked.
But my problem is how to hide this DIV when the export has finished? I just cannot find a way of doing it. I need something like an event that fires when the file has been completely transfered to the browser.
Is this possible? Any help most appreciated.
Thanks,
AJ
What I'd do, long story short :
When the user clicks the "Download" button, use AJAX to call a
processing page asynchronously. This page will generate your Excel
document and store it in a temporary location
When the AJAX request is done, hide the "Loading" panel, and
redirect the user to a download page. Ideally, you should redirect
to a generic (.ashx) handler that opens the file, sets some headers,
streams the temporary file to the user, and deletes the file
afterwards.
Now in more details :
For step one, you should have some temporary folder where you have read and write access. Using the system temp folder is fine, so you could use Path.GetTempFileName. Here is an example of what you could write in an ashx handler :
public class Handler1 : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
string fName = Path.GetTempFileName();
context.Response.ContentType = "text/plain";
try
{
// Generate the Excel document
GenerateExcelInFile(fName);
// Store the file name in session for later use
context.Session["ExcelGeneratorFileName"] = fName;
// Send confirmation to the client
context.Response.Write("ok");
}
catch (Exception e)
{
context.Response.Write("error");
// TODO : Do some logging
}
}
// SNIP : IsReusable
}
After that, use your favorite JS framework to request that handler, and test the returned string. If it is "ok", you call the part two handler :
public class Handler2 : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "application/excel";
// Make sure the browser will show a "save as" dialog to the user
context.Response.AddHeader("content-disposition", "attachment; filename=Export.pdf");
string fName = context.Session["ExcelGeneratorFileName"] as String;
if (fName != null && File.Exists(fName))
{
// Stream the excel file to the response
context.Response.WriteFile(fName);
// Remove the file
File.Delete(fName);
}
}
// SNIP : IsReusable
}
You can call this page in javascript simply using a window.location = url. The content-disposition header will tell the browser that this URL should not be displayed, only downloaded, so your user should stay on the download page.