ng-file-upload-shim.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. /**!
  2. * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort,
  3. * progress, resize, thumbnail, preview, validation and CORS
  4. * FileAPI Flash shim for old browsers not supporting FormData
  5. * @author Danial <danial.farid@gmail.com>
  6. * @version 12.2.13
  7. */
  8. (function () {
  9. /** @namespace FileAPI.noContentTimeout */
  10. function patchXHR(fnName, newFn) {
  11. window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);
  12. }
  13. function redefineProp(xhr, prop, fn) {
  14. try {
  15. Object.defineProperty(xhr, prop, {get: fn});
  16. } catch (e) {/*ignore*/
  17. }
  18. }
  19. if (!window.FileAPI) {
  20. window.FileAPI = {};
  21. }
  22. if (!window.XMLHttpRequest) {
  23. throw 'AJAX is not supported. XMLHttpRequest is not defined.';
  24. }
  25. FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;
  26. if (FileAPI.shouldLoad) {
  27. var initializeUploadListener = function (xhr) {
  28. if (!xhr.__listeners) {
  29. if (!xhr.upload) xhr.upload = {};
  30. xhr.__listeners = [];
  31. var origAddEventListener = xhr.upload.addEventListener;
  32. xhr.upload.addEventListener = function (t, fn) {
  33. xhr.__listeners[t] = fn;
  34. if (origAddEventListener) origAddEventListener.apply(this, arguments);
  35. };
  36. }
  37. };
  38. patchXHR('open', function (orig) {
  39. return function (m, url, b) {
  40. initializeUploadListener(this);
  41. this.__url = url;
  42. try {
  43. orig.apply(this, [m, url, b]);
  44. } catch (e) {
  45. if (e.message.indexOf('Access is denied') > -1) {
  46. this.__origError = e;
  47. orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);
  48. }
  49. }
  50. };
  51. });
  52. patchXHR('getResponseHeader', function (orig) {
  53. return function (h) {
  54. return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));
  55. };
  56. });
  57. patchXHR('getAllResponseHeaders', function (orig) {
  58. return function () {
  59. return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));
  60. };
  61. });
  62. patchXHR('abort', function (orig) {
  63. return function () {
  64. return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));
  65. };
  66. });
  67. patchXHR('setRequestHeader', function (orig) {
  68. return function (header, value) {
  69. if (header === '__setXHR_') {
  70. initializeUploadListener(this);
  71. var val = value(this);
  72. // fix for angular < 1.2.0
  73. if (val instanceof Function) {
  74. val(this);
  75. }
  76. } else {
  77. this.__requestHeaders = this.__requestHeaders || {};
  78. this.__requestHeaders[header] = value;
  79. orig.apply(this, arguments);
  80. }
  81. };
  82. });
  83. patchXHR('send', function (orig) {
  84. return function () {
  85. var xhr = this;
  86. if (arguments[0] && arguments[0].__isFileAPIShim) {
  87. var formData = arguments[0];
  88. var config = {
  89. url: xhr.__url,
  90. jsonp: false, //removes the callback form param
  91. cache: true, //removes the ?fileapiXXX in the url
  92. complete: function (err, fileApiXHR) {
  93. if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {
  94. // this error seems to be fine the file is being uploaded properly.
  95. err = null;
  96. }
  97. xhr.__completed = true;
  98. if (!err && xhr.__listeners.load)
  99. xhr.__listeners.load({
  100. type: 'load',
  101. loaded: xhr.__loaded,
  102. total: xhr.__total,
  103. target: xhr,
  104. lengthComputable: true
  105. });
  106. if (!err && xhr.__listeners.loadend)
  107. xhr.__listeners.loadend({
  108. type: 'loadend',
  109. loaded: xhr.__loaded,
  110. total: xhr.__total,
  111. target: xhr,
  112. lengthComputable: true
  113. });
  114. if (err === 'abort' && xhr.__listeners.abort)
  115. xhr.__listeners.abort({
  116. type: 'abort',
  117. loaded: xhr.__loaded,
  118. total: xhr.__total,
  119. target: xhr,
  120. lengthComputable: true
  121. });
  122. if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {
  123. return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;
  124. });
  125. if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {
  126. return fileApiXHR.statusText;
  127. });
  128. redefineProp(xhr, 'readyState', function () {
  129. return 4;
  130. });
  131. if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {
  132. return fileApiXHR.response;
  133. });
  134. var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);
  135. redefineProp(xhr, 'responseText', function () {
  136. return resp;
  137. });
  138. redefineProp(xhr, 'response', function () {
  139. return resp;
  140. });
  141. if (err) redefineProp(xhr, 'err', function () {
  142. return err;
  143. });
  144. xhr.__fileApiXHR = fileApiXHR;
  145. if (xhr.onreadystatechange) xhr.onreadystatechange();
  146. if (xhr.onload) xhr.onload();
  147. },
  148. progress: function (e) {
  149. e.target = xhr;
  150. if (xhr.__listeners.progress) xhr.__listeners.progress(e);
  151. xhr.__total = e.total;
  152. xhr.__loaded = e.loaded;
  153. if (e.total === e.loaded) {
  154. // fix flash issue that doesn't call complete if there is no response text from the server
  155. var _this = this;
  156. setTimeout(function () {
  157. if (!xhr.__completed) {
  158. xhr.getAllResponseHeaders = function () {
  159. };
  160. _this.complete(null, {status: 204, statusText: 'No Content'});
  161. }
  162. }, FileAPI.noContentTimeout || 10000);
  163. }
  164. },
  165. headers: xhr.__requestHeaders
  166. };
  167. config.data = {};
  168. config.files = {};
  169. for (var i = 0; i < formData.data.length; i++) {
  170. var item = formData.data[i];
  171. if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {
  172. config.files[item.key] = item.val;
  173. } else {
  174. config.data[item.key] = item.val;
  175. }
  176. }
  177. setTimeout(function () {
  178. if (!FileAPI.hasFlash) {
  179. throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
  180. }
  181. xhr.__fileApiXHR = FileAPI.upload(config);
  182. }, 1);
  183. } else {
  184. if (this.__origError) {
  185. throw this.__origError;
  186. }
  187. orig.apply(xhr, arguments);
  188. }
  189. };
  190. });
  191. window.XMLHttpRequest.__isFileAPIShim = true;
  192. window.FormData = FormData = function () {
  193. return {
  194. append: function (key, val, name) {
  195. if (val.__isFileAPIBlobShim) {
  196. val = val.data[0];
  197. }
  198. this.data.push({
  199. key: key,
  200. val: val,
  201. name: name
  202. });
  203. },
  204. data: [],
  205. __isFileAPIShim: true
  206. };
  207. };
  208. window.Blob = Blob = function (b) {
  209. return {
  210. data: b,
  211. __isFileAPIBlobShim: true
  212. };
  213. };
  214. }
  215. })();
  216. (function () {
  217. /** @namespace FileAPI.forceLoad */
  218. /** @namespace window.FileAPI.jsUrl */
  219. /** @namespace window.FileAPI.jsPath */
  220. function isInputTypeFile(elem) {
  221. return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';
  222. }
  223. function hasFlash() {
  224. try {
  225. var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
  226. if (fo) return true;
  227. } catch (e) {
  228. if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;
  229. }
  230. return false;
  231. }
  232. function getOffset(obj) {
  233. var left = 0, top = 0;
  234. if (window.jQuery) {
  235. return jQuery(obj).offset();
  236. }
  237. if (obj.offsetParent) {
  238. do {
  239. left += (obj.offsetLeft - obj.scrollLeft);
  240. top += (obj.offsetTop - obj.scrollTop);
  241. obj = obj.offsetParent;
  242. } while (obj);
  243. }
  244. return {
  245. left: left,
  246. top: top
  247. };
  248. }
  249. if (FileAPI.shouldLoad) {
  250. FileAPI.hasFlash = hasFlash();
  251. //load FileAPI
  252. if (FileAPI.forceLoad) {
  253. FileAPI.html5 = false;
  254. }
  255. if (!FileAPI.upload) {
  256. var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;
  257. if (window.FileAPI.jsUrl) {
  258. jsUrl = window.FileAPI.jsUrl;
  259. } else if (window.FileAPI.jsPath) {
  260. basePath = window.FileAPI.jsPath;
  261. } else {
  262. for (i = 0; i < allScripts.length; i++) {
  263. src = allScripts[i].src;
  264. index = src.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/);
  265. if (index > -1) {
  266. basePath = src.substring(0, index + 1);
  267. break;
  268. }
  269. }
  270. }
  271. if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;
  272. script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js');
  273. document.getElementsByTagName('head')[0].appendChild(script);
  274. }
  275. FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {
  276. if (!hasFlash()) {
  277. throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
  278. }
  279. var fixInputStyle = function () {
  280. var label = fileElem.parent();
  281. if (elem.attr('disabled')) {
  282. if (label) label.removeClass('js-fileapi-wrapper');
  283. } else {
  284. if (!fileElem.attr('__ngf_flash_')) {
  285. fileElem.unbind('change');
  286. fileElem.unbind('click');
  287. fileElem.bind('change', function (evt) {
  288. fileApiChangeFn.apply(this, [evt]);
  289. changeFn.apply(this, [evt]);
  290. });
  291. fileElem.attr('__ngf_flash_', 'true');
  292. }
  293. label.addClass('js-fileapi-wrapper');
  294. if (!isInputTypeFile(elem)) {
  295. label.css('position', 'absolute')
  296. .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')
  297. .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
  298. .css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))
  299. .css('overflow', 'hidden').css('z-index', '900000')
  300. .css('visibility', 'visible');
  301. fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
  302. .css('position', 'absolute').css('top', '0px').css('left', '0px');
  303. }
  304. }
  305. };
  306. elem.bind('mouseenter', fixInputStyle);
  307. var fileApiChangeFn = function (evt) {
  308. var files = FileAPI.getFiles(evt);
  309. //just a double check for #233
  310. for (var i = 0; i < files.length; i++) {
  311. if (files[i].size === undefined) files[i].size = 0;
  312. if (files[i].name === undefined) files[i].name = 'file';
  313. if (files[i].type === undefined) files[i].type = 'undefined';
  314. }
  315. if (!evt.target) {
  316. evt.target = {};
  317. }
  318. evt.target.files = files;
  319. // if evt.target.files is not writable use helper field
  320. if (evt.target.files !== files) {
  321. evt.__files_ = files;
  322. }
  323. (evt.__files_ || evt.target.files).item = function (i) {
  324. return (evt.__files_ || evt.target.files)[i] || null;
  325. };
  326. };
  327. };
  328. FileAPI.disableFileInput = function (elem, disable) {
  329. if (disable) {
  330. elem.removeClass('js-fileapi-wrapper');
  331. } else {
  332. elem.addClass('js-fileapi-wrapper');
  333. }
  334. };
  335. }
  336. })();
  337. if (!window.FileReader) {
  338. window.FileReader = function () {
  339. var _this = this, loadStarted = false;
  340. this.listeners = {};
  341. this.addEventListener = function (type, fn) {
  342. _this.listeners[type] = _this.listeners[type] || [];
  343. _this.listeners[type].push(fn);
  344. };
  345. this.removeEventListener = function (type, fn) {
  346. if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);
  347. };
  348. this.dispatchEvent = function (evt) {
  349. var list = _this.listeners[evt.type];
  350. if (list) {
  351. for (var i = 0; i < list.length; i++) {
  352. list[i].call(_this, evt);
  353. }
  354. }
  355. };
  356. this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;
  357. var constructEvent = function (type, evt) {
  358. var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};
  359. if (evt.result != null) e.target.result = evt.result;
  360. return e;
  361. };
  362. var listener = function (evt) {
  363. if (!loadStarted) {
  364. loadStarted = true;
  365. if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));
  366. }
  367. var e;
  368. if (evt.type === 'load') {
  369. if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));
  370. e = constructEvent('load', evt);
  371. if (_this.onload) _this.onload(e);
  372. _this.dispatchEvent(e);
  373. } else if (evt.type === 'progress') {
  374. e = constructEvent('progress', evt);
  375. if (_this.onprogress) _this.onprogress(e);
  376. _this.dispatchEvent(e);
  377. } else {
  378. e = constructEvent('error', evt);
  379. if (_this.onerror) _this.onerror(e);
  380. _this.dispatchEvent(e);
  381. }
  382. };
  383. this.readAsDataURL = function (file) {
  384. FileAPI.readAsDataURL(file, listener);
  385. };
  386. this.readAsText = function (file) {
  387. FileAPI.readAsText(file, listener);
  388. };
  389. };
  390. }