Tau*_*ren 241 javascript ajax rest jquery
我有一个基于jquery的单页webapp.它通过AJAX调用与RESTful Web服务进行通信.
我正在努力完成以下任务:
我现在有1和2工作,客户端jquery应用程序通过基于JSON数据创建DOM元素来显示网页中返回的数据.从Web服务的角度来看,我也有#3工作,这意味着如果给出正确的JSON参数,它将创建并返回二进制文件.但我不确定在客户端javascript代码中处理#3的最佳方法.
是否有可能从这样的ajax调用中获取可下载的文件?如何让浏览器下载并保存文件?
$.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;
}
});
Run Code Online (Sandbox Code Playgroud)
服务器响应以下标头:
Content-Disposition:attachment; filename=export-1282022272283.pdf
Content-Length:5120
Content-Type:application/pdf
Server:Jetty(6.1.11)
Run Code Online (Sandbox Code Playgroud)
另一个想法是生成PDF并将其存储在服务器上并返回包含该文件的URL的JSON.然后,在ajax成功处理程序中发出另一个调用,执行以下操作:
success: function(json,status) {
window.location.href = json.url;
}
Run Code Online (Sandbox Code Playgroud)
但这样做意味着我需要对服务器进行多次调用,而我的服务器需要构建可下载的文件,将它们存储在某处,然后定期清理该存储区域.
必须有一种更简单的方法来实现这一目标.想法?
编辑:在查看$ .ajax的文档后,我看到响应dataType只能是其中之一xml, html, script, json, jsonp, text
,所以我猜测没有办法使用ajax请求直接下载文件,除非我在使用中嵌入二进制文件@VinayC答案中建议的数据URI方案(这不是我想做的事情).
所以我想我的选择是:
不使用ajax而是提交表单帖子并将我的JSON数据嵌入到表单值中.可能需要搞乱隐藏的iframe等.
不使用ajax而是将我的JSON数据转换为查询字符串以构建标准GET请求并将window.location.href设置为此URL.可能需要在我的单击处理程序中使用event.preventDefault()以防止浏览器从应用程序URL更改.
使用我上面的其他想法,但通过@naikus答案的建议增强.使用一些参数提交AJAX请求,该参数允许Web服务知道通过ajax调用调用它.如果从ajax调用调用Web服务,只需返回带有URL的JSON到生成的资源.如果直接调用资源,则返回实际的二进制文件.
我想的越多,我越喜欢最后一个选项.通过这种方式,我可以获得有关请求的信息(生成时间,文件大小,错误消息等),我可以在开始下载之前对该信息采取行动.缺点是服务器上的额外文件管理.
还有其他方法吗?我应该注意这些方法的任何利弊?
Sam*_*ens 168
letronje的解决方案仅适用于非常简单的页面.document.body.innerHTML +=
获取正文的HTML文本,附加iframe HTML,并将页面的innerHTML设置为该字符串.这将消除您的页面具有的任何事件绑定,以及其他内容.创建一个元素并appendChild
改为使用.
$.post('/create_binary_file.php', postData, function(retData) {
var iframe = document.createElement("iframe");
iframe.setAttribute("src", retData.url);
iframe.setAttribute("style", "display: none");
document.body.appendChild(iframe);
});
Run Code Online (Sandbox Code Playgroud)
或者使用jQuery
$.post('/create_binary_file.php', postData, function(retData) {
$("body").append("<iframe src='" + retData.url+ "' style='display: none;' ></iframe>");
});
Run Code Online (Sandbox Code Playgroud)
这实际上是做什么的:用变量postData中的数据对/create_binary_file.php执行一个帖子; 如果该帖子成功完成,请将新的iframe添加到页面正文中.假设来自/create_binary_file.php的响应将包含值'url',这是可以从中下载生成的PDF/XLS/etc文件的URL.假设Web服务器具有适当的mime类型配置,将iframe添加到引用该URL的页面将导致浏览器促使用户下载该文件.
Jos*_*rke 41
我一直在玩另一个使用blob的选项.我已经设法让它下载文本文档,我已下载PDF(但它们已损坏).
使用blob API,您将能够执行以下操作:
$.post(/*...*/,function (result)
{
var blob=new Blob([result]);
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="myFileName.txt";
link.click();
});
Run Code Online (Sandbox Code Playgroud)
这是IE 10 +,Chrome 8 +,FF 4+.请参阅https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL
它只会在Chrome,Firefox和Opera中下载该文件.这使用锚标记上的download属性来强制浏览器下载它.
ame*_*rsk 15
我知道这种老了,但我想我已经想出了一个更优雅的解决方案.我有同样的问题.我对解决方案提出的问题是,他们都要求将文件保存在服务器上,但我不想将文件保存在服务器上,因为它引入了其他问题(安全性:文件可以通过以下方式访问:未经过身份验证的用户,清理:如何以及何时删除文件).和你一样,我的数据是复杂的,嵌套的JSON对象很难放入表单中.
我所做的是创建两个服务器功能.第一个验证了数据.如果有错误,将返回.如果不是错误,我将所有参数序列化/编码的参数作为base64字符串返回.然后,在客户端上,我有一个只有一个隐藏输入并发布到第二个服务器功能的表单.我将隐藏的输入设置为base64字符串并提交格式.第二个服务器功能解码/反序列化参数并生成文件.表单可以提交到页面上的新窗口或iframe,文件将打开.
还有一些工作涉及,也许还有一点点处理,但总的来说,我觉得这个解决方案要好得多.
代码在C#/ MVC中
public JsonResult Validate(int reportId, string format, ReportParamModel[] parameters)
{
// TODO: do validation
if (valid)
{
GenerateParams generateParams = new GenerateParams(reportId, format, parameters);
string data = new EntityBase64Converter<GenerateParams>().ToBase64(generateParams);
return Json(new { State = "Success", Data = data });
}
return Json(new { State = "Error", Data = "Error message" });
}
public ActionResult Generate(string data)
{
GenerateParams generateParams = new EntityBase64Converter<GenerateParams>().ToEntity(data);
// TODO: Generate file
return File(bytes, mimeType);
}
Run Code Online (Sandbox Code Playgroud)
在客户端上
function generate(reportId, format, parameters)
{
var data = {
reportId: reportId,
format: format,
params: params
};
$.ajax(
{
url: "/Validate",
type: 'POST',
data: JSON.stringify(data),
dataType: 'json',
contentType: 'application/json; charset=utf-8',
success: generateComplete
});
}
function generateComplete(result)
{
if (result.State == "Success")
{
// this could/should already be set in the HTML
formGenerate.action = "/Generate";
formGenerate.target = iframeFile;
hidData = result.Data;
formGenerate.submit();
}
else
// TODO: display error messages
}
Run Code Online (Sandbox Code Playgroud)
有一种更简单的方法,创建一个表单并发布它,如果返回的mime类型是浏览器打开的东西,这会冒着重置页面的风险,但对于csv而言这是完美的
示例需要下划线和jquery
var postData = {
filename:filename,
filecontent:filecontent
};
var fakeFormHtmlFragment = "<form style='display: none;' method='POST' action='"+SAVEAS_PHP_MODE_URL+"'>";
_.each(postData, function(postValue, postKey){
var escapedKey = postKey.replace("\\", "\\\\").replace("'", "\'");
var escapedValue = postValue.replace("\\", "\\\\").replace("'", "\'");
fakeFormHtmlFragment += "<input type='hidden' name='"+escapedKey+"' value='"+escapedValue+"'>";
});
fakeFormHtmlFragment += "</form>";
$fakeFormDom = $(fakeFormHtmlFragment);
$("body").append($fakeFormDom);
$fakeFormDom.submit();
Run Code Online (Sandbox Code Playgroud)
对于像html,text等这样的东西,请确保mimetype类似于application/octet-stream
PHP代码
<?php
/**
* get HTTP POST variable which is a string ?foo=bar
* @param string $param
* @param bool $required
* @return string
*/
function getHTTPPostString ($param, $required = false) {
if(!isset($_POST[$param])) {
if($required) {
echo "required POST param '$param' missing";
exit 1;
} else {
return "";
}
}
return trim($_POST[$param]);
}
$filename = getHTTPPostString("filename", true);
$filecontent = getHTTPPostString("filecontent", true);
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$filename\"");
echo $filecontent;
Run Code Online (Sandbox Code Playgroud)
自从提出这个问题以来已经有一段时间了,但是我遇到了同样的挑战,想要分享我的解决方案.它使用了其他答案中的元素,但我无法完整地找到它.它不使用表单或iframe,但它确实需要post/get请求对.它不是在请求之间保存文件,而是保存发布数据.它似乎既简单又有效.
var apples = new Array();
// construct data - replace with your own
$.ajax({
type: "POST",
url: '/Home/Download',
data: JSON.stringify(apples),
contentType: "application/json",
dataType: "text",
success: function (data) {
var url = '/Home/Download?id=' + data;
window.location = url;
});
});
Run Code Online (Sandbox Code Playgroud)
[HttpPost]
// called first
public ActionResult Download(Apple[] apples)
{
string json = new JavaScriptSerializer().Serialize(apples);
string id = Guid.NewGuid().ToString();
string path = Server.MapPath(string.Format("~/temp/{0}.json", id));
System.IO.File.WriteAllText(path, json);
return Content(id);
}
// called next
public ActionResult Download(string id)
{
string path = Server.MapPath(string.Format("~/temp/{0}.json", id));
string json = System.IO.File.ReadAllText(path);
System.IO.File.Delete(path);
Apple[] apples = new JavaScriptSerializer().Deserialize<Apple[]>(json);
// work with apples to build your file in memory
byte[] file = createPdf(apples);
Response.AddHeader("Content-Disposition", "attachment; filename=juicy.pdf");
return File(file, "application/pdf");
}
Run Code Online (Sandbox Code Playgroud)
$scope.downloadSearchAsCSV = function(httpOptions) {
var httpOptions = _.extend({
method: 'POST',
url: '',
data: null
}, httpOptions);
$http(httpOptions).then(function(response) {
if( response.status >= 400 ) {
alert(response.status + " - Server Error \nUnable to download CSV from POST\n" + JSON.stringify(httpOptions.data));
} else {
$scope.downloadResponseAsCSVFile(response)
}
})
};
/**
* @source: https://github.com/asafdav/ng-csv/blob/master/src/ng-csv/directives/ng-csv.js
* @param response
*/
$scope.downloadResponseAsCSVFile = function(response) {
var charset = "utf-8";
var filename = "search_results.csv";
var blob = new Blob([response.data], {
type: "text/csv;charset="+ charset + ";"
});
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, filename); // @untested
} else {
var downloadContainer = angular.element('<div data-tap-disabled="true"><a></a></div>');
var downloadLink = angular.element(downloadContainer.children()[0]);
downloadLink.attr('href', window.URL.createObjectURL(blob));
downloadLink.attr('download', "search_results.csv");
downloadLink.attr('target', '_blank');
$document.find('body').append(downloadContainer);
$timeout(function() {
downloadLink[0].click();
downloadLink.remove();
}, null);
}
//// Gets blocked by Chrome popup-blocker
//var csv_window = window.open("","","");
//csv_window.document.write('<meta name="content-type" content="text/csv">');
//csv_window.document.write('<meta name="content-disposition" content="attachment; filename=data.csv"> ');
//csv_window.document.write(response.data);
};
Run Code Online (Sandbox Code Playgroud)
不完全是对原始帖子的回答,而是一个快速而肮脏的解决方案,用于将 json 对象发布到服务器并动态生成下载。
客户端 jQuery:
var download = function(resource, payload) {
$("#downloadFormPoster").remove();
$("<div id='downloadFormPoster' style='display: none;'><iframe name='downloadFormPosterIframe'></iframe></div>").appendTo('body');
$("<form action='" + resource + "' target='downloadFormPosterIframe' method='post'>" +
"<input type='hidden' name='jsonstring' value='" + JSON.stringify(payload) + "'/>" +
"</form>")
.appendTo("#downloadFormPoster")
.submit();
}
Run Code Online (Sandbox Code Playgroud)
..然后在服务器端解码 json 字符串并设置下载标头(PHP 示例):
$request = json_decode($_POST['jsonstring']), true);
header('Content-Type: application/csv');
header('Content-Disposition: attachment; filename=export.csv');
header('Pragma: no-cache');
Run Code Online (Sandbox Code Playgroud)
我认为最好的方法是使用组合,您的第二种方法似乎是涉及浏览器的优雅解决方案。
所以取决于调用的方式。(无论是浏览器还是 Web 服务调用)您可以结合使用两者,将 URL 发送到浏览器并将原始数据发送到任何其他 Web 服务客户端。
归档时间: |
|
查看次数: |
341272 次 |
最近记录: |