Blame | Last modification | View Log | RSS feed
/** jQuery File Upload Plugin 5.21.1* https://github.com/blueimp/jQuery-File-Upload** Copyright 2010, Sebastian Tschan* https://blueimp.net** Licensed under the MIT license:* http://www.opensource.org/licenses/MIT*//*jslint nomen: true, unparam: true, regexp: true *//*global define, window, document, File, Blob, FormData, location */(function (factory) {'use strict';if (typeof define === 'function' && define.amd) {// Register as an anonymous AMD module:define(['jquery','jquery.ui.widget'], factory);} else {// Browser globals:factory(window.jQuery);}}(function ($) {'use strict';// The FileReader API is not actually used, but works as feature detection,// as e.g. Safari supports XHR file uploads via the FormData API,// but not non-multipart XHR file uploads:$.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);$.support.xhrFormDataFileUpload = !!window.FormData;// The form.elements propHook is added to filter serialized elements// to not include file inputs in jQuery 1.9.0.// This hooks directly into jQuery.fn.serializeArray.// For more info, see http://bugs.jquery.com/ticket/13306$.propHooks.elements = {get: function (form) {if ($.nodeName(form, 'form')) {return $.grep(form.elements, function (elem) {return !$.nodeName(elem, 'input') || elem.type !== 'file';});}return null;}};// The fileupload widget listens for change events on file input fields defined// via fileInput setting and paste or drop events of the given dropZone.// In addition to the default jQuery Widget methods, the fileupload widget// exposes the "add" and "send" methods, to add or directly send files using// the fileupload API.// By default, files added via file input selection, paste, drag & drop or// "add" method are uploaded immediately, but it is possible to override// the "add" callback option to queue file uploads.$.widget('blueimp.fileupload', {options: {// The drop target element(s), by the default the complete document.// Set to null to disable drag & drop support:dropZone: $(document),// The paste target element(s), by the default the complete document.// Set to null to disable paste support:pasteZone: $(document),// The file input field(s), that are listened to for change events.// If undefined, it is set to the file input fields inside// of the widget element on plugin initialization.// Set to null to disable the change listener.fileInput: undefined,// By default, the file input field is replaced with a clone after// each input field change event. This is required for iframe transport// queues and allows change events to be fired for the same file// selection, but can be disabled by setting the following option to false:replaceFileInput: true,// The parameter name for the file form data (the request argument name).// If undefined or empty, the name property of the file input field is// used, or "files[]" if the file input name property is also empty,// can be a string or an array of strings:paramName: undefined,// By default, each file of a selection is uploaded using an individual// request for XHR type uploads. Set to false to upload file// selections in one request each:singleFileUploads: true,// To limit the number of files uploaded with one XHR request,// set the following option to an integer greater than 0:limitMultiFileUploads: undefined,// Set the following option to true to issue all file upload requests// in a sequential order:sequentialUploads: false,// To limit the number of concurrent uploads,// set the following option to an integer greater than 0:limitConcurrentUploads: undefined,// Set the following option to true to force iframe transport uploads:forceIframeTransport: false,// Set the following option to the location of a redirect url on the// origin server, for cross-domain iframe transport uploads:redirect: undefined,// The parameter name for the redirect url, sent as part of the form// data and set to 'redirect' if this option is empty:redirectParamName: undefined,// Set the following option to the location of a postMessage window,// to enable postMessage transport uploads:postMessage: undefined,// By default, XHR file uploads are sent as multipart/form-data.// The iframe transport is always using multipart/form-data.// Set to false to enable non-multipart XHR uploads:multipart: true,// To upload large files in smaller chunks, set the following option// to a preferred maximum chunk size. If set to 0, null or undefined,// or the browser does not support the required Blob API, files will// be uploaded as a whole.maxChunkSize: undefined,// When a non-multipart upload or a chunked multipart upload has been// aborted, this option can be used to resume the upload by setting// it to the size of the already uploaded bytes. This option is most// useful when modifying the options object inside of the "add" or// "send" callbacks, as the options are cloned for each file upload.uploadedBytes: undefined,// By default, failed (abort or error) file uploads are removed from the// global progress calculation. Set the following option to false to// prevent recalculating the global progress data:recalculateProgress: true,// Interval in milliseconds to calculate and trigger progress events:progressInterval: 100,// Interval in milliseconds to calculate progress bitrate:bitrateInterval: 500,// Additional form data to be sent along with the file uploads can be set// using this option, which accepts an array of objects with name and// value properties, a function returning such an array, a FormData// object (for XHR file uploads), or a simple object.// The form of the first fileInput is given as parameter to the function:formData: function (form) {return form.serializeArray();},// The add callback is invoked as soon as files are added to the fileupload// widget (via file input selection, drag & drop, paste or add API call).// If the singleFileUploads option is enabled, this callback will be// called once for each file in the selection for XHR file uplaods, else// once for each file selection.// The upload starts when the submit method is invoked on the data parameter.// The data object contains a files property holding the added files// and allows to override plugin options as well as define ajax settings.// Listeners for this callback can also be bound the following way:// .bind('fileuploadadd', func);// data.submit() returns a Promise object and allows to attach additional// handlers using jQuery's Deferred callbacks:// data.submit().done(func).fail(func).always(func);add: function (e, data) {data.submit();},// Other callbacks:// Callback for the submit event of each file upload:// submit: function (e, data) {}, // .bind('fileuploadsubmit', func);// Callback for the start of each file upload request:// send: function (e, data) {}, // .bind('fileuploadsend', func);// Callback for successful uploads:// done: function (e, data) {}, // .bind('fileuploaddone', func);// Callback for failed (abort or error) uploads:// fail: function (e, data) {}, // .bind('fileuploadfail', func);// Callback for completed (success, abort or error) requests:// always: function (e, data) {}, // .bind('fileuploadalways', func);// Callback for upload progress events:// progress: function (e, data) {}, // .bind('fileuploadprogress', func);// Callback for global upload progress events:// progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);// Callback for uploads start, equivalent to the global ajaxStart event:// start: function (e) {}, // .bind('fileuploadstart', func);// Callback for uploads stop, equivalent to the global ajaxStop event:// stop: function (e) {}, // .bind('fileuploadstop', func);// Callback for change events of the fileInput(s):// change: function (e, data) {}, // .bind('fileuploadchange', func);// Callback for paste events to the pasteZone(s):// paste: function (e, data) {}, // .bind('fileuploadpaste', func);// Callback for drop events of the dropZone(s):// drop: function (e, data) {}, // .bind('fileuploaddrop', func);// Callback for dragover events of the dropZone(s):// dragover: function (e) {}, // .bind('fileuploaddragover', func);// Callback for the start of each chunk upload request:// chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);// Callback for successful chunk uploads:// chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);// Callback for failed (abort or error) chunk uploads:// chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);// Callback for completed (success, abort or error) chunk upload requests:// chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);// The plugin options are used as settings object for the ajax calls.// The following are jQuery ajax settings required for the file uploads:processData: false,contentType: false,cache: false},// A list of options that require a refresh after assigning a new value:_refreshOptionsList: ['fileInput','dropZone','pasteZone','multipart','forceIframeTransport'],_BitrateTimer: function () {this.timestamp = +(new Date());this.loaded = 0;this.bitrate = 0;this.getBitrate = function (now, loaded, interval) {var timeDiff = now - this.timestamp;if (!this.bitrate || !interval || timeDiff > interval) {this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;this.loaded = loaded;this.timestamp = now;}return this.bitrate;};},_isXHRUpload: function (options) {return !options.forceIframeTransport &&((!options.multipart && $.support.xhrFileUpload) ||$.support.xhrFormDataFileUpload);},_getFormData: function (options) {var formData;if (typeof options.formData === 'function') {return options.formData(options.form);}if ($.isArray(options.formData)) {return options.formData;}if (options.formData) {formData = [];$.each(options.formData, function (name, value) {formData.push({name: name, value: value});});return formData;}return [];},_getTotal: function (files) {var total = 0;$.each(files, function (index, file) {total += file.size || 1;});return total;},_onProgress: function (e, data) {if (e.lengthComputable) {var now = +(new Date()),total,loaded;if (data._time && data.progressInterval &&(now - data._time < data.progressInterval) &&e.loaded !== e.total) {return;}data._time = now;total = data.total || this._getTotal(data.files);loaded = parseInt(e.loaded / e.total * (data.chunkSize || total),10) + (data.uploadedBytes || 0);this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);data.lengthComputable = true;data.loaded = loaded;data.total = total;data.bitrate = data._bitrateTimer.getBitrate(now,loaded,data.bitrateInterval);// Trigger a custom progress event with a total data property set// to the file size(s) of the current upload and a loaded data// property calculated accordingly:this._trigger('progress', e, data);// Trigger a global progress event for all current file uploads,// including ajax calls queued for sequential file uploads:this._trigger('progressall', e, {lengthComputable: true,loaded: this._loaded,total: this._total,bitrate: this._bitrateTimer.getBitrate(now,this._loaded,data.bitrateInterval)});}},_initProgressListener: function (options) {var that = this,xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();// Accesss to the native XHR object is required to add event listeners// for the upload progress event:if (xhr.upload) {$(xhr.upload).bind('progress', function (e) {var oe = e.originalEvent;// Make sure the progress event properties get copied over:e.lengthComputable = oe.lengthComputable;e.loaded = oe.loaded;e.total = oe.total;that._onProgress(e, options);});options.xhr = function () {return xhr;};}},_initXHRData: function (options) {var formData,file = options.files[0],// Ignore non-multipart setting if not supported:multipart = options.multipart || !$.support.xhrFileUpload,paramName = options.paramName[0];options.headers = options.headers || {};if (options.contentRange) {options.headers['Content-Range'] = options.contentRange;}if (!multipart) {options.headers['Content-Disposition'] = 'attachment; filename="' +encodeURI(file.name) + '"';options.contentType = file.type;options.data = options.blob || file;} else if ($.support.xhrFormDataFileUpload) {if (options.postMessage) {// window.postMessage does not allow sending FormData// objects, so we just add the File/Blob objects to// the formData array and let the postMessage window// create the FormData object out of this array:formData = this._getFormData(options);if (options.blob) {formData.push({name: paramName,value: options.blob});} else {$.each(options.files, function (index, file) {formData.push({name: options.paramName[index] || paramName,value: file});});}} else {if (options.formData instanceof FormData) {formData = options.formData;} else {formData = new FormData();$.each(this._getFormData(options), function (index, field) {formData.append(field.name, field.value);});}if (options.blob) {options.headers['Content-Disposition'] = 'attachment; filename="' +encodeURI(file.name) + '"';formData.append(paramName, options.blob, file.name);} else {$.each(options.files, function (index, file) {// Files are also Blob instances, but some browsers// (Firefox 3.6) support the File API but not Blobs.// This check allows the tests to run with// dummy objects:if ((window.Blob && file instanceof Blob) ||(window.File && file instanceof File)) {formData.append(options.paramName[index] || paramName,file,file.name);}});}}options.data = formData;}// Blob reference is not needed anymore, free memory:options.blob = null;},_initIframeSettings: function (options) {// Setting the dataType to iframe enables the iframe transport:options.dataType = 'iframe ' + (options.dataType || '');// The iframe transport accepts a serialized array as form data:options.formData = this._getFormData(options);// Add redirect url to form data on cross-domain uploads:if (options.redirect && $('<a></a>').prop('href', options.url).prop('host') !== location.host) {options.formData.push({name: options.redirectParamName || 'redirect',value: options.redirect});}},_initDataSettings: function (options) {if (this._isXHRUpload(options)) {if (!this._chunkedUpload(options, true)) {if (!options.data) {this._initXHRData(options);}this._initProgressListener(options);}if (options.postMessage) {// Setting the dataType to postmessage enables the// postMessage transport:options.dataType = 'postmessage ' + (options.dataType || '');}} else {this._initIframeSettings(options, 'iframe');}},_getParamName: function (options) {var fileInput = $(options.fileInput),paramName = options.paramName;if (!paramName) {paramName = [];fileInput.each(function () {var input = $(this),name = input.prop('name') || 'files[]',i = (input.prop('files') || [1]).length;while (i) {paramName.push(name);i -= 1;}});if (!paramName.length) {paramName = [fileInput.prop('name') || 'files[]'];}} else if (!$.isArray(paramName)) {paramName = [paramName];}return paramName;},_initFormSettings: function (options) {// Retrieve missing options from the input field and the// associated form, if available:if (!options.form || !options.form.length) {options.form = $(options.fileInput.prop('form'));// If the given file input doesn't have an associated form,// use the default widget file input's form:if (!options.form.length) {options.form = $(this.options.fileInput.prop('form'));}}options.paramName = this._getParamName(options);if (!options.url) {options.url = options.form.prop('action') || location.href;}// The HTTP request method must be "POST" or "PUT":options.type = (options.type || options.form.prop('method') || '').toUpperCase();if (options.type !== 'POST' && options.type !== 'PUT' &&options.type !== 'PATCH') {options.type = 'POST';}if (!options.formAcceptCharset) {options.formAcceptCharset = options.form.attr('accept-charset');}},_getAJAXSettings: function (data) {var options = $.extend({}, this.options, data);this._initFormSettings(options);this._initDataSettings(options);return options;},// Maps jqXHR callbacks to the equivalent// methods of the given Promise object:_enhancePromise: function (promise) {promise.success = promise.done;promise.error = promise.fail;promise.complete = promise.always;return promise;},// Creates and returns a Promise object enhanced with// the jqXHR methods abort, success, error and complete:_getXHRPromise: function (resolveOrReject, context, args) {var dfd = $.Deferred(),promise = dfd.promise();context = context || this.options.context || promise;if (resolveOrReject === true) {dfd.resolveWith(context, args);} else if (resolveOrReject === false) {dfd.rejectWith(context, args);}promise.abort = dfd.promise;return this._enhancePromise(promise);},// Parses the Range header from the server response// and returns the uploaded bytes:_getUploadedBytes: function (jqXHR) {var range = jqXHR.getResponseHeader('Range'),parts = range && range.split('-'),upperBytesPos = parts && parts.length > 1 &&parseInt(parts[1], 10);return upperBytesPos && upperBytesPos + 1;},// Uploads a file in multiple, sequential requests// by splitting the file up in multiple blob chunks.// If the second parameter is true, only tests if the file// should be uploaded in chunks, but does not invoke any// upload requests:_chunkedUpload: function (options, testOnly) {var that = this,file = options.files[0],fs = file.size,ub = options.uploadedBytes = options.uploadedBytes || 0,mcs = options.maxChunkSize || fs,slice = file.slice || file.webkitSlice || file.mozSlice,dfd = $.Deferred(),promise = dfd.promise(),jqXHR,upload;if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||options.data) {return false;}if (testOnly) {return true;}if (ub >= fs) {file.error = 'Uploaded bytes exceed file size';return this._getXHRPromise(false,options.context,[null, 'error', file.error]);}// The chunk upload method:upload = function () {// Clone the options object for each chunk upload:var o = $.extend({}, options);o.blob = slice.call(file,ub,ub + mcs,file.type);// Store the current chunk size, as the blob itself// will be dereferenced after data processing:o.chunkSize = o.blob.size;// Expose the chunk bytes position range:o.contentRange = 'bytes ' + ub + '-' +(ub + o.chunkSize - 1) + '/' + fs;// Process the upload data (the blob and potential form data):that._initXHRData(o);// Add progress listeners for this chunk upload:that._initProgressListener(o);jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||that._getXHRPromise(false, o.context)).done(function (result, textStatus, jqXHR) {ub = that._getUploadedBytes(jqXHR) ||(ub + o.chunkSize);// Create a progress event if upload is done and no progress// event has been invoked for this chunk, or there has been// no progress event with loaded equaling total:if (!o.loaded || o.loaded < o.total) {that._onProgress($.Event('progress', {lengthComputable: true,loaded: ub - o.uploadedBytes,total: ub - o.uploadedBytes}), o);}options.uploadedBytes = o.uploadedBytes = ub;o.result = result;o.textStatus = textStatus;o.jqXHR = jqXHR;that._trigger('chunkdone', null, o);that._trigger('chunkalways', null, o);if (ub < fs) {// File upload not yet complete,// continue with the next chunk:upload();} else {dfd.resolveWith(o.context,[result, textStatus, jqXHR]);}}).fail(function (jqXHR, textStatus, errorThrown) {o.jqXHR = jqXHR;o.textStatus = textStatus;o.errorThrown = errorThrown;that._trigger('chunkfail', null, o);that._trigger('chunkalways', null, o);dfd.rejectWith(o.context,[jqXHR, textStatus, errorThrown]);});};this._enhancePromise(promise);promise.abort = function () {return jqXHR.abort();};upload();return promise;},_beforeSend: function (e, data) {if (this._active === 0) {// the start callback is triggered when an upload starts// and no other uploads are currently running,// equivalent to the global ajaxStart event:this._trigger('start');// Set timer for global bitrate progress calculation:this._bitrateTimer = new this._BitrateTimer();}this._active += 1;// Initialize the global progress values:this._loaded += data.uploadedBytes || 0;this._total += this._getTotal(data.files);},_onDone: function (result, textStatus, jqXHR, options) {if (!this._isXHRUpload(options) || !options.loaded ||options.loaded < options.total) {var total = this._getTotal(options.files) || 1;// Create a progress event for each iframe load,// or if there has been no progress event with// loaded equaling total for XHR uploads:this._onProgress($.Event('progress', {lengthComputable: true,loaded: total,total: total}), options);}options.result = result;options.textStatus = textStatus;options.jqXHR = jqXHR;this._trigger('done', null, options);},_onFail: function (jqXHR, textStatus, errorThrown, options) {options.jqXHR = jqXHR;options.textStatus = textStatus;options.errorThrown = errorThrown;this._trigger('fail', null, options);if (options.recalculateProgress) {// Remove the failed (error or abort) file upload from// the global progress calculation:this._loaded -= options.loaded || options.uploadedBytes || 0;this._total -= options.total || this._getTotal(options.files);}},_onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {// jqXHRorResult, textStatus and jqXHRorError are added to the// options object via done and fail callbacksthis._active -= 1;this._trigger('always', null, options);if (this._active === 0) {// The stop callback is triggered when all uploads have// been completed, equivalent to the global ajaxStop event:this._trigger('stop');// Reset the global progress values:this._loaded = this._total = 0;this._bitrateTimer = null;}},_onSend: function (e, data) {var that = this,jqXHR,aborted,slot,pipe,options = that._getAJAXSettings(data),send = function () {that._sending += 1;// Set timer for bitrate progress calculation:options._bitrateTimer = new that._BitrateTimer();jqXHR = jqXHR || (((aborted || that._trigger('send', e, options) === false) &&that._getXHRPromise(false, options.context, aborted)) ||that._chunkedUpload(options) || $.ajax(options)).done(function (result, textStatus, jqXHR) {that._onDone(result, textStatus, jqXHR, options);}).fail(function (jqXHR, textStatus, errorThrown) {that._onFail(jqXHR, textStatus, errorThrown, options);}).always(function (jqXHRorResult, textStatus, jqXHRorError) {that._sending -= 1;that._onAlways(jqXHRorResult,textStatus,jqXHRorError,options);if (options.limitConcurrentUploads &&options.limitConcurrentUploads > that._sending) {// Start the next queued upload,// that has not been aborted:var nextSlot = that._slots.shift(),isPending;while (nextSlot) {// jQuery 1.6 doesn't provide .state(),// while jQuery 1.8+ removed .isRejected():isPending = nextSlot.state ?nextSlot.state() === 'pending' :!nextSlot.isRejected();if (isPending) {nextSlot.resolve();break;}nextSlot = that._slots.shift();}}});return jqXHR;};this._beforeSend(e, options);if (this.options.sequentialUploads ||(this.options.limitConcurrentUploads &&this.options.limitConcurrentUploads <= this._sending)) {if (this.options.limitConcurrentUploads > 1) {slot = $.Deferred();this._slots.push(slot);pipe = slot.pipe(send);} else {pipe = (this._sequence = this._sequence.pipe(send, send));}// Return the piped Promise object, enhanced with an abort method,// which is delegated to the jqXHR object of the current upload,// and jqXHR callbacks mapped to the equivalent Promise methods:pipe.abort = function () {aborted = [undefined, 'abort', 'abort'];if (!jqXHR) {if (slot) {slot.rejectWith(options.context, aborted);}return send();}return jqXHR.abort();};return this._enhancePromise(pipe);}return send();},_onAdd: function (e, data) {var that = this,result = true,options = $.extend({}, this.options, data),limit = options.limitMultiFileUploads,paramName = this._getParamName(options),paramNameSet,paramNameSlice,fileSet,i;if (!(options.singleFileUploads || limit) ||!this._isXHRUpload(options)) {fileSet = [data.files];paramNameSet = [paramName];} else if (!options.singleFileUploads && limit) {fileSet = [];paramNameSet = [];for (i = 0; i < data.files.length; i += limit) {fileSet.push(data.files.slice(i, i + limit));paramNameSlice = paramName.slice(i, i + limit);if (!paramNameSlice.length) {paramNameSlice = paramName;}paramNameSet.push(paramNameSlice);}} else {paramNameSet = paramName;}data.originalFiles = data.files;$.each(fileSet || data.files, function (index, element) {var newData = $.extend({}, data);newData.files = fileSet ? element : [element];newData.paramName = paramNameSet[index];newData.submit = function () {newData.jqXHR = this.jqXHR =(that._trigger('submit', e, this) !== false) &&that._onSend(e, this);return this.jqXHR;};result = that._trigger('add', e, newData);return result;});return result;},_replaceFileInput: function (input) {var inputClone = input.clone(true);$('<form></form>').append(inputClone)[0].reset();// Detaching allows to insert the fileInput on another form// without loosing the file input value:input.after(inputClone).detach();// Avoid memory leaks with the detached file input:$.cleanData(input.unbind('remove'));// Replace the original file input element in the fileInput// elements set with the clone, which has been copied including// event handlers:this.options.fileInput = this.options.fileInput.map(function (i, el) {if (el === input[0]) {return inputClone[0];}return el;});// If the widget has been initialized on the file input itself,// override this.element with the file input clone:if (input[0] === this.element[0]) {this.element = inputClone;}},_handleFileTreeEntry: function (entry, path) {var that = this,dfd = $.Deferred(),errorHandler = function (e) {if (e && !e.entry) {e.entry = entry;}// Since $.when returns immediately if one// Deferred is rejected, we use resolve instead.// This allows valid files and invalid items// to be returned together in one set:dfd.resolve([e]);},dirReader;path = path || '';if (entry.isFile) {if (entry._file) {// Workaround for Chrome bug #149735entry._file.relativePath = path;dfd.resolve(entry._file);} else {entry.file(function (file) {file.relativePath = path;dfd.resolve(file);}, errorHandler);}} else if (entry.isDirectory) {dirReader = entry.createReader();dirReader.readEntries(function (entries) {that._handleFileTreeEntries(entries,path + entry.name + '/').done(function (files) {dfd.resolve(files);}).fail(errorHandler);}, errorHandler);} else {// Return an empy list for file system items// other than files or directories:dfd.resolve([]);}return dfd.promise();},_handleFileTreeEntries: function (entries, path) {var that = this;return $.when.apply($,$.map(entries, function (entry) {return that._handleFileTreeEntry(entry, path);})).pipe(function () {return Array.prototype.concat.apply([],arguments);});},_getDroppedFiles: function (dataTransfer) {dataTransfer = dataTransfer || {};var items = dataTransfer.items;if (items && items.length && (items[0].webkitGetAsEntry ||items[0].getAsEntry)) {return this._handleFileTreeEntries($.map(items, function (item) {var entry;if (item.webkitGetAsEntry) {entry = item.webkitGetAsEntry();if (entry) {// Workaround for Chrome bug #149735:entry._file = item.getAsFile();}return entry;}return item.getAsEntry();}));}return $.Deferred().resolve($.makeArray(dataTransfer.files)).promise();},_getSingleFileInputFiles: function (fileInput) {fileInput = $(fileInput);var entries = fileInput.prop('webkitEntries') ||fileInput.prop('entries'),files,value;if (entries && entries.length) {return this._handleFileTreeEntries(entries);}files = $.makeArray(fileInput.prop('files'));if (!files.length) {value = fileInput.prop('value');if (!value) {return $.Deferred().resolve([]).promise();}// If the files property is not available, the browser does not// support the File API and we add a pseudo File object with// the input value as name with path information removed:files = [{name: value.replace(/^.*\\/, '')}];} else if (files[0].name === undefined && files[0].fileName) {// File normalization for Safari 4 and Firefox 3:$.each(files, function (index, file) {file.name = file.fileName;file.size = file.fileSize;});}return $.Deferred().resolve(files).promise();},_getFileInputFiles: function (fileInput) {if (!(fileInput instanceof $) || fileInput.length === 1) {return this._getSingleFileInputFiles(fileInput);}return $.when.apply($,$.map(fileInput, this._getSingleFileInputFiles)).pipe(function () {return Array.prototype.concat.apply([],arguments);});},_onChange: function (e) {var that = this,data = {fileInput: $(e.target),form: $(e.target.form)};this._getFileInputFiles(data.fileInput).always(function (files) {data.files = files;if (that.options.replaceFileInput) {that._replaceFileInput(data.fileInput);}if (that._trigger('change', e, data) !== false) {that._onAdd(e, data);}});},_onPaste: function (e) {var cbd = e.originalEvent.clipboardData,items = (cbd && cbd.items) || [],data = {files: []};$.each(items, function (index, item) {var file = item.getAsFile && item.getAsFile();if (file) {data.files.push(file);}});if (this._trigger('paste', e, data) === false ||this._onAdd(e, data) === false) {return false;}},_onDrop: function (e) {var that = this,dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,data = {};if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {e.preventDefault();}this._getDroppedFiles(dataTransfer).always(function (files) {data.files = files;if (that._trigger('drop', e, data) !== false) {that._onAdd(e, data);}});},_onDragOver: function (e) {var dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;if (this._trigger('dragover', e) === false) {return false;}if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1) {dataTransfer.dropEffect = 'copy';e.preventDefault();}},_initEventHandlers: function () {if (this._isXHRUpload(this.options)) {this._on(this.options.dropZone, {dragover: this._onDragOver,drop: this._onDrop});this._on(this.options.pasteZone, {paste: this._onPaste});}this._on(this.options.fileInput, {change: this._onChange});},_destroyEventHandlers: function () {this._off(this.options.dropZone, 'dragover drop');this._off(this.options.pasteZone, 'paste');this._off(this.options.fileInput, 'change');},_setOption: function (key, value) {var refresh = $.inArray(key, this._refreshOptionsList) !== -1;if (refresh) {this._destroyEventHandlers();}this._super(key, value);if (refresh) {this._initSpecialOptions();this._initEventHandlers();}},_initSpecialOptions: function () {var options = this.options;if (options.fileInput === undefined) {options.fileInput = this.element.is('input[type="file"]') ?this.element : this.element.find('input[type="file"]');} else if (!(options.fileInput instanceof $)) {options.fileInput = $(options.fileInput);}if (!(options.dropZone instanceof $)) {options.dropZone = $(options.dropZone);}if (!(options.pasteZone instanceof $)) {options.pasteZone = $(options.pasteZone);}},_create: function () {var options = this.options;// Initialize options set via HTML5 data-attributes:$.extend(options, $(this.element[0].cloneNode(false)).data());this._initSpecialOptions();this._slots = [];this._sequence = this._getXHRPromise(true);this._sending = this._active = this._loaded = this._total = 0;this._initEventHandlers();},_destroy: function () {this._destroyEventHandlers();},// This method is exposed to the widget API and allows adding files// using the fileupload API. The data parameter accepts an object which// must have a files property and can contain additional options:// .fileupload('add', {files: filesList});add: function (data) {var that = this;if (!data || this.options.disabled) {return;}if (data.fileInput && !data.files) {this._getFileInputFiles(data.fileInput).always(function (files) {data.files = files;that._onAdd(null, data);});} else {data.files = $.makeArray(data.files);this._onAdd(null, data);}},// This method is exposed to the widget API and allows sending files// using the fileupload API. The data parameter accepts an object which// must have a files or fileInput property and can contain additional options:// .fileupload('send', {files: filesList});// The method returns a Promise object for the file upload call.send: function (data) {if (data && !this.options.disabled) {if (data.fileInput && !data.files) {var that = this,dfd = $.Deferred(),promise = dfd.promise(),jqXHR,aborted;promise.abort = function () {aborted = true;if (jqXHR) {return jqXHR.abort();}dfd.reject(null, 'abort', 'abort');return promise;};this._getFileInputFiles(data.fileInput).always(function (files) {if (aborted) {return;}data.files = files;jqXHR = that._onSend(null, data).then(function (result, textStatus, jqXHR) {dfd.resolve(result, textStatus, jqXHR);},function (jqXHR, textStatus, errorThrown) {dfd.reject(jqXHR, textStatus, errorThrown);});});return this._enhancePromise(promise);}data.files = $.makeArray(data.files);if (data.files.length) {return this._onSend(null, data);}}return this._getXHRPromise(false, data && data.context);}});}));