fine_uploader 2.1.1 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,558 @@
1
+ qq.FineUploaderBasic = function(o){
2
+ var that = this;
3
+ this._options = {
4
+ debug: false,
5
+ button: null,
6
+ multiple: true,
7
+ maxConnections: 3,
8
+ disableCancelForFormUploads: false,
9
+ autoUpload: true,
10
+ request: {
11
+ endpoint: '/server/upload',
12
+ params: {},
13
+ paramsInBody: false,
14
+ customHeaders: {},
15
+ forceMultipart: false,
16
+ inputName: 'qqfile'
17
+ },
18
+ validation: {
19
+ allowedExtensions: [],
20
+ sizeLimit: 0,
21
+ minSizeLimit: 0,
22
+ stopOnFirstInvalidFile: true
23
+ },
24
+ callbacks: {
25
+ onSubmit: function(id, fileName){}, // return false to cancel submit
26
+ onComplete: function(id, fileName, responseJSON){},
27
+ onCancel: function(id, fileName){},
28
+ onUpload: function(id, fileName, xhr){},
29
+ onProgress: function(id, fileName, loaded, total){},
30
+ onError: function(id, fileName, reason) {},
31
+ onAutoRetry: function(id, fileName, attemptNumber) {},
32
+ onManualRetry: function(id, fileName) {},
33
+ onValidate: function(fileData) {} // return false to prevent upload
34
+ },
35
+ messages: {
36
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
37
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
38
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
39
+ emptyError: "{file} is empty, please select files again without it.",
40
+ noFilesError: "No files to upload.",
41
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
42
+ },
43
+ retry: {
44
+ enableAuto: false,
45
+ maxAutoAttempts: 3,
46
+ autoAttemptDelay: 5,
47
+ preventRetryResponseProperty: 'preventRetry'
48
+ },
49
+ classes: {
50
+ buttonHover: 'qq-upload-button-hover',
51
+ buttonFocus: 'qq-upload-button-focus'
52
+ }
53
+ };
54
+
55
+ qq.extend(this._options, o, true);
56
+ this._wrapCallbacks();
57
+ this._disposeSupport = new qq.DisposeSupport();
58
+
59
+ // number of files being uploaded
60
+ this._filesInProgress = 0;
61
+
62
+ this._storedFileIds = [];
63
+
64
+ this._autoRetries = [];
65
+ this._retryTimeouts = [];
66
+ this._preventRetries = [];
67
+
68
+ this._paramsStore = this._createParamsStore();
69
+
70
+ this._handler = this._createUploadHandler();
71
+
72
+ if (this._options.button){
73
+ this._button = this._createUploadButton(this._options.button);
74
+ }
75
+
76
+ this._preventLeaveInProgress();
77
+ };
78
+
79
+ qq.FineUploaderBasic.prototype = {
80
+ log: function(str, level) {
81
+ if (this._options.debug && (!level || level === 'info')) {
82
+ qq.log('[FineUploader] ' + str);
83
+ }
84
+ else if (level && level !== 'info') {
85
+ qq.log('[FineUploader] ' + str, level);
86
+
87
+ }
88
+ },
89
+ setParams: function(params, fileId){
90
+ if (fileId === undefined) {
91
+ this._options.request.params = params;
92
+ }
93
+ else {
94
+ this._paramsStore.setParams(params, fileId);
95
+ }
96
+ },
97
+ getInProgress: function(){
98
+ return this._filesInProgress;
99
+ },
100
+ uploadStoredFiles: function(){
101
+ "use strict";
102
+ while(this._storedFileIds.length) {
103
+ this._filesInProgress++;
104
+ this._handler.upload(this._storedFileIds.shift());
105
+ }
106
+ },
107
+ clearStoredFiles: function(){
108
+ this._storedFileIds = [];
109
+ },
110
+ retry: function(id) {
111
+ if (this._onBeforeManualRetry(id)) {
112
+ this._handler.retry(id);
113
+ return true;
114
+ }
115
+ else {
116
+ return false;
117
+ }
118
+ },
119
+ cancel: function(fileId) {
120
+ this._handler.cancel(fileId);
121
+ },
122
+ reset: function() {
123
+ this.log("Resetting uploader...");
124
+ this._handler.reset();
125
+ this._filesInProgress = 0;
126
+ this._storedFileIds = [];
127
+ this._autoRetries = [];
128
+ this._retryTimeouts = [];
129
+ this._preventRetries = [];
130
+ this._button.reset();
131
+ this._paramsStore.reset();
132
+ },
133
+ addFiles: function(filesOrInputs) {
134
+ var self = this,
135
+ verifiedFilesOrInputs = [],
136
+ index, fileOrInput;
137
+
138
+ if (filesOrInputs) {
139
+ if (!window.FileList || !filesOrInputs instanceof FileList) {
140
+ filesOrInputs = [].concat(filesOrInputs);
141
+ }
142
+
143
+ for (index = 0; index < filesOrInputs.length; index+=1) {
144
+ fileOrInput = filesOrInputs[index];
145
+
146
+ if (qq.isFileOrInput(fileOrInput)) {
147
+ verifiedFilesOrInputs.push(fileOrInput);
148
+ }
149
+ else {
150
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
151
+ }
152
+ }
153
+
154
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
155
+ this._uploadFileList(verifiedFilesOrInputs);
156
+ }
157
+ },
158
+ _createUploadButton: function(element){
159
+ var self = this;
160
+
161
+ var button = new qq.UploadButton({
162
+ element: element,
163
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
164
+ acceptFiles: this._options.validation.acceptFiles,
165
+ onChange: function(input){
166
+ self._onInputChange(input);
167
+ },
168
+ hoverClass: this._options.classes.buttonHover,
169
+ focusClass: this._options.classes.buttonFocus
170
+ });
171
+
172
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
173
+ return button;
174
+ },
175
+ _createUploadHandler: function(){
176
+ var self = this,
177
+ handlerClass;
178
+
179
+ if(qq.isXhrUploadSupported()){
180
+ handlerClass = 'UploadHandlerXhr';
181
+ } else {
182
+ handlerClass = 'UploadHandlerForm';
183
+ }
184
+
185
+ var handler = new qq[handlerClass]({
186
+ debug: this._options.debug,
187
+ endpoint: this._options.request.endpoint,
188
+ forceMultipart: this._options.request.forceMultipart,
189
+ maxConnections: this._options.maxConnections,
190
+ customHeaders: this._options.request.customHeaders,
191
+ inputName: this._options.request.inputName,
192
+ demoMode: this._options.demoMode,
193
+ log: this.log,
194
+ paramsInBody: this._options.request.paramsInBody,
195
+ paramsStore: this._paramsStore,
196
+ onProgress: function(id, fileName, loaded, total){
197
+ self._onProgress(id, fileName, loaded, total);
198
+ self._options.callbacks.onProgress(id, fileName, loaded, total);
199
+ },
200
+ onComplete: function(id, fileName, result, xhr){
201
+ self._onComplete(id, fileName, result, xhr);
202
+ self._options.callbacks.onComplete(id, fileName, result);
203
+ },
204
+ onCancel: function(id, fileName){
205
+ self._onCancel(id, fileName);
206
+ self._options.callbacks.onCancel(id, fileName);
207
+ },
208
+ onUpload: function(id, fileName, xhr){
209
+ self._onUpload(id, fileName, xhr);
210
+ self._options.callbacks.onUpload(id, fileName, xhr);
211
+ },
212
+ onAutoRetry: function(id, fileName, responseJSON, xhr) {
213
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
214
+
215
+ if (self._shouldAutoRetry(id, fileName, responseJSON)) {
216
+ self._maybeParseAndSendUploadError(id, fileName, responseJSON, xhr);
217
+ self._options.callbacks.onAutoRetry(id, fileName, self._autoRetries[id] + 1);
218
+ self._onBeforeAutoRetry(id, fileName);
219
+
220
+ self._retryTimeouts[id] = setTimeout(function() {
221
+ self._onAutoRetry(id, fileName, responseJSON)
222
+ }, self._options.retry.autoAttemptDelay * 1000);
223
+
224
+ return true;
225
+ }
226
+ else {
227
+ return false;
228
+ }
229
+ }
230
+ });
231
+
232
+ return handler;
233
+ },
234
+ _preventLeaveInProgress: function(){
235
+ var self = this;
236
+
237
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
238
+ if (!self._filesInProgress){return;}
239
+
240
+ var e = e || window.event;
241
+ // for ie, ff
242
+ e.returnValue = self._options.messages.onLeave;
243
+ // for webkit
244
+ return self._options.messages.onLeave;
245
+ });
246
+ },
247
+ _onSubmit: function(id, fileName){
248
+ if (this._options.autoUpload) {
249
+ this._filesInProgress++;
250
+ }
251
+ },
252
+ _onProgress: function(id, fileName, loaded, total){
253
+ },
254
+ _onComplete: function(id, fileName, result, xhr){
255
+ this._filesInProgress--;
256
+ this._maybeParseAndSendUploadError(id, fileName, result, xhr);
257
+ },
258
+ _onCancel: function(id, fileName){
259
+ clearTimeout(this._retryTimeouts[id]);
260
+
261
+ var storedFileIndex = qq.indexOf(this._storedFileIds, id);
262
+ if (this._options.autoUpload || storedFileIndex < 0) {
263
+ this._filesInProgress--;
264
+ }
265
+ else if (!this._options.autoUpload) {
266
+ this._storedFileIds.splice(storedFileIndex, 1);
267
+ }
268
+ },
269
+ _onUpload: function(id, fileName, xhr){
270
+ },
271
+ _onInputChange: function(input){
272
+ if (this._handler instanceof qq.UploadHandlerXhr){
273
+ this.addFiles(input.files);
274
+ } else {
275
+ if (this._validateFile(input)){
276
+ this.addFiles(input);
277
+ }
278
+ }
279
+ this._button.reset();
280
+ },
281
+ _onBeforeAutoRetry: function(id, fileName) {
282
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + fileName + "...");
283
+ },
284
+ _onAutoRetry: function(id, fileName, responseJSON) {
285
+ this.log("Retrying " + fileName + "...");
286
+ this._autoRetries[id]++;
287
+ this._handler.retry(id);
288
+ },
289
+ _shouldAutoRetry: function(id, fileName, responseJSON) {
290
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
291
+ if (this._autoRetries[id] === undefined) {
292
+ this._autoRetries[id] = 0;
293
+ }
294
+
295
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts
296
+ }
297
+
298
+ return false;
299
+ },
300
+ //return false if we should not attempt the requested retry
301
+ _onBeforeManualRetry: function(id) {
302
+ if (this._preventRetries[id]) {
303
+ this.log("Retries are forbidden for id " + id, 'warn');
304
+ return false;
305
+ }
306
+ else if (this._handler.isValid(id)) {
307
+ var fileName = this._handler.getName(id);
308
+
309
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
310
+ return false;
311
+ }
312
+
313
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
314
+ this._filesInProgress++;
315
+ return true;
316
+ }
317
+ else {
318
+ this.log("'" + id + "' is not a valid file ID", 'error');
319
+ return false;
320
+ }
321
+ },
322
+ _maybeParseAndSendUploadError: function(id, fileName, response, xhr) {
323
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
324
+ if (!response.success){
325
+ if (xhr && xhr.status !== 200 && !response.error) {
326
+ this._options.callbacks.onError(id, fileName, "XHR returned response code " + xhr.status);
327
+ }
328
+ else {
329
+ var errorReason = response.error ? response.error : "Upload failure reason unknown";
330
+ this._options.callbacks.onError(id, fileName, errorReason);
331
+ }
332
+ }
333
+ },
334
+ _uploadFileList: function(files){
335
+ var validationDescriptors, index, batchInvalid;
336
+
337
+ validationDescriptors = this._getValidationDescriptors(files);
338
+ batchInvalid = this._options.callbacks.onValidate(validationDescriptors) === false;
339
+
340
+ if (!batchInvalid) {
341
+ if (files.length > 0) {
342
+ for (index = 0; index < files.length; index++){
343
+ if (this._validateFile(files[index])){
344
+ this._uploadFile(files[index]);
345
+ } else {
346
+ if (this._options.validation.stopOnFirstInvalidFile){
347
+ return;
348
+ }
349
+ }
350
+ }
351
+ }
352
+ else {
353
+ this._error('noFilesError', "");
354
+ }
355
+ }
356
+ },
357
+ _uploadFile: function(fileContainer){
358
+ var id = this._handler.add(fileContainer);
359
+ var fileName = this._handler.getName(id);
360
+
361
+ if (this._options.callbacks.onSubmit(id, fileName) !== false){
362
+ this._onSubmit(id, fileName);
363
+ if (this._options.autoUpload) {
364
+ this._handler.upload(id);
365
+ }
366
+ else {
367
+ this._storeFileForLater(id);
368
+ }
369
+ }
370
+ },
371
+ _storeFileForLater: function(id) {
372
+ this._storedFileIds.push(id);
373
+ },
374
+ _validateFile: function(file){
375
+ var validationDescriptor, name, size;
376
+
377
+ validationDescriptor = this._getValidationDescriptor(file);
378
+ name = validationDescriptor.name;
379
+ size = validationDescriptor.size;
380
+
381
+ if (this._options.callbacks.onValidate([validationDescriptor]) === false) {
382
+ return false;
383
+ }
384
+
385
+ if (!this._isAllowedExtension(name)){
386
+ this._error('typeError', name);
387
+ return false;
388
+
389
+ }
390
+ else if (size === 0){
391
+ this._error('emptyError', name);
392
+ return false;
393
+
394
+ }
395
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
396
+ this._error('sizeError', name);
397
+ return false;
398
+
399
+ }
400
+ else if (size && size < this._options.validation.minSizeLimit){
401
+ this._error('minSizeError', name);
402
+ return false;
403
+ }
404
+
405
+ return true;
406
+ },
407
+ _error: function(code, fileName){
408
+ var message = this._options.messages[code];
409
+ function r(name, replacement){ message = message.replace(name, replacement); }
410
+
411
+ var extensions = this._options.validation.allowedExtensions.join(', ');
412
+
413
+ r('{file}', this._formatFileName(fileName));
414
+ r('{extensions}', extensions);
415
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
416
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
417
+
418
+ this._options.callbacks.onError(null, fileName, message);
419
+
420
+ return message;
421
+ },
422
+ _formatFileName: function(name){
423
+ if (name.length > 33){
424
+ name = name.slice(0, 19) + '...' + name.slice(-13);
425
+ }
426
+ return name;
427
+ },
428
+ _isAllowedExtension: function(fileName){
429
+ var ext = (-1 !== fileName.indexOf('.'))
430
+ ? fileName.replace(/.*[.]/, '').toLowerCase()
431
+ : '';
432
+ var allowed = this._options.validation.allowedExtensions;
433
+
434
+ if (!allowed.length){return true;}
435
+
436
+ for (var i=0; i<allowed.length; i++){
437
+ if (allowed[i].toLowerCase() == ext){ return true;}
438
+ }
439
+
440
+ return false;
441
+ },
442
+ _formatSize: function(bytes){
443
+ var i = -1;
444
+ do {
445
+ bytes = bytes / 1024;
446
+ i++;
447
+ } while (bytes > 99);
448
+
449
+ return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
450
+ },
451
+ _wrapCallbacks: function() {
452
+ var self, safeCallback;
453
+
454
+ self = this;
455
+
456
+ safeCallback = function(name, callback, args) {
457
+ try {
458
+ return callback.apply(self, args);
459
+ }
460
+ catch (exception) {
461
+ self.log("Caught exception in '" + name + "' callback - " + exception, 'error');
462
+ }
463
+ }
464
+
465
+ for (var prop in this._options.callbacks) {
466
+ (function() {
467
+ var callbackName, callbackFunc;
468
+ callbackName = prop;
469
+ callbackFunc = self._options.callbacks[callbackName];
470
+ self._options.callbacks[callbackName] = function() {
471
+ return safeCallback(callbackName, callbackFunc, arguments);
472
+ }
473
+ }());
474
+ }
475
+ },
476
+ _parseFileName: function(file) {
477
+ var name;
478
+
479
+ if (file.value){
480
+ // it is a file input
481
+ // get input value and remove path to normalize
482
+ name = file.value.replace(/.*(\/|\\)/, "");
483
+ } else {
484
+ // fix missing properties in Safari 4 and firefox 11.0a2
485
+ name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
486
+ }
487
+
488
+ return name;
489
+ },
490
+ _parseFileSize: function(file) {
491
+ var size;
492
+
493
+ if (!file.value){
494
+ // fix missing properties in Safari 4 and firefox 11.0a2
495
+ size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size;
496
+ }
497
+
498
+ return size;
499
+ },
500
+ _getValidationDescriptor: function(file) {
501
+ var name, size, fileDescriptor;
502
+
503
+ fileDescriptor = {};
504
+ name = this._parseFileName(file);
505
+ size = this._parseFileSize(file);
506
+
507
+ fileDescriptor.name = name;
508
+ if (size) {
509
+ fileDescriptor.size = size;
510
+ }
511
+
512
+ return fileDescriptor;
513
+ },
514
+ _getValidationDescriptors: function(files) {
515
+ var index, fileDescriptors;
516
+
517
+ fileDescriptors = [];
518
+
519
+ for (index = 0; index < files.length; index++) {
520
+ fileDescriptors.push(files[index]);
521
+ }
522
+
523
+ return fileDescriptors;
524
+ },
525
+ _createParamsStore: function() {
526
+ var paramsStore = {},
527
+ self = this;
528
+
529
+ return {
530
+ setParams: function(params, fileId) {
531
+ var paramsCopy = {};
532
+ qq.extend(paramsCopy, params);
533
+ paramsStore[fileId] = paramsCopy;
534
+ },
535
+
536
+ getParams: function(fileId) {
537
+ var paramsCopy = {};
538
+
539
+ if (fileId !== undefined && paramsStore[fileId]) {
540
+ qq.extend(paramsCopy, paramsStore[fileId]);
541
+ }
542
+ else {
543
+ qq.extend(paramsCopy, self._options.request.params);
544
+ }
545
+
546
+ return paramsCopy;
547
+ },
548
+
549
+ remove: function(fileId) {
550
+ return delete paramsStore[fileId];
551
+ },
552
+
553
+ reset: function() {
554
+ paramsStore = {};
555
+ }
556
+ }
557
+ }
558
+ };