summaryrefslogtreecommitdiff
path: root/ikiwiki/jquery.fileupload.js
blob: 1e3c6bf971a0e63bf29384a159688633e8f1aed2 (plain)
  1. /*
  2.  * jQuery File Upload Plugin 5.0.2
  3. * https://github.com/blueimp/jQuery-File-Upload
  4. *
  5. * Copyright 2010, Sebastian Tschan
  6. * https://blueimp.net
  7. *
  8. * Licensed under the MIT license:
  9. * http://creativecommons.org/licenses/MIT/
  10. */
  11. /*jslint nomen: true, unparam: true, regexp: true */
  12. /*global document, XMLHttpRequestUpload, Blob, File, FormData, location, jQuery */
  13. (function ($) {
  14. 'use strict';
  15. // The fileupload widget listens for change events on file input fields
  16. // defined via fileInput setting and drop events of the given dropZone.
  17. // In addition to the default jQuery Widget methods, the fileupload widget
  18. // exposes the "add" and "send" methods, to add or directly send files
  19. // using the fileupload API.
  20. // By default, files added via file input selection, drag & drop or
  21. // "add" method are uploaded immediately, but it is possible to override
  22. // the "add" callback option to queue file uploads.
  23. $.widget('blueimp.fileupload', {
  24. options: {
  25. // The namespace used for event handler binding on the dropZone and
  26. // fileInput collections.
  27. // If not set, the name of the widget ("fileupload") is used.
  28. namespace: undefined,
  29. // The drop target collection, by the default the complete document.
  30. // Set to null or an empty collection to disable drag & drop support:
  31. dropZone: $(document),
  32. // The file input field collection, that is listened for change events.
  33. // If undefined, it is set to the file input fields inside
  34. // of the widget element on plugin initialization.
  35. // Set to null or an empty collection to disable the change listener.
  36. fileInput: undefined,
  37. // By default, the file input field is replaced with a clone after
  38. // each input field change event. This is required for iframe transport
  39. // queues and allows change events to be fired for the same file
  40. // selection, but can be disabled by setting the following option to false:
  41. replaceFileInput: true,
  42. // The parameter name for the file form data (the request argument name).
  43. // If undefined or empty, the name property of the file input field is
  44. // used, or "files[]" if the file input name property is also empty:
  45. paramName: undefined,
  46. // By default, each file of a selection is uploaded using an individual
  47. // request for XHR type uploads. Set to false to upload file
  48. // selections in one request each:
  49. singleFileUploads: true,
  50. // Set the following option to true to issue all file upload requests
  51. // in a sequential order:
  52. sequentialUploads: false,
  53. // Set the following option to true to force iframe transport uploads:
  54. forceIframeTransport: false,
  55. // By default, XHR file uploads are sent as multipart/form-data.
  56. // The iframe transport is always using multipart/form-data.
  57. // Set to false to enable non-multipart XHR uploads:
  58. multipart: true,
  59. // To upload large files in smaller chunks, set the following option
  60. // to a preferred maximum chunk size. If set to 0, null or undefined,
  61. // or the browser does not support the required Blob API, files will
  62. // be uploaded as a whole.
  63. maxChunkSize: undefined,
  64. // When a non-multipart upload or a chunked multipart upload has been
  65. // aborted, this option can be used to resume the upload by setting
  66. // it to the size of the already uploaded bytes. This option is most
  67. // useful when modifying the options object inside of the "add" or
  68. // "send" callbacks, as the options are cloned for each file upload.
  69. uploadedBytes: undefined,
  70. // By default, failed (abort or error) file uploads are removed from the
  71. // global progress calculation. Set the following option to false to
  72. // prevent recalculating the global progress data:
  73. recalculateProgress: true,
  74. // Additional form data to be sent along with the file uploads can be set
  75. // using this option, which accepts an array of objects with name and
  76. // value properties, a function returning such an array, a FormData
  77. // object (for XHR file uploads), or a simple object.
  78. // The form of the first fileInput is given as parameter to the function:
  79. formData: function (form) {
  80. return form.serializeArray();
  81. },
  82. // The add callback is invoked as soon as files are added to the fileupload
  83. // widget (via file input selection, drag & drop or add API call).
  84. // If the singleFileUploads option is enabled, this callback will be
  85. // called once for each file in the selection for XHR file uplaods, else
  86. // once for each file selection.
  87. // The upload starts when the submit method is invoked on the data parameter.
  88. // The data object contains a files property holding the added files
  89. // and allows to override plugin options as well as define ajax settings.
  90. // Listeners for this callback can also be bound the following way:
  91. // .bind('fileuploadadd', func);
  92. // data.submit() returns a Promise object and allows to attach additional
  93. // handlers using jQuery's Deferred callbacks:
  94. // data.submit().done(func).fail(func).always(func);
  95. add: function (e, data) {
  96. data.submit();
  97. },
  98. // Other callbacks:
  99. // Callback for the start of each file upload request:
  100. // send: function (e, data) {}, // .bind('fileuploadsend', func);
  101. // Callback for successful uploads:
  102. // done: function (e, data) {}, // .bind('fileuploaddone', func);
  103. // Callback for failed (abort or error) uploads:
  104. // fail: function (e, data) {}, // .bind('fileuploadfail', func);
  105. // Callback for completed (success, abort or error) requests:
  106. // always: function (e, data) {}, // .bind('fileuploadalways', func);
  107. // Callback for upload progress events:
  108. // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
  109. // Callback for global upload progress events:
  110. // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
  111. // Callback for uploads start, equivalent to the global ajaxStart event:
  112. // start: function (e) {}, // .bind('fileuploadstart', func);
  113. // Callback for uploads stop, equivalent to the global ajaxStop event:
  114. // stop: function (e) {}, // .bind('fileuploadstop', func);
  115. // Callback for change events of the fileInput collection:
  116. // change: function (e, data) {}, // .bind('fileuploadchange', func);
  117. // Callback for drop events of the dropZone collection:
  118. // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
  119. // Callback for dragover events of the dropZone collection:
  120. // dragover: function (e) {}, // .bind('fileuploaddragover', func);
  121. // The plugin options are used as settings object for the ajax calls.
  122. // The following are jQuery ajax settings required for the file uploads:
  123. processData: false,
  124. contentType: false,
  125. cache: false
  126. },
  127. // A list of options that require a refresh after assigning a new value:
  128. _refreshOptionsList: ['namespace', 'dropZone', 'fileInput'],
  129. _isXHRUpload: function (options) {
  130. var undef = 'undefined';
  131. return !options.forceIframeTransport &&
  132. typeof XMLHttpRequestUpload !== undef && typeof File !== undef &&
  133. (!options.multipart || typeof FormData !== undef);
  134. },
  135. _getFormData: function (options) {
  136. var formData;
  137. if (typeof options.formData === 'function') {
  138. return options.formData(options.form);
  139. } else if ($.isArray(options.formData)) {
  140. return options.formData;
  141. } else if (options.formData) {
  142. formData = [];
  143. $.each(options.formData, function (name, value) {
  144. formData.push({name: name, value: value});
  145. });
  146. return formData;
  147. }
  148. return [];
  149. },
  150. _getTotal: function (files) {
  151. var total = 0;
  152. $.each(files, function (index, file) {
  153. total += file.size || 1;
  154. });
  155. return total;
  156. },
  157. _onProgress: function (e, data) {
  158. if (e.lengthComputable) {
  159. var total = data.total || this._getTotal(data.files),
  160. loaded = parseInt(
  161. e.loaded / e.total * (data.chunkSize || total),
  162. 10
  163. ) + (data.uploadedBytes || 0);
  164. this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);
  165. data.lengthComputable = true;
  166. data.loaded = loaded;
  167. data.total = total;
  168. // Trigger a custom progress event with a total data property set
  169. // to the file size(s) of the current upload and a loaded data
  170. // property calculated accordingly:
  171. this._trigger('progress', e, data);
  172. // Trigger a global progress event for all current file uploads,
  173. // including ajax calls queued for sequential file uploads:
  174. this._trigger('progressall', e, {
  175. lengthComputable: true,
  176. loaded: this._loaded,
  177. total: this._total
  178. });
  179. }
  180. },
  181. _initProgressListener: function (options) {
  182. var that = this,
  183. xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
  184. // Accesss to the native XHR object is required to add event listeners
  185. // for the upload progress event:
  186. if (xhr.upload && xhr.upload.addEventListener) {
  187. xhr.upload.addEventListener('progress', function (e) {
  188. that._onProgress(e, options);
  189. }, false);
  190. options.xhr = function () {
  191. return xhr;
  192. };
  193. }
  194. },
  195. _initXHRData: function (options) {
  196. var formData,
  197. file = options.files[0];
  198. if (!options.multipart || options.blob) {
  199. // For non-multipart uploads and chunked uploads,
  200. // file meta data is not part of the request body,
  201. // so we transmit this data as part of the HTTP headers.
  202. // For cross domain requests, these headers must be allowed
  203. // via Access-Control-Allow-Headers or removed using
  204. // the beforeSend callback:
  205. options.headers = $.extend(options.headers, {
  206. 'X-File-Name': file.name,
  207. 'X-File-Type': file.type,
  208. 'X-File-Size': file.size
  209. });
  210. if (!options.blob) {
  211. // Non-chunked non-multipart upload:
  212. options.contentType = file.type;
  213. options.data = file;
  214. } else if (!options.multipart) {
  215. // Chunked non-multipart upload:
  216. options.contentType = 'application/octet-stream';
  217. options.data = options.blob;
  218. }
  219. }
  220. if (options.multipart && typeof FormData !== 'undefined') {
  221. if (options.formData instanceof FormData) {
  222. formData = options.formData;
  223. } else {
  224. formData = new FormData();
  225. $.each(this._getFormData(options), function (index, field) {
  226. formData.append(field.name, field.value);
  227. });
  228. }
  229. if (options.blob) {
  230. formData.append(options.paramName, options.blob);
  231. } else {
  232. $.each(options.files, function (index, file) {
  233. // File objects are also Blob instances.
  234. // This check allows the tests to run with
  235. // dummy objects:
  236. if (file instanceof Blob) {
  237. formData.append(options.paramName, file);
  238. }
  239. });
  240. }
  241. options.data = formData;
  242. }
  243. // Blob reference is not needed anymore, free memory:
  244. options.blob = null;
  245. },
  246. _initIframeSettings: function (options) {
  247. // Setting the dataType to iframe enables the iframe transport:
  248. options.dataType = 'iframe ' + (options.dataType || '');
  249. // The iframe transport accepts a serialized array as form data:
  250. options.formData = this._getFormData(options);
  251. },
  252. _initDataSettings: function (options) {
  253. if (this._isXHRUpload(options)) {
  254. if (!this._chunkedUpload(options, true)) {
  255. if (!options.data) {
  256. this._initXHRData(options);
  257. }
  258. this._initProgressListener(options);
  259. }
  260. } else {
  261. this._initIframeSettings(options);
  262. }
  263. },
  264. _initFormSettings: function (options) {
  265. // Retrieve missing options from the input field and the
  266. // associated form, if available:
  267. if (!options.form || !options.form.length) {
  268. options.form = $(options.fileInput.prop('form'));
  269. }
  270. if (!options.paramName) {
  271. options.paramName = options.fileInput.prop('name') ||
  272. 'files[]';
  273. }
  274. if (!options.url) {
  275. options.url = options.form.prop('action') || location.href;
  276. }
  277. // The HTTP request method must be "POST" or "PUT":
  278. options.type = (options.type || options.form.prop('method') || '')
  279. .toUpperCase();
  280. if (options.type !== 'POST' && options.type !== 'PUT') {
  281. options.type = 'POST';
  282. }
  283. },
  284. _getAJAXSettings: function (data) {
  285. var options = $.extend({}, this.options, data);
  286. this._initFormSettings(options);
  287. this._initDataSettings(options);
  288. return options;
  289. },
  290. // Maps jqXHR callbacks to the equivalent
  291. // methods of the given Promise object:
  292. _enhancePromise: function (promise) {
  293. promise.success = promise.done;
  294. promise.error = promise.fail;
  295. promise.complete = promise.always;
  296. return promise;
  297. },
  298. // Creates and returns a Promise object enhanced with
  299. // the jqXHR methods abort, success, error and complete:
  300. _getXHRPromise: function (resolveOrReject, context, args) {
  301. var dfd = $.Deferred(),
  302. promise = dfd.promise();
  303. context = context || this.options.context || promise;
  304. if (resolveOrReject === true) {
  305. dfd.resolveWith(context, args);
  306. } else if (resolveOrReject === false) {
  307. dfd.rejectWith(context, args);
  308. }
  309. promise.abort = dfd.promise;
  310. return this._enhancePromise(promise);
  311. },
  312. // Uploads a file in multiple, sequential requests
  313. // by splitting the file up in multiple blob chunks.
  314. // If the second parameter is true, only tests if the file
  315. // should be uploaded in chunks, but does not invoke any
  316. // upload requests:
  317. _chunkedUpload: function (options, testOnly) {
  318. var that = this,
  319. file = options.files[0],
  320. fs = file.size,
  321. ub = options.uploadedBytes = options.uploadedBytes || 0,
  322. mcs = options.maxChunkSize || fs,
  323. // Use the Blob methods with the slice implementation
  324. // according to the W3C Blob API specification:
  325. slice = file.webkitSlice || file.mozSlice || file.slice,
  326. upload,
  327. n,
  328. jqXHR,
  329. pipe;
  330. if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
  331. options.data) {
  332. return false;
  333. }
  334. if (testOnly) {
  335. return true;
  336. }
  337. if (ub >= fs) {
  338. file.error = 'uploadedBytes';
  339. return this._getXHRPromise(false);
  340. }
  341. // n is the number of blobs to upload,
  342. // calculated via filesize, uploaded bytes and max chunk size:
  343. n = Math.ceil((fs - ub) / mcs);
  344. // The chunk upload method accepting the chunk number as parameter:
  345. upload = function (i) {
  346. if (!i) {
  347. return that._getXHRPromise(true);
  348. }
  349. // Upload the blobs in sequential order:
  350. return upload(i -= 1).pipe(function () {
  351. // Clone the options object for each chunk upload:
  352. var o = $.extend({}, options);
  353. o.blob = slice.call(
  354. file,
  355. ub + i * mcs,
  356. ub + (i + 1) * mcs
  357. );
  358. // Store the current chunk size, as the blob itself
  359. // will be dereferenced after data processing:
  360. o.chunkSize = o.blob.size;
  361. // Process the upload data (the blob and potential form data):
  362. that._initXHRData(o);
  363. // Add progress listeners for this chunk upload:
  364. that._initProgressListener(o);
  365. jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context))
  366. .done(function () {
  367. // Create a progress event if upload is done and
  368. // no progress event has been invoked for this chunk:
  369. if (!o.loaded) {
  370. that._onProgress($.Event('progress', {
  371. lengthComputable: true,
  372. loaded: o.chunkSize,
  373. total: o.chunkSize
  374. }), o);
  375. }
  376. options.uploadedBytes = o.uploadedBytes
  377. += o.chunkSize;
  378. });
  379. return jqXHR;
  380. });
  381. };
  382. // Return the piped Promise object, enhanced with an abort method,
  383. // which is delegated to the jqXHR object of the current upload,
  384. // and jqXHR callbacks mapped to the equivalent Promise methods:
  385. pipe = upload(n);
  386. pipe.abort = function () {
  387. return jqXHR.abort();
  388. };
  389. return this._enhancePromise(pipe);
  390. },
  391. _beforeSend: function (e, data) {
  392. if (this._active === 0) {
  393. // the start callback is triggered when an upload starts
  394. // and no other uploads are currently running,
  395. // equivalent to the global ajaxStart event:
  396. this._trigger('start');
  397. }
  398. this._active += 1;
  399. // Initialize the global progress values:
  400. this._loaded += data.uploadedBytes || 0;
  401. this._total += this._getTotal(data.files);
  402. },
  403. _onDone: function (result, textStatus, jqXHR, options) {
  404. if (!this._isXHRUpload(options)) {
  405. // Create a progress event for each iframe load:
  406. this._onProgress($.Event('progress', {
  407. lengthComputable: true,
  408. loaded: 1,
  409. total: 1
  410. }), options);
  411. }
  412. options.result = result;
  413. options.textStatus = textStatus;
  414. options.jqXHR = jqXHR;
  415. this._trigger('done', null, options);
  416. },
  417. _onFail: function (jqXHR, textStatus, errorThrown, options) {
  418. options.jqXHR = jqXHR;
  419. options.textStatus = textStatus;
  420. options.errorThrown = errorThrown;
  421. this._trigger('fail', null, options);
  422. if (options.recalculateProgress) {
  423. // Remove the failed (error or abort) file upload from
  424. // the global progress calculation:
  425. this._loaded -= options.loaded || options.uploadedBytes || 0;
  426. this._total -= options.total || this._getTotal(options.files);
  427. }
  428. },
  429. _onAlways: function (result, textStatus, jqXHR, errorThrown, options) {
  430. this._active -= 1;
  431. options.result = result;
  432. options.textStatus = textStatus;
  433. options.jqXHR = jqXHR;
  434. options.errorThrown = errorThrown;
  435. this._trigger('always', null, options);
  436. if (this._active === 0) {
  437. // The stop callback is triggered when all uploads have
  438. // been completed, equivalent to the global ajaxStop event:
  439. this._trigger('stop');
  440. // Reset the global progress values:
  441. this._loaded = this._total = 0;
  442. }
  443. },
  444. _onSend: function (e, data) {
  445. var that = this,
  446. jqXHR,
  447. pipe,
  448. options = that._getAJAXSettings(data),
  449. send = function (resolve, args) {
  450. jqXHR = jqXHR || (
  451. (resolve !== false &&
  452. that._trigger('send', e, options) !== false &&
  453. (that._chunkedUpload(options) || $.ajax(options))) ||
  454. that._getXHRPromise(false, options.context, args)
  455. ).done(function (result, textStatus, jqXHR) {
  456. that._onDone(result, textStatus, jqXHR, options);
  457. }).fail(function (jqXHR, textStatus, errorThrown) {
  458. that._onFail(jqXHR, textStatus, errorThrown, options);
  459. }).always(function (a1, a2, a3) {
  460. if (!a3 || typeof a3 === 'string') {
  461. that._onAlways(undefined, a2, a1, a3, options);
  462. } else {
  463. that._onAlways(a1, a2, a3, undefined, options);
  464. }
  465. });
  466. return jqXHR;
  467. };
  468. this._beforeSend(e, options);
  469. if (this.options.sequentialUploads) {
  470. // Return the piped Promise object, enhanced with an abort method,
  471. // which is delegated to the jqXHR object of the current upload,
  472. // and jqXHR callbacks mapped to the equivalent Promise methods:
  473. pipe = (this._sequence = this._sequence.pipe(send, send));
  474. pipe.abort = function () {
  475. if (!jqXHR) {
  476. return send(false, [undefined, 'abort', 'abort']);
  477. }
  478. return jqXHR.abort();
  479. };
  480. return this._enhancePromise(pipe);
  481. }
  482. return send();
  483. },
  484. _onAdd: function (e, data) {
  485. var that = this,
  486. result = true,
  487. options = $.extend({}, this.options, data);
  488. if (options.singleFileUploads && this._isXHRUpload(options)) {
  489. $.each(data.files, function (index, file) {
  490. var newData = $.extend({}, data, {files: [file]});
  491. newData.submit = function () {
  492. return that._onSend(e, newData);
  493. };
  494. return (result = that._trigger('add', e, newData));
  495. });
  496. return result;
  497. } else if (data.files.length) {
  498. data = $.extend({}, data);
  499. data.submit = function () {
  500. return that._onSend(e, data);
  501. };
  502. return this._trigger('add', e, data);
  503. }
  504. },
  505. // File Normalization for Gecko 1.9.1 (Firefox 3.5) support:
  506. _normalizeFile: function (index, file) {
  507. if (file.name === undefined && file.size === undefined) {
  508. file.name = file.fileName;
  509. file.size = file.fileSize;
  510. }
  511. },
  512. _replaceFileInput: function (input) {
  513. var inputClone = input.clone(true);
  514. $('<form></form>').append(inputClone)[0].reset();
  515. // Detaching allows to insert the fileInput on another form
  516. // without loosing the file input value:
  517. input.after(inputClone).detach();
  518. // Replace the original file input element in the fileInput
  519. // collection with the clone, which has been copied including
  520. // event handlers:
  521. this.options.fileInput = this.options.fileInput.map(function (i, el) {
  522. if (el === input[0]) {
  523. return inputClone[0];
  524. }
  525. return el;
  526. });
  527. },
  528. _onChange: function (e) {
  529. var that = e.data.fileupload,
  530. data = {
  531. files: $.each($.makeArray(e.target.files), that._normalizeFile),
  532. fileInput: $(e.target),
  533. form: $(e.target.form)
  534. };
  535. if (!data.files.length) {
  536. // If the files property is not available, the browser does not
  537. // support the File API and we add a pseudo File object with
  538. // the input value as name with path information removed:
  539. data.files = [{name: e.target.value.replace(/^.*\\/, '')}];
  540. }
  541. // Store the form reference as jQuery data for other event handlers,
  542. // as the form property is not available after replacing the file input:
  543. if (data.form.length) {
  544. data.fileInput.data('blueimp.fileupload.form', data.form);
  545. } else {
  546. data.form = data.fileInput.data('blueimp.fileupload.form');
  547. }
  548. if (that.options.replaceFileInput) {
  549. that._replaceFileInput(data.fileInput);
  550. }
  551. if (that._trigger('change', e, data) === false ||
  552. that._onAdd(e, data) === false) {
  553. return false;
  554. }
  555. },
  556. _onDrop: function (e) {
  557. var that = e.data.fileupload,
  558. dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
  559. data = {
  560. files: $.each(
  561. $.makeArray(dataTransfer && dataTransfer.files),
  562. that._normalizeFile
  563. )
  564. };
  565. if (that._trigger('drop', e, data) === false ||
  566. that._onAdd(e, data) === false) {
  567. return false;
  568. }
  569. e.preventDefault();
  570. },
  571. _onDragOver: function (e) {
  572. var that = e.data.fileupload,
  573. dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
  574. if (that._trigger('dragover', e) === false) {
  575. return false;
  576. }
  577. if (dataTransfer) {
  578. dataTransfer.dropEffect = dataTransfer.effectAllowed = 'copy';
  579. }
  580. e.preventDefault();
  581. },
  582. _initEventHandlers: function () {
  583. var ns = this.options.namespace || this.name;
  584. this.options.dropZone
  585. .bind('dragover.' + ns, {fileupload: this}, this._onDragOver)
  586. .bind('drop.' + ns, {fileupload: this}, this._onDrop);
  587. this.options.fileInput
  588. .bind('change.' + ns, {fileupload: this}, this._onChange);
  589. },
  590. _destroyEventHandlers: function () {
  591. var ns = this.options.namespace || this.name;
  592. this.options.dropZone
  593. .unbind('dragover.' + ns, this._onDragOver)
  594. .unbind('drop.' + ns, this._onDrop);
  595. this.options.fileInput
  596. .unbind('change.' + ns, this._onChange);
  597. },
  598. _beforeSetOption: function (key, value) {
  599. this._destroyEventHandlers();
  600. },
  601. _afterSetOption: function (key, value) {
  602. var options = this.options;
  603. if (!options.fileInput) {
  604. options.fileInput = $();
  605. }
  606. if (!options.dropZone) {
  607. options.dropZone = $();
  608. }
  609. this._initEventHandlers();
  610. },
  611. _setOption: function (key, value) {
  612. var refresh = $.inArray(key, this._refreshOptionsList) !== -1;
  613. if (refresh) {
  614. this._beforeSetOption(key, value);
  615. }
  616. $.Widget.prototype._setOption.call(this, key, value);
  617. if (refresh) {
  618. this._afterSetOption(key, value);
  619. }
  620. },
  621. _create: function () {
  622. var options = this.options;
  623. if (options.fileInput === undefined) {
  624. options.fileInput = this.element.is('input:file') ?
  625. this.element : this.element.find('input:file');
  626. } else if (!options.fileInput) {
  627. options.fileInput = $();
  628. }
  629. if (!options.dropZone) {
  630. options.dropZone = $();
  631. }
  632. this._sequence = this._getXHRPromise(true);
  633. this._active = this._loaded = this._total = 0;
  634. this._initEventHandlers();
  635. },
  636. destroy: function () {
  637. this._destroyEventHandlers();
  638. $.Widget.prototype.destroy.call(this);
  639. },
  640. enable: function () {
  641. $.Widget.prototype.enable.call(this);
  642. this._initEventHandlers();
  643. },
  644. disable: function () {
  645. this._destroyEventHandlers();
  646. $.Widget.prototype.disable.call(this);
  647. },
  648. // This method is exposed to the widget API and allows adding files
  649. // using the fileupload API. The data parameter accepts an object which
  650. // must have a files property and can contain additional options:
  651. // .fileupload('add', {files: filesList});
  652. add: function (data) {
  653. if (!data || this.options.disabled) {
  654. return;
  655. }
  656. data.files = $.each($.makeArray(data.files), this._normalizeFile);
  657. this._onAdd(null, data);
  658. },
  659. // This method is exposed to the widget API and allows sending files
  660. // using the fileupload API. The data parameter accepts an object which
  661. // must have a files property and can contain additional options:
  662. // .fileupload('send', {files: filesList});
  663. // The method returns a Promise object for the file upload call.
  664. send: function (data) {
  665. if (data && !this.options.disabled) {
  666. data.files = $.each($.makeArray(data.files), this._normalizeFile);
  667. if (data.files.length) {
  668. return this._onSend(null, data);
  669. }
  670. }
  671. return this._getXHRPromise(false, data && data.context);
  672. }
  673. });
  674. }(jQuery));