jquery.fileDownload.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. /*
  2. * jQuery File Download Plugin v1.4.2
  3. *
  4. * http://www.johnculviner.com
  5. *
  6. * Copyright (c) 2013 - John Culviner
  7. *
  8. * Licensed under the MIT license:
  9. * http://www.opensource.org/licenses/mit-license.php
  10. *
  11. * !!!!NOTE!!!!
  12. * You must also write a cookie in conjunction with using this plugin as mentioned in the orignal post:
  13. * http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/
  14. * !!!!NOTE!!!!
  15. */
  16. (function($, window){
  17. // i'll just put them here to get evaluated on script load
  18. var htmlSpecialCharsRegEx = /[<>&\r\n"']/gm;
  19. var htmlSpecialCharsPlaceHolders = {
  20. '<': 'lt;',
  21. '>': 'gt;',
  22. '&': 'amp;',
  23. '\r': "#13;",
  24. '\n': "#10;",
  25. '"': 'quot;',
  26. "'": 'apos;' /*single quotes just to be safe*/
  27. };
  28. $.extend({
  29. //
  30. //$.fileDownload('/path/to/url/', options)
  31. // see directly below for possible 'options'
  32. fileDownload: function (fileUrl, options) {
  33. //provide some reasonable defaults to any unspecified options below
  34. var settings = $.extend({
  35. //
  36. //Requires jQuery UI: provide a message to display to the user when the file download is being prepared before the browser's dialog appears
  37. //
  38. preparingMessageHtml: null,
  39. //
  40. //Requires jQuery UI: provide a message to display to the user when a file download fails
  41. //
  42. failMessageHtml: null,
  43. //
  44. //the stock android browser straight up doesn't support file downloads initiated by a non GET: http://code.google.com/p/android/issues/detail?id=1780
  45. //specify a message here to display if a user tries with an android browser
  46. //if jQuery UI is installed this will be a dialog, otherwise it will be an alert
  47. //
  48. androidPostUnsupportedMessageHtml: "Unfortunately your Android browser doesn't support this type of file download. Please try again with a different browser.",
  49. //
  50. //Requires jQuery UI: options to pass into jQuery UI Dialog
  51. //
  52. dialogOptions: { modal: true },
  53. //
  54. //a function to call while the dowload is being prepared before the browser's dialog appears
  55. //Args:
  56. // url - the original url attempted
  57. //
  58. prepareCallback: function (url) { },
  59. //
  60. //a function to call after a file download dialog/ribbon has appeared
  61. //Args:
  62. // url - the original url attempted
  63. //
  64. successCallback: function (url) { console.log("sq2xxxxxxxxxxxxxx") },
  65. //
  66. //a function to call after a file download dialog/ribbon has appeared
  67. //Args:
  68. // responseHtml - the html that came back in response to the file download. this won't necessarily come back depending on the browser.
  69. // in less than IE9 a cross domain error occurs because 500+ errors cause a cross domain issue due to IE subbing out the
  70. // server's error message with a "helpful" IE built in message
  71. // url - the original url attempted
  72. //
  73. failCallback: function (responseHtml, url) { },
  74. //
  75. // the HTTP method to use. Defaults to "GET".
  76. //
  77. httpMethod: "GET",
  78. //
  79. // if specified will perform a "httpMethod" request to the specified 'fileUrl' using the specified data.
  80. // data must be an object (which will be $.param serialized) or already a key=value param string
  81. //
  82. data: null,
  83. //
  84. //a period in milliseconds to poll to determine if a successful file download has occured or not
  85. //
  86. checkInterval: 500,
  87. dc:0,
  88. //
  89. //the cookie name to indicate if a file download has occured
  90. //
  91. cookieName: "fileDownload",
  92. //
  93. //the cookie value for the above name to indicate that a file download has occured
  94. //
  95. cookieValue: "true",
  96. //
  97. //the cookie path for above name value pair
  98. //
  99. cookiePath: "/",
  100. //
  101. //the title for the popup second window as a download is processing in the case of a mobile browser
  102. //
  103. popupWindowTitle: "Initiating file download...",
  104. //
  105. //Functionality to encode HTML entities for a POST, need this if data is an object with properties whose values contains strings with quotation marks.
  106. //HTML entity encoding is done by replacing all &,<,>,',",\r,\n characters.
  107. //Note that some browsers will POST the string htmlentity-encoded whilst others will decode it before POSTing.
  108. //It is recommended that on the server, htmlentity decoding is done irrespective.
  109. //
  110. encodeHTMLEntities: true
  111. }, options);
  112. var deferred = new $.Deferred();
  113. //Setup mobile browser detection: Partial credit: http://detectmobilebrowser.com/
  114. var userAgent = (navigator.userAgent || navigator.vendor || window.opera).toLowerCase();
  115. var isIos; //has full support of features in iOS 4.0+, uses a new window to accomplish this.
  116. var isAndroid; //has full support of GET features in 4.0+ by using a new window. Non-GET is completely unsupported by the browser. See above for specifying a message.
  117. var isOtherMobileBrowser; //there is no way to reliably guess here so all other mobile devices will GET and POST to the current window.
  118. if (/ip(ad|hone|od)/.test(userAgent)) {
  119. isIos = true;
  120. } else if (userAgent.indexOf('android') !== -1) {
  121. isAndroid = true;
  122. } else {
  123. isOtherMobileBrowser = /avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|playbook|silk|iemobile|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(userAgent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(userAgent.substr(0, 4));
  124. }
  125. var httpMethodUpper = settings.httpMethod.toUpperCase();
  126. if (isAndroid && httpMethodUpper !== "GET") {
  127. //the stock android browser straight up doesn't support file downloads initiated by non GET requests: http://code.google.com/p/android/issues/detail?id=1780
  128. if ($().dialog) {
  129. $("<div>").html(settings.androidPostUnsupportedMessageHtml).dialog(settings.dialogOptions);
  130. } else {
  131. alert(settings.androidPostUnsupportedMessageHtml);
  132. }
  133. return deferred.reject();
  134. }
  135. var $preparingDialog = null;
  136. var internalCallbacks = {
  137. onPrepare: function (url) {
  138. //wire up a jquery dialog to display the preparing message if specified
  139. if (settings.preparingMessageHtml) {
  140. $preparingDialog = $("<div>").html(settings.preparingMessageHtml).dialog(settings.dialogOptions);
  141. } else if (settings.prepareCallback) {
  142. settings.prepareCallback(url);
  143. }
  144. },
  145. onSuccess: function (url) {
  146. console.log("jqxxxxxxxxxxxxx");
  147. //remove the perparing message if it was specified
  148. if ($preparingDialog) {
  149. $preparingDialog.dialog('close');
  150. };
  151. settings.successCallback(url);
  152. deferred.resolve(url);
  153. },
  154. onFail: function (responseHtml, url) {
  155. //remove the perparing message if it was specified
  156. if ($preparingDialog) {
  157. $preparingDialog.dialog('close');
  158. };
  159. //wire up a jquery dialog to display the fail message if specified
  160. if (settings.failMessageHtml) {
  161. $("<div>").html(settings.failMessageHtml).dialog(settings.dialogOptions);
  162. }
  163. settings.failCallback(responseHtml, url);
  164. deferred.reject(responseHtml, url);
  165. }
  166. };
  167. internalCallbacks.onPrepare(fileUrl);
  168. //make settings.data a param string if it exists and isn't already
  169. if (settings.data !== null && typeof settings.data !== "string") {
  170. settings.data = $.param(settings.data);
  171. }
  172. var $iframe,
  173. downloadWindow,
  174. formDoc,
  175. $form;
  176. if (httpMethodUpper === "GET") {
  177. if (settings.data !== null) {
  178. //need to merge any fileUrl params with the data object
  179. var qsStart = fileUrl.indexOf('?');
  180. if (qsStart !== -1) {
  181. //we have a querystring in the url
  182. if (fileUrl.substring(fileUrl.length - 1) !== "&") {
  183. fileUrl = fileUrl + "&";
  184. }
  185. } else {
  186. fileUrl = fileUrl + "?";
  187. }
  188. fileUrl = fileUrl + settings.data;
  189. }
  190. if (isIos || isAndroid) {
  191. downloadWindow = window.open(fileUrl);
  192. downloadWindow.document.title = settings.popupWindowTitle;
  193. window.focus();
  194. } else if (isOtherMobileBrowser) {
  195. window.location(fileUrl);
  196. } else {
  197. //create a temporary iframe that is used to request the fileUrl as a GET request
  198. $iframe = $("<iframe>")
  199. .hide()
  200. .prop("src", fileUrl)
  201. .appendTo("body");
  202. }
  203. } else {
  204. var formInnerHtml = "";
  205. if (settings.data !== null) {
  206. $.each(settings.data.replace(/\+/g, ' ').split("&"), function () {
  207. var kvp = this.split("=");
  208. var key = settings.encodeHTMLEntities ? htmlSpecialCharsEntityEncode(decodeURIComponent(kvp[0])) : decodeURIComponent(kvp[0]);
  209. if (key) {
  210. var value = settings.encodeHTMLEntities ? htmlSpecialCharsEntityEncode(decodeURIComponent(kvp[1])) : decodeURIComponent(kvp[1]);
  211. formInnerHtml += '<input type="hidden" name="' + key + '" value="' + value + '" />';
  212. }
  213. });
  214. }
  215. if (isOtherMobileBrowser) {
  216. $form = $("<form>").appendTo("body");
  217. $form.hide()
  218. .prop('method', settings.httpMethod)
  219. .prop('action', fileUrl)
  220. .html(formInnerHtml);
  221. } else {
  222. if (isIos) {
  223. downloadWindow = window.open("about:blank");
  224. downloadWindow.document.title = settings.popupWindowTitle;
  225. formDoc = downloadWindow.document;
  226. window.focus();
  227. } else {
  228. $iframe = $("<iframe style='display: none' src='about:blank'></iframe>").appendTo("body");
  229. formDoc = getiframeDocument($iframe);
  230. }
  231. formDoc.write("<html><head></head><body><form method='" + settings.httpMethod + "' action='" + fileUrl + "'>" + formInnerHtml + "</form>" + settings.popupWindowTitle + "</body></html>");
  232. $form = $(formDoc).find('form');
  233. }
  234. $form.submit();
  235. }
  236. //check if the file download has completed every checkInterval ms
  237. setTimeout(checkFileDownloadComplete, settings.checkInterval);
  238. function checkFileDownloadComplete() {
  239. //console.log(document.cookie, settings.cookieName + "=" + settings.cookieValue);
  240. //has the cookie been written due to a file download occuring?
  241. //2547120855408614437
  242. //if (down_document != null) {
  243. // console.log("filedown", down_document.cookie);
  244. //}
  245. //if (down_document != null && down_document.cookie.indexOf(settings.cookieName + "=" + settings.cookieValue) != -1) {
  246. // //execute specified callback
  247. // internalCallbacks.onSuccess(fileUrl);
  248. // //remove the cookie and iframe
  249. // down_document.cookie = "fileDownLoad=; expires=" + new Date(1000).toUTCString() + "; path=" + settings.cookiePath;
  250. // cleanUp(false);
  251. // down_document = null;
  252. // return;
  253. //}
  254. //if (down_document == null) {
  255. // var frame = getDownFrameObj();//document.getElementById('downback_frame');
  256. // frame.postMessage('cookie', '*');
  257. //}
  258. //if (document.cookie.indexOf(settings.cookieName + "=" + settings.cookieValue) != -1) {
  259. // //execute specified callback
  260. // internalCallbacks.onSuccess(fileUrl);
  261. // //remove the cookie and iframe
  262. // document.cookie = "erp_df_cookie=; expires=" + new Date(1000).toUTCString() + "; path=" + settings.cookiePath;
  263. // cleanUp(false);
  264. // return;
  265. //}
  266. //console.log($iframe,getiframeDocument($iframe), getiframeDocument($iframe).cookie);
  267. //has an error occured?
  268. //if neither containers exist below then the file download is occuring on the current window
  269. settings.dc++;
  270. if (downloadWindow || $iframe) {
  271. //has an error occured?
  272. try {
  273. var formDoc = downloadWindow ? downloadWindow.document : getiframeDocument($iframe);
  274. if (formDoc && formDoc.body != null && formDoc.body.innerHTML.length) {
  275. var isFailure = true;
  276. if ($form && $form.length) {
  277. var $contents = $(formDoc.body).contents().first();
  278. if ($contents.length && $contents[0] === $form[0]) {
  279. isFailure = false;
  280. }
  281. }
  282. if (isFailure) {
  283. internalCallbacks.onFail(formDoc.body.innerHTML, fileUrl);
  284. cleanUp(true);
  285. return;
  286. }
  287. }
  288. else {
  289. //ĬÈϳɹ¦
  290. //if (formDoc != null && formDoc.cookie.indexOf(settings.cookieName + "=" + settings.cookieValue) != -1) {
  291. //execute specified callback
  292. if (settings.dc > 2) {
  293. internalCallbacks.onSuccess(fileUrl);
  294. //remove the cookie and iframe
  295. //formDoc.cookie = "erp_df_cookie=; expires=" + new Date(1000).toUTCString() + "; path=" + settings.cookiePath;
  296. cleanUp(false);
  297. return;
  298. }
  299. //}
  300. }
  301. }
  302. catch (err) {
  303. //alert('cach' + err);
  304. //500 error less than IE9
  305. internalCallbacks.onFail('', fileUrl);
  306. cleanUp(true);
  307. return;
  308. }
  309. }
  310. //keep checking...
  311. setTimeout(checkFileDownloadComplete, settings.checkInterval);
  312. }
  313. //gets an iframes document in a cross browser compatible manner
  314. function getiframeDocument($iframe) {
  315. var iframeDoc = $iframe[0].contentWindow || $iframe[0].contentDocument;
  316. if (iframeDoc.document) {
  317. iframeDoc = iframeDoc.document;
  318. }
  319. return iframeDoc;
  320. }
  321. function cleanUp(isFailure) {
  322. setTimeout(function() {
  323. if (downloadWindow) {
  324. if (isAndroid) {
  325. downloadWindow.close();
  326. }
  327. if (isIos) {
  328. if (downloadWindow.focus) {
  329. downloadWindow.focus(); //ios safari bug doesn't allow a window to be closed unless it is focused
  330. if (isFailure) {
  331. downloadWindow.close();
  332. }
  333. }
  334. }
  335. }
  336. //iframe cleanup appears to randomly cause the download to fail
  337. //not doing it seems better than failure...
  338. //if ($iframe) {
  339. // $iframe.remove();
  340. //}
  341. }, 0);
  342. }
  343. function htmlSpecialCharsEntityEncode(str) {
  344. return str.replace(htmlSpecialCharsRegEx, function(match) {
  345. return '&' + htmlSpecialCharsPlaceHolders[match];
  346. });
  347. }
  348. return deferred.promise();
  349. }
  350. });
  351. })(jQuery, this);