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