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