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
Related
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
});
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'm trying to prevent defaults on a click, call a page with ajax and trigger the click on complete, using this answer.
<a id="mylink" href="file.csv" download >Dowload</a>
<script>
var flag = false;
$('#mylink').on('click',function(e) {
// Result is the same with :
//$(document).on("click","#mylink",function(e){
if (flag === true) {
flag = false;
return;
}
e.preventDefault();
$.ajax({
url: "index.php?controller=admin&action=refreshFile",
complete: function() {
console.log('control'); // This is called
flag = true;
$('#mylink').trigger('click'); // This is not called
}
});
});
</script>
The call works but the link is not triggered after. The result is the same when the ajax call is set inside a separate function.
use window.location to call the link href
$('#mylink').on('click',function(e) {
e.preventDefault();
$.ajax({
url: "index.php?controller=admin&action=refreshFile",
complete: function() {
console.log('control'); // This is called
window.location = $('#mylink').attr("href");
}
});
});
or with one event listeners
var eventListener = function(e) {
e.preventDefault();
$.ajax({
url: "index.php?controller=admin&action=refreshFile",
complete: function() {
console.log('control'); // This is called
$('#mylink')[0].click();
$('#mylink').one('click', eventListener);
}
});
};
$('#mylink').one('click', eventListener);
I'm not sure what your flag is supposed to do. In your example it would mean the link only works every 2nd click.
P.s. Using the complete callback means it also works even when the ajax fails. You might want to change it to success.
Update
#Racil Hilan has a point: this solution is a little overkill when you could just call the link directly and return the correct file after the refreshFile action has been called.
TRy
var flag = false;
$('#mylink').on('click',function(e) {
// Result is the same with :
//$(document).on("click","#mylink",function(e){
if (flag === true) {
flag = false;
windows.location="file.csv";
}
e.preventDefault();
$.ajax({
url: "index.php?controller=admin&action=fileDownload",
complete: function() {
console.log('control'); // This is called
flag = true;
$('#mylink').trigger('click'); // This is not called
}
});
});
In my humble opinion, this is not the right design. Your Ajax is calling the index.php on the server before triggering the download. If the index.php is doing some security or critical stuff that MUST be done before allowing the user to download the file, then this design is absolutely insecure. You don't even need to be a hacker, simply copy the link file.csv and paste it in your browser's address bar, and you'll get the file without the Ajax.
You need to place the file.csv file outside your website folder (or maybe it is generated on the fly by the server code, so that' good too) and then the PHP page must run all the checks and if all run OK, it reads the file (or generate it) and returns the download to the browser (or an error message if the checks failed). This is how to secure file downloads on the server.
After doing all of that, it is a matter of preference whether you call the PHP directly from your link, or the link calls the Ajax function which in turn calls the PHP page and parse the download (this is a bit more complex, but doable). The only difference between the two methods is whether you want the page refreshed when the download (or error message) come back from the server.
If you want to take this advice, rephrase your question and select which way you want to go (i.e. direct link, or through Ajax), so we can help you.
I have an html file with many <a> tags with href links.
I would like to have the page do nothing when these links point to an outside url (http://....) or an internal link that is broken.
The final goal is to have the html page used offline without having any broken links. Any thoughts?
I have tried using a Python script to change all links but it got very messy.
Currently I am trying to use JavaScript and calls such as $("a").click(function(event) {} to handle these clicks, but these have not been working offline.
Also, caching the pages will not be an option because they will never be opened online. In the long run, this may also need to be adapted to src attributes, and will be used in thousands of html files.
Lastly, it would be preferable to use only standard and built in libraries, as external libraries may not be accessible in the final solution.
UPDATE: This is what I have tried so far:
//Register link clicks
$("a").click(function(event) {
checkLink(this, event);
});
//Checks to see if the clicked link is available
function checkLink(link, event){
//Is this an outside link?
var outside = (link.href).indexOf("http") >= 0 || (link.href).indexOf("https") >= 0;
//Is this an internal link?
if (!outside) {
if (isInside(link.href)){
console.log("GOOD INSIDE LINK CLICKED: " + link.href);
return true;
}
else{
console.log("BROKEN INSIDE LINK CLICKED: " + link.href);
event.preventDefault();
return false;
}
}
else {
//This is outside, so stop the event
console.log("OUTSIDE LINK CLICKED: " + link.href);
event.preventDefault();
return false;
}
}
//DOESNT WORK
function isInside(link){
$.ajax({
url: link, //or your url
success: function(data){
return true;
},
error: function(data){
return false;
},
})
}
Also an example:
Outside Link : Do Nothing ('#')
Outside Link : Do Nothing ('#')
Existing Inside Link : Follow Link
Inexistent Inside Link : Do Nothing ('#')
Javascript based solution:
If you want to use javascript, you can fix your isInside() function by setting the $.ajax() to be non asynchronous. That is will cause it to wait for a response before returning. See jQuery.ajax. Pay attention to the warning that synchronous requests may temporarily lock the browser, disabling any actions while the request is active (This may be good in your case)
Also instead of doing a 'GET' which is what $.ajax() does by default, your request should be 'HEAD' (assuming your internal webserver hasn't disabled responding to this HTTP verb). 'HEAD' is like 'GET' except it doesn't return the body of the response. So it's a good way to find out if a resource exists on a web server without having to download the entire resource
// Formerly isInside. Renamed it to reflect its function.
function isWorking(link){
$.ajax({
url: link,
type: 'HEAD',
async: false,
success: function(){ return true; },
error: function(){ return false; },
})
// If we get here, it obviously did not succeed.
return false;
}
Python based solution:
If you don't mind preprocessing the html page (and even caching the result), I would go with parsing the HTML in Python using a library like BeautifulSoup.
Essentially I would find all the links on the page, and replace the href attribute of those starting with http or https with #. You can then use a library like requests to check the internal urls and update the appropriate urls as suggested.
Here is some javascript that will prevent you from going to external site:
var anchors = document.getElementsByTagName('a');
for(var i=0, ii=anchors.length; i < ii; i++){
anchors[i].addEventListener('click',function(evt){
if(this.href.slice(0,4) === "http"){
evt.preventDefault();
}
});
}
EDIT:
As far as checking if a local path is good on the client side, you would have to send and ajax call and then check the status code of the call (infamous 404). However, you can't do ajax from a static html file (e.g. file://index.html). It would need to be running on some kind of local server.
Here is another stackoverflow that talks about that issue.
This is similar to: How to open a file using JavaScript?
Goal: to retrieve/open a file on an image's double click
function getFile(filename){
// setting mime this way is for example only
var mime = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
jQuery.ajax({ url : 'get_file.pl',
data : {filename:filename},
success : function(data){
var win = window.open('','title');
win.document.open(mime);
win.document.write(data);
win.document.close();
}
});
}
jQuery('#imgID').dblclick(function(){
getFile('someFile.docx');
});
I'm doing this off the top of my head, but I think the above would work for text files, but not binary. Is there a plugin that does this properly? The ideal would be to open the file in the browser (or application), rather than download, but I doubt that is a dream. If the file must be downloaded with the save/open dialog, that's fine.
Edit:
One piece of information that I forgot to mention is that I'd like this to be a POST request. This is partly why I was looking at AJAX to begin with. I've seen workarounds that have created forms/iframes to do something similar, but I was looking for a better handler of the returned info.
Seems to me there's no reason to do this via AJAX. Just open the new window to get_file.pl?filename=... and let the browser handle it. If the user has a plugin capable of handling the Content-Type sent by get_file.pl, the file will display; otherwise, it should download like any other file.
function getFile(filename) {
window.open('get_file.pl?filename=' + filename,'title');
}
jQuery('#imgID').dblclick(function() {
getFile('someFile.docx');
});
Edit: If you want to POST to your script, you can do it with some <form> hackery:
function getFile(filename) {
var win = 'w' + Math.floor(Math.random() * 10000000000000);
window.open('', win,'width=250,height=100');
var f = $('<form></form>')
.attr({target: win, method:'post', action: 'get_file.pl'})
.appendTo(document.body);
var i = $('<input>')
.attr({type:'hidden',name:'filename',value:filename})
.appendTo(f);
f[0].submit();
f.remove();
}
Of course, this is somewhat silly since it is impossible to hide your data from "prying eyes" with developer tools. If your filename really is sensitive, issue access tokens to the client, and look up the data in your sever script.