Subversion-Projekte lars-tiefland.webanos.zeldi.de

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
4 lars 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
});