module.js 48 KB

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