comparison PalanthirExplorer/libs/jquery-file-upload/js/jquery.fileupload-ui.js @ 45:33d67e1ab173

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