fine_uploader 2.1.1 → 3.1.1

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.
@@ -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
+ };