jquery.fileupload-ui.js 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. /*
  2. * jQuery File Upload User Interface Plugin 6.10
  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://www.opensource.org/licenses/MIT
  10. */
  11. /*jslint nomen: true, unparam: true, regexp: true */
  12. /*global define, window, URL, webkitURL, FileReader */
  13. (function (factory) {
  14. 'use strict';
  15. if (typeof define === 'function' && define.amd) {
  16. // Register as an anonymous AMD module:
  17. define([
  18. 'jquery',
  19. 'tmpl',
  20. 'load-image',
  21. './jquery.fileupload-fp'
  22. ], factory);
  23. } else {
  24. // Browser globals:
  25. factory(
  26. window.jQuery,
  27. window.tmpl,
  28. window.loadImage
  29. );
  30. }
  31. }(function ($, tmpl, loadImage) {
  32. 'use strict';
  33. // The UI version extends the file upload widget
  34. // and adds complete user interface interaction:
  35. $.widget('blueimp.fileupload', $.blueimp.fileupload, {
  36. options: {
  37. // By default, files added to the widget are uploaded as soon
  38. // as the user clicks on the start buttons. To enable automatic
  39. // uploads, set the following option to true:
  40. autoUpload: false,
  41. // The following option limits the number of files that are
  42. // allowed to be uploaded using this widget:
  43. maxNumberOfFiles: undefined,
  44. // The maximum allowed file size:
  45. maxFileSize: undefined,
  46. // The minimum allowed file size:
  47. minFileSize: undefined,
  48. // The regular expression for allowed file types, matches
  49. // against either file type or file name:
  50. acceptFileTypes: /.+$/i,
  51. // The regular expression to define for which files a preview
  52. // image is shown, matched against the file type:
  53. previewSourceFileTypes: /^image\/(gif|jpeg|png)$/,
  54. // The maximum file size of images that are to be displayed as preview:
  55. previewSourceMaxFileSize: 5000000, // 5MB
  56. // The maximum width of the preview images:
  57. previewMaxWidth: 80,
  58. // The maximum height of the preview images:
  59. previewMaxHeight: 80,
  60. // By default, preview images are displayed as canvas elements
  61. // if supported by the browser. Set the following option to false
  62. // to always display preview images as img elements:
  63. previewAsCanvas: true,
  64. // The ID of the upload template:
  65. uploadTemplateId: 'template-upload',
  66. // The ID of the download template:
  67. downloadTemplateId: 'template-download',
  68. // The container for the list of files. If undefined, it is set to
  69. // an element with class "files" inside of the widget element:
  70. filesContainer: undefined,
  71. // By default, files are appended to the files container.
  72. // Set the following option to true, to prepend files instead:
  73. prependFiles: false,
  74. // The expected data type of the upload response, sets the dataType
  75. // option of the $.ajax upload requests:
  76. dataType: 'json',
  77. // The add callback is invoked as soon as files are added to the fileupload
  78. // widget (via file input selection, drag & drop or add API call).
  79. // See the basic file upload widget for more information:
  80. add: function (e, data) {
  81. var that = $(this).data('fileupload'),
  82. options = that.options,
  83. files = data.files;
  84. $(this).fileupload('process', data).done(function () {
  85. that._adjustMaxNumberOfFiles(-files.length);
  86. data.maxNumberOfFilesAdjusted = true;
  87. data.files.valid = data.isValidated = that._validate(files);
  88. data.context = that._renderUpload(files).data('data', data);
  89. options.filesContainer[
  90. options.prependFiles ? 'prepend' : 'append'
  91. ](data.context);
  92. that._renderPreviews(files, data.context);
  93. that._forceReflow(data.context);
  94. that._transition(data.context).done(
  95. function () {
  96. if ((that._trigger('added', e, data) !== false) &&
  97. (options.autoUpload || data.autoUpload) &&
  98. data.autoUpload !== false && data.isValidated) {
  99. data.submit();
  100. }
  101. }
  102. );
  103. });
  104. },
  105. // Callback for the start of each file upload request:
  106. send: function (e, data) {
  107. var that = $(this).data('fileupload');
  108. if (!data.isValidated) {
  109. if (!data.maxNumberOfFilesAdjusted) {
  110. that._adjustMaxNumberOfFiles(-data.files.length);
  111. data.maxNumberOfFilesAdjusted = true;
  112. }
  113. if (!that._validate(data.files)) {
  114. return false;
  115. }
  116. }
  117. if (data.context && data.dataType &&
  118. data.dataType.substr(0, 6) === 'iframe') {
  119. // Iframe Transport does not support progress events.
  120. // In lack of an indeterminate progress bar, we set
  121. // the progress to 100%, showing the full animated bar:
  122. data.context
  123. .find('.progress').addClass(
  124. !$.support.transition && 'progress-animated'
  125. )
  126. .attr('aria-valuenow', 100)
  127. .find('.bar').css(
  128. 'width',
  129. '100%'
  130. );
  131. }
  132. return that._trigger('sent', e, data);
  133. },
  134. // Callback for successful uploads:
  135. done: function (e, data) {
  136. var that = $(this).data('fileupload'),
  137. template;
  138. if (data.context) {
  139. data.context.each(function (index) {
  140. var file = ($.isArray(data.result) &&
  141. data.result[index]) ||
  142. {error: 'Empty file upload result'};
  143. if (file.error) {
  144. that._adjustMaxNumberOfFiles(1);
  145. }
  146. that._transition($(this)).done(
  147. function () {
  148. var node = $(this);
  149. template = that._renderDownload([file])
  150. .replaceAll(node);
  151. that._forceReflow(template);
  152. that._transition(template).done(
  153. function () {
  154. data.context = $(this);
  155. that._trigger('completed', e, data);
  156. }
  157. );
  158. }
  159. );
  160. });
  161. } else {
  162. if ($.isArray(data.result)) {
  163. $.each(data.result, function (index, file) {
  164. if (data.maxNumberOfFilesAdjusted && file.error) {
  165. that._adjustMaxNumberOfFiles(1);
  166. } else if (!data.maxNumberOfFilesAdjusted &&
  167. !file.error) {
  168. that._adjustMaxNumberOfFiles(-1);
  169. }
  170. });
  171. data.maxNumberOfFilesAdjusted = true;
  172. }
  173. template = that._renderDownload(data.result)
  174. .appendTo(that.options.filesContainer);
  175. that._forceReflow(template);
  176. that._transition(template).done(
  177. function () {
  178. data.context = $(this);
  179. that._trigger('completed', e, data);
  180. }
  181. );
  182. }
  183. },
  184. // Callback for failed (abort or error) uploads:
  185. fail: function (e, data) {
  186. var that = $(this).data('fileupload'),
  187. template;
  188. if (data.maxNumberOfFilesAdjusted) {
  189. that._adjustMaxNumberOfFiles(data.files.length);
  190. }
  191. if (data.context) {
  192. data.context.each(function (index) {
  193. if (data.errorThrown !== 'abort') {
  194. var file = data.files[index];
  195. file.error = file.error || data.errorThrown ||
  196. true;
  197. that._transition($(this)).done(
  198. function () {
  199. var node = $(this);
  200. template = that._renderDownload([file])
  201. .replaceAll(node);
  202. that._forceReflow(template);
  203. that._transition(template).done(
  204. function () {
  205. data.context = $(this);
  206. that._trigger('failed', e, data);
  207. }
  208. );
  209. }
  210. );
  211. } else {
  212. that._transition($(this)).done(
  213. function () {
  214. $(this).remove();
  215. that._trigger('failed', e, data);
  216. }
  217. );
  218. }
  219. });
  220. } else if (data.errorThrown !== 'abort') {
  221. data.context = that._renderUpload(data.files)
  222. .appendTo(that.options.filesContainer)
  223. .data('data', data);
  224. that._forceReflow(data.context);
  225. that._transition(data.context).done(
  226. function () {
  227. data.context = $(this);
  228. that._trigger('failed', e, data);
  229. }
  230. );
  231. } else {
  232. that._trigger('failed', e, data);
  233. }
  234. },
  235. // Callback for upload progress events:
  236. progress: function (e, data) {
  237. if (data.context) {
  238. var progress = parseInt(data.loaded / data.total * 100, 10);
  239. data.context.find('.progress')
  240. .attr('aria-valuenow', progress)
  241. .find('.bar').css(
  242. 'width',
  243. progress + '%'
  244. );
  245. }
  246. },
  247. // Callback for global upload progress events:
  248. progressall: function (e, data) {
  249. var $this = $(this),
  250. progress = parseInt(data.loaded / data.total * 100, 10),
  251. globalProgressNode = $this.find('.fileupload-progress'),
  252. extendedProgressNode = globalProgressNode
  253. .find('.progress-extended');
  254. if (extendedProgressNode.length) {
  255. extendedProgressNode.html(
  256. $this.data('fileupload')._renderExtendedProgress(data)
  257. );
  258. }
  259. globalProgressNode
  260. .find('.progress')
  261. .attr('aria-valuenow', progress)
  262. .find('.bar').css(
  263. 'width',
  264. progress + '%'
  265. );
  266. },
  267. // Callback for uploads start, equivalent to the global ajaxStart event:
  268. start: function (e) {
  269. var that = $(this).data('fileupload');
  270. that._transition($(this).find('.fileupload-progress')).done(
  271. function () {
  272. that._trigger('started', e);
  273. }
  274. );
  275. },
  276. // Callback for uploads stop, equivalent to the global ajaxStop event:
  277. stop: function (e) {
  278. var that = $(this).data('fileupload');
  279. that._transition($(this).find('.fileupload-progress')).done(
  280. function () {
  281. $(this).find('.progress')
  282. .attr('aria-valuenow', '0')
  283. .find('.bar').css('width', '0%');
  284. $(this).find('.progress-extended').html(' ');
  285. that._trigger('stopped', e);
  286. }
  287. );
  288. },
  289. // Callback for file deletion:
  290. destroy: function (e, data) {
  291. var that = $(this).data('fileupload');
  292. if (data.url) {
  293. $.ajax(data);
  294. that._adjustMaxNumberOfFiles(1);
  295. }
  296. that._transition(data.context).done(
  297. function () {
  298. $(this).remove();
  299. that._trigger('destroyed', e, data);
  300. }
  301. );
  302. }
  303. },
  304. // Link handler, that allows to download files
  305. // by drag & drop of the links to the desktop:
  306. _enableDragToDesktop: function () {
  307. var link = $(this),
  308. url = link.prop('href'),
  309. name = link.prop('download'),
  310. type = 'application/octet-stream';
  311. link.bind('dragstart', function (e) {
  312. try {
  313. e.originalEvent.dataTransfer.setData(
  314. 'DownloadURL',
  315. [type, name, url].join(':')
  316. );
  317. } catch (err) {}
  318. });
  319. },
  320. _adjustMaxNumberOfFiles: function (operand) {
  321. if (typeof this.options.maxNumberOfFiles === 'number') {
  322. this.options.maxNumberOfFiles += operand;
  323. if (this.options.maxNumberOfFiles < 1) {
  324. this._disableFileInputButton();
  325. } else {
  326. this._enableFileInputButton();
  327. }
  328. }
  329. },
  330. _formatFileSize: function (bytes) {
  331. if (typeof bytes !== 'number') {
  332. return '';
  333. }
  334. if (bytes >= 1000000000) {
  335. return (bytes / 1000000000).toFixed(2) + ' GB';
  336. }
  337. if (bytes >= 1000000) {
  338. return (bytes / 1000000).toFixed(2) + ' MB';
  339. }
  340. return (bytes / 1000).toFixed(2) + ' KB';
  341. },
  342. _formatBitrate: function (bits) {
  343. if (typeof bits !== 'number') {
  344. return '';
  345. }
  346. if (bits >= 1000000000) {
  347. return (bits / 1000000000).toFixed(2) + ' Gbit/s';
  348. }
  349. if (bits >= 1000000) {
  350. return (bits / 1000000).toFixed(2) + ' Mbit/s';
  351. }
  352. if (bits >= 1000) {
  353. return (bits / 1000).toFixed(2) + ' kbit/s';
  354. }
  355. return bits + ' bit/s';
  356. },
  357. _formatTime: function (seconds) {
  358. var date = new Date(seconds * 1000),
  359. days = parseInt(seconds / 86400, 10);
  360. days = days ? days + 'd ' : '';
  361. return days +
  362. ('0' + date.getUTCHours()).slice(-2) + ':' +
  363. ('0' + date.getUTCMinutes()).slice(-2) + ':' +
  364. ('0' + date.getUTCSeconds()).slice(-2);
  365. },
  366. _formatPercentage: function (floatValue) {
  367. return (floatValue * 100).toFixed(2) + ' %';
  368. },
  369. _renderExtendedProgress: function (data) {
  370. return this._formatBitrate(data.bitrate) + ' | ' +
  371. this._formatTime(
  372. (data.total - data.loaded) * 8 / data.bitrate
  373. ) + ' | ' +
  374. this._formatPercentage(
  375. data.loaded / data.total
  376. ) + ' | ' +
  377. this._formatFileSize(data.loaded) + ' / ' +
  378. this._formatFileSize(data.total);
  379. },
  380. _hasError: function (file) {
  381. if (file.error) {
  382. return file.error;
  383. }
  384. // The number of added files is subtracted from
  385. // maxNumberOfFiles before validation, so we check if
  386. // maxNumberOfFiles is below 0 (instead of below 1):
  387. if (this.options.maxNumberOfFiles < 0) {
  388. return 'Maximum number of files exceeded';
  389. }
  390. // Files are accepted if either the file type or the file name
  391. // matches against the acceptFileTypes regular expression, as
  392. // only browsers with support for the File API report the type:
  393. if (!(this.options.acceptFileTypes.test(file.type) ||
  394. this.options.acceptFileTypes.test(file.name))) {
  395. return 'Filetype not allowed';
  396. }
  397. if (this.options.maxFileSize &&
  398. file.size > this.options.maxFileSize) {
  399. return 'File is too big';
  400. }
  401. if (typeof file.size === 'number' &&
  402. file.size < this.options.minFileSize) {
  403. return 'File is too small';
  404. }
  405. return null;
  406. },
  407. _validate: function (files) {
  408. var that = this,
  409. valid = !!files.length;
  410. $.each(files, function (index, file) {
  411. file.error = that._hasError(file);
  412. if (file.error) {
  413. valid = false;
  414. }
  415. });
  416. return valid;
  417. },
  418. _renderTemplate: function (func, files) {
  419. if (!func) {
  420. return $();
  421. }
  422. var result = func({
  423. files: files,
  424. formatFileSize: this._formatFileSize,
  425. options: this.options
  426. });
  427. if (result instanceof $) {
  428. return result;
  429. }
  430. return $(this.options.templatesContainer).html(result).children();
  431. },
  432. _renderPreview: function (file, node) {
  433. var that = this,
  434. options = this.options,
  435. dfd = $.Deferred();
  436. return ((loadImage && loadImage(
  437. file,
  438. function (img) {
  439. node.append(img);
  440. that._forceReflow(node);
  441. that._transition(node).done(function () {
  442. dfd.resolveWith(node);
  443. });
  444. if (!$.contains(that.document[0].body, node[0])) {
  445. // If the element is not part of the DOM,
  446. // transition events are not triggered,
  447. // so we have to resolve manually:
  448. dfd.resolveWith(node);
  449. }
  450. },
  451. {
  452. maxWidth: options.previewMaxWidth,
  453. maxHeight: options.previewMaxHeight,
  454. canvas: options.previewAsCanvas
  455. }
  456. )) || dfd.resolveWith(node)) && dfd;
  457. },
  458. _renderPreviews: function (files, nodes) {
  459. var that = this,
  460. options = this.options;
  461. nodes.find('.preview span').each(function (index, element) {
  462. var file = files[index];
  463. if (options.previewSourceFileTypes.test(file.type) &&
  464. ($.type(options.previewSourceMaxFileSize) !== 'number' ||
  465. file.size < options.previewSourceMaxFileSize)) {
  466. that._processingQueue = that._processingQueue.pipe(function () {
  467. var dfd = $.Deferred();
  468. that._renderPreview(file, $(element)).done(
  469. function () {
  470. dfd.resolveWith(that);
  471. }
  472. );
  473. return dfd.promise();
  474. });
  475. }
  476. });
  477. return this._processingQueue;
  478. },
  479. _renderUpload: function (files) {
  480. return this._renderTemplate(
  481. this.options.uploadTemplate,
  482. files
  483. );
  484. },
  485. _renderDownload: function (files) {
  486. return this._renderTemplate(
  487. this.options.downloadTemplate,
  488. files
  489. ).find('a[download]').each(this._enableDragToDesktop).end();
  490. },
  491. _startHandler: function (e) {
  492. e.preventDefault();
  493. var button = $(e.currentTarget),
  494. template = button.closest('.template-upload'),
  495. data = template.data('data');
  496. if (data && data.submit && !data.jqXHR && data.submit()) {
  497. button.prop('disabled', true);
  498. }
  499. },
  500. _cancelHandler: function (e) {
  501. e.preventDefault();
  502. var template = $(e.currentTarget).closest('.template-upload'),
  503. data = template.data('data') || {};
  504. if (!data.jqXHR) {
  505. data.errorThrown = 'abort';
  506. this._trigger('fail', e, data);
  507. } else {
  508. data.jqXHR.abort();
  509. }
  510. },
  511. _deleteHandler: function (e) {
  512. e.preventDefault();
  513. var button = $(e.currentTarget);
  514. this._trigger('destroy', e, {
  515. context: button.closest('.template-download'),
  516. url: button.attr('data-url'),
  517. type: button.attr('data-type') || 'DELETE',
  518. dataType: this.options.dataType
  519. });
  520. },
  521. _forceReflow: function (node) {
  522. return $.support.transition && node.length &&
  523. node[0].offsetWidth;
  524. },
  525. _transition: function (node) {
  526. var dfd = $.Deferred();
  527. if ($.support.transition && node.hasClass('fade')) {
  528. node.bind(
  529. $.support.transition.end,
  530. function (e) {
  531. // Make sure we don't respond to other transitions events
  532. // in the container element, e.g. from button elements:
  533. if (e.target === node[0]) {
  534. node.unbind($.support.transition.end);
  535. dfd.resolveWith(node);
  536. }
  537. }
  538. ).toggleClass('in');
  539. } else {
  540. node.toggleClass('in');
  541. dfd.resolveWith(node);
  542. }
  543. return dfd;
  544. },
  545. _initButtonBarEventHandlers: function () {
  546. var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
  547. filesList = this.options.filesContainer;
  548. this._on(fileUploadButtonBar.find('.start'), {
  549. click: function (e) {
  550. e.preventDefault();
  551. filesList.find('.start button').click();
  552. }
  553. });
  554. this._on(fileUploadButtonBar.find('.cancel'), {
  555. click: function (e) {
  556. e.preventDefault();
  557. filesList.find('.cancel button').click();
  558. }
  559. });
  560. this._on(fileUploadButtonBar.find('.delete'), {
  561. click: function (e) {
  562. e.preventDefault();
  563. filesList.find('.delete input:checked')
  564. .siblings('button').click();
  565. fileUploadButtonBar.find('.toggle')
  566. .prop('checked', false);
  567. }
  568. });
  569. this._on(fileUploadButtonBar.find('.toggle'), {
  570. change: function (e) {
  571. filesList.find('.delete input').prop(
  572. 'checked',
  573. $(e.currentTarget).is(':checked')
  574. );
  575. }
  576. });
  577. },
  578. _destroyButtonBarEventHandlers: function () {
  579. this._off(
  580. this.element.find('.fileupload-buttonbar button'),
  581. 'click'
  582. );
  583. this._off(
  584. this.element.find('.fileupload-buttonbar .toggle'),
  585. 'change.'
  586. );
  587. },
  588. _initEventHandlers: function () {
  589. this._super();
  590. this._on(this.options.filesContainer, {
  591. 'click .start button': this._startHandler,
  592. 'click .cancel button': this._cancelHandler,
  593. 'click .delete button': this._deleteHandler
  594. });
  595. this._initButtonBarEventHandlers();
  596. },
  597. _destroyEventHandlers: function () {
  598. this._destroyButtonBarEventHandlers();
  599. this._off(this.options.filesContainer, 'click');
  600. this._super();
  601. },
  602. _enableFileInputButton: function () {
  603. this.element.find('.fileinput-button input')
  604. .prop('disabled', false)
  605. .parent().removeClass('disabled');
  606. },
  607. _disableFileInputButton: function () {
  608. this.element.find('.fileinput-button input')
  609. .prop('disabled', true)
  610. .parent().addClass('disabled');
  611. },
  612. _initTemplates: function () {
  613. var options = this.options;
  614. options.templatesContainer = this.document[0].createElement(
  615. options.filesContainer.prop('nodeName')
  616. );
  617. if (tmpl) {
  618. if (options.uploadTemplateId) {
  619. options.uploadTemplate = tmpl(options.uploadTemplateId);
  620. }
  621. if (options.downloadTemplateId) {
  622. options.downloadTemplate = tmpl(options.downloadTemplateId);
  623. }
  624. }
  625. },
  626. _initFilesContainer: function () {
  627. var options = this.options;
  628. if (options.filesContainer === undefined) {
  629. options.filesContainer = this.element.find('.files');
  630. } else if (!(options.filesContainer instanceof $)) {
  631. options.filesContainer = $(options.filesContainer);
  632. }
  633. },
  634. _stringToRegExp: function (str) {
  635. var parts = str.split('/'),
  636. modifiers = parts.pop();
  637. parts.shift();
  638. return new RegExp(parts.join('/'), modifiers);
  639. },
  640. _initRegExpOptions: function () {
  641. var options = this.options;
  642. if ($.type(options.acceptFileTypes) === 'string') {
  643. options.acceptFileTypes = this._stringToRegExp(
  644. options.acceptFileTypes
  645. );
  646. }
  647. if ($.type(options.previewSourceFileTypes) === 'string') {
  648. options.previewSourceFileTypes = this._stringToRegExp(
  649. options.previewSourceFileTypes
  650. );
  651. }
  652. },
  653. _initSpecialOptions: function () {
  654. this._super();
  655. this._initFilesContainer();
  656. this._initTemplates();
  657. this._initRegExpOptions();
  658. },
  659. _create: function () {
  660. this._super();
  661. this._refreshOptionsList.push(
  662. 'filesContainer',
  663. 'uploadTemplateId',
  664. 'downloadTemplateId'
  665. );
  666. if (!this._processingQueue) {
  667. this._processingQueue = $.Deferred().resolveWith(this).promise();
  668. this.process = function () {
  669. return this._processingQueue;
  670. };
  671. }
  672. },
  673. enable: function () {
  674. var wasDisabled = false;
  675. if (this.options.disabled) {
  676. wasDisabled = true;
  677. }
  678. this._super();
  679. if (wasDisabled) {
  680. this.element.find('input, button').prop('disabled', false);
  681. this._enableFileInputButton();
  682. }
  683. },
  684. disable: function () {
  685. if (!this.options.disabled) {
  686. this.element.find('input, button').prop('disabled', true);
  687. this._disableFileInputButton();
  688. }
  689. this._super();
  690. }
  691. });
  692. }));