glebtv-rails-uploader 0.1.3 → 0.2.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 +7 -0
- data/README.md +17 -10
- data/{vendor → app}/assets/images/uploader/but_del_tag2.png +0 -0
- data/{vendor → app}/assets/images/uploader/ico_attach.png +0 -0
- data/{vendor → app}/assets/images/uploader/preloader.gif +0 -0
- data/{vendor → app}/assets/images/uploader/progressBarFillBg.png +0 -0
- data/app/assets/javascripts/uploader/application.js +2 -2
- data/app/assets/javascripts/uploader/canvas-to-blob.js +95 -0
- data/app/assets/javascripts/uploader/jquery.fileupload-angular.js +348 -0
- data/app/assets/javascripts/uploader/jquery.fileupload-process.js +158 -0
- data/app/assets/javascripts/uploader/jquery.fileupload-resize.js +212 -0
- data/{vendor → app}/assets/javascripts/uploader/jquery.fileupload-ui.js +265 -269
- data/app/assets/javascripts/uploader/jquery.fileupload-validate.js +116 -0
- data/{vendor → app}/assets/javascripts/uploader/jquery.fileupload.js +655 -258
- data/{vendor → app}/assets/javascripts/uploader/jquery.iframe-transport.js +29 -9
- data/app/assets/javascripts/uploader/jquery.ui.widget.js +530 -0
- data/app/assets/javascripts/uploader/load-image.js +381 -0
- data/{vendor → app}/assets/javascripts/uploader/locales/en.js +0 -0
- data/{vendor → app}/assets/javascripts/uploader/locales/ru.js +0 -0
- data/{vendor → app}/assets/javascripts/uploader/locales/uk.js +0 -0
- data/app/assets/javascripts/uploader/rails_admin.js +26 -24
- data/app/assets/javascripts/uploader/tmpl.js +86 -0
- data/{vendor → app}/assets/stylesheets/uploader/default.css +0 -0
- data/{vendor → app}/assets/stylesheets/uploader/jquery.fileupload-ui.css +0 -0
- data/app/controllers/uploader/attachments_controller.rb +13 -10
- data/app/views/rails_admin/main/_form_rails_uploader.haml +9 -9
- data/app/views/uploader/default/_container.html.erb +1 -2
- data/app/views/uploader/default/_download.html.erb +1 -1
- data/lib/uploader/asset.rb +2 -2
- data/lib/uploader/engine.rb +4 -4
- data/lib/uploader/fileuploads.rb +18 -18
- data/lib/uploader/helpers/field_tag.rb +16 -17
- data/lib/uploader/helpers/form_builder.rb +1 -1
- data/lib/uploader/helpers/form_tag_helper.rb +1 -1
- data/lib/uploader/hooks/formtastic.rb +1 -2
- data/lib/uploader/rails_admin/field.rb +27 -27
- data/lib/uploader/version.rb +1 -1
- metadata +115 -131
- data/vendor/assets/javascripts/uploader/jquery.fileupload-ip.js +0 -160
- data/vendor/assets/javascripts/uploader/jquery.ui.widget.js +0 -282
- data/vendor/assets/javascripts/uploader/load-image.min.js +0 -1
- data/vendor/assets/javascripts/uploader/tmpl.min.js +0 -1
@@ -0,0 +1,116 @@
|
|
1
|
+
/*
|
2
|
+
* jQuery File Upload Validation Plugin 1.0.2
|
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
|
+
/*jslint nomen: true, unparam: true, regexp: true */
|
13
|
+
/*global define, window */
|
14
|
+
|
15
|
+
(function (factory) {
|
16
|
+
'use strict';
|
17
|
+
if (typeof define === 'function' && define.amd) {
|
18
|
+
// Register as an anonymous AMD module:
|
19
|
+
define([
|
20
|
+
'jquery',
|
21
|
+
'./jquery.fileupload-process'
|
22
|
+
], factory);
|
23
|
+
} else {
|
24
|
+
// Browser globals:
|
25
|
+
factory(
|
26
|
+
window.jQuery
|
27
|
+
);
|
28
|
+
}
|
29
|
+
}(function ($) {
|
30
|
+
'use strict';
|
31
|
+
|
32
|
+
// Append to the default processQueue:
|
33
|
+
$.blueimp.fileupload.prototype.options.processQueue.push(
|
34
|
+
{
|
35
|
+
action: 'validate',
|
36
|
+
// Always trigger this action,
|
37
|
+
// even if the previous action was rejected:
|
38
|
+
always: true,
|
39
|
+
// Options taken from the global options map:
|
40
|
+
acceptFileTypes: '@acceptFileTypes',
|
41
|
+
maxFileSize: '@maxFileSize',
|
42
|
+
minFileSize: '@minFileSize',
|
43
|
+
maxNumberOfFiles: '@maxNumberOfFiles',
|
44
|
+
disabled: '@disableValidation'
|
45
|
+
}
|
46
|
+
);
|
47
|
+
|
48
|
+
// The File Upload Validation plugin extends the fileupload widget
|
49
|
+
// with file validation functionality:
|
50
|
+
$.widget('blueimp.fileupload', $.blueimp.fileupload, {
|
51
|
+
|
52
|
+
options: {
|
53
|
+
/*
|
54
|
+
// The regular expression for allowed file types, matches
|
55
|
+
// against either file type or file name:
|
56
|
+
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
|
57
|
+
// The maximum allowed file size in bytes:
|
58
|
+
maxFileSize: 10000000, // 10 MB
|
59
|
+
// The minimum allowed file size in bytes:
|
60
|
+
minFileSize: undefined, // No minimal file size
|
61
|
+
// The limit of files to be uploaded:
|
62
|
+
maxNumberOfFiles: 10,
|
63
|
+
*/
|
64
|
+
|
65
|
+
// Function returning the current number of files,
|
66
|
+
// has to be overriden for maxNumberOfFiles validation:
|
67
|
+
getNumberOfFiles: $.noop,
|
68
|
+
|
69
|
+
// Error and info messages:
|
70
|
+
messages: {
|
71
|
+
maxNumberOfFiles: 'Maximum number of files exceeded',
|
72
|
+
acceptFileTypes: 'File type not allowed',
|
73
|
+
maxFileSize: 'File is too large',
|
74
|
+
minFileSize: 'File is too small'
|
75
|
+
}
|
76
|
+
},
|
77
|
+
|
78
|
+
processActions: {
|
79
|
+
|
80
|
+
validate: function (data, options) {
|
81
|
+
if (options.disabled) {
|
82
|
+
return data;
|
83
|
+
}
|
84
|
+
var dfd = $.Deferred(),
|
85
|
+
settings = this.options,
|
86
|
+
file = data.files[data.index],
|
87
|
+
numberOfFiles = settings.getNumberOfFiles();
|
88
|
+
if (numberOfFiles && $.type(options.maxNumberOfFiles) === 'number' &&
|
89
|
+
numberOfFiles + data.files.length > options.maxNumberOfFiles) {
|
90
|
+
file.error = settings.i18n('maxNumberOfFiles');
|
91
|
+
} else if (options.acceptFileTypes &&
|
92
|
+
!(options.acceptFileTypes.test(file.type) ||
|
93
|
+
options.acceptFileTypes.test(file.name))) {
|
94
|
+
file.error = settings.i18n('acceptFileTypes');
|
95
|
+
} else if (options.maxFileSize && file.size > options.maxFileSize) {
|
96
|
+
file.error = settings.i18n('maxFileSize');
|
97
|
+
} else if ($.type(file.size) === 'number' &&
|
98
|
+
file.size < options.minFileSize) {
|
99
|
+
file.error = settings.i18n('minFileSize');
|
100
|
+
} else {
|
101
|
+
delete file.error;
|
102
|
+
}
|
103
|
+
if (file.error || data.files.error) {
|
104
|
+
data.files.error = true;
|
105
|
+
dfd.rejectWith(this, [data]);
|
106
|
+
} else {
|
107
|
+
dfd.resolveWith(this, [data]);
|
108
|
+
}
|
109
|
+
return dfd.promise();
|
110
|
+
}
|
111
|
+
|
112
|
+
}
|
113
|
+
|
114
|
+
});
|
115
|
+
|
116
|
+
}));
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
* jQuery File Upload Plugin 5.
|
2
|
+
* jQuery File Upload Plugin 5.31.1
|
3
3
|
* https://github.com/blueimp/jQuery-File-Upload
|
4
4
|
*
|
5
5
|
* Copyright 2010, Sebastian Tschan
|
@@ -10,7 +10,7 @@
|
|
10
10
|
*/
|
11
11
|
|
12
12
|
/*jslint nomen: true, unparam: true, regexp: true */
|
13
|
-
/*global define, window, document, Blob, FormData, location */
|
13
|
+
/*global define, window, document, File, Blob, FormData, location */
|
14
14
|
|
15
15
|
(function (factory) {
|
16
16
|
'use strict';
|
@@ -44,17 +44,16 @@
|
|
44
44
|
$.widget('blueimp.fileupload', {
|
45
45
|
|
46
46
|
options: {
|
47
|
-
// The
|
48
|
-
//
|
49
|
-
// If not set, the name of the widget ("fileupload") is used.
|
50
|
-
namespace: undefined,
|
51
|
-
// The drop target collection, by the default the complete document.
|
52
|
-
// Set to null or an empty collection to disable drag & drop support:
|
47
|
+
// The drop target element(s), by the default the complete document.
|
48
|
+
// Set to null to disable drag & drop support:
|
53
49
|
dropZone: $(document),
|
54
|
-
// The
|
50
|
+
// The paste target element(s), by the default the complete document.
|
51
|
+
// Set to null to disable paste support:
|
52
|
+
pasteZone: $(document),
|
53
|
+
// The file input field(s), that are listened to for change events.
|
55
54
|
// If undefined, it is set to the file input fields inside
|
56
55
|
// of the widget element on plugin initialization.
|
57
|
-
// Set to null
|
56
|
+
// Set to null to disable the change listener.
|
58
57
|
fileInput: undefined,
|
59
58
|
// By default, the file input field is replaced with a clone after
|
60
59
|
// each input field change event. This is required for iframe transport
|
@@ -109,6 +108,29 @@
|
|
109
108
|
// global progress calculation. Set the following option to false to
|
110
109
|
// prevent recalculating the global progress data:
|
111
110
|
recalculateProgress: true,
|
111
|
+
// Interval in milliseconds to calculate and trigger progress events:
|
112
|
+
progressInterval: 100,
|
113
|
+
// Interval in milliseconds to calculate progress bitrate:
|
114
|
+
bitrateInterval: 500,
|
115
|
+
// By default, uploads are started automatically when adding files:
|
116
|
+
autoUpload: true,
|
117
|
+
|
118
|
+
// Error and info messages:
|
119
|
+
messages: {
|
120
|
+
uploadedBytes: 'Uploaded bytes exceed file size'
|
121
|
+
},
|
122
|
+
|
123
|
+
// Translation function, gets the message key to be translated
|
124
|
+
// and an object with context specific data as arguments:
|
125
|
+
i18n: function (message, context) {
|
126
|
+
message = this.messages[message] || message.toString();
|
127
|
+
if (context) {
|
128
|
+
$.each(context, function (key, value) {
|
129
|
+
message = message.replace('{' + key + '}', value);
|
130
|
+
});
|
131
|
+
}
|
132
|
+
return message;
|
133
|
+
},
|
112
134
|
|
113
135
|
// Additional form data to be sent along with the file uploads can be set
|
114
136
|
// using this option, which accepts an array of objects with name and
|
@@ -133,37 +155,67 @@
|
|
133
155
|
// handlers using jQuery's Deferred callbacks:
|
134
156
|
// data.submit().done(func).fail(func).always(func);
|
135
157
|
add: function (e, data) {
|
136
|
-
data.
|
158
|
+
if (data.autoUpload || (data.autoUpload !== false &&
|
159
|
+
$(this).fileupload('option', 'autoUpload'))) {
|
160
|
+
data.process().done(function () {
|
161
|
+
data.submit();
|
162
|
+
});
|
163
|
+
}
|
137
164
|
},
|
138
165
|
|
139
166
|
// Other callbacks:
|
167
|
+
|
140
168
|
// Callback for the submit event of each file upload:
|
141
169
|
// submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
|
170
|
+
|
142
171
|
// Callback for the start of each file upload request:
|
143
172
|
// send: function (e, data) {}, // .bind('fileuploadsend', func);
|
173
|
+
|
144
174
|
// Callback for successful uploads:
|
145
175
|
// done: function (e, data) {}, // .bind('fileuploaddone', func);
|
176
|
+
|
146
177
|
// Callback for failed (abort or error) uploads:
|
147
178
|
// fail: function (e, data) {}, // .bind('fileuploadfail', func);
|
179
|
+
|
148
180
|
// Callback for completed (success, abort or error) requests:
|
149
181
|
// always: function (e, data) {}, // .bind('fileuploadalways', func);
|
182
|
+
|
150
183
|
// Callback for upload progress events:
|
151
184
|
// progress: function (e, data) {}, // .bind('fileuploadprogress', func);
|
185
|
+
|
152
186
|
// Callback for global upload progress events:
|
153
187
|
// progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
|
188
|
+
|
154
189
|
// Callback for uploads start, equivalent to the global ajaxStart event:
|
155
190
|
// start: function (e) {}, // .bind('fileuploadstart', func);
|
191
|
+
|
156
192
|
// Callback for uploads stop, equivalent to the global ajaxStop event:
|
157
193
|
// stop: function (e) {}, // .bind('fileuploadstop', func);
|
158
|
-
|
194
|
+
|
195
|
+
// Callback for change events of the fileInput(s):
|
159
196
|
// change: function (e, data) {}, // .bind('fileuploadchange', func);
|
160
|
-
|
197
|
+
|
198
|
+
// Callback for paste events to the pasteZone(s):
|
161
199
|
// paste: function (e, data) {}, // .bind('fileuploadpaste', func);
|
162
|
-
|
200
|
+
|
201
|
+
// Callback for drop events of the dropZone(s):
|
163
202
|
// drop: function (e, data) {}, // .bind('fileuploaddrop', func);
|
164
|
-
|
203
|
+
|
204
|
+
// Callback for dragover events of the dropZone(s):
|
165
205
|
// dragover: function (e) {}, // .bind('fileuploaddragover', func);
|
166
206
|
|
207
|
+
// Callback for the start of each chunk upload request:
|
208
|
+
// chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
|
209
|
+
|
210
|
+
// Callback for successful chunk uploads:
|
211
|
+
// chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
|
212
|
+
|
213
|
+
// Callback for failed (abort or error) chunk uploads:
|
214
|
+
// chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
|
215
|
+
|
216
|
+
// Callback for completed (success, abort or error) chunk upload requests:
|
217
|
+
// chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
|
218
|
+
|
167
219
|
// The plugin options are used as settings object for the ajax calls.
|
168
220
|
// The following are jQuery ajax settings required for the file uploads:
|
169
221
|
processData: false,
|
@@ -171,15 +223,31 @@
|
|
171
223
|
cache: false
|
172
224
|
},
|
173
225
|
|
174
|
-
// A list of options that require
|
175
|
-
|
176
|
-
|
177
|
-
'dropZone',
|
226
|
+
// A list of options that require reinitializing event listeners and/or
|
227
|
+
// special initialization code:
|
228
|
+
_specialOptions: [
|
178
229
|
'fileInput',
|
230
|
+
'dropZone',
|
231
|
+
'pasteZone',
|
179
232
|
'multipart',
|
180
233
|
'forceIframeTransport'
|
181
234
|
],
|
182
235
|
|
236
|
+
_BitrateTimer: function () {
|
237
|
+
this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
|
238
|
+
this.loaded = 0;
|
239
|
+
this.bitrate = 0;
|
240
|
+
this.getBitrate = function (now, loaded, interval) {
|
241
|
+
var timeDiff = now - this.timestamp;
|
242
|
+
if (!this.bitrate || !interval || timeDiff > interval) {
|
243
|
+
this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
|
244
|
+
this.loaded = loaded;
|
245
|
+
this.timestamp = now;
|
246
|
+
}
|
247
|
+
return this.bitrate;
|
248
|
+
};
|
249
|
+
},
|
250
|
+
|
183
251
|
_isXHRUpload: function (options) {
|
184
252
|
return !options.forceIframeTransport &&
|
185
253
|
((!options.multipart && $.support.xhrFileUpload) ||
|
@@ -190,9 +258,11 @@
|
|
190
258
|
var formData;
|
191
259
|
if (typeof options.formData === 'function') {
|
192
260
|
return options.formData(options.form);
|
193
|
-
}
|
261
|
+
}
|
262
|
+
if ($.isArray(options.formData)) {
|
194
263
|
return options.formData;
|
195
|
-
}
|
264
|
+
}
|
265
|
+
if ($.type(options.formData) === 'object') {
|
196
266
|
formData = [];
|
197
267
|
$.each(options.formData, function (name, value) {
|
198
268
|
formData.push({name: name, value: value});
|
@@ -210,28 +280,66 @@
|
|
210
280
|
return total;
|
211
281
|
},
|
212
282
|
|
283
|
+
_initProgressObject: function (obj) {
|
284
|
+
var progress = {
|
285
|
+
loaded: 0,
|
286
|
+
total: 0,
|
287
|
+
bitrate: 0
|
288
|
+
};
|
289
|
+
if (obj._progress) {
|
290
|
+
$.extend(obj._progress, progress);
|
291
|
+
} else {
|
292
|
+
obj._progress = progress;
|
293
|
+
}
|
294
|
+
},
|
295
|
+
|
296
|
+
_initResponseObject: function (obj) {
|
297
|
+
var prop;
|
298
|
+
if (obj._response) {
|
299
|
+
for (prop in obj._response) {
|
300
|
+
if (obj._response.hasOwnProperty(prop)) {
|
301
|
+
delete obj._response[prop];
|
302
|
+
}
|
303
|
+
}
|
304
|
+
} else {
|
305
|
+
obj._response = {};
|
306
|
+
}
|
307
|
+
},
|
308
|
+
|
213
309
|
_onProgress: function (e, data) {
|
214
310
|
if (e.lengthComputable) {
|
215
|
-
var
|
216
|
-
loaded
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
data.
|
223
|
-
|
311
|
+
var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
|
312
|
+
loaded;
|
313
|
+
if (data._time && data.progressInterval &&
|
314
|
+
(now - data._time < data.progressInterval) &&
|
315
|
+
e.loaded !== e.total) {
|
316
|
+
return;
|
317
|
+
}
|
318
|
+
data._time = now;
|
319
|
+
loaded = Math.floor(
|
320
|
+
e.loaded / e.total * (data.chunkSize || data._progress.total)
|
321
|
+
) + (data.uploadedBytes || 0);
|
322
|
+
// Add the difference from the previously loaded state
|
323
|
+
// to the global loaded counter:
|
324
|
+
this._progress.loaded += (loaded - data._progress.loaded);
|
325
|
+
this._progress.bitrate = this._bitrateTimer.getBitrate(
|
326
|
+
now,
|
327
|
+
this._progress.loaded,
|
328
|
+
data.bitrateInterval
|
329
|
+
);
|
330
|
+
data._progress.loaded = data.loaded = loaded;
|
331
|
+
data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
|
332
|
+
now,
|
333
|
+
loaded,
|
334
|
+
data.bitrateInterval
|
335
|
+
);
|
224
336
|
// Trigger a custom progress event with a total data property set
|
225
337
|
// to the file size(s) of the current upload and a loaded data
|
226
338
|
// property calculated accordingly:
|
227
339
|
this._trigger('progress', e, data);
|
228
340
|
// Trigger a global progress event for all current file uploads,
|
229
341
|
// including ajax calls queued for sequential file uploads:
|
230
|
-
this._trigger('progressall', e,
|
231
|
-
lengthComputable: true,
|
232
|
-
loaded: this._loaded,
|
233
|
-
total: this._total
|
234
|
-
});
|
342
|
+
this._trigger('progressall', e, this._progress);
|
235
343
|
}
|
236
344
|
},
|
237
345
|
|
@@ -255,35 +363,28 @@
|
|
255
363
|
}
|
256
364
|
},
|
257
365
|
|
366
|
+
_isInstanceOf: function (type, obj) {
|
367
|
+
// Cross-frame instanceof check
|
368
|
+
return Object.prototype.toString.call(obj) === '[object ' + type + ']';
|
369
|
+
},
|
370
|
+
|
258
371
|
_initXHRData: function (options) {
|
259
|
-
var
|
372
|
+
var that = this,
|
373
|
+
formData,
|
260
374
|
file = options.files[0],
|
261
375
|
// Ignore non-multipart setting if not supported:
|
262
376
|
multipart = options.multipart || !$.support.xhrFileUpload,
|
263
377
|
paramName = options.paramName[0];
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
// so we transmit this data as part of the HTTP headers.
|
268
|
-
// For cross domain requests, these headers must be allowed
|
269
|
-
// via Access-Control-Allow-Headers or removed using
|
270
|
-
// the beforeSend callback:
|
271
|
-
options.headers = $.extend(options.headers, {
|
272
|
-
'X-File-Name': file.name,
|
273
|
-
'X-File-Type': file.type,
|
274
|
-
'X-File-Size': file.size
|
275
|
-
});
|
276
|
-
if (!options.blob) {
|
277
|
-
// Non-chunked non-multipart upload:
|
278
|
-
options.contentType = file.type;
|
279
|
-
options.data = file;
|
280
|
-
} else if (!multipart) {
|
281
|
-
// Chunked non-multipart upload:
|
282
|
-
options.contentType = 'application/octet-stream';
|
283
|
-
options.data = options.blob;
|
284
|
-
}
|
378
|
+
options.headers = options.headers || {};
|
379
|
+
if (options.contentRange) {
|
380
|
+
options.headers['Content-Range'] = options.contentRange;
|
285
381
|
}
|
286
|
-
if (multipart
|
382
|
+
if (!multipart) {
|
383
|
+
options.headers['Content-Disposition'] = 'attachment; filename="' +
|
384
|
+
encodeURI(file.name) + '"';
|
385
|
+
options.contentType = file.type;
|
386
|
+
options.data = options.blob || file;
|
387
|
+
} else if ($.support.xhrFormDataFileUpload) {
|
287
388
|
if (options.postMessage) {
|
288
389
|
// window.postMessage does not allow sending FormData
|
289
390
|
// objects, so we just add the File/Blob objects to
|
@@ -304,7 +405,7 @@
|
|
304
405
|
});
|
305
406
|
}
|
306
407
|
} else {
|
307
|
-
if (options.formData
|
408
|
+
if (that._isInstanceOf('FormData', options.formData)) {
|
308
409
|
formData = options.formData;
|
309
410
|
} else {
|
310
411
|
formData = new FormData();
|
@@ -313,13 +414,15 @@
|
|
313
414
|
});
|
314
415
|
}
|
315
416
|
if (options.blob) {
|
417
|
+
options.headers['Content-Disposition'] = 'attachment; filename="' +
|
418
|
+
encodeURI(file.name) + '"';
|
316
419
|
formData.append(paramName, options.blob, file.name);
|
317
420
|
} else {
|
318
421
|
$.each(options.files, function (index, file) {
|
319
|
-
// File objects are also Blob instances.
|
320
422
|
// This check allows the tests to run with
|
321
423
|
// dummy objects:
|
322
|
-
if (file
|
424
|
+
if (that._isInstanceOf('File', file) ||
|
425
|
+
that._isInstanceOf('Blob', file)) {
|
323
426
|
formData.append(
|
324
427
|
options.paramName[index] || paramName,
|
325
428
|
file,
|
@@ -364,7 +467,7 @@
|
|
364
467
|
options.dataType = 'postmessage ' + (options.dataType || '');
|
365
468
|
}
|
366
469
|
} else {
|
367
|
-
this._initIframeSettings(options
|
470
|
+
this._initIframeSettings(options);
|
368
471
|
}
|
369
472
|
},
|
370
473
|
|
@@ -396,6 +499,11 @@
|
|
396
499
|
// associated form, if available:
|
397
500
|
if (!options.form || !options.form.length) {
|
398
501
|
options.form = $(options.fileInput.prop('form'));
|
502
|
+
// If the given file input doesn't have an associated form,
|
503
|
+
// use the default widget file input's form:
|
504
|
+
if (!options.form.length) {
|
505
|
+
options.form = $(this.options.fileInput.prop('form'));
|
506
|
+
}
|
399
507
|
}
|
400
508
|
options.paramName = this._getParamName(options);
|
401
509
|
if (!options.url) {
|
@@ -404,9 +512,13 @@
|
|
404
512
|
// The HTTP request method must be "POST" or "PUT":
|
405
513
|
options.type = (options.type || options.form.prop('method') || '')
|
406
514
|
.toUpperCase();
|
407
|
-
if (options.type !== 'POST' && options.type !== 'PUT'
|
515
|
+
if (options.type !== 'POST' && options.type !== 'PUT' &&
|
516
|
+
options.type !== 'PATCH') {
|
408
517
|
options.type = 'POST';
|
409
518
|
}
|
519
|
+
if (!options.formAcceptCharset) {
|
520
|
+
options.formAcceptCharset = options.form.attr('accept-charset');
|
521
|
+
}
|
410
522
|
},
|
411
523
|
|
412
524
|
_getAJAXSettings: function (data) {
|
@@ -416,6 +528,21 @@
|
|
416
528
|
return options;
|
417
529
|
},
|
418
530
|
|
531
|
+
// jQuery 1.6 doesn't provide .state(),
|
532
|
+
// while jQuery 1.8+ removed .isRejected() and .isResolved():
|
533
|
+
_getDeferredState: function (deferred) {
|
534
|
+
if (deferred.state) {
|
535
|
+
return deferred.state();
|
536
|
+
}
|
537
|
+
if (deferred.isResolved()) {
|
538
|
+
return 'resolved';
|
539
|
+
}
|
540
|
+
if (deferred.isRejected()) {
|
541
|
+
return 'rejected';
|
542
|
+
}
|
543
|
+
return 'pending';
|
544
|
+
},
|
545
|
+
|
419
546
|
// Maps jqXHR callbacks to the equivalent
|
420
547
|
// methods of the given Promise object:
|
421
548
|
_enhancePromise: function (promise) {
|
@@ -440,6 +567,60 @@
|
|
440
567
|
return this._enhancePromise(promise);
|
441
568
|
},
|
442
569
|
|
570
|
+
// Adds convenience methods to the data callback argument:
|
571
|
+
_addConvenienceMethods: function (e, data) {
|
572
|
+
var that = this,
|
573
|
+
getPromise = function (data) {
|
574
|
+
return $.Deferred().resolveWith(that, [data]).promise();
|
575
|
+
};
|
576
|
+
data.process = function (resolveFunc, rejectFunc) {
|
577
|
+
if (resolveFunc || rejectFunc) {
|
578
|
+
data._processQueue = this._processQueue =
|
579
|
+
(this._processQueue || getPromise(this))
|
580
|
+
.pipe(resolveFunc, rejectFunc);
|
581
|
+
}
|
582
|
+
return this._processQueue || getPromise(this);
|
583
|
+
};
|
584
|
+
data.submit = function () {
|
585
|
+
if (this.state() !== 'pending') {
|
586
|
+
data.jqXHR = this.jqXHR =
|
587
|
+
(that._trigger('submit', e, this) !== false) &&
|
588
|
+
that._onSend(e, this);
|
589
|
+
}
|
590
|
+
return this.jqXHR || that._getXHRPromise();
|
591
|
+
};
|
592
|
+
data.abort = function () {
|
593
|
+
if (this.jqXHR) {
|
594
|
+
return this.jqXHR.abort();
|
595
|
+
}
|
596
|
+
return that._getXHRPromise();
|
597
|
+
};
|
598
|
+
data.state = function () {
|
599
|
+
if (this.jqXHR) {
|
600
|
+
return that._getDeferredState(this.jqXHR);
|
601
|
+
}
|
602
|
+
if (this._processQueue) {
|
603
|
+
return that._getDeferredState(this._processQueue);
|
604
|
+
}
|
605
|
+
};
|
606
|
+
data.progress = function () {
|
607
|
+
return this._progress;
|
608
|
+
};
|
609
|
+
data.response = function () {
|
610
|
+
return this._response;
|
611
|
+
};
|
612
|
+
},
|
613
|
+
|
614
|
+
// Parses the Range header from the server response
|
615
|
+
// and returns the uploaded bytes:
|
616
|
+
_getUploadedBytes: function (jqXHR) {
|
617
|
+
var range = jqXHR.getResponseHeader('Range'),
|
618
|
+
parts = range && range.split('-'),
|
619
|
+
upperBytesPos = parts && parts.length > 1 &&
|
620
|
+
parseInt(parts[1], 10);
|
621
|
+
return upperBytesPos && upperBytesPos + 1;
|
622
|
+
},
|
623
|
+
|
443
624
|
// Uploads a file in multiple, sequential requests
|
444
625
|
// by splitting the file up in multiple blob chunks.
|
445
626
|
// If the second parameter is true, only tests if the file
|
@@ -451,13 +632,11 @@
|
|
451
632
|
fs = file.size,
|
452
633
|
ub = options.uploadedBytes = options.uploadedBytes || 0,
|
453
634
|
mcs = options.maxChunkSize || fs,
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
upload,
|
458
|
-
n,
|
635
|
+
slice = file.slice || file.webkitSlice || file.mozSlice,
|
636
|
+
dfd = $.Deferred(),
|
637
|
+
promise = dfd.promise(),
|
459
638
|
jqXHR,
|
460
|
-
|
639
|
+
upload;
|
461
640
|
if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
|
462
641
|
options.data) {
|
463
642
|
return false;
|
@@ -466,62 +645,84 @@
|
|
466
645
|
return true;
|
467
646
|
}
|
468
647
|
if (ub >= fs) {
|
469
|
-
file.error = 'uploadedBytes';
|
648
|
+
file.error = options.i18n('uploadedBytes');
|
470
649
|
return this._getXHRPromise(
|
471
650
|
false,
|
472
651
|
options.context,
|
473
652
|
[null, 'error', file.error]
|
474
653
|
);
|
475
654
|
}
|
476
|
-
//
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
);
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
}
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
655
|
+
// The chunk upload method:
|
656
|
+
upload = function () {
|
657
|
+
// Clone the options object for each chunk upload:
|
658
|
+
var o = $.extend({}, options),
|
659
|
+
currentLoaded = o._progress.loaded;
|
660
|
+
o.blob = slice.call(
|
661
|
+
file,
|
662
|
+
ub,
|
663
|
+
ub + mcs,
|
664
|
+
file.type
|
665
|
+
);
|
666
|
+
// Store the current chunk size, as the blob itself
|
667
|
+
// will be dereferenced after data processing:
|
668
|
+
o.chunkSize = o.blob.size;
|
669
|
+
// Expose the chunk bytes position range:
|
670
|
+
o.contentRange = 'bytes ' + ub + '-' +
|
671
|
+
(ub + o.chunkSize - 1) + '/' + fs;
|
672
|
+
// Process the upload data (the blob and potential form data):
|
673
|
+
that._initXHRData(o);
|
674
|
+
// Add progress listeners for this chunk upload:
|
675
|
+
that._initProgressListener(o);
|
676
|
+
jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
|
677
|
+
that._getXHRPromise(false, o.context))
|
678
|
+
.done(function (result, textStatus, jqXHR) {
|
679
|
+
ub = that._getUploadedBytes(jqXHR) ||
|
680
|
+
(ub + o.chunkSize);
|
681
|
+
// Create a progress event if no final progress event
|
682
|
+
// with loaded equaling total has been triggered
|
683
|
+
// for this chunk:
|
684
|
+
if (currentLoaded + o.chunkSize - o._progress.loaded) {
|
685
|
+
that._onProgress($.Event('progress', {
|
686
|
+
lengthComputable: true,
|
687
|
+
loaded: ub - o.uploadedBytes,
|
688
|
+
total: ub - o.uploadedBytes
|
689
|
+
}), o);
|
690
|
+
}
|
691
|
+
options.uploadedBytes = o.uploadedBytes = ub;
|
692
|
+
o.result = result;
|
693
|
+
o.textStatus = textStatus;
|
694
|
+
o.jqXHR = jqXHR;
|
695
|
+
that._trigger('chunkdone', null, o);
|
696
|
+
that._trigger('chunkalways', null, o);
|
697
|
+
if (ub < fs) {
|
698
|
+
// File upload not yet complete,
|
699
|
+
// continue with the next chunk:
|
700
|
+
upload();
|
701
|
+
} else {
|
702
|
+
dfd.resolveWith(
|
703
|
+
o.context,
|
704
|
+
[result, textStatus, jqXHR]
|
705
|
+
);
|
706
|
+
}
|
707
|
+
})
|
708
|
+
.fail(function (jqXHR, textStatus, errorThrown) {
|
709
|
+
o.jqXHR = jqXHR;
|
710
|
+
o.textStatus = textStatus;
|
711
|
+
o.errorThrown = errorThrown;
|
712
|
+
that._trigger('chunkfail', null, o);
|
713
|
+
that._trigger('chunkalways', null, o);
|
714
|
+
dfd.rejectWith(
|
715
|
+
o.context,
|
716
|
+
[jqXHR, textStatus, errorThrown]
|
717
|
+
);
|
718
|
+
});
|
516
719
|
};
|
517
|
-
|
518
|
-
|
519
|
-
// and jqXHR callbacks mapped to the equivalent Promise methods:
|
520
|
-
pipe = upload(n);
|
521
|
-
pipe.abort = function () {
|
720
|
+
this._enhancePromise(promise);
|
721
|
+
promise.abort = function () {
|
522
722
|
return jqXHR.abort();
|
523
723
|
};
|
524
|
-
|
724
|
+
upload();
|
725
|
+
return promise;
|
525
726
|
},
|
526
727
|
|
527
728
|
_beforeSend: function (e, data) {
|
@@ -530,99 +731,113 @@
|
|
530
731
|
// and no other uploads are currently running,
|
531
732
|
// equivalent to the global ajaxStart event:
|
532
733
|
this._trigger('start');
|
734
|
+
// Set timer for global bitrate progress calculation:
|
735
|
+
this._bitrateTimer = new this._BitrateTimer();
|
736
|
+
// Reset the global progress values:
|
737
|
+
this._progress.loaded = this._progress.total = 0;
|
738
|
+
this._progress.bitrate = 0;
|
533
739
|
}
|
740
|
+
// Make sure the container objects for the .response() and
|
741
|
+
// .progress() methods on the data object are available
|
742
|
+
// and reset to their initial state:
|
743
|
+
this._initResponseObject(data);
|
744
|
+
this._initProgressObject(data);
|
745
|
+
data._progress.loaded = data.loaded = data.uploadedBytes || 0;
|
746
|
+
data._progress.total = data.total = this._getTotal(data.files) || 1;
|
747
|
+
data._progress.bitrate = data.bitrate = 0;
|
534
748
|
this._active += 1;
|
535
749
|
// Initialize the global progress values:
|
536
|
-
this.
|
537
|
-
this.
|
750
|
+
this._progress.loaded += data.loaded;
|
751
|
+
this._progress.total += data.total;
|
538
752
|
},
|
539
753
|
|
540
754
|
_onDone: function (result, textStatus, jqXHR, options) {
|
541
|
-
|
542
|
-
|
755
|
+
var total = options._progress.total,
|
756
|
+
response = options._response;
|
757
|
+
if (options._progress.loaded < total) {
|
758
|
+
// Create a progress event if no final progress event
|
759
|
+
// with loaded equaling total has been triggered:
|
543
760
|
this._onProgress($.Event('progress', {
|
544
761
|
lengthComputable: true,
|
545
|
-
loaded:
|
546
|
-
total:
|
762
|
+
loaded: total,
|
763
|
+
total: total
|
547
764
|
}), options);
|
548
765
|
}
|
549
|
-
options.result = result;
|
550
|
-
options.textStatus = textStatus;
|
551
|
-
options.jqXHR = jqXHR;
|
766
|
+
response.result = options.result = result;
|
767
|
+
response.textStatus = options.textStatus = textStatus;
|
768
|
+
response.jqXHR = options.jqXHR = jqXHR;
|
552
769
|
this._trigger('done', null, options);
|
553
770
|
},
|
554
771
|
|
555
772
|
_onFail: function (jqXHR, textStatus, errorThrown, options) {
|
556
|
-
|
557
|
-
options.textStatus = textStatus;
|
558
|
-
options.errorThrown = errorThrown;
|
559
|
-
this._trigger('fail', null, options);
|
773
|
+
var response = options._response;
|
560
774
|
if (options.recalculateProgress) {
|
561
775
|
// Remove the failed (error or abort) file upload from
|
562
776
|
// the global progress calculation:
|
563
|
-
this.
|
564
|
-
this.
|
777
|
+
this._progress.loaded -= options._progress.loaded;
|
778
|
+
this._progress.total -= options._progress.total;
|
565
779
|
}
|
780
|
+
response.jqXHR = options.jqXHR = jqXHR;
|
781
|
+
response.textStatus = options.textStatus = textStatus;
|
782
|
+
response.errorThrown = options.errorThrown = errorThrown;
|
783
|
+
this._trigger('fail', null, options);
|
566
784
|
},
|
567
785
|
|
568
786
|
_onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
|
569
|
-
|
570
|
-
options
|
571
|
-
if (jqXHRorError && jqXHRorError.always) {
|
572
|
-
options.jqXHR = jqXHRorError;
|
573
|
-
options.result = jqXHRorResult;
|
574
|
-
} else {
|
575
|
-
options.jqXHR = jqXHRorResult;
|
576
|
-
options.errorThrown = jqXHRorError;
|
577
|
-
}
|
787
|
+
// jqXHRorResult, textStatus and jqXHRorError are added to the
|
788
|
+
// options object via done and fail callbacks
|
578
789
|
this._trigger('always', null, options);
|
579
|
-
if (this._active === 0) {
|
580
|
-
// The stop callback is triggered when all uploads have
|
581
|
-
// been completed, equivalent to the global ajaxStop event:
|
582
|
-
this._trigger('stop');
|
583
|
-
// Reset the global progress values:
|
584
|
-
this._loaded = this._total = 0;
|
585
|
-
}
|
586
790
|
},
|
587
791
|
|
588
792
|
_onSend: function (e, data) {
|
793
|
+
if (!data.submit) {
|
794
|
+
this._addConvenienceMethods(e, data);
|
795
|
+
}
|
589
796
|
var that = this,
|
590
797
|
jqXHR,
|
798
|
+
aborted,
|
591
799
|
slot,
|
592
800
|
pipe,
|
593
801
|
options = that._getAJAXSettings(data),
|
594
|
-
send = function (
|
802
|
+
send = function () {
|
595
803
|
that._sending += 1;
|
804
|
+
// Set timer for bitrate progress calculation:
|
805
|
+
options._bitrateTimer = new that._BitrateTimer();
|
596
806
|
jqXHR = jqXHR || (
|
597
|
-
(
|
598
|
-
that.
|
599
|
-
|
600
|
-
that._getXHRPromise(false, options.context, args)
|
807
|
+
((aborted || that._trigger('send', e, options) === false) &&
|
808
|
+
that._getXHRPromise(false, options.context, aborted)) ||
|
809
|
+
that._chunkedUpload(options) || $.ajax(options)
|
601
810
|
).done(function (result, textStatus, jqXHR) {
|
602
811
|
that._onDone(result, textStatus, jqXHR, options);
|
603
812
|
}).fail(function (jqXHR, textStatus, errorThrown) {
|
604
813
|
that._onFail(jqXHR, textStatus, errorThrown, options);
|
605
814
|
}).always(function (jqXHRorResult, textStatus, jqXHRorError) {
|
606
|
-
that._sending -= 1;
|
607
815
|
that._onAlways(
|
608
816
|
jqXHRorResult,
|
609
817
|
textStatus,
|
610
818
|
jqXHRorError,
|
611
819
|
options
|
612
820
|
);
|
821
|
+
that._sending -= 1;
|
822
|
+
that._active -= 1;
|
613
823
|
if (options.limitConcurrentUploads &&
|
614
824
|
options.limitConcurrentUploads > that._sending) {
|
615
825
|
// Start the next queued upload,
|
616
826
|
// that has not been aborted:
|
617
827
|
var nextSlot = that._slots.shift();
|
618
828
|
while (nextSlot) {
|
619
|
-
if (
|
829
|
+
if (that._getDeferredState(nextSlot) === 'pending') {
|
620
830
|
nextSlot.resolve();
|
621
831
|
break;
|
622
832
|
}
|
623
833
|
nextSlot = that._slots.shift();
|
624
834
|
}
|
625
835
|
}
|
836
|
+
if (that._active === 0) {
|
837
|
+
// The stop callback is triggered when all uploads have
|
838
|
+
// been completed, equivalent to the global ajaxStop event:
|
839
|
+
that._trigger('stop');
|
840
|
+
}
|
626
841
|
});
|
627
842
|
return jqXHR;
|
628
843
|
};
|
@@ -641,12 +856,12 @@
|
|
641
856
|
// which is delegated to the jqXHR object of the current upload,
|
642
857
|
// and jqXHR callbacks mapped to the equivalent Promise methods:
|
643
858
|
pipe.abort = function () {
|
644
|
-
|
859
|
+
aborted = [undefined, 'abort', 'abort'];
|
645
860
|
if (!jqXHR) {
|
646
861
|
if (slot) {
|
647
|
-
slot.rejectWith(
|
862
|
+
slot.rejectWith(options.context, aborted);
|
648
863
|
}
|
649
|
-
return send(
|
864
|
+
return send();
|
650
865
|
}
|
651
866
|
return jqXHR.abort();
|
652
867
|
};
|
@@ -688,25 +903,15 @@
|
|
688
903
|
var newData = $.extend({}, data);
|
689
904
|
newData.files = fileSet ? element : [element];
|
690
905
|
newData.paramName = paramNameSet[index];
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
};
|
697
|
-
return (result = that._trigger('add', e, newData));
|
906
|
+
that._initResponseObject(newData);
|
907
|
+
that._initProgressObject(newData);
|
908
|
+
that._addConvenienceMethods(e, newData);
|
909
|
+
result = that._trigger('add', e, newData);
|
910
|
+
return result;
|
698
911
|
});
|
699
912
|
return result;
|
700
913
|
},
|
701
914
|
|
702
|
-
// File Normalization for Gecko 1.9.1 (Firefox 3.5) support:
|
703
|
-
_normalizeFile: function (index, file) {
|
704
|
-
if (file.name === undefined && file.size === undefined) {
|
705
|
-
file.name = file.fileName;
|
706
|
-
file.size = file.fileSize;
|
707
|
-
}
|
708
|
-
},
|
709
|
-
|
710
915
|
_replaceFileInput: function (input) {
|
711
916
|
var inputClone = input.clone(true);
|
712
917
|
$('<form></form>').append(inputClone)[0].reset();
|
@@ -716,7 +921,7 @@
|
|
716
921
|
// Avoid memory leaks with the detached file input:
|
717
922
|
$.cleanData(input.unbind('remove'));
|
718
923
|
// Replace the original file input element in the fileInput
|
719
|
-
//
|
924
|
+
// elements set with the clone, which has been copied including
|
720
925
|
// event handlers:
|
721
926
|
this.options.fileInput = this.options.fileInput.map(function (i, el) {
|
722
927
|
if (el === input[0]) {
|
@@ -731,102 +936,227 @@
|
|
731
936
|
}
|
732
937
|
},
|
733
938
|
|
734
|
-
|
735
|
-
var that =
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
939
|
+
_handleFileTreeEntry: function (entry, path) {
|
940
|
+
var that = this,
|
941
|
+
dfd = $.Deferred(),
|
942
|
+
errorHandler = function (e) {
|
943
|
+
if (e && !e.entry) {
|
944
|
+
e.entry = entry;
|
945
|
+
}
|
946
|
+
// Since $.when returns immediately if one
|
947
|
+
// Deferred is rejected, we use resolve instead.
|
948
|
+
// This allows valid files and invalid items
|
949
|
+
// to be returned together in one set:
|
950
|
+
dfd.resolve([e]);
|
951
|
+
},
|
952
|
+
dirReader;
|
953
|
+
path = path || '';
|
954
|
+
if (entry.isFile) {
|
955
|
+
if (entry._file) {
|
956
|
+
// Workaround for Chrome bug #149735
|
957
|
+
entry._file.relativePath = path;
|
958
|
+
dfd.resolve(entry._file);
|
959
|
+
} else {
|
960
|
+
entry.file(function (file) {
|
961
|
+
file.relativePath = path;
|
962
|
+
dfd.resolve(file);
|
963
|
+
}, errorHandler);
|
964
|
+
}
|
965
|
+
} else if (entry.isDirectory) {
|
966
|
+
dirReader = entry.createReader();
|
967
|
+
dirReader.readEntries(function (entries) {
|
968
|
+
that._handleFileTreeEntries(
|
969
|
+
entries,
|
970
|
+
path + entry.name + '/'
|
971
|
+
).done(function (files) {
|
972
|
+
dfd.resolve(files);
|
973
|
+
}).fail(errorHandler);
|
974
|
+
}, errorHandler);
|
975
|
+
} else {
|
976
|
+
// Return an empy list for file system items
|
977
|
+
// other than files or directories:
|
978
|
+
dfd.resolve([]);
|
979
|
+
}
|
980
|
+
return dfd.promise();
|
981
|
+
},
|
982
|
+
|
983
|
+
_handleFileTreeEntries: function (entries, path) {
|
984
|
+
var that = this;
|
985
|
+
return $.when.apply(
|
986
|
+
$,
|
987
|
+
$.map(entries, function (entry) {
|
988
|
+
return that._handleFileTreeEntry(entry, path);
|
989
|
+
})
|
990
|
+
).pipe(function () {
|
991
|
+
return Array.prototype.concat.apply(
|
992
|
+
[],
|
993
|
+
arguments
|
994
|
+
);
|
995
|
+
});
|
996
|
+
},
|
997
|
+
|
998
|
+
_getDroppedFiles: function (dataTransfer) {
|
999
|
+
dataTransfer = dataTransfer || {};
|
1000
|
+
var items = dataTransfer.items;
|
1001
|
+
if (items && items.length && (items[0].webkitGetAsEntry ||
|
1002
|
+
items[0].getAsEntry)) {
|
1003
|
+
return this._handleFileTreeEntries(
|
1004
|
+
$.map(items, function (item) {
|
1005
|
+
var entry;
|
1006
|
+
if (item.webkitGetAsEntry) {
|
1007
|
+
entry = item.webkitGetAsEntry();
|
1008
|
+
if (entry) {
|
1009
|
+
// Workaround for Chrome bug #149735:
|
1010
|
+
entry._file = item.getAsFile();
|
1011
|
+
}
|
1012
|
+
return entry;
|
1013
|
+
}
|
1014
|
+
return item.getAsEntry();
|
1015
|
+
})
|
1016
|
+
);
|
1017
|
+
}
|
1018
|
+
return $.Deferred().resolve(
|
1019
|
+
$.makeArray(dataTransfer.files)
|
1020
|
+
).promise();
|
1021
|
+
},
|
1022
|
+
|
1023
|
+
_getSingleFileInputFiles: function (fileInput) {
|
1024
|
+
fileInput = $(fileInput);
|
1025
|
+
var entries = fileInput.prop('webkitEntries') ||
|
1026
|
+
fileInput.prop('entries'),
|
1027
|
+
files,
|
1028
|
+
value;
|
1029
|
+
if (entries && entries.length) {
|
1030
|
+
return this._handleFileTreeEntries(entries);
|
1031
|
+
}
|
1032
|
+
files = $.makeArray(fileInput.prop('files'));
|
1033
|
+
if (!files.length) {
|
1034
|
+
value = fileInput.prop('value');
|
1035
|
+
if (!value) {
|
1036
|
+
return $.Deferred().resolve([]).promise();
|
1037
|
+
}
|
742
1038
|
// If the files property is not available, the browser does not
|
743
1039
|
// support the File API and we add a pseudo File object with
|
744
1040
|
// the input value as name with path information removed:
|
745
|
-
|
746
|
-
}
|
747
|
-
|
748
|
-
|
1041
|
+
files = [{name: value.replace(/^.*\\/, '')}];
|
1042
|
+
} else if (files[0].name === undefined && files[0].fileName) {
|
1043
|
+
// File normalization for Safari 4 and Firefox 3:
|
1044
|
+
$.each(files, function (index, file) {
|
1045
|
+
file.name = file.fileName;
|
1046
|
+
file.size = file.fileSize;
|
1047
|
+
});
|
749
1048
|
}
|
750
|
-
|
751
|
-
|
752
|
-
|
1049
|
+
return $.Deferred().resolve(files).promise();
|
1050
|
+
},
|
1051
|
+
|
1052
|
+
_getFileInputFiles: function (fileInput) {
|
1053
|
+
if (!(fileInput instanceof $) || fileInput.length === 1) {
|
1054
|
+
return this._getSingleFileInputFiles(fileInput);
|
753
1055
|
}
|
1056
|
+
return $.when.apply(
|
1057
|
+
$,
|
1058
|
+
$.map(fileInput, this._getSingleFileInputFiles)
|
1059
|
+
).pipe(function () {
|
1060
|
+
return Array.prototype.concat.apply(
|
1061
|
+
[],
|
1062
|
+
arguments
|
1063
|
+
);
|
1064
|
+
});
|
1065
|
+
},
|
1066
|
+
|
1067
|
+
_onChange: function (e) {
|
1068
|
+
var that = this,
|
1069
|
+
data = {
|
1070
|
+
fileInput: $(e.target),
|
1071
|
+
form: $(e.target.form)
|
1072
|
+
};
|
1073
|
+
this._getFileInputFiles(data.fileInput).always(function (files) {
|
1074
|
+
data.files = files;
|
1075
|
+
if (that.options.replaceFileInput) {
|
1076
|
+
that._replaceFileInput(data.fileInput);
|
1077
|
+
}
|
1078
|
+
if (that._trigger('change', e, data) !== false) {
|
1079
|
+
that._onAdd(e, data);
|
1080
|
+
}
|
1081
|
+
});
|
754
1082
|
},
|
755
1083
|
|
756
1084
|
_onPaste: function (e) {
|
757
|
-
var
|
758
|
-
|
759
|
-
items = (cbd && cbd.items) || [],
|
1085
|
+
var items = e.originalEvent && e.originalEvent.clipboardData &&
|
1086
|
+
e.originalEvent.clipboardData.items,
|
760
1087
|
data = {files: []};
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
1088
|
+
if (items && items.length) {
|
1089
|
+
$.each(items, function (index, item) {
|
1090
|
+
var file = item.getAsFile && item.getAsFile();
|
1091
|
+
if (file) {
|
1092
|
+
data.files.push(file);
|
1093
|
+
}
|
1094
|
+
});
|
1095
|
+
if (this._trigger('paste', e, data) === false ||
|
1096
|
+
this._onAdd(e, data) === false) {
|
1097
|
+
return false;
|
765
1098
|
}
|
766
|
-
});
|
767
|
-
if (that._trigger('paste', e, data) === false ||
|
768
|
-
that._onAdd(e, data) === false) {
|
769
|
-
return false;
|
770
1099
|
}
|
771
1100
|
},
|
772
1101
|
|
773
1102
|
_onDrop: function (e) {
|
774
|
-
var that =
|
775
|
-
dataTransfer = e.dataTransfer = e.originalEvent
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
1103
|
+
var that = this,
|
1104
|
+
dataTransfer = e.dataTransfer = e.originalEvent &&
|
1105
|
+
e.originalEvent.dataTransfer,
|
1106
|
+
data = {};
|
1107
|
+
if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
|
1108
|
+
e.preventDefault();
|
1109
|
+
this._getDroppedFiles(dataTransfer).always(function (files) {
|
1110
|
+
data.files = files;
|
1111
|
+
if (that._trigger('drop', e, data) !== false) {
|
1112
|
+
that._onAdd(e, data);
|
1113
|
+
}
|
1114
|
+
});
|
785
1115
|
}
|
786
|
-
e.preventDefault();
|
787
1116
|
},
|
788
1117
|
|
789
1118
|
_onDragOver: function (e) {
|
790
|
-
var
|
791
|
-
|
792
|
-
if (that._trigger('dragover', e) === false) {
|
793
|
-
return false;
|
794
|
-
}
|
1119
|
+
var dataTransfer = e.dataTransfer = e.originalEvent &&
|
1120
|
+
e.originalEvent.dataTransfer;
|
795
1121
|
if (dataTransfer) {
|
796
|
-
|
1122
|
+
if (this._trigger('dragover', e) === false) {
|
1123
|
+
return false;
|
1124
|
+
}
|
1125
|
+
if ($.inArray('Files', dataTransfer.types) !== -1) {
|
1126
|
+
dataTransfer.dropEffect = 'copy';
|
1127
|
+
e.preventDefault();
|
1128
|
+
}
|
797
1129
|
}
|
798
|
-
e.preventDefault();
|
799
1130
|
},
|
800
1131
|
|
801
1132
|
_initEventHandlers: function () {
|
802
|
-
var ns = this.options.namespace;
|
803
1133
|
if (this._isXHRUpload(this.options)) {
|
804
|
-
this.options.dropZone
|
805
|
-
|
806
|
-
|
807
|
-
|
1134
|
+
this._on(this.options.dropZone, {
|
1135
|
+
dragover: this._onDragOver,
|
1136
|
+
drop: this._onDrop
|
1137
|
+
});
|
1138
|
+
this._on(this.options.pasteZone, {
|
1139
|
+
paste: this._onPaste
|
1140
|
+
});
|
808
1141
|
}
|
809
|
-
this.options.fileInput
|
810
|
-
|
1142
|
+
this._on(this.options.fileInput, {
|
1143
|
+
change: this._onChange
|
1144
|
+
});
|
811
1145
|
},
|
812
1146
|
|
813
1147
|
_destroyEventHandlers: function () {
|
814
|
-
|
815
|
-
this.options.
|
816
|
-
|
817
|
-
.unbind('drop.' + ns, this._onDrop)
|
818
|
-
.unbind('paste.' + ns, this._onPaste);
|
819
|
-
this.options.fileInput
|
820
|
-
.unbind('change.' + ns, this._onChange);
|
1148
|
+
this._off(this.options.dropZone, 'dragover drop');
|
1149
|
+
this._off(this.options.pasteZone, 'paste');
|
1150
|
+
this._off(this.options.fileInput, 'change');
|
821
1151
|
},
|
822
1152
|
|
823
1153
|
_setOption: function (key, value) {
|
824
|
-
var
|
825
|
-
if (
|
1154
|
+
var reinit = $.inArray(key, this._specialOptions) !== -1;
|
1155
|
+
if (reinit) {
|
826
1156
|
this._destroyEventHandlers();
|
827
1157
|
}
|
828
|
-
|
829
|
-
if (
|
1158
|
+
this._super(key, value);
|
1159
|
+
if (reinit) {
|
830
1160
|
this._initSpecialOptions();
|
831
1161
|
this._initEventHandlers();
|
832
1162
|
}
|
@@ -835,41 +1165,68 @@
|
|
835
1165
|
_initSpecialOptions: function () {
|
836
1166
|
var options = this.options;
|
837
1167
|
if (options.fileInput === undefined) {
|
838
|
-
options.fileInput = this.element.is('input
|
839
|
-
this.element : this.element.find('input
|
1168
|
+
options.fileInput = this.element.is('input[type="file"]') ?
|
1169
|
+
this.element : this.element.find('input[type="file"]');
|
840
1170
|
} else if (!(options.fileInput instanceof $)) {
|
841
1171
|
options.fileInput = $(options.fileInput);
|
842
1172
|
}
|
843
1173
|
if (!(options.dropZone instanceof $)) {
|
844
1174
|
options.dropZone = $(options.dropZone);
|
845
1175
|
}
|
1176
|
+
if (!(options.pasteZone instanceof $)) {
|
1177
|
+
options.pasteZone = $(options.pasteZone);
|
1178
|
+
}
|
846
1179
|
},
|
847
1180
|
|
848
|
-
|
849
|
-
var
|
1181
|
+
_getRegExp: function (str) {
|
1182
|
+
var parts = str.split('/'),
|
1183
|
+
modifiers = parts.pop();
|
1184
|
+
parts.shift();
|
1185
|
+
return new RegExp(parts.join('/'), modifiers);
|
1186
|
+
},
|
1187
|
+
|
1188
|
+
_isRegExpOption: function (key, value) {
|
1189
|
+
return key !== 'url' && $.type(value) === 'string' &&
|
1190
|
+
/^\/.*\/[igm]{0,3}$/.test(value);
|
1191
|
+
},
|
1192
|
+
|
1193
|
+
_initDataAttributes: function () {
|
1194
|
+
var that = this,
|
1195
|
+
options = this.options;
|
850
1196
|
// Initialize options set via HTML5 data-attributes:
|
851
|
-
$.
|
852
|
-
|
1197
|
+
$.each(
|
1198
|
+
$(this.element[0].cloneNode(false)).data(),
|
1199
|
+
function (key, value) {
|
1200
|
+
if (that._isRegExpOption(key, value)) {
|
1201
|
+
value = that._getRegExp(value);
|
1202
|
+
}
|
1203
|
+
options[key] = value;
|
1204
|
+
}
|
1205
|
+
);
|
1206
|
+
},
|
1207
|
+
|
1208
|
+
_create: function () {
|
1209
|
+
this._initDataAttributes();
|
853
1210
|
this._initSpecialOptions();
|
854
1211
|
this._slots = [];
|
855
1212
|
this._sequence = this._getXHRPromise(true);
|
856
|
-
this._sending = this._active =
|
1213
|
+
this._sending = this._active = 0;
|
1214
|
+
this._initProgressObject(this);
|
857
1215
|
this._initEventHandlers();
|
858
1216
|
},
|
859
1217
|
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
enable: function () {
|
866
|
-
$.Widget.prototype.enable.call(this);
|
867
|
-
this._initEventHandlers();
|
1218
|
+
// This method is exposed to the widget API and allows to query
|
1219
|
+
// the number of active uploads:
|
1220
|
+
active: function () {
|
1221
|
+
return this._active;
|
868
1222
|
},
|
869
1223
|
|
870
|
-
|
871
|
-
|
872
|
-
|
1224
|
+
// This method is exposed to the widget API and allows to query
|
1225
|
+
// the widget upload progress.
|
1226
|
+
// It returns an object with loaded, total and bitrate properties
|
1227
|
+
// for the running uploads:
|
1228
|
+
progress: function () {
|
1229
|
+
return this._progress;
|
873
1230
|
},
|
874
1231
|
|
875
1232
|
// This method is exposed to the widget API and allows adding files
|
@@ -877,21 +1234,61 @@
|
|
877
1234
|
// must have a files property and can contain additional options:
|
878
1235
|
// .fileupload('add', {files: filesList});
|
879
1236
|
add: function (data) {
|
1237
|
+
var that = this;
|
880
1238
|
if (!data || this.options.disabled) {
|
881
1239
|
return;
|
882
1240
|
}
|
883
|
-
data.
|
884
|
-
|
1241
|
+
if (data.fileInput && !data.files) {
|
1242
|
+
this._getFileInputFiles(data.fileInput).always(function (files) {
|
1243
|
+
data.files = files;
|
1244
|
+
that._onAdd(null, data);
|
1245
|
+
});
|
1246
|
+
} else {
|
1247
|
+
data.files = $.makeArray(data.files);
|
1248
|
+
this._onAdd(null, data);
|
1249
|
+
}
|
885
1250
|
},
|
886
1251
|
|
887
1252
|
// This method is exposed to the widget API and allows sending files
|
888
1253
|
// using the fileupload API. The data parameter accepts an object which
|
889
|
-
// must have a files property and can contain additional options:
|
1254
|
+
// must have a files or fileInput property and can contain additional options:
|
890
1255
|
// .fileupload('send', {files: filesList});
|
891
1256
|
// The method returns a Promise object for the file upload call.
|
892
1257
|
send: function (data) {
|
893
1258
|
if (data && !this.options.disabled) {
|
894
|
-
data.
|
1259
|
+
if (data.fileInput && !data.files) {
|
1260
|
+
var that = this,
|
1261
|
+
dfd = $.Deferred(),
|
1262
|
+
promise = dfd.promise(),
|
1263
|
+
jqXHR,
|
1264
|
+
aborted;
|
1265
|
+
promise.abort = function () {
|
1266
|
+
aborted = true;
|
1267
|
+
if (jqXHR) {
|
1268
|
+
return jqXHR.abort();
|
1269
|
+
}
|
1270
|
+
dfd.reject(null, 'abort', 'abort');
|
1271
|
+
return promise;
|
1272
|
+
};
|
1273
|
+
this._getFileInputFiles(data.fileInput).always(
|
1274
|
+
function (files) {
|
1275
|
+
if (aborted) {
|
1276
|
+
return;
|
1277
|
+
}
|
1278
|
+
data.files = files;
|
1279
|
+
jqXHR = that._onSend(null, data).then(
|
1280
|
+
function (result, textStatus, jqXHR) {
|
1281
|
+
dfd.resolve(result, textStatus, jqXHR);
|
1282
|
+
},
|
1283
|
+
function (jqXHR, textStatus, errorThrown) {
|
1284
|
+
dfd.reject(jqXHR, textStatus, errorThrown);
|
1285
|
+
}
|
1286
|
+
);
|
1287
|
+
}
|
1288
|
+
);
|
1289
|
+
return this._enhancePromise(promise);
|
1290
|
+
}
|
1291
|
+
data.files = $.makeArray(data.files);
|
895
1292
|
if (data.files.length) {
|
896
1293
|
return this._onSend(null, data);
|
897
1294
|
}
|