update jQuery File Upload Plugin (9.12.5) to support dragleave

This commit is contained in:
Vincent Chan 2016-06-13 22:26:49 +02:00
parent 01f8434a75
commit 2abd663419
1 changed files with 228 additions and 84 deletions

View File

@ -1,5 +1,5 @@
/*
* jQuery File Upload Plugin 5.32.2
* jQuery File Upload Plugin 9.12.5
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2010, Sebastian Tschan
@ -9,10 +9,10 @@
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true, unparam: true, regexp: true */
/*global define, window, document, location, File, Blob, FormData */
/* jshint nomen:false */
/* global define, require, window, document, location, Blob, FormData */
(function (factory) {
;(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
@ -20,6 +20,12 @@
'jquery',
'jquery.ui.widget'
], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS:
factory(
require('jquery'),
require('./vendor/jquery.ui.widget')
);
} else {
// Browser globals:
factory(window.jQuery);
@ -40,15 +46,36 @@
$('<input type="file">').prop('disabled'));
// 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);
// as some Safari versions (5?) support XHR file uploads via the FormData API,
// but not non-multipart XHR file uploads.
// window.XMLHttpRequestUpload is not available on IE10, so we check for
// window.ProgressEvent instead to detect XHR2 file upload capability:
$.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
$.support.xhrFormDataFileUpload = !!window.FormData;
// Detect support for Blob slicing (required for chunked uploads):
$.support.blobSlice = window.Blob && (Blob.prototype.slice ||
Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
// Helper function to create drag handlers for dragover/dragenter/dragleave:
function getDragHandler(type) {
var isDragOver = type === 'dragover';
return function (e) {
e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
var dataTransfer = e.dataTransfer;
if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
this._trigger(
type,
$.Event(type, {delegatedEvent: e})
) !== false) {
e.preventDefault();
if (isDragOver) {
dataTransfer.dropEffect = 'copy';
}
}
};
}
// 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
@ -63,9 +90,9 @@
// 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 paste target element(s), by the default undefined.
// Set to a DOM node or jQuery object to enable file pasting:
pasteZone: undefined,
// 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.
@ -88,6 +115,14 @@
// To limit the number of files uploaded with one XHR request,
// set the following option to an integer greater than 0:
limitMultiFileUploads: undefined,
// The following option limits the number of files uploaded with one
// XHR request to keep the request size under or equal to the defined
// limit in bytes:
limitMultiFileUploadSize: undefined,
// Multipart file uploads add a number of bytes to each uploaded file,
// therefore the following option adds an overhead for each file used
// in the limitMultiFileUploadSize configuration:
limitMultiFileUploadSizeOverhead: 512,
// Set the following option to true to issue all file upload requests
// in a sequential order:
sequentialUploads: false,
@ -174,6 +209,9 @@
// handlers using jQuery's Deferred callbacks:
// data.submit().done(func).fail(func).always(func);
add: function (e, data) {
if (e.isDefaultPrevented()) {
return false;
}
if (data.autoUpload || (data.autoUpload !== false &&
$(this).fileupload('option', 'autoUpload'))) {
data.process().done(function () {
@ -239,7 +277,8 @@
// The following are jQuery ajax settings required for the file uploads:
processData: false,
contentType: false,
cache: false
cache: false,
timeout: 0
},
// A list of options that require reinitializing event listeners and/or
@ -280,7 +319,7 @@
_getFormData: function (options) {
var formData;
if (typeof options.formData === 'function') {
if ($.type(options.formData) === 'function') {
return options.formData(options.form);
}
if ($.isArray(options.formData)) {
@ -360,10 +399,18 @@
// 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);
this._trigger(
'progress',
$.Event('progress', {delegatedEvent: e}),
data
);
// Trigger a global progress event for all current file uploads,
// including ajax calls queued for sequential file uploads:
this._trigger('progressall', e, this._progress);
this._trigger(
'progressall',
$.Event('progressall', {delegatedEvent: e}),
this._progress
);
}
},
@ -398,8 +445,9 @@
file = options.files[0],
// Ignore non-multipart setting if not supported:
multipart = options.multipart || !$.support.xhrFileUpload,
paramName = options.paramName[0];
options.headers = options.headers || {};
paramName = $.type(options.paramName) === 'array' ?
options.paramName[0] : options.paramName;
options.headers = $.extend({}, options.headers);
if (options.contentRange) {
options.headers['Content-Range'] = options.contentRange;
}
@ -408,7 +456,7 @@
encodeURI(file.name) + '"';
}
if (!multipart) {
options.contentType = file.type;
options.contentType = file.type || 'application/octet-stream';
options.data = options.blob || file;
} else if ($.support.xhrFormDataFileUpload) {
if (options.postMessage) {
@ -425,7 +473,8 @@
} else {
$.each(options.files, function (index, file) {
formData.push({
name: options.paramName[index] || paramName,
name: ($.type(options.paramName) === 'array' &&
options.paramName[index]) || paramName,
value: file
});
});
@ -448,9 +497,10 @@
if (that._isInstanceOf('File', file) ||
that._isInstanceOf('Blob', file)) {
formData.append(
options.paramName[index] || paramName,
($.type(options.paramName) === 'array' &&
options.paramName[index]) || paramName,
file,
file.name
file.uploadName || file.name
);
}
});
@ -534,8 +584,10 @@
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();
options.type = (options.type ||
($.type(options.form.prop('method')) === 'string' &&
options.form.prop('method')) || ''
).toUpperCase();
if (options.type !== 'POST' && options.type !== 'PUT' &&
options.type !== 'PATCH') {
options.type = 'POST';
@ -594,22 +646,32 @@
// Adds convenience methods to the data callback argument:
_addConvenienceMethods: function (e, data) {
var that = this,
getPromise = function (data) {
return $.Deferred().resolveWith(that, [data]).promise();
getPromise = function (args) {
return $.Deferred().resolveWith(that, args).promise();
};
data.process = function (resolveFunc, rejectFunc) {
if (resolveFunc || rejectFunc) {
data._processQueue = this._processQueue =
(this._processQueue || getPromise(this))
.pipe(resolveFunc, rejectFunc);
(this._processQueue || getPromise([this])).then(
function () {
if (data.errorThrown) {
return $.Deferred()
.rejectWith(that, [data]).promise();
}
return getPromise(arguments);
}
).then(resolveFunc, rejectFunc);
}
return this._processQueue || getPromise(this);
return this._processQueue || getPromise([this]);
};
data.submit = function () {
if (this.state() !== 'pending') {
data.jqXHR = this.jqXHR =
(that._trigger('submit', e, this) !== false) &&
that._onSend(e, this);
(that._trigger(
'submit',
$.Event('submit', {delegatedEvent: e}),
this
) !== false) && that._onSend(e, this);
}
return this.jqXHR || that._getXHRPromise();
};
@ -617,7 +679,9 @@
if (this.jqXHR) {
return this.jqXHR.abort();
}
return that._getXHRPromise();
this.errorThrown = 'abort';
that._trigger('fail', null, this);
return that._getXHRPromise(false);
};
data.state = function () {
if (this.jqXHR) {
@ -627,6 +691,10 @@
return that._getDeferredState(this._processQueue);
}
};
data.processing = function () {
return !this.jqXHR && this._processQueue && that
._getDeferredState(this._processQueue) === 'pending';
};
data.progress = function () {
return this._progress;
};
@ -829,7 +897,11 @@
// Set timer for bitrate progress calculation:
options._bitrateTimer = new that._BitrateTimer();
jqXHR = jqXHR || (
((aborted || that._trigger('send', e, options) === false) &&
((aborted || that._trigger(
'send',
$.Event('send', {delegatedEvent: e}),
options
) === false) &&
that._getXHRPromise(false, options.context, aborted)) ||
that._chunkedUpload(options) || $.ajax(options)
).done(function (result, textStatus, jqXHR) {
@ -873,9 +945,9 @@
if (this.options.limitConcurrentUploads > 1) {
slot = $.Deferred();
this._slots.push(slot);
pipe = slot.pipe(send);
pipe = slot.then(send);
} else {
this._sequence = this._sequence.pipe(send, send);
this._sequence = this._sequence.then(send, send);
pipe = this._sequence;
}
// Return the piped Promise object, enhanced with an abort method,
@ -900,50 +972,93 @@
var that = this,
result = true,
options = $.extend({}, this.options, data),
files = data.files,
filesLength = files.length,
limit = options.limitMultiFileUploads,
limitSize = options.limitMultiFileUploadSize,
overhead = options.limitMultiFileUploadSizeOverhead,
batchSize = 0,
paramName = this._getParamName(options),
paramNameSet,
paramNameSlice,
fileSet,
i;
if (!(options.singleFileUploads || limit) ||
i,
j = 0;
if (!filesLength) {
return false;
}
if (limitSize && files[0].size === undefined) {
limitSize = undefined;
}
if (!(options.singleFileUploads || limit || limitSize) ||
!this._isXHRUpload(options)) {
fileSet = [data.files];
fileSet = [files];
paramNameSet = [paramName];
} else if (!options.singleFileUploads && limit) {
} else if (!(options.singleFileUploads || limitSize) && limit) {
fileSet = [];
paramNameSet = [];
for (i = 0; i < data.files.length; i += limit) {
fileSet.push(data.files.slice(i, i + limit));
for (i = 0; i < filesLength; i += limit) {
fileSet.push(files.slice(i, i + limit));
paramNameSlice = paramName.slice(i, i + limit);
if (!paramNameSlice.length) {
paramNameSlice = paramName;
}
paramNameSet.push(paramNameSlice);
}
} else if (!options.singleFileUploads && limitSize) {
fileSet = [];
paramNameSet = [];
for (i = 0; i < filesLength; i = i + 1) {
batchSize += files[i].size + overhead;
if (i + 1 === filesLength ||
((batchSize + files[i + 1].size + overhead) > limitSize) ||
(limit && i + 1 - j >= limit)) {
fileSet.push(files.slice(j, i + 1));
paramNameSlice = paramName.slice(j, i + 1);
if (!paramNameSlice.length) {
paramNameSlice = paramName;
}
paramNameSet.push(paramNameSlice);
j = i + 1;
batchSize = 0;
}
}
} else {
paramNameSet = paramName;
}
data.originalFiles = data.files;
$.each(fileSet || data.files, function (index, element) {
data.originalFiles = files;
$.each(fileSet || files, function (index, element) {
var newData = $.extend({}, data);
newData.files = fileSet ? element : [element];
newData.paramName = paramNameSet[index];
that._initResponseObject(newData);
that._initProgressObject(newData);
that._addConvenienceMethods(e, newData);
result = that._trigger('add', e, newData);
result = that._trigger(
'add',
$.Event('add', {delegatedEvent: e}),
newData
);
return result;
});
return result;
},
_replaceFileInput: function (input) {
var inputClone = input.clone(true);
_replaceFileInput: function (data) {
var input = data.fileInput,
inputClone = input.clone(true),
restoreFocus = input.is(document.activeElement);
// Add a reference for the new cloned file input to the data argument:
data.fileInputClone = inputClone;
$('<form></form>').append(inputClone)[0].reset();
// Detaching allows to insert the fileInput on another form
// without losing the file input value:
// without loosing the file input value:
input.after(inputClone).detach();
// If the fileInput had focus before it was detached,
// restore focus to the inputClone.
if (restoreFocus) {
inputClone.focus();
}
// Avoid memory leaks with the detached file input:
$.cleanData(input.unbind('remove'));
// Replace the original file input element in the fileInput
@ -975,7 +1090,25 @@
// to be returned together in one set:
dfd.resolve([e]);
},
dirReader;
successHandler = function (entries) {
that._handleFileTreeEntries(
entries,
path + entry.name + '/'
).done(function (files) {
dfd.resolve(files);
}).fail(errorHandler);
},
readEntries = function () {
dirReader.readEntries(function (results) {
if (!results.length) {
successHandler(entries);
} else {
entries = entries.concat(results);
readEntries();
}
}, errorHandler);
},
dirReader, entries = [];
path = path || '';
if (entry.isFile) {
if (entry._file) {
@ -990,14 +1123,7 @@
}
} 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);
readEntries();
} else {
// Return an empy list for file system items
// other than files or directories:
@ -1013,7 +1139,7 @@
$.map(entries, function (entry) {
return that._handleFileTreeEntry(entry, path);
})
).pipe(function () {
).then(function () {
return Array.prototype.concat.apply(
[],
arguments
@ -1082,7 +1208,7 @@
return $.when.apply(
$,
$.map(fileInput, this._getSingleFileInputFiles)
).pipe(function () {
).then(function () {
return Array.prototype.concat.apply(
[],
arguments
@ -1099,9 +1225,13 @@
this._getFileInputFiles(data.fileInput).always(function (files) {
data.files = files;
if (that.options.replaceFileInput) {
that._replaceFileInput(data.fileInput);
that._replaceFileInput(data);
}
if (that._trigger('change', e, data) !== false) {
if (that._trigger(
'change',
$.Event('change', {delegatedEvent: e}),
data
) !== false) {
that._onAdd(e, data);
}
});
@ -1118,9 +1248,12 @@
data.files.push(file);
}
});
if (this._trigger('paste', e, data) === false ||
this._onAdd(e, data) === false) {
return false;
if (this._trigger(
'paste',
$.Event('paste', {delegatedEvent: e}),
data
) !== false) {
this._onAdd(e, data);
}
}
},
@ -1134,32 +1267,32 @@
e.preventDefault();
this._getDroppedFiles(dataTransfer).always(function (files) {
data.files = files;
if (that._trigger('drop', e, data) !== false) {
if (that._trigger(
'drop',
$.Event('drop', {delegatedEvent: e}),
data
) !== false) {
that._onAdd(e, data);
}
});
}
},
_onDragOver: function (e) {
e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
var dataTransfer = e.dataTransfer;
if (dataTransfer) {
if (this._trigger('dragover', e) === false) {
return false;
}
if ($.inArray('Files', dataTransfer.types) !== -1) {
dataTransfer.dropEffect = 'copy';
e.preventDefault();
}
}
},
_onDragOver: getDragHandler('dragover'),
_onDragEnter: getDragHandler('dragenter'),
_onDragLeave: getDragHandler('dragleave'),
_initEventHandlers: function () {
if (this._isXHRUpload(this.options)) {
this._on(this.options.dropZone, {
dragover: this._onDragOver,
drop: this._onDrop
drop: this._onDrop,
// event.preventDefault() on dragenter is required for IE10+:
dragenter: this._onDragEnter,
// dragleave is not required, but added for completeness:
dragleave: this._onDragLeave
});
this._on(this.options.pasteZone, {
paste: this._onPaste
@ -1173,7 +1306,7 @@
},
_destroyEventHandlers: function () {
this._off(this.options.dropZone, 'dragover drop');
this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
this._off(this.options.pasteZone, 'paste');
this._off(this.options.fileInput, 'change');
},
@ -1220,15 +1353,25 @@
_initDataAttributes: function () {
var that = this,
options = this.options;
options = this.options,
data = this.element.data();
// Initialize options set via HTML5 data-attributes:
$.each(
$(this.element[0].cloneNode(false)).data(),
function (key, value) {
if (that._isRegExpOption(key, value)) {
value = that._getRegExp(value);
this.element[0].attributes,
function (index, attr) {
var key = attr.name.toLowerCase(),
value;
if (/^data-/.test(key)) {
// Convert hyphen-ated key to camelCase:
key = key.slice(5).replace(/-[a-z]/g, function (str) {
return str.charAt(1).toUpperCase();
});
value = data[key];
if (that._isRegExpOption(key, value)) {
value = that._getRegExp(value);
}
options[key] = value;
}
options[key] = value;
}
);
},
@ -1308,7 +1451,8 @@
return;
}
data.files = files;
jqXHR = that._onSend(null, data).then(
jqXHR = that._onSend(null, data);
jqXHR.then(
function (result, textStatus, jqXHR) {
dfd.resolve(result, textStatus, jqXHR);
},