Changeset 6107 for trunk/prototype/plugins/fileupload/jquery.fileupload.js
- Timestamp:
- 05/04/12 18:00:28 (12 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/prototype/plugins/fileupload/jquery.fileupload.js
r5341 r6107 1 1 /* 2 * jQuery File Upload Plugin 5. 22 * jQuery File Upload Plugin 5.10.1 3 3 * https://github.com/blueimp/jQuery-File-Upload 4 4 * … … 7 7 * 8 8 * Licensed under the MIT license: 9 * http:// creativecommons.org/licenses/MIT/9 * http://www.opensource.org/licenses/MIT 10 10 */ 11 11 12 12 /*jslint nomen: true, unparam: true, regexp: true */ 13 /*global d ocument, XMLHttpRequestUpload, Blob, File, FormData, location, jQuery*/14 15 (function ( $) {13 /*global define, window, document, Blob, FormData, location */ 14 15 (function (factory) { 16 16 'use strict'; 17 18 // The fileupload widget listens for change events on file input fields 19 // defined via fileInput setting and drop events of the given dropZone. 17 if (typeof define === 'function' && define.amd) { 18 // Register as an anonymous AMD module: 19 define([ 20 'jquery', 21 'jquery.ui.widget' 22 ], factory); 23 } else { 24 // Browser globals: 25 factory(window.jQuery); 26 } 27 }(function ($) { 28 'use strict'; 29 30 // The FileReader API is not actually used, but works as feature detection, 31 // as e.g. Safari supports XHR file uploads via the FormData API, 32 // but not non-multipart XHR file uploads: 33 $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader); 34 $.support.xhrFormDataFileUpload = !!window.FormData; 35 36 // The fileupload widget listens for change events on file input fields defined 37 // via fileInput setting and paste or drop events of the given dropZone. 20 38 // In addition to the default jQuery Widget methods, the fileupload widget 21 // exposes the "add" and "send" methods, to add or directly send files 22 // usingthe fileupload API.23 // By default, files added via file input selection, drag & drop or39 // exposes the "add" and "send" methods, to add or directly send files using 40 // the fileupload API. 41 // By default, files added via file input selection, paste, drag & drop or 24 42 // "add" method are uploaded immediately, but it is possible to override 25 43 // the "add" callback option to queue file uploads. 26 44 $.widget('blueimp.fileupload', { 27 45 28 46 options: { 29 47 // The namespace used for event handler binding on the dropZone and … … 46 64 // The parameter name for the file form data (the request argument name). 47 65 // If undefined or empty, the name property of the file input field is 48 // used, or "files[]" if the file input name property is also empty: 66 // used, or "files[]" if the file input name property is also empty, 67 // can be a string or an array of strings: 49 68 paramName: undefined, 50 69 // By default, each file of a selection is uploaded using an individual … … 63 82 // Set the following option to true to force iframe transport uploads: 64 83 forceIframeTransport: false, 84 // Set the following option to the location of a redirect url on the 85 // origin server, for cross-domain iframe transport uploads: 86 redirect: undefined, 87 // The parameter name for the redirect url, sent as part of the form 88 // data and set to 'redirect' if this option is empty: 89 redirectParamName: undefined, 90 // Set the following option to the location of a postMessage window, 91 // to enable postMessage transport uploads: 92 postMessage: undefined, 65 93 // By default, XHR file uploads are sent as multipart/form-data. 66 94 // The iframe transport is always using multipart/form-data. … … 82 110 // prevent recalculating the global progress data: 83 111 recalculateProgress: true, 84 112 85 113 // Additional form data to be sent along with the file uploads can be set 86 114 // using this option, which accepts an array of objects with name and … … 91 119 return form.serializeArray(); 92 120 }, 93 121 94 122 // The add callback is invoked as soon as files are added to the fileupload 95 // widget (via file input selection, drag & drop or add API call).123 // widget (via file input selection, drag & drop, paste or add API call). 96 124 // If the singleFileUploads option is enabled, this callback will be 97 125 // called once for each file in the selection for XHR file uplaods, else … … 108 136 data.submit(); 109 137 }, 110 138 111 139 // Other callbacks: 140 // Callback for the submit event of each file upload: 141 // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); 112 142 // Callback for the start of each file upload request: 113 143 // send: function (e, data) {}, // .bind('fileuploadsend', func); … … 128 158 // Callback for change events of the fileInput collection: 129 159 // change: function (e, data) {}, // .bind('fileuploadchange', func); 160 // Callback for paste events to the dropZone collection: 161 // paste: function (e, data) {}, // .bind('fileuploadpaste', func); 130 162 // Callback for drop events of the dropZone collection: 131 163 // drop: function (e, data) {}, // .bind('fileuploaddrop', func); 132 164 // Callback for dragover events of the dropZone collection: 133 165 // dragover: function (e) {}, // .bind('fileuploaddragover', func); 134 166 135 167 // The plugin options are used as settings object for the ajax calls. 136 168 // The following are jQuery ajax settings required for the file uploads: … … 139 171 cache: false 140 172 }, 141 173 142 174 // A list of options that require a refresh after assigning a new value: 143 _refreshOptionsList: ['namespace', 'dropZone', 'fileInput'], 175 _refreshOptionsList: [ 176 'namespace', 177 'dropZone', 178 'fileInput', 179 'multipart', 180 'forceIframeTransport' 181 ], 144 182 145 183 _isXHRUpload: function (options) { 146 var undef = 'undefined';147 184 return !options.forceIframeTransport && 148 typeof XMLHttpRequestUpload !== undef && typeof File !== undef &&149 (!options.multipart || typeof FormData !== undef);185 ((!options.multipart && $.support.xhrFileUpload) || 186 $.support.xhrFormDataFileUpload); 150 187 }, 151 188 … … 204 241 // Accesss to the native XHR object is required to add event listeners 205 242 // for the upload progress event: 206 if (xhr.upload && xhr.upload.addEventListener) { 207 xhr.upload.addEventListener('progress', function (e) { 243 if (xhr.upload) { 244 $(xhr.upload).bind('progress', function (e) { 245 var oe = e.originalEvent; 246 // Make sure the progress event properties get copied over: 247 e.lengthComputable = oe.lengthComputable; 248 e.loaded = oe.loaded; 249 e.total = oe.total; 208 250 that._onProgress(e, options); 209 } , false);251 }); 210 252 options.xhr = function () { 211 253 return xhr; … … 216 258 _initXHRData: function (options) { 217 259 var formData, 218 file = options.files[0]; 219 if (!options.multipart || options.blob) { 260 file = options.files[0], 261 // Ignore non-multipart setting if not supported: 262 multipart = options.multipart || !$.support.xhrFileUpload, 263 paramName = options.paramName[0]; 264 if (!multipart || options.blob) { 220 265 // For non-multipart uploads and chunked uploads, 221 266 // file meta data is not part of the request body, … … 233 278 options.contentType = file.type; 234 279 options.data = file; 235 } else if (! options.multipart) {280 } else if (!multipart) { 236 281 // Chunked non-multipart upload: 237 282 options.contentType = 'application/octet-stream'; … … 239 284 } 240 285 } 241 if (options.multipart && typeof FormData !== 'undefined') { 242 if (options.formData instanceof FormData) { 243 formData = options.formData; 286 if (multipart && $.support.xhrFormDataFileUpload) { 287 if (options.postMessage) { 288 // window.postMessage does not allow sending FormData 289 // objects, so we just add the File/Blob objects to 290 // the formData array and let the postMessage window 291 // create the FormData object out of this array: 292 formData = this._getFormData(options); 293 if (options.blob) { 294 formData.push({ 295 name: paramName, 296 value: options.blob 297 }); 298 } else { 299 $.each(options.files, function (index, file) { 300 formData.push({ 301 name: options.paramName[index] || paramName, 302 value: file 303 }); 304 }); 305 } 244 306 } else { 245 formData = new FormData(); 246 $.each(this._getFormData(options), function (index, field) { 247 formData.append(field.name, field.value); 248 }); 249 } 250 if (options.blob) { 251 formData.append(options.paramName, options.blob); 252 } else { 253 $.each(options.files, function (index, file) { 254 // File objects are also Blob instances. 255 // This check allows the tests to run with 256 // dummy objects: 257 if (file instanceof Blob) { 258 formData.append(options.paramName, file); 259 } 260 }); 307 if (options.formData instanceof FormData) { 308 formData = options.formData; 309 } else { 310 formData = new FormData(); 311 $.each(this._getFormData(options), function (index, field) { 312 formData.append(field.name, field.value); 313 }); 314 } 315 if (options.blob) { 316 formData.append(paramName, options.blob, file.name); 317 } else { 318 $.each(options.files, function (index, file) { 319 // File objects are also Blob instances. 320 // This check allows the tests to run with 321 // dummy objects: 322 if (file instanceof Blob) { 323 formData.append( 324 options.paramName[index] || paramName, 325 file, 326 file.name 327 ); 328 } 329 }); 330 } 261 331 } 262 332 options.data = formData; … … 265 335 options.blob = null; 266 336 }, 267 337 268 338 _initIframeSettings: function (options) { 269 339 // Setting the dataType to iframe enables the iframe transport: … … 271 341 // The iframe transport accepts a serialized array as form data: 272 342 options.formData = this._getFormData(options); 273 }, 274 343 // Add redirect url to form data on cross-domain uploads: 344 if (options.redirect && $('<a></a>').prop('href', options.url) 345 .prop('host') !== location.host) { 346 options.formData.push({ 347 name: options.redirectParamName || 'redirect', 348 value: options.redirect 349 }); 350 } 351 }, 352 275 353 _initDataSettings: function (options) { 276 354 if (this._isXHRUpload(options)) { … … 281 359 this._initProgressListener(options); 282 360 } 361 if (options.postMessage) { 362 // Setting the dataType to postmessage enables the 363 // postMessage transport: 364 options.dataType = 'postmessage ' + (options.dataType || ''); 365 } 283 366 } else { 284 this._initIframeSettings(options); 285 } 286 }, 287 367 this._initIframeSettings(options, 'iframe'); 368 } 369 }, 370 371 _getParamName: function (options) { 372 var fileInput = $(options.fileInput), 373 paramName = options.paramName; 374 if (!paramName) { 375 paramName = []; 376 fileInput.each(function () { 377 var input = $(this), 378 name = input.prop('name') || 'files[]', 379 i = (input.prop('files') || [1]).length; 380 while (i) { 381 paramName.push(name); 382 i -= 1; 383 } 384 }); 385 if (!paramName.length) { 386 paramName = [fileInput.prop('name') || 'files[]']; 387 } 388 } else if (!$.isArray(paramName)) { 389 paramName = [paramName]; 390 } 391 return paramName; 392 }, 393 288 394 _initFormSettings: function (options) { 289 395 // Retrieve missing options from the input field and the … … 292 398 options.form = $(options.fileInput.prop('form')); 293 399 } 294 if (!options.paramName) { 295 options.paramName = options.fileInput.prop('name') || 296 'files[]'; 297 } 400 options.paramName = this._getParamName(options); 298 401 if (!options.url) { 299 402 options.url = options.form.prop('action') || location.href; … … 306 409 } 307 410 }, 308 411 309 412 _getAJAXSettings: function (data) { 310 413 var options = $.extend({}, this.options, data); … … 365 468 if (ub >= fs) { 366 469 file.error = 'uploadedBytes'; 367 return this._getXHRPromise(false); 470 return this._getXHRPromise( 471 false, 472 options.context, 473 [null, 'error', file.error] 474 ); 368 475 } 369 476 // n is the number of blobs to upload, … … 373 480 upload = function (i) { 374 481 if (!i) { 375 return that._getXHRPromise(true );482 return that._getXHRPromise(true, options.context); 376 483 } 377 484 // Upload the blobs in sequential order: … … 402 509 }), o); 403 510 } 404 options.uploadedBytes = o.uploadedBytes 405 +=o.chunkSize;511 options.uploadedBytes = o.uploadedBytes += 512 o.chunkSize; 406 513 }); 407 514 return jqXHR; … … 459 566 }, 460 567 461 _onAlways: function ( result, textStatus, jqXHR, errorThrown, options) {568 _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { 462 569 this._active -= 1; 463 options.result = result;464 570 options.textStatus = textStatus; 465 options.jqXHR = jqXHR; 466 options.errorThrown = errorThrown; 571 if (jqXHRorError && jqXHRorError.always) { 572 options.jqXHR = jqXHRorError; 573 options.result = jqXHRorResult; 574 } else { 575 options.jqXHR = jqXHRorResult; 576 options.errorThrown = jqXHRorError; 577 } 467 578 this._trigger('always', null, options); 468 579 if (this._active === 0) { … … 492 603 }).fail(function (jqXHR, textStatus, errorThrown) { 493 604 that._onFail(jqXHR, textStatus, errorThrown, options); 494 }).always(function ( a1, a2, a3) {605 }).always(function (jqXHRorResult, textStatus, jqXHRorError) { 495 606 that._sending -= 1; 496 if (a3 && a3.done) { 497 that._onAlways(a1, a2, a3, undefined, options); 498 } else { 499 that._onAlways(undefined, a2, a1, a3, options); 500 } 607 that._onAlways( 608 jqXHRorResult, 609 textStatus, 610 jqXHRorError, 611 options 612 ); 501 613 if (options.limitConcurrentUploads && 502 614 options.limitConcurrentUploads > that._sending) { … … 543 655 return send(); 544 656 }, 545 657 546 658 _onAdd: function (e, data) { 547 659 var that = this, 548 660 result = true, 549 661 options = $.extend({}, this.options, data), 550 fileSet = data.files,551 662 limit = options.limitMultiFileUploads, 663 paramName = this._getParamName(options), 664 paramNameSet, 665 paramNameSlice, 666 fileSet, 552 667 i; 553 668 if (!(options.singleFileUploads || limit) || 554 669 !this._isXHRUpload(options)) { 555 fileSet = [fileSet]; 670 fileSet = [data.files]; 671 paramNameSet = [paramName]; 556 672 } else if (!options.singleFileUploads && limit) { 557 673 fileSet = []; 674 paramNameSet = []; 558 675 for (i = 0; i < data.files.length; i += limit) { 559 676 fileSet.push(data.files.slice(i, i + limit)); 560 } 561 } 562 $.each(fileSet, function (index, file) { 563 var files = $.isArray(file) ? file : [file], 564 newData = $.extend({}, data, {files: files}); 677 paramNameSlice = paramName.slice(i, i + limit); 678 if (!paramNameSlice.length) { 679 paramNameSlice = paramName; 680 } 681 paramNameSet.push(paramNameSlice); 682 } 683 } else { 684 paramNameSet = paramName; 685 } 686 data.originalFiles = data.files; 687 $.each(fileSet || data.files, function (index, element) { 688 var newData = $.extend({}, data); 689 newData.files = fileSet ? element : [element]; 690 newData.paramName = paramNameSet[index]; 565 691 newData.submit = function () { 566 return that._onSend(e, newData); 692 newData.jqXHR = this.jqXHR = 693 (that._trigger('submit', e, this) !== false) && 694 that._onSend(e, this); 695 return this.jqXHR; 567 696 }; 568 697 return (result = that._trigger('add', e, newData)); … … 570 699 return result; 571 700 }, 572 701 573 702 // File Normalization for Gecko 1.9.1 (Firefox 3.5) support: 574 703 _normalizeFile: function (index, file) { … … 585 714 // without loosing the file input value: 586 715 input.after(inputClone).detach(); 716 // Avoid memory leaks with the detached file input: 717 $.cleanData(input.unbind('remove')); 587 718 // Replace the original file input element in the fileInput 588 719 // collection with the clone, which has been copied including … … 594 725 return el; 595 726 }); 596 }, 597 727 // If the widget has been initialized on the file input itself, 728 // override this.element with the file input clone: 729 if (input[0] === this.element[0]) { 730 this.element = inputClone; 731 } 732 }, 733 598 734 _onChange: function (e) { 599 735 var that = e.data.fileupload, … … 609 745 data.files = [{name: e.target.value.replace(/^.*\\/, '')}]; 610 746 } 611 // Store the form reference as jQuery data for other event handlers,612 // as the form property is not available after replacing the file input:613 if (data.form.length) {614 data.fileInput.data('blueimp.fileupload.form', data.form);615 } else {616 data.form = data.fileInput.data('blueimp.fileupload.form');617 }618 747 if (that.options.replaceFileInput) { 619 748 that._replaceFileInput(data.fileInput); … … 624 753 } 625 754 }, 626 755 756 _onPaste: function (e) { 757 var that = e.data.fileupload, 758 cbd = e.originalEvent.clipboardData, 759 items = (cbd && cbd.items) || [], 760 data = {files: []}; 761 $.each(items, function (index, item) { 762 var file = item.getAsFile && item.getAsFile(); 763 if (file) { 764 data.files.push(file); 765 } 766 }); 767 if (that._trigger('paste', e, data) === false || 768 that._onAdd(e, data) === false) { 769 return false; 770 } 771 }, 772 627 773 _onDrop: function (e) { 628 774 var that = e.data.fileupload, … … 640 786 e.preventDefault(); 641 787 }, 642 788 643 789 _onDragOver: function (e) { 644 790 var that = e.data.fileupload, … … 652 798 e.preventDefault(); 653 799 }, 654 800 655 801 _initEventHandlers: function () { 656 var ns = this.options.namespace || this.name; 657 this.options.dropZone 658 .bind('dragover.' + ns, {fileupload: this}, this._onDragOver) 659 .bind('drop.' + ns, {fileupload: this}, this._onDrop); 802 var ns = this.options.namespace; 803 if (this._isXHRUpload(this.options)) { 804 this.options.dropZone 805 .bind('dragover.' + ns, {fileupload: this}, this._onDragOver) 806 .bind('drop.' + ns, {fileupload: this}, this._onDrop) 807 .bind('paste.' + ns, {fileupload: this}, this._onPaste); 808 } 660 809 this.options.fileInput 661 810 .bind('change.' + ns, {fileupload: this}, this._onChange); … … 663 812 664 813 _destroyEventHandlers: function () { 665 var ns = this.options.namespace || this.name;814 var ns = this.options.namespace; 666 815 this.options.dropZone 667 816 .unbind('dragover.' + ns, this._onDragOver) 668 .unbind('drop.' + ns, this._onDrop); 817 .unbind('drop.' + ns, this._onDrop) 818 .unbind('paste.' + ns, this._onPaste); 669 819 this.options.fileInput 670 820 .unbind('change.' + ns, this._onChange); 671 821 }, 672 673 _beforeSetOption: function (key, value) { 674 this._destroyEventHandlers(); 675 }, 676 677 _afterSetOption: function (key, value) { 678 var options = this.options; 679 if (!options.fileInput) { 680 options.fileInput = $(); 681 } 682 if (!options.dropZone) { 683 options.dropZone = $(); 684 } 685 this._initEventHandlers(); 686 }, 687 822 688 823 _setOption: function (key, value) { 689 824 var refresh = $.inArray(key, this._refreshOptionsList) !== -1; 690 825 if (refresh) { 691 this._ beforeSetOption(key, value);826 this._destroyEventHandlers(); 692 827 } 693 828 $.Widget.prototype._setOption.call(this, key, value); 694 829 if (refresh) { 695 this._afterSetOption(key, value); 696 } 697 }, 698 699 _create: function () { 830 this._initSpecialOptions(); 831 this._initEventHandlers(); 832 } 833 }, 834 835 _initSpecialOptions: function () { 700 836 var options = this.options; 701 837 if (options.fileInput === undefined) { 702 838 options.fileInput = this.element.is('input:file') ? 703 this.element : this.element.find('input:file'); 704 } else if (!options.fileInput) { 705 options.fileInput = $(); 706 } 707 if (!options.dropZone) { 708 options.dropZone = $(); 709 } 839 this.element : this.element.find('input:file'); 840 } else if (!(options.fileInput instanceof $)) { 841 options.fileInput = $(options.fileInput); 842 } 843 if (!(options.dropZone instanceof $)) { 844 options.dropZone = $(options.dropZone); 845 } 846 }, 847 848 _create: function () { 849 var options = this.options; 850 // Initialize options set via HTML5 data-attributes: 851 $.extend(options, $(this.element[0].cloneNode(false)).data()); 852 options.namespace = options.namespace || this.widgetName; 853 this._initSpecialOptions(); 710 854 this._slots = []; 711 855 this._sequence = this._getXHRPromise(true); … … 713 857 this._initEventHandlers(); 714 858 }, 715 859 716 860 destroy: function () { 717 861 this._destroyEventHandlers(); … … 723 867 this._initEventHandlers(); 724 868 }, 725 869 726 870 disable: function () { 727 871 this._destroyEventHandlers(); … … 740 884 this._onAdd(null, data); 741 885 }, 742 886 743 887 // This method is exposed to the widget API and allows sending files 744 888 // using the fileupload API. The data parameter accepts an object which … … 755 899 return this._getXHRPromise(false, data && data.context); 756 900 } 757 901 758 902 }); 759 760 } (jQuery));903 904 }));
Note: See TracChangeset
for help on using the changeset viewer.