rails-uploader 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +162 -86
- data/app/assets/javascripts/uploader/application.js +6 -4
- data/app/assets/javascripts/uploader/jquery.uploader.js.coffee +58 -0
- data/app/controllers/uploader/attachments_controller.rb +63 -39
- data/app/views/uploader/default/_container.html.erb +19 -45
- data/app/views/uploader/default/_download.html.erb +4 -5
- data/app/views/uploader/default/_upload.html.erb +0 -1
- data/config/locales/en.yml +2 -0
- data/config/locales/ru.yml +3 -0
- data/config/locales/uk.yml +3 -0
- data/config/routes.rb +1 -1
- data/lib/uploader/asset.rb +63 -84
- data/lib/uploader/authorization.rb +52 -0
- data/lib/uploader/authorization_adapter.rb +24 -0
- data/lib/uploader/chunked_uploads.rb +15 -0
- data/lib/uploader/engine.rb +6 -0
- data/lib/uploader/file_part.rb +18 -0
- data/lib/uploader/fileuploads.rb +42 -65
- data/lib/uploader/helpers/field_tag.rb +10 -5
- data/lib/uploader/hooks/formtastic.rb +0 -13
- data/lib/uploader/upload_request.rb +72 -0
- data/lib/uploader/version.rb +1 -1
- data/lib/uploader.rb +41 -8
- data/spec/dummy/app/models/asset.rb +12 -0
- data/spec/dummy/app/models/picture.rb +6 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/test.log +325 -0
- data/spec/dummy/public/uploads/picture/data/1/thumb_rails.png +0 -0
- data/spec/dummy/public/uploads/picture/data/3/thumb_rails.png +0 -0
- data/spec/fileuploads_spec.rb +4 -4
- data/spec/requests/attachments_controller_spec.rb +11 -12
- data/vendor/assets/javascripts/uploader/jquery.fileupload-process.js +175 -0
- data/vendor/assets/javascripts/uploader/jquery.fileupload-ui.js +164 -261
- data/vendor/assets/javascripts/uploader/jquery.fileupload-validate.js +122 -0
- data/vendor/assets/javascripts/uploader/jquery.fileupload.js +335 -101
- data/vendor/assets/javascripts/uploader/jquery.iframe-transport.js +47 -15
- data/vendor/assets/javascripts/uploader/vendor/jquery.ui.widget.js +572 -0
- data/vendor/assets/javascripts/uploader/vendor/tmpl.min.js +1 -0
- data/vendor/assets/stylesheets/uploader/default.css +26 -19
- metadata +12 -9
- data/vendor/assets/javascripts/uploader/jquery.fileupload-fp.js +0 -227
- data/vendor/assets/javascripts/uploader/jquery.ui.widget.js +0 -530
- data/vendor/assets/javascripts/uploader/load-image.min.js +0 -1
- data/vendor/assets/javascripts/uploader/locales/en.js +0 -27
- data/vendor/assets/javascripts/uploader/locales/ru.js +0 -27
- data/vendor/assets/javascripts/uploader/locales/uk.js +0 -27
- data/vendor/assets/javascripts/uploader/tmpl.min.js +0 -1
@@ -0,0 +1,122 @@
|
|
1
|
+
/*
|
2
|
+
* jQuery File Upload Validation Plugin
|
3
|
+
* https://github.com/blueimp/jQuery-File-Upload
|
4
|
+
*
|
5
|
+
* Copyright 2013, Sebastian Tschan
|
6
|
+
* https://blueimp.net
|
7
|
+
*
|
8
|
+
* Licensed under the MIT license:
|
9
|
+
* http://www.opensource.org/licenses/MIT
|
10
|
+
*/
|
11
|
+
|
12
|
+
/* global define, require, window */
|
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
|
+
'./jquery.fileupload-process'
|
21
|
+
], factory);
|
22
|
+
} else if (typeof exports === 'object') {
|
23
|
+
// Node/CommonJS:
|
24
|
+
factory(require('jquery'));
|
25
|
+
} else {
|
26
|
+
// Browser globals:
|
27
|
+
factory(
|
28
|
+
window.jQuery
|
29
|
+
);
|
30
|
+
}
|
31
|
+
}(function ($) {
|
32
|
+
'use strict';
|
33
|
+
|
34
|
+
// Append to the default processQueue:
|
35
|
+
$.blueimp.fileupload.prototype.options.processQueue.push(
|
36
|
+
{
|
37
|
+
action: 'validate',
|
38
|
+
// Always trigger this action,
|
39
|
+
// even if the previous action was rejected:
|
40
|
+
always: true,
|
41
|
+
// Options taken from the global options map:
|
42
|
+
acceptFileTypes: '@',
|
43
|
+
maxFileSize: '@',
|
44
|
+
minFileSize: '@',
|
45
|
+
maxNumberOfFiles: '@',
|
46
|
+
disabled: '@disableValidation'
|
47
|
+
}
|
48
|
+
);
|
49
|
+
|
50
|
+
// The File Upload Validation plugin extends the fileupload widget
|
51
|
+
// with file validation functionality:
|
52
|
+
$.widget('blueimp.fileupload', $.blueimp.fileupload, {
|
53
|
+
|
54
|
+
options: {
|
55
|
+
/*
|
56
|
+
// The regular expression for allowed file types, matches
|
57
|
+
// against either file type or file name:
|
58
|
+
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
|
59
|
+
// The maximum allowed file size in bytes:
|
60
|
+
maxFileSize: 10000000, // 10 MB
|
61
|
+
// The minimum allowed file size in bytes:
|
62
|
+
minFileSize: undefined, // No minimal file size
|
63
|
+
// The limit of files to be uploaded:
|
64
|
+
maxNumberOfFiles: 10,
|
65
|
+
*/
|
66
|
+
|
67
|
+
// Function returning the current number of files,
|
68
|
+
// has to be overriden for maxNumberOfFiles validation:
|
69
|
+
getNumberOfFiles: $.noop,
|
70
|
+
|
71
|
+
// Error and info messages:
|
72
|
+
messages: {
|
73
|
+
maxNumberOfFiles: 'Maximum number of files exceeded',
|
74
|
+
acceptFileTypes: 'File type not allowed',
|
75
|
+
maxFileSize: 'File is too large',
|
76
|
+
minFileSize: 'File is too small'
|
77
|
+
}
|
78
|
+
},
|
79
|
+
|
80
|
+
processActions: {
|
81
|
+
|
82
|
+
validate: function (data, options) {
|
83
|
+
if (options.disabled) {
|
84
|
+
return data;
|
85
|
+
}
|
86
|
+
var dfd = $.Deferred(),
|
87
|
+
settings = this.options,
|
88
|
+
file = data.files[data.index],
|
89
|
+
fileSize;
|
90
|
+
if (options.minFileSize || options.maxFileSize) {
|
91
|
+
fileSize = file.size;
|
92
|
+
}
|
93
|
+
if ($.type(options.maxNumberOfFiles) === 'number' &&
|
94
|
+
(settings.getNumberOfFiles() || 0) + data.files.length >
|
95
|
+
options.maxNumberOfFiles) {
|
96
|
+
file.error = settings.i18n('maxNumberOfFiles');
|
97
|
+
} else if (options.acceptFileTypes &&
|
98
|
+
!(options.acceptFileTypes.test(file.type) ||
|
99
|
+
options.acceptFileTypes.test(file.name))) {
|
100
|
+
file.error = settings.i18n('acceptFileTypes');
|
101
|
+
} else if (fileSize > options.maxFileSize) {
|
102
|
+
file.error = settings.i18n('maxFileSize');
|
103
|
+
} else if ($.type(fileSize) === 'number' &&
|
104
|
+
fileSize < options.minFileSize) {
|
105
|
+
file.error = settings.i18n('minFileSize');
|
106
|
+
} else {
|
107
|
+
delete file.error;
|
108
|
+
}
|
109
|
+
if (file.error || data.files.error) {
|
110
|
+
data.files.error = true;
|
111
|
+
dfd.rejectWith(this, [data]);
|
112
|
+
} else {
|
113
|
+
dfd.resolveWith(this, [data]);
|
114
|
+
}
|
115
|
+
return dfd.promise();
|
116
|
+
}
|
117
|
+
|
118
|
+
}
|
119
|
+
|
120
|
+
});
|
121
|
+
|
122
|
+
}));
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
* jQuery File Upload Plugin
|
2
|
+
* jQuery File Upload Plugin
|
3
3
|
* https://github.com/blueimp/jQuery-File-Upload
|
4
4
|
*
|
5
5
|
* Copyright 2010, Sebastian Tschan
|
@@ -9,10 +9,10 @@
|
|
9
9
|
* http://www.opensource.org/licenses/MIT
|
10
10
|
*/
|
11
11
|
|
12
|
-
/*
|
13
|
-
/*global define, window, document,
|
12
|
+
/* jshint nomen:false */
|
13
|
+
/* global define, require, window, document, location, Blob, FormData */
|
14
14
|
|
15
|
-
(function (factory) {
|
15
|
+
;(function (factory) {
|
16
16
|
'use strict';
|
17
17
|
if (typeof define === 'function' && define.amd) {
|
18
18
|
// Register as an anonymous AMD module:
|
@@ -20,6 +20,12 @@
|
|
20
20
|
'jquery',
|
21
21
|
'jquery.ui.widget'
|
22
22
|
], factory);
|
23
|
+
} else if (typeof exports === 'object') {
|
24
|
+
// Node/CommonJS:
|
25
|
+
factory(
|
26
|
+
require('jquery'),
|
27
|
+
require('./vendor/jquery.ui.widget')
|
28
|
+
);
|
23
29
|
} else {
|
24
30
|
// Browser globals:
|
25
31
|
factory(window.jQuery);
|
@@ -27,12 +33,49 @@
|
|
27
33
|
}(function ($) {
|
28
34
|
'use strict';
|
29
35
|
|
36
|
+
// Detect file input support, based on
|
37
|
+
// http://viljamis.com/blog/2012/file-upload-support-on-mobile/
|
38
|
+
$.support.fileInput = !(new RegExp(
|
39
|
+
// Handle devices which give false positives for the feature detection:
|
40
|
+
'(Android (1\\.[0156]|2\\.[01]))' +
|
41
|
+
'|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
|
42
|
+
'|(w(eb)?OSBrowser)|(webOS)' +
|
43
|
+
'|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
|
44
|
+
).test(window.navigator.userAgent) ||
|
45
|
+
// Feature detection for all other devices:
|
46
|
+
$('<input type="file">').prop('disabled'));
|
47
|
+
|
30
48
|
// The FileReader API is not actually used, but works as feature detection,
|
31
|
-
// as
|
32
|
-
// but not non-multipart XHR file uploads
|
33
|
-
|
49
|
+
// as some Safari versions (5?) support XHR file uploads via the FormData API,
|
50
|
+
// but not non-multipart XHR file uploads.
|
51
|
+
// window.XMLHttpRequestUpload is not available on IE10, so we check for
|
52
|
+
// window.ProgressEvent instead to detect XHR2 file upload capability:
|
53
|
+
$.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
|
34
54
|
$.support.xhrFormDataFileUpload = !!window.FormData;
|
35
55
|
|
56
|
+
// Detect support for Blob slicing (required for chunked uploads):
|
57
|
+
$.support.blobSlice = window.Blob && (Blob.prototype.slice ||
|
58
|
+
Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
|
59
|
+
|
60
|
+
// Helper function to create drag handlers for dragover/dragenter/dragleave:
|
61
|
+
function getDragHandler(type) {
|
62
|
+
var isDragOver = type === 'dragover';
|
63
|
+
return function (e) {
|
64
|
+
e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
|
65
|
+
var dataTransfer = e.dataTransfer;
|
66
|
+
if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
|
67
|
+
this._trigger(
|
68
|
+
type,
|
69
|
+
$.Event(type, {delegatedEvent: e})
|
70
|
+
) !== false) {
|
71
|
+
e.preventDefault();
|
72
|
+
if (isDragOver) {
|
73
|
+
dataTransfer.dropEffect = 'copy';
|
74
|
+
}
|
75
|
+
}
|
76
|
+
};
|
77
|
+
}
|
78
|
+
|
36
79
|
// The fileupload widget listens for change events on file input fields defined
|
37
80
|
// via fileInput setting and paste or drop events of the given dropZone.
|
38
81
|
// In addition to the default jQuery Widget methods, the fileupload widget
|
@@ -47,9 +90,9 @@
|
|
47
90
|
// The drop target element(s), by the default the complete document.
|
48
91
|
// Set to null to disable drag & drop support:
|
49
92
|
dropZone: $(document),
|
50
|
-
// The paste target element(s), by the default
|
51
|
-
// Set to
|
52
|
-
pasteZone:
|
93
|
+
// The paste target element(s), by the default undefined.
|
94
|
+
// Set to a DOM node or jQuery object to enable file pasting:
|
95
|
+
pasteZone: undefined,
|
53
96
|
// The file input field(s), that are listened to for change events.
|
54
97
|
// If undefined, it is set to the file input fields inside
|
55
98
|
// of the widget element on plugin initialization.
|
@@ -72,6 +115,14 @@
|
|
72
115
|
// To limit the number of files uploaded with one XHR request,
|
73
116
|
// set the following option to an integer greater than 0:
|
74
117
|
limitMultiFileUploads: undefined,
|
118
|
+
// The following option limits the number of files uploaded with one
|
119
|
+
// XHR request to keep the request size under or equal to the defined
|
120
|
+
// limit in bytes:
|
121
|
+
limitMultiFileUploadSize: undefined,
|
122
|
+
// Multipart file uploads add a number of bytes to each uploaded file,
|
123
|
+
// therefore the following option adds an overhead for each file used
|
124
|
+
// in the limitMultiFileUploadSize configuration:
|
125
|
+
limitMultiFileUploadSizeOverhead: 512,
|
75
126
|
// Set the following option to true to issue all file upload requests
|
76
127
|
// in a sequential order:
|
77
128
|
sequentialUploads: false,
|
@@ -115,6 +166,23 @@
|
|
115
166
|
// By default, uploads are started automatically when adding files:
|
116
167
|
autoUpload: true,
|
117
168
|
|
169
|
+
// Error and info messages:
|
170
|
+
messages: {
|
171
|
+
uploadedBytes: 'Uploaded bytes exceed file size'
|
172
|
+
},
|
173
|
+
|
174
|
+
// Translation function, gets the message key to be translated
|
175
|
+
// and an object with context specific data as arguments:
|
176
|
+
i18n: function (message, context) {
|
177
|
+
message = this.messages[message] || message.toString();
|
178
|
+
if (context) {
|
179
|
+
$.each(context, function (key, value) {
|
180
|
+
message = message.replace('{' + key + '}', value);
|
181
|
+
});
|
182
|
+
}
|
183
|
+
return message;
|
184
|
+
},
|
185
|
+
|
118
186
|
// Additional form data to be sent along with the file uploads can be set
|
119
187
|
// using this option, which accepts an array of objects with name and
|
120
188
|
// value properties, a function returning such an array, a FormData
|
@@ -127,21 +195,28 @@
|
|
127
195
|
// The add callback is invoked as soon as files are added to the fileupload
|
128
196
|
// widget (via file input selection, drag & drop, paste or add API call).
|
129
197
|
// If the singleFileUploads option is enabled, this callback will be
|
130
|
-
// called once for each file in the selection for XHR file
|
198
|
+
// called once for each file in the selection for XHR file uploads, else
|
131
199
|
// once for each file selection.
|
200
|
+
//
|
132
201
|
// The upload starts when the submit method is invoked on the data parameter.
|
133
202
|
// The data object contains a files property holding the added files
|
134
|
-
// and allows to override plugin options as well as define ajax settings.
|
203
|
+
// and allows you to override plugin options as well as define ajax settings.
|
204
|
+
//
|
135
205
|
// Listeners for this callback can also be bound the following way:
|
136
206
|
// .bind('fileuploadadd', func);
|
207
|
+
//
|
137
208
|
// data.submit() returns a Promise object and allows to attach additional
|
138
209
|
// handlers using jQuery's Deferred callbacks:
|
139
210
|
// data.submit().done(func).fail(func).always(func);
|
140
211
|
add: function (e, data) {
|
212
|
+
if (e.isDefaultPrevented()) {
|
213
|
+
return false;
|
214
|
+
}
|
141
215
|
if (data.autoUpload || (data.autoUpload !== false &&
|
142
|
-
|
143
|
-
|
144
|
-
|
216
|
+
$(this).fileupload('option', 'autoUpload'))) {
|
217
|
+
data.process().done(function () {
|
218
|
+
data.submit();
|
219
|
+
});
|
145
220
|
}
|
146
221
|
},
|
147
222
|
|
@@ -202,11 +277,13 @@
|
|
202
277
|
// The following are jQuery ajax settings required for the file uploads:
|
203
278
|
processData: false,
|
204
279
|
contentType: false,
|
205
|
-
cache: false
|
280
|
+
cache: false,
|
281
|
+
timeout: 0
|
206
282
|
},
|
207
283
|
|
208
|
-
// A list of options that require
|
209
|
-
|
284
|
+
// A list of options that require reinitializing event listeners and/or
|
285
|
+
// special initialization code:
|
286
|
+
_specialOptions: [
|
210
287
|
'fileInput',
|
211
288
|
'dropZone',
|
212
289
|
'pasteZone',
|
@@ -214,6 +291,11 @@
|
|
214
291
|
'forceIframeTransport'
|
215
292
|
],
|
216
293
|
|
294
|
+
_blobSlice: $.support.blobSlice && function () {
|
295
|
+
var slice = this.slice || this.webkitSlice || this.mozSlice;
|
296
|
+
return slice.apply(this, arguments);
|
297
|
+
},
|
298
|
+
|
217
299
|
_BitrateTimer: function () {
|
218
300
|
this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
|
219
301
|
this.loaded = 0;
|
@@ -237,7 +319,7 @@
|
|
237
319
|
|
238
320
|
_getFormData: function (options) {
|
239
321
|
var formData;
|
240
|
-
if (
|
322
|
+
if ($.type(options.formData) === 'function') {
|
241
323
|
return options.formData(options.form);
|
242
324
|
}
|
243
325
|
if ($.isArray(options.formData)) {
|
@@ -317,10 +399,18 @@
|
|
317
399
|
// Trigger a custom progress event with a total data property set
|
318
400
|
// to the file size(s) of the current upload and a loaded data
|
319
401
|
// property calculated accordingly:
|
320
|
-
this._trigger(
|
402
|
+
this._trigger(
|
403
|
+
'progress',
|
404
|
+
$.Event('progress', {delegatedEvent: e}),
|
405
|
+
data
|
406
|
+
);
|
321
407
|
// Trigger a global progress event for all current file uploads,
|
322
408
|
// including ajax calls queued for sequential file uploads:
|
323
|
-
this._trigger(
|
409
|
+
this._trigger(
|
410
|
+
'progressall',
|
411
|
+
$.Event('progressall', {delegatedEvent: e}),
|
412
|
+
this._progress
|
413
|
+
);
|
324
414
|
}
|
325
415
|
},
|
326
416
|
|
@@ -355,15 +445,18 @@
|
|
355
445
|
file = options.files[0],
|
356
446
|
// Ignore non-multipart setting if not supported:
|
357
447
|
multipart = options.multipart || !$.support.xhrFileUpload,
|
358
|
-
paramName = options.paramName
|
359
|
-
|
448
|
+
paramName = $.type(options.paramName) === 'array' ?
|
449
|
+
options.paramName[0] : options.paramName;
|
450
|
+
options.headers = $.extend({}, options.headers);
|
360
451
|
if (options.contentRange) {
|
361
452
|
options.headers['Content-Range'] = options.contentRange;
|
362
453
|
}
|
363
|
-
if (!multipart) {
|
454
|
+
if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
|
364
455
|
options.headers['Content-Disposition'] = 'attachment; filename="' +
|
365
456
|
encodeURI(file.name) + '"';
|
366
|
-
|
457
|
+
}
|
458
|
+
if (!multipart) {
|
459
|
+
options.contentType = file.type || 'application/octet-stream';
|
367
460
|
options.data = options.blob || file;
|
368
461
|
} else if ($.support.xhrFormDataFileUpload) {
|
369
462
|
if (options.postMessage) {
|
@@ -380,7 +473,8 @@
|
|
380
473
|
} else {
|
381
474
|
$.each(options.files, function (index, file) {
|
382
475
|
formData.push({
|
383
|
-
name: options.paramName
|
476
|
+
name: ($.type(options.paramName) === 'array' &&
|
477
|
+
options.paramName[index]) || paramName,
|
384
478
|
value: file
|
385
479
|
});
|
386
480
|
});
|
@@ -395,8 +489,6 @@
|
|
395
489
|
});
|
396
490
|
}
|
397
491
|
if (options.blob) {
|
398
|
-
options.headers['Content-Disposition'] = 'attachment; filename="' +
|
399
|
-
encodeURI(file.name) + '"';
|
400
492
|
formData.append(paramName, options.blob, file.name);
|
401
493
|
} else {
|
402
494
|
$.each(options.files, function (index, file) {
|
@@ -405,9 +497,10 @@
|
|
405
497
|
if (that._isInstanceOf('File', file) ||
|
406
498
|
that._isInstanceOf('Blob', file)) {
|
407
499
|
formData.append(
|
408
|
-
options.paramName
|
500
|
+
($.type(options.paramName) === 'array' &&
|
501
|
+
options.paramName[index]) || paramName,
|
409
502
|
file,
|
410
|
-
file.name
|
503
|
+
file.uploadName || file.name
|
411
504
|
);
|
412
505
|
}
|
413
506
|
});
|
@@ -420,13 +513,13 @@
|
|
420
513
|
},
|
421
514
|
|
422
515
|
_initIframeSettings: function (options) {
|
516
|
+
var targetHost = $('<a></a>').prop('href', options.url).prop('host');
|
423
517
|
// Setting the dataType to iframe enables the iframe transport:
|
424
518
|
options.dataType = 'iframe ' + (options.dataType || '');
|
425
519
|
// The iframe transport accepts a serialized array as form data:
|
426
520
|
options.formData = this._getFormData(options);
|
427
521
|
// Add redirect url to form data on cross-domain uploads:
|
428
|
-
if (options.redirect &&
|
429
|
-
.prop('host') !== location.host) {
|
522
|
+
if (options.redirect && targetHost && targetHost !== location.host) {
|
430
523
|
options.formData.push({
|
431
524
|
name: options.redirectParamName || 'redirect',
|
432
525
|
value: options.redirect
|
@@ -491,8 +584,10 @@
|
|
491
584
|
options.url = options.form.prop('action') || location.href;
|
492
585
|
}
|
493
586
|
// The HTTP request method must be "POST" or "PUT":
|
494
|
-
options.type = (options.type ||
|
495
|
-
.
|
587
|
+
options.type = (options.type ||
|
588
|
+
($.type(options.form.prop('method')) === 'string' &&
|
589
|
+
options.form.prop('method')) || ''
|
590
|
+
).toUpperCase();
|
496
591
|
if (options.type !== 'POST' && options.type !== 'PUT' &&
|
497
592
|
options.type !== 'PATCH') {
|
498
593
|
options.type = 'POST';
|
@@ -548,14 +643,35 @@
|
|
548
643
|
return this._enhancePromise(promise);
|
549
644
|
},
|
550
645
|
|
551
|
-
// Adds convenience methods to the callback
|
646
|
+
// Adds convenience methods to the data callback argument:
|
552
647
|
_addConvenienceMethods: function (e, data) {
|
553
|
-
var that = this
|
648
|
+
var that = this,
|
649
|
+
getPromise = function (args) {
|
650
|
+
return $.Deferred().resolveWith(that, args).promise();
|
651
|
+
};
|
652
|
+
data.process = function (resolveFunc, rejectFunc) {
|
653
|
+
if (resolveFunc || rejectFunc) {
|
654
|
+
data._processQueue = this._processQueue =
|
655
|
+
(this._processQueue || getPromise([this])).then(
|
656
|
+
function () {
|
657
|
+
if (data.errorThrown) {
|
658
|
+
return $.Deferred()
|
659
|
+
.rejectWith(that, [data]).promise();
|
660
|
+
}
|
661
|
+
return getPromise(arguments);
|
662
|
+
}
|
663
|
+
).then(resolveFunc, rejectFunc);
|
664
|
+
}
|
665
|
+
return this._processQueue || getPromise([this]);
|
666
|
+
};
|
554
667
|
data.submit = function () {
|
555
668
|
if (this.state() !== 'pending') {
|
556
669
|
data.jqXHR = this.jqXHR =
|
557
|
-
(that._trigger(
|
558
|
-
|
670
|
+
(that._trigger(
|
671
|
+
'submit',
|
672
|
+
$.Event('submit', {delegatedEvent: e}),
|
673
|
+
this
|
674
|
+
) !== false) && that._onSend(e, this);
|
559
675
|
}
|
560
676
|
return this.jqXHR || that._getXHRPromise();
|
561
677
|
};
|
@@ -563,12 +679,21 @@
|
|
563
679
|
if (this.jqXHR) {
|
564
680
|
return this.jqXHR.abort();
|
565
681
|
}
|
566
|
-
|
682
|
+
this.errorThrown = 'abort';
|
683
|
+
that._trigger('fail', null, this);
|
684
|
+
return that._getXHRPromise(false);
|
567
685
|
};
|
568
686
|
data.state = function () {
|
569
687
|
if (this.jqXHR) {
|
570
688
|
return that._getDeferredState(this.jqXHR);
|
571
689
|
}
|
690
|
+
if (this._processQueue) {
|
691
|
+
return that._getDeferredState(this._processQueue);
|
692
|
+
}
|
693
|
+
};
|
694
|
+
data.processing = function () {
|
695
|
+
return !this.jqXHR && this._processQueue && that
|
696
|
+
._getDeferredState(this._processQueue) === 'pending';
|
572
697
|
};
|
573
698
|
data.progress = function () {
|
574
699
|
return this._progress;
|
@@ -594,12 +719,13 @@
|
|
594
719
|
// should be uploaded in chunks, but does not invoke any
|
595
720
|
// upload requests:
|
596
721
|
_chunkedUpload: function (options, testOnly) {
|
722
|
+
options.uploadedBytes = options.uploadedBytes || 0;
|
597
723
|
var that = this,
|
598
724
|
file = options.files[0],
|
599
725
|
fs = file.size,
|
600
|
-
ub = options.uploadedBytes
|
726
|
+
ub = options.uploadedBytes,
|
601
727
|
mcs = options.maxChunkSize || fs,
|
602
|
-
slice =
|
728
|
+
slice = this._blobSlice,
|
603
729
|
dfd = $.Deferred(),
|
604
730
|
promise = dfd.promise(),
|
605
731
|
jqXHR,
|
@@ -612,7 +738,7 @@
|
|
612
738
|
return true;
|
613
739
|
}
|
614
740
|
if (ub >= fs) {
|
615
|
-
file.error = '
|
741
|
+
file.error = options.i18n('uploadedBytes');
|
616
742
|
return this._getXHRPromise(
|
617
743
|
false,
|
618
744
|
options.context,
|
@@ -648,7 +774,7 @@
|
|
648
774
|
// Create a progress event if no final progress event
|
649
775
|
// with loaded equaling total has been triggered
|
650
776
|
// for this chunk:
|
651
|
-
if (o._progress.loaded
|
777
|
+
if (currentLoaded + o.chunkSize - o._progress.loaded) {
|
652
778
|
that._onProgress($.Event('progress', {
|
653
779
|
lengthComputable: true,
|
654
780
|
loaded: ub - o.uploadedBytes,
|
@@ -771,7 +897,11 @@
|
|
771
897
|
// Set timer for bitrate progress calculation:
|
772
898
|
options._bitrateTimer = new that._BitrateTimer();
|
773
899
|
jqXHR = jqXHR || (
|
774
|
-
((aborted || that._trigger(
|
900
|
+
((aborted || that._trigger(
|
901
|
+
'send',
|
902
|
+
$.Event('send', {delegatedEvent: e}),
|
903
|
+
options
|
904
|
+
) === false) &&
|
775
905
|
that._getXHRPromise(false, options.context, aborted)) ||
|
776
906
|
that._chunkedUpload(options) || $.ajax(options)
|
777
907
|
).done(function (result, textStatus, jqXHR) {
|
@@ -815,9 +945,10 @@
|
|
815
945
|
if (this.options.limitConcurrentUploads > 1) {
|
816
946
|
slot = $.Deferred();
|
817
947
|
this._slots.push(slot);
|
818
|
-
pipe = slot.
|
948
|
+
pipe = slot.then(send);
|
819
949
|
} else {
|
820
|
-
|
950
|
+
this._sequence = this._sequence.then(send, send);
|
951
|
+
pipe = this._sequence;
|
821
952
|
}
|
822
953
|
// Return the piped Promise object, enhanced with an abort method,
|
823
954
|
// which is delegated to the jqXHR object of the current upload,
|
@@ -841,50 +972,93 @@
|
|
841
972
|
var that = this,
|
842
973
|
result = true,
|
843
974
|
options = $.extend({}, this.options, data),
|
975
|
+
files = data.files,
|
976
|
+
filesLength = files.length,
|
844
977
|
limit = options.limitMultiFileUploads,
|
978
|
+
limitSize = options.limitMultiFileUploadSize,
|
979
|
+
overhead = options.limitMultiFileUploadSizeOverhead,
|
980
|
+
batchSize = 0,
|
845
981
|
paramName = this._getParamName(options),
|
846
982
|
paramNameSet,
|
847
983
|
paramNameSlice,
|
848
984
|
fileSet,
|
849
|
-
i
|
850
|
-
|
985
|
+
i,
|
986
|
+
j = 0;
|
987
|
+
if (!filesLength) {
|
988
|
+
return false;
|
989
|
+
}
|
990
|
+
if (limitSize && files[0].size === undefined) {
|
991
|
+
limitSize = undefined;
|
992
|
+
}
|
993
|
+
if (!(options.singleFileUploads || limit || limitSize) ||
|
851
994
|
!this._isXHRUpload(options)) {
|
852
|
-
fileSet = [
|
995
|
+
fileSet = [files];
|
853
996
|
paramNameSet = [paramName];
|
854
|
-
} else if (!options.singleFileUploads && limit) {
|
997
|
+
} else if (!(options.singleFileUploads || limitSize) && limit) {
|
855
998
|
fileSet = [];
|
856
999
|
paramNameSet = [];
|
857
|
-
for (i = 0; i <
|
858
|
-
fileSet.push(
|
1000
|
+
for (i = 0; i < filesLength; i += limit) {
|
1001
|
+
fileSet.push(files.slice(i, i + limit));
|
859
1002
|
paramNameSlice = paramName.slice(i, i + limit);
|
860
1003
|
if (!paramNameSlice.length) {
|
861
1004
|
paramNameSlice = paramName;
|
862
1005
|
}
|
863
1006
|
paramNameSet.push(paramNameSlice);
|
864
1007
|
}
|
1008
|
+
} else if (!options.singleFileUploads && limitSize) {
|
1009
|
+
fileSet = [];
|
1010
|
+
paramNameSet = [];
|
1011
|
+
for (i = 0; i < filesLength; i = i + 1) {
|
1012
|
+
batchSize += files[i].size + overhead;
|
1013
|
+
if (i + 1 === filesLength ||
|
1014
|
+
((batchSize + files[i + 1].size + overhead) > limitSize) ||
|
1015
|
+
(limit && i + 1 - j >= limit)) {
|
1016
|
+
fileSet.push(files.slice(j, i + 1));
|
1017
|
+
paramNameSlice = paramName.slice(j, i + 1);
|
1018
|
+
if (!paramNameSlice.length) {
|
1019
|
+
paramNameSlice = paramName;
|
1020
|
+
}
|
1021
|
+
paramNameSet.push(paramNameSlice);
|
1022
|
+
j = i + 1;
|
1023
|
+
batchSize = 0;
|
1024
|
+
}
|
1025
|
+
}
|
865
1026
|
} else {
|
866
1027
|
paramNameSet = paramName;
|
867
1028
|
}
|
868
|
-
data.originalFiles =
|
869
|
-
$.each(fileSet ||
|
1029
|
+
data.originalFiles = files;
|
1030
|
+
$.each(fileSet || files, function (index, element) {
|
870
1031
|
var newData = $.extend({}, data);
|
871
1032
|
newData.files = fileSet ? element : [element];
|
872
1033
|
newData.paramName = paramNameSet[index];
|
873
1034
|
that._initResponseObject(newData);
|
874
1035
|
that._initProgressObject(newData);
|
875
1036
|
that._addConvenienceMethods(e, newData);
|
876
|
-
result = that._trigger(
|
1037
|
+
result = that._trigger(
|
1038
|
+
'add',
|
1039
|
+
$.Event('add', {delegatedEvent: e}),
|
1040
|
+
newData
|
1041
|
+
);
|
877
1042
|
return result;
|
878
1043
|
});
|
879
1044
|
return result;
|
880
1045
|
},
|
881
1046
|
|
882
|
-
_replaceFileInput: function (
|
883
|
-
var
|
1047
|
+
_replaceFileInput: function (data) {
|
1048
|
+
var input = data.fileInput,
|
1049
|
+
inputClone = input.clone(true),
|
1050
|
+
restoreFocus = input.is(document.activeElement);
|
1051
|
+
// Add a reference for the new cloned file input to the data argument:
|
1052
|
+
data.fileInputClone = inputClone;
|
884
1053
|
$('<form></form>').append(inputClone)[0].reset();
|
885
1054
|
// Detaching allows to insert the fileInput on another form
|
886
1055
|
// without loosing the file input value:
|
887
1056
|
input.after(inputClone).detach();
|
1057
|
+
// If the fileInput had focus before it was detached,
|
1058
|
+
// restore focus to the inputClone.
|
1059
|
+
if (restoreFocus) {
|
1060
|
+
inputClone.focus();
|
1061
|
+
}
|
888
1062
|
// Avoid memory leaks with the detached file input:
|
889
1063
|
$.cleanData(input.unbind('remove'));
|
890
1064
|
// Replace the original file input element in the fileInput
|
@@ -916,7 +1090,25 @@
|
|
916
1090
|
// to be returned together in one set:
|
917
1091
|
dfd.resolve([e]);
|
918
1092
|
},
|
919
|
-
|
1093
|
+
successHandler = function (entries) {
|
1094
|
+
that._handleFileTreeEntries(
|
1095
|
+
entries,
|
1096
|
+
path + entry.name + '/'
|
1097
|
+
).done(function (files) {
|
1098
|
+
dfd.resolve(files);
|
1099
|
+
}).fail(errorHandler);
|
1100
|
+
},
|
1101
|
+
readEntries = function () {
|
1102
|
+
dirReader.readEntries(function (results) {
|
1103
|
+
if (!results.length) {
|
1104
|
+
successHandler(entries);
|
1105
|
+
} else {
|
1106
|
+
entries = entries.concat(results);
|
1107
|
+
readEntries();
|
1108
|
+
}
|
1109
|
+
}, errorHandler);
|
1110
|
+
},
|
1111
|
+
dirReader, entries = [];
|
920
1112
|
path = path || '';
|
921
1113
|
if (entry.isFile) {
|
922
1114
|
if (entry._file) {
|
@@ -931,14 +1123,7 @@
|
|
931
1123
|
}
|
932
1124
|
} else if (entry.isDirectory) {
|
933
1125
|
dirReader = entry.createReader();
|
934
|
-
|
935
|
-
that._handleFileTreeEntries(
|
936
|
-
entries,
|
937
|
-
path + entry.name + '/'
|
938
|
-
).done(function (files) {
|
939
|
-
dfd.resolve(files);
|
940
|
-
}).fail(errorHandler);
|
941
|
-
}, errorHandler);
|
1126
|
+
readEntries();
|
942
1127
|
} else {
|
943
1128
|
// Return an empy list for file system items
|
944
1129
|
// other than files or directories:
|
@@ -954,7 +1139,7 @@
|
|
954
1139
|
$.map(entries, function (entry) {
|
955
1140
|
return that._handleFileTreeEntry(entry, path);
|
956
1141
|
})
|
957
|
-
).
|
1142
|
+
).then(function () {
|
958
1143
|
return Array.prototype.concat.apply(
|
959
1144
|
[],
|
960
1145
|
arguments
|
@@ -1023,7 +1208,7 @@
|
|
1023
1208
|
return $.when.apply(
|
1024
1209
|
$,
|
1025
1210
|
$.map(fileInput, this._getSingleFileInputFiles)
|
1026
|
-
).
|
1211
|
+
).then(function () {
|
1027
1212
|
return Array.prototype.concat.apply(
|
1028
1213
|
[],
|
1029
1214
|
arguments
|
@@ -1040,9 +1225,13 @@
|
|
1040
1225
|
this._getFileInputFiles(data.fileInput).always(function (files) {
|
1041
1226
|
data.files = files;
|
1042
1227
|
if (that.options.replaceFileInput) {
|
1043
|
-
that._replaceFileInput(data
|
1228
|
+
that._replaceFileInput(data);
|
1044
1229
|
}
|
1045
|
-
if (that._trigger(
|
1230
|
+
if (that._trigger(
|
1231
|
+
'change',
|
1232
|
+
$.Event('change', {delegatedEvent: e}),
|
1233
|
+
data
|
1234
|
+
) !== false) {
|
1046
1235
|
that._onAdd(e, data);
|
1047
1236
|
}
|
1048
1237
|
});
|
@@ -1059,71 +1248,76 @@
|
|
1059
1248
|
data.files.push(file);
|
1060
1249
|
}
|
1061
1250
|
});
|
1062
|
-
if (this._trigger(
|
1063
|
-
|
1064
|
-
|
1251
|
+
if (this._trigger(
|
1252
|
+
'paste',
|
1253
|
+
$.Event('paste', {delegatedEvent: e}),
|
1254
|
+
data
|
1255
|
+
) !== false) {
|
1256
|
+
this._onAdd(e, data);
|
1065
1257
|
}
|
1066
1258
|
}
|
1067
1259
|
},
|
1068
1260
|
|
1069
1261
|
_onDrop: function (e) {
|
1262
|
+
e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
|
1070
1263
|
var that = this,
|
1071
|
-
dataTransfer = e.dataTransfer
|
1072
|
-
e.originalEvent.dataTransfer,
|
1264
|
+
dataTransfer = e.dataTransfer,
|
1073
1265
|
data = {};
|
1074
1266
|
if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
|
1075
1267
|
e.preventDefault();
|
1076
1268
|
this._getDroppedFiles(dataTransfer).always(function (files) {
|
1077
1269
|
data.files = files;
|
1078
|
-
if (that._trigger(
|
1270
|
+
if (that._trigger(
|
1271
|
+
'drop',
|
1272
|
+
$.Event('drop', {delegatedEvent: e}),
|
1273
|
+
data
|
1274
|
+
) !== false) {
|
1079
1275
|
that._onAdd(e, data);
|
1080
1276
|
}
|
1081
1277
|
});
|
1082
1278
|
}
|
1083
1279
|
},
|
1084
1280
|
|
1085
|
-
_onDragOver:
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
return false;
|
1091
|
-
}
|
1092
|
-
if ($.inArray('Files', dataTransfer.types) !== -1) {
|
1093
|
-
dataTransfer.dropEffect = 'copy';
|
1094
|
-
e.preventDefault();
|
1095
|
-
}
|
1096
|
-
}
|
1097
|
-
},
|
1281
|
+
_onDragOver: getDragHandler('dragover'),
|
1282
|
+
|
1283
|
+
_onDragEnter: getDragHandler('dragenter'),
|
1284
|
+
|
1285
|
+
_onDragLeave: getDragHandler('dragleave'),
|
1098
1286
|
|
1099
1287
|
_initEventHandlers: function () {
|
1100
1288
|
if (this._isXHRUpload(this.options)) {
|
1101
1289
|
this._on(this.options.dropZone, {
|
1102
1290
|
dragover: this._onDragOver,
|
1103
|
-
drop: this._onDrop
|
1291
|
+
drop: this._onDrop,
|
1292
|
+
// event.preventDefault() on dragenter is required for IE10+:
|
1293
|
+
dragenter: this._onDragEnter,
|
1294
|
+
// dragleave is not required, but added for completeness:
|
1295
|
+
dragleave: this._onDragLeave
|
1104
1296
|
});
|
1105
1297
|
this._on(this.options.pasteZone, {
|
1106
1298
|
paste: this._onPaste
|
1107
1299
|
});
|
1108
1300
|
}
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1301
|
+
if ($.support.fileInput) {
|
1302
|
+
this._on(this.options.fileInput, {
|
1303
|
+
change: this._onChange
|
1304
|
+
});
|
1305
|
+
}
|
1112
1306
|
},
|
1113
1307
|
|
1114
1308
|
_destroyEventHandlers: function () {
|
1115
|
-
this._off(this.options.dropZone, 'dragover drop');
|
1309
|
+
this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
|
1116
1310
|
this._off(this.options.pasteZone, 'paste');
|
1117
1311
|
this._off(this.options.fileInput, 'change');
|
1118
1312
|
},
|
1119
1313
|
|
1120
1314
|
_setOption: function (key, value) {
|
1121
|
-
var
|
1122
|
-
if (
|
1315
|
+
var reinit = $.inArray(key, this._specialOptions) !== -1;
|
1316
|
+
if (reinit) {
|
1123
1317
|
this._destroyEventHandlers();
|
1124
1318
|
}
|
1125
1319
|
this._super(key, value);
|
1126
|
-
if (
|
1320
|
+
if (reinit) {
|
1127
1321
|
this._initSpecialOptions();
|
1128
1322
|
this._initEventHandlers();
|
1129
1323
|
}
|
@@ -1145,10 +1339,45 @@
|
|
1145
1339
|
}
|
1146
1340
|
},
|
1147
1341
|
|
1148
|
-
|
1149
|
-
var
|
1342
|
+
_getRegExp: function (str) {
|
1343
|
+
var parts = str.split('/'),
|
1344
|
+
modifiers = parts.pop();
|
1345
|
+
parts.shift();
|
1346
|
+
return new RegExp(parts.join('/'), modifiers);
|
1347
|
+
},
|
1348
|
+
|
1349
|
+
_isRegExpOption: function (key, value) {
|
1350
|
+
return key !== 'url' && $.type(value) === 'string' &&
|
1351
|
+
/^\/.*\/[igm]{0,3}$/.test(value);
|
1352
|
+
},
|
1353
|
+
|
1354
|
+
_initDataAttributes: function () {
|
1355
|
+
var that = this,
|
1356
|
+
options = this.options,
|
1357
|
+
data = this.element.data();
|
1150
1358
|
// Initialize options set via HTML5 data-attributes:
|
1151
|
-
$.
|
1359
|
+
$.each(
|
1360
|
+
this.element[0].attributes,
|
1361
|
+
function (index, attr) {
|
1362
|
+
var key = attr.name.toLowerCase(),
|
1363
|
+
value;
|
1364
|
+
if (/^data-/.test(key)) {
|
1365
|
+
// Convert hyphen-ated key to camelCase:
|
1366
|
+
key = key.slice(5).replace(/-[a-z]/g, function (str) {
|
1367
|
+
return str.charAt(1).toUpperCase();
|
1368
|
+
});
|
1369
|
+
value = data[key];
|
1370
|
+
if (that._isRegExpOption(key, value)) {
|
1371
|
+
value = that._getRegExp(value);
|
1372
|
+
}
|
1373
|
+
options[key] = value;
|
1374
|
+
}
|
1375
|
+
}
|
1376
|
+
);
|
1377
|
+
},
|
1378
|
+
|
1379
|
+
_create: function () {
|
1380
|
+
this._initDataAttributes();
|
1152
1381
|
this._initSpecialOptions();
|
1153
1382
|
this._slots = [];
|
1154
1383
|
this._sequence = this._getXHRPromise(true);
|
@@ -1217,8 +1446,13 @@
|
|
1217
1446
|
if (aborted) {
|
1218
1447
|
return;
|
1219
1448
|
}
|
1449
|
+
if (!files.length) {
|
1450
|
+
dfd.reject();
|
1451
|
+
return;
|
1452
|
+
}
|
1220
1453
|
data.files = files;
|
1221
|
-
jqXHR = that._onSend(null, data)
|
1454
|
+
jqXHR = that._onSend(null, data);
|
1455
|
+
jqXHR.then(
|
1222
1456
|
function (result, textStatus, jqXHR) {
|
1223
1457
|
dfd.resolve(result, textStatus, jqXHR);
|
1224
1458
|
},
|