views:

391

answers:

3

I have a jquery-based single-page webapp. It communicates with a RESTful web service via AJAX calls.

I'm trying to accomplish the following:

  1. Submit a POST that contains JSON data to a REST url.
  2. If the request specifies a JSON response, then JSON is returned.
  3. If the request specifies a PDF/XLS/etc response, then a downloadable binary is returned.

I have 1 & 2 working now, and the client jquery app displays the returned data in the web page by creating DOM elements based on the JSON data. I also have #3 working from the web-service point of view, meaning it will create and return a binary file if given the correct JSON parameters. But I'm unsure the best way to deal with #3 in the client javascript code.

Is it possible to get a downloadable file back from an ajax call like this? How do I get the browser to download and save the file?

$.ajax({
    type: "POST",
    url: "/services/test",
    contentType: "application/json",
    data: JSON.stringify({category: 42, sort: 3, type: "pdf"}),
    dataType: "json",
    success: function(json, status){
        if (status != "success") {
            log("Error loading data");
            return;
        }
        log("Data loaded!");
    },
    error: function(result, status, err) {
        log("Error loading data");
        return;
    }
});

The server responds with the following headers:

Content-Disposition:attachment; filename=export-1282022272283.pdf
Content-Length:5120
Content-Type:application/pdf
Server:Jetty(6.1.11)

Another idea is to generate the PDF and store it on the server and return JSON that includes a URL to the file. Then, issue another call in the ajax success handler to do something like the following:

success: function(json,status) {
    window.location.href = json.url;
}

But doing that means I would need to make more than one call to the server, and my server would need to build downloadable files, store them somewhere, then periodically clean up that storage area.

There must be a simpler way to accomplish this. Ideas?


EDIT: After reviewing the docs for $.ajax, I see that the response dataType can only be one of xml, html, script, json, jsonp, text, so I'm guessing there is no way to directly download a file using an ajax request, unless I embed the binary file in using Data URI scheme as suggested in the @VinayC answer (which is not something I want to do).

So I guess my options are:

  1. Not use ajax and instead submit a form post and embed my JSON data into the form values. Would probably need to mess with hidden iframes and such.

  2. Not use ajax and instead convert my JSON data into a query string to build a standard GET request and set window.location.href to this URL. May need to use event.preventDefault() in my click handler to keep browser from changing from the application URL.

  3. Use my other idea above, but enhanced with suggestions from the @naikus answer. Submit AJAX request with some parameter that lets web-service know this is being called via an ajax call. If the web service is called from an ajax call, simply return JSON with a URL to the generated resource. If the resource is called directly, then return the actual binary file.

The more I think about it, the more I like the last option. This way I can get information back about the request (time to generate, size of file, error messages, etc.) and I can act on that information before starting the download. The downside is extra file management on the server.

Any other ways to accomplish this? Any pros/cons to these methods I should be aware of?

+1  A: 

I think the best approach is to use a combination, Your second approach seems to be an elegant solution where browsers are involved.

So depending on the how the call is made. (whether its a browser or a web service call) you can use a combination of the two, with sending a URL to the browser and sending raw data to any other web service client.

naikus
My second approach is looking more appealing to me now too. Thanks for confirming it is a worthwhile solution. Are you suggesting that I pass an additional value in the json object that indicates this request is being made from a browser as an ajax call instead of a web service call? There are several ways to accomplish this that I can think of, but what technique you would use?
Tauren
can't that be determined by the User-Agent http header? Or any other http header?
naikus
Yes, of course. That would be a good way to do it. Thanks!
Tauren
+1  A: 

In short, there is no simpler way. You need to make another server request to show PDF file. Al though, there are few alternatives but they are not perfect and won't work on all browsers:

  1. Look at data URI scheme. If binary data is small then you can perhaps use javascript to open window passing data in URI.
  2. Windows/IE only solution would be to have .NET control or FileSystemObject to save the data on local file system and open it from there.
VinayC
Thanks, I was not aware of the Data URI scheme. It looks like that may be the only way to do it in a single request. But it isn't really the direction I want to go.
Tauren
+4  A: 

Once the binary file has been generated on the server, assuming that there is a publicly accessible URL to the generated file, a hidden iframe can be used to get the job done without using a redirect.

Here's how it can be done:

$.post('/create_binary_file.php', postData, function(retData){
  var binUrl = retData.url;
  document.body.innerHTML += "<iframe src='" + binUrl + "' style='display: none;' ></iframe>"
}); 
letronje
Ah yes, that might just do the trick! So this solution is essentially like #3, but instead of setting `window.location.href`, it uses a hidden iframe. I still get to do an ajax post with my json, and get back a json object with a URL and other data. I'll give this a try, thanks!
Tauren
This worked perfectly.
Tauren