angular-file-upload.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332
  1. /*
  2. angular-file-upload v1.1.5
  3. https://github.com/nervgh/angular-file-upload
  4. */
  5. (function(angular, factory) {
  6. if (typeof define === 'function' && define.amd) {
  7. define('angular-file-upload', ['angular'], function(angular) {
  8. return factory(angular);
  9. });
  10. } else {
  11. return factory(angular);
  12. }
  13. }(typeof angular === 'undefined' ? null : angular, function(angular) {
  14. var module = angular.module('angularFileUpload', []);
  15. 'use strict';
  16. /**
  17. * Classes
  18. *
  19. * FileUploader
  20. * FileUploader.FileLikeObject
  21. * FileUploader.FileItem
  22. * FileUploader.FileDirective
  23. * FileUploader.FileSelect
  24. * FileUploader.FileDrop
  25. * FileUploader.FileOver
  26. */
  27. module
  28. .value('fileUploaderOptions', {
  29. url: '/',
  30. alias: 'file',
  31. headers: {},
  32. queue: [],
  33. progress: 0,
  34. autoUpload: false,
  35. removeAfterUpload: false,
  36. method: 'POST',
  37. filters: [],
  38. formData: [],
  39. queueLimit: Number.MAX_VALUE,
  40. withCredentials: false
  41. })
  42. .factory('FileUploader', ['fileUploaderOptions', '$rootScope', '$http', '$window', '$compile',
  43. function(fileUploaderOptions, $rootScope, $http, $window, $compile) {
  44. /**
  45. * Creates an instance of FileUploader
  46. * @param {Object} [options]
  47. * @constructor
  48. */
  49. function FileUploader(options) {
  50. var settings = angular.copy(fileUploaderOptions);
  51. angular.extend(this, settings, options, {
  52. isUploading: false,
  53. _nextIndex: 0,
  54. _failFilterIndex: -1,
  55. _directives: {select: [], drop: [], over: []}
  56. });
  57. // add default filters
  58. this.filters.unshift({name: 'queueLimit', fn: this._queueLimitFilter});
  59. this.filters.unshift({name: 'folder', fn: this._folderFilter});
  60. }
  61. /**********************
  62. * PUBLIC
  63. **********************/
  64. /**
  65. * Checks a support the html5 uploader
  66. * @returns {Boolean}
  67. * @readonly
  68. */
  69. FileUploader.prototype.isHTML5 = !!($window.File && $window.FormData);
  70. /**
  71. * Adds items to the queue
  72. * @param {File|HTMLInputElement|Object|FileList|Array<Object>} files
  73. * @param {Object} [options]
  74. * @param {Array<Function>|String} filters
  75. */
  76. FileUploader.prototype.addToQueue = function(files, options, filters) {
  77. var list = this.isArrayLikeObject(files) ? files: [files];
  78. var arrayOfFilters = this._getFilters(filters);
  79. var count = this.queue.length;
  80. var addedFileItems = [];
  81. angular.forEach(list, function(some /*{File|HTMLInputElement|Object}*/) {
  82. var temp = new FileUploader.FileLikeObject(some);
  83. if (this._isValidFile(temp, arrayOfFilters, options)) {
  84. var fileItem = new FileUploader.FileItem(this, some, options);
  85. addedFileItems.push(fileItem);
  86. this.queue.push(fileItem);
  87. this._onAfterAddingFile(fileItem);
  88. } else {
  89. var filter = this.filters[this._failFilterIndex];
  90. this._onWhenAddingFileFailed(temp, filter, options);
  91. }
  92. }, this);
  93. if(this.queue.length !== count) {
  94. this._onAfterAddingAll(addedFileItems);
  95. this.progress = this._getTotalProgress();
  96. }
  97. this._render();
  98. if (this.autoUpload) this.uploadAll();
  99. };
  100. /**
  101. * Remove items from the queue. Remove last: index = -1
  102. * @param {FileItem|Number} value
  103. */
  104. FileUploader.prototype.removeFromQueue = function(value) {
  105. var index = this.getIndexOfItem(value);
  106. var item = this.queue[index];
  107. if (item.isUploading) item.cancel();
  108. this.queue.splice(index, 1);
  109. item._destroy();
  110. this.progress = this._getTotalProgress();
  111. };
  112. /**
  113. * Clears the queue
  114. */
  115. FileUploader.prototype.clearQueue = function() {
  116. while(this.queue.length) {
  117. this.queue[0].remove();
  118. }
  119. this.progress = 0;
  120. };
  121. /**
  122. * Uploads a item from the queue
  123. * @param {FileItem|Number} value
  124. */
  125. FileUploader.prototype.uploadItem = function(value) {
  126. var index = this.getIndexOfItem(value);
  127. var item = this.queue[index];
  128. var transport = this.isHTML5 ? '_xhrTransport' : '_iframeTransport';
  129. item._prepareToUploading();
  130. if(this.isUploading) return;
  131. this.isUploading = true;
  132. this[transport](item);
  133. };
  134. /**
  135. * Cancels uploading of item from the queue
  136. * @param {FileItem|Number} value
  137. */
  138. FileUploader.prototype.cancelItem = function(value) {
  139. var index = this.getIndexOfItem(value);
  140. var item = this.queue[index];
  141. var prop = this.isHTML5 ? '_xhr' : '_form';
  142. if (item && item.isUploading) item[prop].abort();
  143. };
  144. /**
  145. * Uploads all not uploaded items of queue
  146. */
  147. FileUploader.prototype.uploadAll = function() {
  148. var items = this.getNotUploadedItems().filter(function(item) {
  149. return !item.isUploading;
  150. });
  151. if (!items.length) return;
  152. angular.forEach(items, function(item) {
  153. item._prepareToUploading();
  154. });
  155. items[0].upload();
  156. };
  157. /**
  158. * Cancels all uploads
  159. */
  160. FileUploader.prototype.cancelAll = function() {
  161. var items = this.getNotUploadedItems();
  162. angular.forEach(items, function(item) {
  163. item.cancel();
  164. });
  165. };
  166. /**
  167. * Returns "true" if value an instance of File
  168. * @param {*} value
  169. * @returns {Boolean}
  170. * @private
  171. */
  172. FileUploader.prototype.isFile = function(value) {
  173. var fn = $window.File;
  174. return (fn && value instanceof fn);
  175. };
  176. /**
  177. * Returns "true" if value an instance of FileLikeObject
  178. * @param {*} value
  179. * @returns {Boolean}
  180. * @private
  181. */
  182. FileUploader.prototype.isFileLikeObject = function(value) {
  183. return value instanceof FileUploader.FileLikeObject;
  184. };
  185. /**
  186. * Returns "true" if value is array like object
  187. * @param {*} value
  188. * @returns {Boolean}
  189. */
  190. FileUploader.prototype.isArrayLikeObject = function(value) {
  191. return (angular.isObject(value) && 'length' in value);
  192. };
  193. /**
  194. * Returns a index of item from the queue
  195. * @param {Item|Number} value
  196. * @returns {Number}
  197. */
  198. FileUploader.prototype.getIndexOfItem = function(value) {
  199. return angular.isNumber(value) ? value : this.queue.indexOf(value);
  200. };
  201. /**
  202. * Returns not uploaded items
  203. * @returns {Array}
  204. */
  205. FileUploader.prototype.getNotUploadedItems = function() {
  206. return this.queue.filter(function(item) {
  207. return !item.isUploaded;
  208. });
  209. };
  210. /**
  211. * Returns items ready for upload
  212. * @returns {Array}
  213. */
  214. FileUploader.prototype.getReadyItems = function() {
  215. return this.queue
  216. .filter(function(item) {
  217. return (item.isReady && !item.isUploading);
  218. })
  219. .sort(function(item1, item2) {
  220. return item1.index - item2.index;
  221. });
  222. };
  223. /**
  224. * Destroys instance of FileUploader
  225. */
  226. FileUploader.prototype.destroy = function() {
  227. angular.forEach(this._directives, function(key) {
  228. angular.forEach(this._directives[key], function(object) {
  229. object.destroy();
  230. }, this);
  231. }, this);
  232. };
  233. /**
  234. * Callback
  235. * @param {Array} fileItems
  236. */
  237. FileUploader.prototype.onAfterAddingAll = function(fileItems) {};
  238. /**
  239. * Callback
  240. * @param {FileItem} fileItem
  241. */
  242. FileUploader.prototype.onAfterAddingFile = function(fileItem) {};
  243. /**
  244. * Callback
  245. * @param {File|Object} item
  246. * @param {Object} filter
  247. * @param {Object} options
  248. * @private
  249. */
  250. FileUploader.prototype.onWhenAddingFileFailed = function(item, filter, options) {};
  251. /**
  252. * Callback
  253. * @param {FileItem} fileItem
  254. */
  255. FileUploader.prototype.onBeforeUploadItem = function(fileItem) {};
  256. /**
  257. * Callback
  258. * @param {FileItem} fileItem
  259. * @param {Number} progress
  260. */
  261. FileUploader.prototype.onProgressItem = function(fileItem, progress) {};
  262. /**
  263. * Callback
  264. * @param {Number} progress
  265. */
  266. FileUploader.prototype.onProgressAll = function(progress) {};
  267. /**
  268. * Callback
  269. * @param {FileItem} item
  270. * @param {*} response
  271. * @param {Number} status
  272. * @param {Object} headers
  273. */
  274. FileUploader.prototype.onSuccessItem = function(item, response, status, headers) {};
  275. /**
  276. * Callback
  277. * @param {FileItem} item
  278. * @param {*} response
  279. * @param {Number} status
  280. * @param {Object} headers
  281. */
  282. FileUploader.prototype.onErrorItem = function(item, response, status, headers) {};
  283. /**
  284. * Callback
  285. * @param {FileItem} item
  286. * @param {*} response
  287. * @param {Number} status
  288. * @param {Object} headers
  289. */
  290. FileUploader.prototype.onCancelItem = function(item, response, status, headers) {};
  291. /**
  292. * Callback
  293. * @param {FileItem} item
  294. * @param {*} response
  295. * @param {Number} status
  296. * @param {Object} headers
  297. */
  298. FileUploader.prototype.onCompleteItem = function(item, response, status, headers) {};
  299. /**
  300. * Callback
  301. */
  302. FileUploader.prototype.onCompleteAll = function() {};
  303. /**********************
  304. * PRIVATE
  305. **********************/
  306. /**
  307. * Returns the total progress
  308. * @param {Number} [value]
  309. * @returns {Number}
  310. * @private
  311. */
  312. FileUploader.prototype._getTotalProgress = function(value) {
  313. if(this.removeAfterUpload) return value || 0;
  314. var notUploaded = this.getNotUploadedItems().length;
  315. var uploaded = notUploaded ? this.queue.length - notUploaded : this.queue.length;
  316. var ratio = 100 / this.queue.length;
  317. var current = (value || 0) * ratio / 100;
  318. return Math.round(uploaded * ratio + current);
  319. };
  320. /**
  321. * Returns array of filters
  322. * @param {Array<Function>|String} filters
  323. * @returns {Array<Function>}
  324. * @private
  325. */
  326. FileUploader.prototype._getFilters = function(filters) {
  327. if (angular.isUndefined(filters)) return this.filters;
  328. if (angular.isArray(filters)) return filters;
  329. var names = filters.match(/[^\s,]+/g);
  330. return this.filters.filter(function(filter) {
  331. return names.indexOf(filter.name) !== -1;
  332. }, this);
  333. };
  334. /**
  335. * Updates html
  336. * @private
  337. */
  338. FileUploader.prototype._render = function() {
  339. if (!$rootScope.$$phase) $rootScope.$apply();
  340. };
  341. /**
  342. * Returns "true" if item is a file (not folder)
  343. * @param {File|FileLikeObject} item
  344. * @returns {Boolean}
  345. * @private
  346. */
  347. FileUploader.prototype._folderFilter = function(item) {
  348. return !!(item.size || item.type);
  349. };
  350. /**
  351. * Returns "true" if the limit has not been reached
  352. * @returns {Boolean}
  353. * @private
  354. */
  355. FileUploader.prototype._queueLimitFilter = function() {
  356. return this.queue.length < this.queueLimit;
  357. };
  358. /**
  359. * Returns "true" if file pass all filters
  360. * @param {File|Object} file
  361. * @param {Array<Function>} filters
  362. * @param {Object} options
  363. * @returns {Boolean}
  364. * @private
  365. */
  366. FileUploader.prototype._isValidFile = function(file, filters, options) {
  367. this._failFilterIndex = -1;
  368. return !filters.length ? true : filters.every(function(filter) {
  369. this._failFilterIndex++;
  370. return filter.fn.call(this, file, options);
  371. }, this);
  372. };
  373. /**
  374. * Checks whether upload successful
  375. * @param {Number} status
  376. * @returns {Boolean}
  377. * @private
  378. */
  379. FileUploader.prototype._isSuccessCode = function(status) {
  380. return (status >= 200 && status < 300) || status === 304;
  381. };
  382. /**
  383. * Transforms the server response
  384. * @param {*} response
  385. * @param {Object} headers
  386. * @returns {*}
  387. * @private
  388. */
  389. FileUploader.prototype._transformResponse = function(response, headers) {
  390. var headersGetter = this._headersGetter(headers);
  391. angular.forEach($http.defaults.transformResponse, function(transformFn) {
  392. response = transformFn(response, headersGetter);
  393. });
  394. return response;
  395. };
  396. /**
  397. * Parsed response headers
  398. * @param headers
  399. * @returns {Object}
  400. * @see https://github.com/angular/angular.js/blob/master/src/ng/http.js
  401. * @private
  402. */
  403. FileUploader.prototype._parseHeaders = function(headers) {
  404. var parsed = {}, key, val, i;
  405. if (!headers) return parsed;
  406. angular.forEach(headers.split('\n'), function(line) {
  407. i = line.indexOf(':');
  408. key = line.slice(0, i).trim().toLowerCase();
  409. val = line.slice(i + 1).trim();
  410. if (key) {
  411. parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
  412. }
  413. });
  414. return parsed;
  415. };
  416. /**
  417. * Returns function that returns headers
  418. * @param {Object} parsedHeaders
  419. * @returns {Function}
  420. * @private
  421. */
  422. FileUploader.prototype._headersGetter = function(parsedHeaders) {
  423. return function(name) {
  424. if (name) {
  425. return parsedHeaders[name.toLowerCase()] || null;
  426. }
  427. return parsedHeaders;
  428. };
  429. };
  430. /**
  431. * The XMLHttpRequest transport
  432. * @param {FileItem} item
  433. * @private
  434. */
  435. FileUploader.prototype._xhrTransport = function(item) {
  436. var xhr = item._xhr = new XMLHttpRequest();
  437. var form = new FormData();
  438. var that = this;
  439. that._onBeforeUploadItem(item);
  440. angular.forEach(item.formData, function(obj) {
  441. angular.forEach(obj, function(value, key) {
  442. form.append(key, value);
  443. });
  444. });
  445. form.append(item.alias, item._file, item.file.name);
  446. xhr.upload.onprogress = function(event) {
  447. var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
  448. that._onProgressItem(item, progress);
  449. };
  450. xhr.onload = function() {
  451. var headers = that._parseHeaders(xhr.getAllResponseHeaders());
  452. var response = that._transformResponse(xhr.response, headers);
  453. var gist = that._isSuccessCode(xhr.status) ? 'Success' : 'Error';
  454. var method = '_on' + gist + 'Item';
  455. that[method](item, response, xhr.status, headers);
  456. that._onCompleteItem(item, response, xhr.status, headers);
  457. };
  458. xhr.onerror = function() {
  459. var headers = that._parseHeaders(xhr.getAllResponseHeaders());
  460. var response = that._transformResponse(xhr.response, headers);
  461. that._onErrorItem(item, response, xhr.status, headers);
  462. that._onCompleteItem(item, response, xhr.status, headers);
  463. };
  464. xhr.onabort = function() {
  465. var headers = that._parseHeaders(xhr.getAllResponseHeaders());
  466. var response = that._transformResponse(xhr.response, headers);
  467. that._onCancelItem(item, response, xhr.status, headers);
  468. that._onCompleteItem(item, response, xhr.status, headers);
  469. };
  470. xhr.open(item.method, item.url, true);
  471. xhr.withCredentials = item.withCredentials;
  472. angular.forEach(item.headers, function(value, name) {
  473. xhr.setRequestHeader(name, value);
  474. });
  475. xhr.send(form);
  476. this._render();
  477. };
  478. /**
  479. * The IFrame transport
  480. * @param {FileItem} item
  481. * @private
  482. */
  483. FileUploader.prototype._iframeTransport = function(item) {
  484. var form = angular.element('<form style="display: none;" />');
  485. var iframe = angular.element('<iframe name="iframeTransport' + Date.now() + '">');
  486. var input = item._input;
  487. var that = this;
  488. if (item._form) item._form.replaceWith(input); // remove old form
  489. item._form = form; // save link to new form
  490. that._onBeforeUploadItem(item);
  491. input.prop('name', item.alias);
  492. angular.forEach(item.formData, function(obj) {
  493. angular.forEach(obj, function(value, key) {
  494. var element = angular.element('<input type="hidden" name="' + key + '" />');
  495. element.val(value);
  496. form.append(element);
  497. });
  498. });
  499. form.prop({
  500. action: item.url,
  501. method: 'POST',
  502. target: iframe.prop('name'),
  503. enctype: 'multipart/form-data',
  504. encoding: 'multipart/form-data' // old IE
  505. });
  506. iframe.bind('load', function() {
  507. try {
  508. // Fix for legacy IE browsers that loads internal error page
  509. // when failed WS response received. In consequence iframe
  510. // content access denied error is thrown becouse trying to
  511. // access cross domain page. When such thing occurs notifying
  512. // with empty response object. See more info at:
  513. // http://stackoverflow.com/questions/151362/access-is-denied-error-on-accessing-iframe-document-object
  514. // Note that if non standard 4xx or 5xx error code returned
  515. // from WS then response content can be accessed without error
  516. // but 'XHR' status becomes 200. In order to avoid confusion
  517. // returning response via same 'success' event handler.
  518. // fixed angular.contents() for iframes
  519. var html = iframe[0].contentDocument.body.innerHTML;
  520. } catch (e) {}
  521. var xhr = {response: html, status: 200, dummy: true};
  522. var headers = {};
  523. var response = that._transformResponse(xhr.response, headers);
  524. that._onSuccessItem(item, response, xhr.status, headers);
  525. that._onCompleteItem(item, response, xhr.status, headers);
  526. });
  527. form.abort = function() {
  528. var xhr = {status: 0, dummy: true};
  529. var headers = {};
  530. var response;
  531. iframe.unbind('load').prop('src', 'javascript:false;');
  532. form.replaceWith(input);
  533. that._onCancelItem(item, response, xhr.status, headers);
  534. that._onCompleteItem(item, response, xhr.status, headers);
  535. };
  536. input.after(form);
  537. form.append(input).append(iframe);
  538. form[0].submit();
  539. this._render();
  540. };
  541. /**
  542. * Inner callback
  543. * @param {File|Object} item
  544. * @param {Object} filter
  545. * @param {Object} options
  546. * @private
  547. */
  548. FileUploader.prototype._onWhenAddingFileFailed = function(item, filter, options) {
  549. this.onWhenAddingFileFailed(item, filter, options);
  550. };
  551. /**
  552. * Inner callback
  553. * @param {FileItem} item
  554. */
  555. FileUploader.prototype._onAfterAddingFile = function(item) {
  556. this.onAfterAddingFile(item);
  557. };
  558. /**
  559. * Inner callback
  560. * @param {Array<FileItem>} items
  561. */
  562. FileUploader.prototype._onAfterAddingAll = function(items) {
  563. this.onAfterAddingAll(items);
  564. };
  565. /**
  566. * Inner callback
  567. * @param {FileItem} item
  568. * @private
  569. */
  570. FileUploader.prototype._onBeforeUploadItem = function(item) {
  571. item._onBeforeUpload();
  572. this.onBeforeUploadItem(item);
  573. };
  574. /**
  575. * Inner callback
  576. * @param {FileItem} item
  577. * @param {Number} progress
  578. * @private
  579. */
  580. FileUploader.prototype._onProgressItem = function(item, progress) {
  581. var total = this._getTotalProgress(progress);
  582. this.progress = total;
  583. item._onProgress(progress);
  584. this.onProgressItem(item, progress);
  585. this.onProgressAll(total);
  586. this._render();
  587. };
  588. /**
  589. * Inner callback
  590. * @param {FileItem} item
  591. * @param {*} response
  592. * @param {Number} status
  593. * @param {Object} headers
  594. * @private
  595. */
  596. FileUploader.prototype._onSuccessItem = function(item, response, status, headers) {
  597. item._onSuccess(response, status, headers);
  598. this.onSuccessItem(item, response, status, headers);
  599. };
  600. /**
  601. * Inner callback
  602. * @param {FileItem} item
  603. * @param {*} response
  604. * @param {Number} status
  605. * @param {Object} headers
  606. * @private
  607. */
  608. FileUploader.prototype._onErrorItem = function(item, response, status, headers) {
  609. item._onError(response, status, headers);
  610. this.onErrorItem(item, response, status, headers);
  611. };
  612. /**
  613. * Inner callback
  614. * @param {FileItem} item
  615. * @param {*} response
  616. * @param {Number} status
  617. * @param {Object} headers
  618. * @private
  619. */
  620. FileUploader.prototype._onCancelItem = function(item, response, status, headers) {
  621. item._onCancel(response, status, headers);
  622. this.onCancelItem(item, response, status, headers);
  623. };
  624. /**
  625. * Inner callback
  626. * @param {FileItem} item
  627. * @param {*} response
  628. * @param {Number} status
  629. * @param {Object} headers
  630. * @private
  631. */
  632. FileUploader.prototype._onCompleteItem = function(item, response, status, headers) {
  633. item._onComplete(response, status, headers);
  634. this.onCompleteItem(item, response, status, headers);
  635. var nextItem = this.getReadyItems()[0];
  636. this.isUploading = false;
  637. if(angular.isDefined(nextItem)) {
  638. nextItem.upload();
  639. return;
  640. }
  641. this.onCompleteAll();
  642. this.progress = this._getTotalProgress();
  643. this._render();
  644. };
  645. /**********************
  646. * STATIC
  647. **********************/
  648. /**
  649. * @borrows FileUploader.prototype.isFile
  650. */
  651. FileUploader.isFile = FileUploader.prototype.isFile;
  652. /**
  653. * @borrows FileUploader.prototype.isFileLikeObject
  654. */
  655. FileUploader.isFileLikeObject = FileUploader.prototype.isFileLikeObject;
  656. /**
  657. * @borrows FileUploader.prototype.isArrayLikeObject
  658. */
  659. FileUploader.isArrayLikeObject = FileUploader.prototype.isArrayLikeObject;
  660. /**
  661. * @borrows FileUploader.prototype.isHTML5
  662. */
  663. FileUploader.isHTML5 = FileUploader.prototype.isHTML5;
  664. /**
  665. * Inherits a target (Class_1) by a source (Class_2)
  666. * @param {Function} target
  667. * @param {Function} source
  668. */
  669. FileUploader.inherit = function(target, source) {
  670. target.prototype = Object.create(source.prototype);
  671. target.prototype.constructor = target;
  672. target.super_ = source;
  673. };
  674. FileUploader.FileLikeObject = FileLikeObject;
  675. FileUploader.FileItem = FileItem;
  676. FileUploader.FileDirective = FileDirective;
  677. FileUploader.FileSelect = FileSelect;
  678. FileUploader.FileDrop = FileDrop;
  679. FileUploader.FileOver = FileOver;
  680. // ---------------------------
  681. /**
  682. * Creates an instance of FileLikeObject
  683. * @param {File|HTMLInputElement|Object} fileOrInput
  684. * @constructor
  685. */
  686. function FileLikeObject(fileOrInput) {
  687. var isInput = angular.isElement(fileOrInput);
  688. var fakePathOrObject = isInput ? fileOrInput.value : fileOrInput;
  689. var postfix = angular.isString(fakePathOrObject) ? 'FakePath' : 'Object';
  690. var method = '_createFrom' + postfix;
  691. this[method](fakePathOrObject);
  692. }
  693. /**
  694. * Creates file like object from fake path string
  695. * @param {String} path
  696. * @private
  697. */
  698. FileLikeObject.prototype._createFromFakePath = function(path) {
  699. this.lastModifiedDate = null;
  700. this.size = null;
  701. this.type = 'like/' + path.slice(path.lastIndexOf('.') + 1).toLowerCase();
  702. this.name = path.slice(path.lastIndexOf('/') + path.lastIndexOf('\\') + 2);
  703. };
  704. /**
  705. * Creates file like object from object
  706. * @param {File|FileLikeObject} object
  707. * @private
  708. */
  709. FileLikeObject.prototype._createFromObject = function(object) {
  710. this.lastModifiedDate = angular.copy(object.lastModifiedDate);
  711. this.size = object.size;
  712. this.type = object.type;
  713. this.name = object.name;
  714. };
  715. // ---------------------------
  716. /**
  717. * Creates an instance of FileItem
  718. * @param {FileUploader} uploader
  719. * @param {File|HTMLInputElement|Object} some
  720. * @param {Object} options
  721. * @constructor
  722. */
  723. function FileItem(uploader, some, options) {
  724. var isInput = angular.isElement(some);
  725. var input = isInput ? angular.element(some) : null;
  726. var file = !isInput ? some : null;
  727. angular.extend(this, {
  728. url: uploader.url,
  729. alias: uploader.alias,
  730. headers: angular.copy(uploader.headers),
  731. formData: angular.copy(uploader.formData),
  732. removeAfterUpload: uploader.removeAfterUpload,
  733. withCredentials: uploader.withCredentials,
  734. method: uploader.method
  735. }, options, {
  736. uploader: uploader,
  737. file: new FileUploader.FileLikeObject(some),
  738. isReady: false,
  739. isUploading: false,
  740. isUploaded: false,
  741. isSuccess: false,
  742. isCancel: false,
  743. isError: false,
  744. progress: 0,
  745. index: null,
  746. _file: file,
  747. _input: input
  748. });
  749. if (input) this._replaceNode(input);
  750. }
  751. /**********************
  752. * PUBLIC
  753. **********************/
  754. /**
  755. * Uploads a FileItem
  756. */
  757. FileItem.prototype.upload = function() {
  758. this.uploader.uploadItem(this);
  759. };
  760. /**
  761. * Cancels uploading of FileItem
  762. */
  763. FileItem.prototype.cancel = function() {
  764. this.uploader.cancelItem(this);
  765. };
  766. /**
  767. * Removes a FileItem
  768. */
  769. FileItem.prototype.remove = function() {
  770. this.uploader.removeFromQueue(this);
  771. };
  772. /**
  773. * Callback
  774. * @private
  775. */
  776. FileItem.prototype.onBeforeUpload = function() {};
  777. /**
  778. * Callback
  779. * @param {Number} progress
  780. * @private
  781. */
  782. FileItem.prototype.onProgress = function(progress) {};
  783. /**
  784. * Callback
  785. * @param {*} response
  786. * @param {Number} status
  787. * @param {Object} headers
  788. */
  789. FileItem.prototype.onSuccess = function(response, status, headers) {};
  790. /**
  791. * Callback
  792. * @param {*} response
  793. * @param {Number} status
  794. * @param {Object} headers
  795. */
  796. FileItem.prototype.onError = function(response, status, headers) {};
  797. /**
  798. * Callback
  799. * @param {*} response
  800. * @param {Number} status
  801. * @param {Object} headers
  802. */
  803. FileItem.prototype.onCancel = function(response, status, headers) {};
  804. /**
  805. * Callback
  806. * @param {*} response
  807. * @param {Number} status
  808. * @param {Object} headers
  809. */
  810. FileItem.prototype.onComplete = function(response, status, headers) {};
  811. /**********************
  812. * PRIVATE
  813. **********************/
  814. /**
  815. * Inner callback
  816. */
  817. FileItem.prototype._onBeforeUpload = function() {
  818. this.isReady = true;
  819. this.isUploading = true;
  820. this.isUploaded = false;
  821. this.isSuccess = false;
  822. this.isCancel = false;
  823. this.isError = false;
  824. this.progress = 0;
  825. this.onBeforeUpload();
  826. };
  827. /**
  828. * Inner callback
  829. * @param {Number} progress
  830. * @private
  831. */
  832. FileItem.prototype._onProgress = function(progress) {
  833. this.progress = progress;
  834. this.onProgress(progress);
  835. };
  836. /**
  837. * Inner callback
  838. * @param {*} response
  839. * @param {Number} status
  840. * @param {Object} headers
  841. * @private
  842. */
  843. FileItem.prototype._onSuccess = function(response, status, headers) {
  844. this.isReady = false;
  845. this.isUploading = false;
  846. this.isUploaded = true;
  847. this.isSuccess = true;
  848. this.isCancel = false;
  849. this.isError = false;
  850. this.progress = 100;
  851. this.index = null;
  852. this.onSuccess(response, status, headers);
  853. };
  854. /**
  855. * Inner callback
  856. * @param {*} response
  857. * @param {Number} status
  858. * @param {Object} headers
  859. * @private
  860. */
  861. FileItem.prototype._onError = function(response, status, headers) {
  862. this.isReady = false;
  863. this.isUploading = false;
  864. this.isUploaded = true;
  865. this.isSuccess = false;
  866. this.isCancel = false;
  867. this.isError = true;
  868. this.progress = 0;
  869. this.index = null;
  870. this.onError(response, status, headers);
  871. };
  872. /**
  873. * Inner callback
  874. * @param {*} response
  875. * @param {Number} status
  876. * @param {Object} headers
  877. * @private
  878. */
  879. FileItem.prototype._onCancel = function(response, status, headers) {
  880. this.isReady = false;
  881. this.isUploading = false;
  882. this.isUploaded = false;
  883. this.isSuccess = false;
  884. this.isCancel = true;
  885. this.isError = false;
  886. this.progress = 0;
  887. this.index = null;
  888. this.onCancel(response, status, headers);
  889. };
  890. /**
  891. * Inner callback
  892. * @param {*} response
  893. * @param {Number} status
  894. * @param {Object} headers
  895. * @private
  896. */
  897. FileItem.prototype._onComplete = function(response, status, headers) {
  898. this.onComplete(response, status, headers);
  899. if (this.removeAfterUpload) this.remove();
  900. };
  901. /**
  902. * Destroys a FileItem
  903. */
  904. FileItem.prototype._destroy = function() {
  905. if (this._input) this._input.remove();
  906. if (this._form) this._form.remove();
  907. delete this._form;
  908. delete this._input;
  909. };
  910. /**
  911. * Prepares to uploading
  912. * @private
  913. */
  914. FileItem.prototype._prepareToUploading = function() {
  915. this.index = this.index || ++this.uploader._nextIndex;
  916. this.isReady = true;
  917. };
  918. /**
  919. * Replaces input element on his clone
  920. * @param {JQLite|jQuery} input
  921. * @private
  922. */
  923. FileItem.prototype._replaceNode = function(input) {
  924. var clone = $compile(input.clone())(input.scope());
  925. clone.prop('value', null); // FF fix
  926. input.css('display', 'none');
  927. input.after(clone); // remove jquery dependency
  928. };
  929. // ---------------------------
  930. /**
  931. * Creates instance of {FileDirective} object
  932. * @param {Object} options
  933. * @param {Object} options.uploader
  934. * @param {HTMLElement} options.element
  935. * @param {Object} options.events
  936. * @param {String} options.prop
  937. * @constructor
  938. */
  939. function FileDirective(options) {
  940. angular.extend(this, options);
  941. this.uploader._directives[this.prop].push(this);
  942. this._saveLinks();
  943. this.bind();
  944. }
  945. /**
  946. * Map of events
  947. * @type {Object}
  948. */
  949. FileDirective.prototype.events = {};
  950. /**
  951. * Binds events handles
  952. */
  953. FileDirective.prototype.bind = function() {
  954. for(var key in this.events) {
  955. var prop = this.events[key];
  956. this.element.bind(key, this[prop]);
  957. }
  958. };
  959. /**
  960. * Unbinds events handles
  961. */
  962. FileDirective.prototype.unbind = function() {
  963. for(var key in this.events) {
  964. this.element.unbind(key, this.events[key]);
  965. }
  966. };
  967. /**
  968. * Destroys directive
  969. */
  970. FileDirective.prototype.destroy = function() {
  971. var index = this.uploader._directives[this.prop].indexOf(this);
  972. this.uploader._directives[this.prop].splice(index, 1);
  973. this.unbind();
  974. // this.element = null;
  975. };
  976. /**
  977. * Saves links to functions
  978. * @private
  979. */
  980. FileDirective.prototype._saveLinks = function() {
  981. for(var key in this.events) {
  982. var prop = this.events[key];
  983. this[prop] = this[prop].bind(this);
  984. }
  985. };
  986. // ---------------------------
  987. FileUploader.inherit(FileSelect, FileDirective);
  988. /**
  989. * Creates instance of {FileSelect} object
  990. * @param {Object} options
  991. * @constructor
  992. */
  993. function FileSelect(options) {
  994. FileSelect.super_.apply(this, arguments);
  995. if(!this.uploader.isHTML5) {
  996. this.element.removeAttr('multiple');
  997. }
  998. this.element.prop('value', null); // FF fix
  999. }
  1000. /**
  1001. * Map of events
  1002. * @type {Object}
  1003. */
  1004. FileSelect.prototype.events = {
  1005. $destroy: 'destroy',
  1006. change: 'onChange'
  1007. };
  1008. /**
  1009. * Name of property inside uploader._directive object
  1010. * @type {String}
  1011. */
  1012. FileSelect.prototype.prop = 'select';
  1013. /**
  1014. * Returns options
  1015. * @return {Object|undefined}
  1016. */
  1017. FileSelect.prototype.getOptions = function() {};
  1018. /**
  1019. * Returns filters
  1020. * @return {Array<Function>|String|undefined}
  1021. */
  1022. FileSelect.prototype.getFilters = function() {};
  1023. /**
  1024. * If returns "true" then HTMLInputElement will be cleared
  1025. * @returns {Boolean}
  1026. */
  1027. FileSelect.prototype.isEmptyAfterSelection = function() {
  1028. return !!this.element.attr('multiple');
  1029. };
  1030. /**
  1031. * Event handler
  1032. */
  1033. FileSelect.prototype.onChange = function() {
  1034. var files = this.uploader.isHTML5 ? this.element[0].files : this.element[0];
  1035. var options = this.getOptions();
  1036. var filters = this.getFilters();
  1037. if (!this.uploader.isHTML5) this.destroy();
  1038. this.uploader.addToQueue(files, options, filters);
  1039. if (this.isEmptyAfterSelection()) this.element.prop('value', null);
  1040. };
  1041. // ---------------------------
  1042. FileUploader.inherit(FileDrop, FileDirective);
  1043. /**
  1044. * Creates instance of {FileDrop} object
  1045. * @param {Object} options
  1046. * @constructor
  1047. */
  1048. function FileDrop(options) {
  1049. FileDrop.super_.apply(this, arguments);
  1050. }
  1051. /**
  1052. * Map of events
  1053. * @type {Object}
  1054. */
  1055. FileDrop.prototype.events = {
  1056. $destroy: 'destroy',
  1057. drop: 'onDrop',
  1058. dragover: 'onDragOver',
  1059. dragleave: 'onDragLeave'
  1060. };
  1061. /**
  1062. * Name of property inside uploader._directive object
  1063. * @type {String}
  1064. */
  1065. FileDrop.prototype.prop = 'drop';
  1066. /**
  1067. * Returns options
  1068. * @return {Object|undefined}
  1069. */
  1070. FileDrop.prototype.getOptions = function() {};
  1071. /**
  1072. * Returns filters
  1073. * @return {Array<Function>|String|undefined}
  1074. */
  1075. FileDrop.prototype.getFilters = function() {};
  1076. /**
  1077. * Event handler
  1078. */
  1079. FileDrop.prototype.onDrop = function(event) {
  1080. var transfer = this._getTransfer(event);
  1081. if (!transfer) return;
  1082. var options = this.getOptions();
  1083. var filters = this.getFilters();
  1084. this._preventAndStop(event);
  1085. angular.forEach(this.uploader._directives.over, this._removeOverClass, this);
  1086. this.uploader.addToQueue(transfer.files, options, filters);
  1087. };
  1088. /**
  1089. * Event handler
  1090. */
  1091. FileDrop.prototype.onDragOver = function(event) {
  1092. var transfer = this._getTransfer(event);
  1093. if(!this._haveFiles(transfer.types)) return;
  1094. transfer.dropEffect = 'copy';
  1095. this._preventAndStop(event);
  1096. angular.forEach(this.uploader._directives.over, this._addOverClass, this);
  1097. };
  1098. /**
  1099. * Event handler
  1100. */
  1101. FileDrop.prototype.onDragLeave = function(event) {
  1102. if (event.currentTarget !== this.element[0]) return;
  1103. this._preventAndStop(event);
  1104. angular.forEach(this.uploader._directives.over, this._removeOverClass, this);
  1105. };
  1106. /**
  1107. * Helper
  1108. */
  1109. FileDrop.prototype._getTransfer = function(event) {
  1110. return event.dataTransfer ? event.dataTransfer : event.originalEvent.dataTransfer; // jQuery fix;
  1111. };
  1112. /**
  1113. * Helper
  1114. */
  1115. FileDrop.prototype._preventAndStop = function(event) {
  1116. event.preventDefault();
  1117. event.stopPropagation();
  1118. };
  1119. /**
  1120. * Returns "true" if types contains files
  1121. * @param {Object} types
  1122. */
  1123. FileDrop.prototype._haveFiles = function(types) {
  1124. if (!types) return false;
  1125. if (types.indexOf) {
  1126. return types.indexOf('Files') !== -1;
  1127. } else if(types.contains) {
  1128. return types.contains('Files');
  1129. } else {
  1130. return false;
  1131. }
  1132. };
  1133. /**
  1134. * Callback
  1135. */
  1136. FileDrop.prototype._addOverClass = function(item) {
  1137. item.addOverClass();
  1138. };
  1139. /**
  1140. * Callback
  1141. */
  1142. FileDrop.prototype._removeOverClass = function(item) {
  1143. item.removeOverClass();
  1144. };
  1145. // ---------------------------
  1146. FileUploader.inherit(FileOver, FileDirective);
  1147. /**
  1148. * Creates instance of {FileDrop} object
  1149. * @param {Object} options
  1150. * @constructor
  1151. */
  1152. function FileOver(options) {
  1153. FileOver.super_.apply(this, arguments);
  1154. }
  1155. /**
  1156. * Map of events
  1157. * @type {Object}
  1158. */
  1159. FileOver.prototype.events = {
  1160. $destroy: 'destroy'
  1161. };
  1162. /**
  1163. * Name of property inside uploader._directive object
  1164. * @type {String}
  1165. */
  1166. FileOver.prototype.prop = 'over';
  1167. /**
  1168. * Over class
  1169. * @type {string}
  1170. */
  1171. FileOver.prototype.overClass = 'nv-file-over';
  1172. /**
  1173. * Adds over class
  1174. */
  1175. FileOver.prototype.addOverClass = function() {
  1176. this.element.addClass(this.getOverClass());
  1177. };
  1178. /**
  1179. * Removes over class
  1180. */
  1181. FileOver.prototype.removeOverClass = function() {
  1182. this.element.removeClass(this.getOverClass());
  1183. };
  1184. /**
  1185. * Returns over class
  1186. * @returns {String}
  1187. */
  1188. FileOver.prototype.getOverClass = function() {
  1189. return this.overClass;
  1190. };
  1191. return FileUploader;
  1192. }])
  1193. .directive('nvFileSelect', ['$parse', 'FileUploader', function($parse, FileUploader) {
  1194. return {
  1195. link: function(scope, element, attributes) {
  1196. var uploader = scope.$eval(attributes.uploader);
  1197. if (!(uploader instanceof FileUploader)) {
  1198. throw new TypeError('"Uploader" must be an instance of FileUploader');
  1199. }
  1200. var object = new FileUploader.FileSelect({
  1201. uploader: uploader,
  1202. element: element
  1203. });
  1204. object.getOptions = $parse(attributes.options).bind(object, scope);
  1205. object.getFilters = function() {return attributes.filters;};
  1206. }
  1207. };
  1208. }])
  1209. .directive('nvFileDrop', ['$parse', 'FileUploader', function($parse, FileUploader) {
  1210. return {
  1211. link: function(scope, element, attributes) {
  1212. var uploader = scope.$eval(attributes.uploader);
  1213. if (!(uploader instanceof FileUploader)) {
  1214. throw new TypeError('"Uploader" must be an instance of FileUploader');
  1215. }
  1216. if (!uploader.isHTML5) return;
  1217. var object = new FileUploader.FileDrop({
  1218. uploader: uploader,
  1219. element: element
  1220. });
  1221. object.getOptions = $parse(attributes.options).bind(object, scope);
  1222. object.getFilters = function() {return attributes.filters;};
  1223. }
  1224. };
  1225. }])
  1226. .directive('nvFileOver', ['FileUploader', function(FileUploader) {
  1227. return {
  1228. link: function(scope, element, attributes) {
  1229. var uploader = scope.$eval(attributes.uploader);
  1230. if (!(uploader instanceof FileUploader)) {
  1231. throw new TypeError('"Uploader" must be an instance of FileUploader');
  1232. }
  1233. var object = new FileUploader.FileOver({
  1234. uploader: uploader,
  1235. element: element
  1236. });
  1237. object.getOverClass = function() {
  1238. return attributes.overClass || this.overClass;
  1239. };
  1240. }
  1241. };
  1242. }])
  1243. return module;
  1244. }));