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