POST to server, receive PDF, deliver to user w/ jQuery
I have a link that the user clicks to get a PDF. In jQuery, I create a POST ajax call to the server to get the PDF. The PDF comes to me with the correct content headers etc that would normally cause the browser to open the Reader plugin, or allow the user to save the PDF.
Since I am getting the PDF w/ an ajax call, I'm not sure what to do with the data that I get in the OnSucce开发者_如何学编程ss callback. How can I give the data I receive to the browser and allow it to do its default thing with the PDF response?
Take a look at - jQuery Plugin for Requesting Ajax-like File Downloads
The whole plugin
is just about 30 lines of code (including comments).
The call is fairly similar to jquery ajax call.
$.download('/export.php','filename=myPDF&format=pdf&content=' + pdfData );
Ofcourse, you have to set the content-type and Content-Disposition headers on the server side as you would for any such download.
In java I would do something like this
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename="exported.pdf");
You don't need jQuery at all. Just submit your POST via a form normally, and on the server side, add the HTTP header
Content-Disposition: attachment; filename="whatever.pdf"
The browser will do its default thing.
Alternately, if you want to be more careful about reporting any errors that might occur during the PDF generation, you can do this. POST your parameters to your server with jQuery. On the server, generate the binary content and cache it somewhere for a few minutes, accessible via a key that you put in the user's session, and return a "success" Ajax response to your page (or if there was an error, return an "error" response). If the page gets back a success response, it can immediately do something like:
window.location = "/get/my/pdf";
The server then returns the cached PDF content. Be sure to include the Content-Disposition header, as above.
The answer mentioning "jQuery Plugin for Requesting Ajax-like File Downloads" got me headed down the right direction, but it didn't work entirely for my situation since I have a complex object and array of objects to pass in as my search criteria/filter data. I figured I'd share my code in case someone else runs into this situation too.
$.download = function (url, data, method) {
if (url && data) {
//convert the data object into input HTML fields
var inputs = '';
var convertToInput = function (key, keyStr, obj) {
if (typeof obj === 'undefined') {
return;
} else if (typeof obj === "object") {
for (var innerKey in obj) {
if (obj.hasOwnProperty(innerKey)) {
var innerKeyStr = '';
if (keyStr === '') {
innerKeyStr = innerKey.toString();
} else {
innerKeyStr = keyStr + "[" + innerKey.toString() + "]";
}
convertToInput(innerKey, innerKeyStr, obj[innerKey]);
}
}
return;
} else if ($.isArray(obj)) {
obj.forEach(function (item) {
convertToInput(key, keyStr + "[]", item);
});
return;
}
inputs += "<input type='hidden' name='" + keyStr + "' value='" + obj + "' />";
};
convertToInput(null, '', data);
//send request
jQuery('<form action="' + url + '" method="' + (method || 'post') + '">' + inputs + '</form>').appendTo('body').submit().remove();
};
};
$.download('/api/search?format=csv', searchData, 'POST');
It probably doesn't make much of a difference, but to provide some context, I've got a javascript and knockout UI calling into WebAPI, MVC4, and nHibernate. The 'format=csv' part of the query string triggers a MediaTypeFormatter to convert the returned models into a CSV file type. If I leave that off, then I get the models back from the API and can populate a Slick grid for display.
I had the same problem but on top use an RESTFUL webservice
for this and have an complex data object which i must post.
My solution:
like the jQuery Plugin i build a temp formular and submit it. But i send the data object as an parameter with json content (i use here AngularJS
but it should work with jQuery.param()
too.)
Javascript:
$('<form target="_blank" action="' + appConstants.restbaseurl + '/print/pdf" method="POST">' +
"<input name='data' value='" + angular.toJson($scope.versicherung) + "' />" +
'</form>').appendTo('body').submit().remove();
on the server side we use a CXF REST Service
with an JACKSON
Provider:
Spring Config:
<jaxrs:server id="masterdataService" address="/">
<jaxrs:serviceBeans>
<ref bean="printRestServiceBean" />
</jaxrs:serviceBeans>
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
<bean class="de.controller.ExceptionHandler" />
</jaxrs:providers>
</jaxrs:server>
in the controller i extracted the param and converted it back to an Java Pojo:
package de.controller;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
@Path(Constants.PRINT_PATH)
@Consumes({ MediaType.APPLICATION_JSON, "application/x-www-form-urlencoded"})
@Produces("application/pdf; charset=UTF-8")
public class PrintRestController {
@Autowired
private PrintService printService;
@POST
@Produces("application/pdf")
@Path("/pdf")
public Response getPDF(@FormParam("data") String data) {
return printService.getPDF(json2Versicherung(data));
}
private Versicherung json2Versicherung(String data) {
Versicherung lVersicherung = null;
try {
ObjectMapper mapper = new ObjectMapper();
lVersicherung = mapper.readValue(data, Versicherung.class);
} catch(Exception e) {
LOGGER.error("PrintRestController.json2Versicherung() error", e);
}
return lVersicherung;
}
}
in the PrintService i build the pdf binary and the response:
@Override
public Response getPDF(Versicherung pVersicherung) {
byte[] result = ... //build the pdf from what ever
ResponseBuilder response = Response.ok((Object) result);
response.header("Content-Disposition", "inline; filename=mypdf.pdf");
return response.build();
}
This solution works for all browsers (even for IE9 which can't handle data url's) and on tablets and smartphone and it have no problems with popupblockers
The jQuery Plugin for Requesting Ajax-like File Downloads is - essentially - creating a form, adding the post data as hidden field(s), adding it to the body of the page, submitting it and removing it.
In my case I did not have a form, only a chunk of data to be posted as it was. That made for the following solution. On the server side I can get the data by simply reading the "data" parameter from the request and URI-decoding it.
function postAndDownload(url, data) {
encodedData = encodeURIComponent(data);
$("<form>")
.attr("action", url)
.attr("method", "post")
.append(
$("input")
.attr("type", "hidden")
.attr("name", "data")
.attr("value", encodedData)
)
.appendTo("body")
.submit()
.remove();
};
I fail to understand why you want an ajax request to a file download url! But if it's more like client itself generates some content for download - use a data uri. Works perfectly for Chrome and Firefox 20+. Safari and IE NOT! If Flash is allowed, you could use downloadifier.
Ah after reading your code, I see you want to send a bunch of parameters. Well unless the query string gets too long (IE8- has a limit of 2083) why not simply use an anchor with proper url?
$('a.export-csv').click( function (evt){
linkEl.attr('href','/export?' + encodeURIComponent(formQueryString()));
return true;
});
The above allows you to change the URL before the default event (the click) happens.
I think the best will be to create a temp pdf file in the downloads folder and then load the file using pop-up having an iframe.. chrome will load it instantly but I suppose for other variants Acrobat reader must be installed to view the pdf but again you can use FlashPaper too :)
精彩评论