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,451 @@
1
+ /**
2
+ * Class that creates upload widget with drag-and-drop and file list
3
+ * @inherits qq.FineUploaderBasic
4
+ */
5
+ qq.FineUploader = function(o){
6
+ // call parent constructor
7
+ qq.FineUploaderBasic.apply(this, arguments);
8
+
9
+ // additional options
10
+ qq.extend(this._options, {
11
+ element: null,
12
+ listElement: null,
13
+ dragAndDrop: {
14
+ extraDropzones: [],
15
+ hideDropzones: true,
16
+ disableDefaultDropzone: false
17
+ },
18
+ text: {
19
+ uploadButton: 'Upload a file',
20
+ cancelButton: 'Cancel',
21
+ retryButton: 'Retry',
22
+ failUpload: 'Upload failed',
23
+ dragZone: 'Drop files here to upload',
24
+ dropProcessing: 'Processing dropped files...',
25
+ formatProgress: "{percent}% of {total_size}",
26
+ waitingForResponse: "Processing..."
27
+ },
28
+ template: '<div class="qq-uploader">' +
29
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '<div class="qq-upload-drop-area"><span>{dragZoneText}</span></div>' : '') +
30
+ (!this._options.button ? '<div class="qq-upload-button"><div>{uploadButtonText}</div></div>' : '') +
31
+ '<span class="qq-drop-processing"><span>{dropProcessingText}</span><span class="qq-drop-processing-spinner"></span></span>' +
32
+ (!this._options.listElement ? '<ul class="qq-upload-list"></ul>' : '') +
33
+ '</div>',
34
+
35
+ // template for one item in file list
36
+ fileTemplate: '<li>' +
37
+ '<div class="qq-progress-bar"></div>' +
38
+ '<span class="qq-upload-spinner"></span>' +
39
+ '<span class="qq-upload-finished"></span>' +
40
+ '<span class="qq-upload-file"></span>' +
41
+ '<span class="qq-upload-size"></span>' +
42
+ '<a class="qq-upload-cancel" href="#">{cancelButtonText}</a>' +
43
+ '<a class="qq-upload-retry" href="#">{retryButtonText}</a>' +
44
+ '<span class="qq-upload-status-text">{statusText}</span>' +
45
+ '</li>',
46
+ classes: {
47
+ button: 'qq-upload-button',
48
+ drop: 'qq-upload-drop-area',
49
+ dropActive: 'qq-upload-drop-area-active',
50
+ dropDisabled: 'qq-upload-drop-area-disabled',
51
+ list: 'qq-upload-list',
52
+ progressBar: 'qq-progress-bar',
53
+ file: 'qq-upload-file',
54
+ spinner: 'qq-upload-spinner',
55
+ finished: 'qq-upload-finished',
56
+ retrying: 'qq-upload-retrying',
57
+ retryable: 'qq-upload-retryable',
58
+ size: 'qq-upload-size',
59
+ cancel: 'qq-upload-cancel',
60
+ retry: 'qq-upload-retry',
61
+ statusText: 'qq-upload-status-text',
62
+
63
+ success: 'qq-upload-success',
64
+ fail: 'qq-upload-fail',
65
+
66
+ successIcon: null,
67
+ failIcon: null,
68
+
69
+ dropProcessing: 'qq-drop-processing',
70
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
71
+ },
72
+ failedUploadTextDisplay: {
73
+ mode: 'default', //default, custom, or none
74
+ maxChars: 50,
75
+ responseProperty: 'error',
76
+ enableTooltip: true
77
+ },
78
+ messages: {
79
+ tooManyFilesError: "You may only drop one file"
80
+ },
81
+ retry: {
82
+ showAutoRetryNote: true,
83
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
84
+ showButton: false
85
+ },
86
+ showMessage: function(message){
87
+ alert(message);
88
+ }
89
+ }, true);
90
+
91
+ // overwrite options with user supplied
92
+ qq.extend(this._options, o, true);
93
+ this._wrapCallbacks();
94
+
95
+ // overwrite the upload button text if any
96
+ // same for the Cancel button and Fail message text
97
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
98
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
99
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
100
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
101
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
102
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
103
+
104
+ this._element = this._options.element;
105
+ this._element.innerHTML = this._options.template;
106
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
107
+
108
+ this._classes = this._options.classes;
109
+
110
+ if (!this._button) {
111
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
112
+ }
113
+
114
+ this._bindCancelAndRetryEvents();
115
+
116
+ this._dnd = this._setupDragAndDrop();
117
+ };
118
+
119
+ // inherit from Basic Uploader
120
+ qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
121
+
122
+ qq.extend(qq.FineUploader.prototype, {
123
+ clearStoredFiles: function() {
124
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
125
+ this._listElement.innerHTML = "";
126
+ },
127
+ addExtraDropzone: function(element){
128
+ this._dnd.setupExtraDropzone(element);
129
+ },
130
+ removeExtraDropzone: function(element){
131
+ return this._dnd.removeExtraDropzone(element);
132
+ },
133
+ getItemByFileId: function(id){
134
+ var item = this._listElement.firstChild;
135
+
136
+ // there can't be txt nodes in dynamically created list
137
+ // and we can use nextSibling
138
+ while (item){
139
+ if (item.qqFileId == id) return item;
140
+ item = item.nextSibling;
141
+ }
142
+ },
143
+ cancel: function(fileId) {
144
+ qq.FineUploaderBasic.prototype.cancel.apply(this, arguments);
145
+ var item = this.getItemByFileId(fileId);
146
+ qq(item).remove();
147
+ },
148
+ reset: function() {
149
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
150
+ this._element.innerHTML = this._options.template;
151
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
152
+ if (!this._options.button) {
153
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
154
+ }
155
+ this._bindCancelAndRetryEvents();
156
+ this._dnd.dispose();
157
+ this._dnd = this._setupDragAndDrop();
158
+ },
159
+ _setupDragAndDrop: function() {
160
+ var self = this,
161
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
162
+ dnd, preventSelectFiles, defaultDropAreaEl;
163
+
164
+ preventSelectFiles = function(event) {
165
+ event.preventDefault();
166
+ };
167
+
168
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
169
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
170
+ }
171
+
172
+ dnd = new qq.DragAndDrop({
173
+ dropArea: defaultDropAreaEl,
174
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
175
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
176
+ multiple: this._options.multiple,
177
+ classes: {
178
+ dropActive: this._options.classes.dropActive
179
+ },
180
+ callbacks: {
181
+ dropProcessing: function(isProcessing, files) {
182
+ var input = self._button.getInput();
183
+
184
+ if (isProcessing) {
185
+ qq(dropProcessingEl).css({display: 'block'});
186
+ qq(input).attach('click', preventSelectFiles);
187
+ }
188
+ else {
189
+ qq(dropProcessingEl).hide();
190
+ qq(input).detach('click', preventSelectFiles);
191
+ }
192
+
193
+ if (files) {
194
+ self.addFiles(files);
195
+ }
196
+ },
197
+ error: function(code, filename) {
198
+ self._error(code, filename);
199
+ },
200
+ log: function(message, level) {
201
+ self.log(message, level);
202
+ }
203
+ }
204
+ });
205
+
206
+ dnd.setup();
207
+
208
+ return dnd;
209
+ },
210
+ _leaving_document_out: function(e){
211
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
212
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
213
+ },
214
+ _storeFileForLater: function(id) {
215
+ qq.FineUploaderBasic.prototype._storeFileForLater.apply(this, arguments);
216
+ var item = this.getItemByFileId(id);
217
+ qq(this._find(item, 'spinner')).hide();
218
+ },
219
+ /**
220
+ * Gets one of the elements listed in this._options.classes
221
+ **/
222
+ _find: function(parent, type){
223
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
224
+ if (!element){
225
+ throw new Error('element not found ' + type);
226
+ }
227
+
228
+ return element;
229
+ },
230
+ _onSubmit: function(id, fileName){
231
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
232
+ this._addToList(id, fileName);
233
+ },
234
+ // Update the progress bar & percentage as the file is uploaded
235
+ _onProgress: function(id, fileName, loaded, total){
236
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
237
+
238
+ var item, progressBar, text, percent, cancelLink, size;
239
+
240
+ item = this.getItemByFileId(id);
241
+ progressBar = this._find(item, 'progressBar');
242
+ percent = Math.round(loaded / total * 100);
243
+
244
+ if (loaded === total) {
245
+ cancelLink = this._find(item, 'cancel');
246
+ qq(cancelLink).hide();
247
+
248
+ qq(progressBar).hide();
249
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
250
+
251
+ // If last byte was sent, just display final size
252
+ text = this._formatSize(total);
253
+ }
254
+ else {
255
+ // If still uploading, display percentage
256
+ text = this._formatProgress(loaded, total);
257
+
258
+ qq(progressBar).css({display: 'block'});
259
+ }
260
+
261
+ // Update progress bar element
262
+ qq(progressBar).css({width: percent + '%'});
263
+
264
+ size = this._find(item, 'size');
265
+ qq(size).css({display: 'inline'});
266
+ qq(size).setText(text);
267
+ },
268
+ _onComplete: function(id, fileName, result, xhr){
269
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
270
+
271
+ var item = this.getItemByFileId(id);
272
+
273
+ qq(this._find(item, 'statusText')).clearText();
274
+
275
+ qq(item).removeClass(this._classes.retrying);
276
+ qq(this._find(item, 'progressBar')).hide();
277
+
278
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
279
+ qq(this._find(item, 'cancel')).hide();
280
+ }
281
+ qq(this._find(item, 'spinner')).hide();
282
+
283
+ if (result.success){
284
+ qq(item).addClass(this._classes.success);
285
+ if (this._classes.successIcon) {
286
+ this._find(item, 'finished').style.display = "inline-block";
287
+ qq(item).addClass(this._classes.successIcon);
288
+ }
289
+ } else {
290
+ qq(item).addClass(this._classes.fail);
291
+ if (this._classes.failIcon) {
292
+ this._find(item, 'finished').style.display = "inline-block";
293
+ qq(item).addClass(this._classes.failIcon);
294
+ }
295
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
296
+ qq(item).addClass(this._classes.retryable);
297
+ }
298
+ this._controlFailureTextDisplay(item, result);
299
+ }
300
+ },
301
+ _onUpload: function(id, fileName, xhr){
302
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
303
+
304
+ var item = this.getItemByFileId(id);
305
+ this._showSpinner(item);
306
+ },
307
+ _onBeforeAutoRetry: function(id) {
308
+ var item, progressBar, cancelLink, failTextEl, retryNumForDisplay, maxAuto, retryNote;
309
+
310
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
311
+
312
+ item = this.getItemByFileId(id);
313
+ progressBar = this._find(item, 'progressBar');
314
+
315
+ this._showCancelLink(item);
316
+ progressBar.style.width = 0;
317
+ qq(progressBar).hide();
318
+
319
+ if (this._options.retry.showAutoRetryNote) {
320
+ failTextEl = this._find(item, 'statusText');
321
+ retryNumForDisplay = this._autoRetries[id] + 1;
322
+ maxAuto = this._options.retry.maxAutoAttempts;
323
+
324
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
325
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
326
+
327
+ qq(failTextEl).setText(retryNote);
328
+ if (retryNumForDisplay === 1) {
329
+ qq(item).addClass(this._classes.retrying);
330
+ }
331
+ }
332
+ },
333
+ //return false if we should not attempt the requested retry
334
+ _onBeforeManualRetry: function(id) {
335
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
336
+ var item = this.getItemByFileId(id);
337
+ this._find(item, 'progressBar').style.width = 0;
338
+ qq(item).removeClass(this._classes.fail);
339
+ this._showSpinner(item);
340
+ this._showCancelLink(item);
341
+ return true;
342
+ }
343
+ return false;
344
+ },
345
+ _addToList: function(id, fileName){
346
+ var item = qq.toElement(this._options.fileTemplate);
347
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
348
+ var cancelLink = this._find(item, 'cancel');
349
+ qq(cancelLink).remove();
350
+ }
351
+
352
+ item.qqFileId = id;
353
+
354
+ var fileElement = this._find(item, 'file');
355
+ qq(fileElement).setText(this._formatFileName(fileName));
356
+ qq(this._find(item, 'size')).hide();
357
+ if (!this._options.multiple) this._clearList();
358
+ this._listElement.appendChild(item);
359
+ },
360
+ _clearList: function(){
361
+ this._listElement.innerHTML = '';
362
+ this.clearStoredFiles();
363
+ },
364
+ /**
365
+ * delegate click event for cancel & retry links
366
+ **/
367
+ _bindCancelAndRetryEvents: function(){
368
+ var self = this,
369
+ list = this._listElement;
370
+
371
+ this._disposeSupport.attach(list, 'click', function(e){
372
+ e = e || window.event;
373
+ var target = e.target || e.srcElement;
374
+
375
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry)){
376
+ qq.preventDefault(e);
377
+
378
+ var item = target.parentNode;
379
+ while(item.qqFileId == undefined) {
380
+ item = target = target.parentNode;
381
+ }
382
+
383
+ if (qq(target).hasClass(self._classes.cancel)) {
384
+ self.cancel(item.qqFileId);
385
+ }
386
+ else {
387
+ qq(item).removeClass(self._classes.retryable);
388
+ self.retry(item.qqFileId);
389
+ }
390
+ }
391
+ });
392
+ },
393
+ _formatProgress: function (uploadedSize, totalSize) {
394
+ var message = this._options.text.formatProgress;
395
+ function r(name, replacement) { message = message.replace(name, replacement); }
396
+
397
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
398
+ r('{total_size}', this._formatSize(totalSize));
399
+ return message;
400
+ },
401
+ _controlFailureTextDisplay: function(item, response) {
402
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
403
+
404
+ mode = this._options.failedUploadTextDisplay.mode;
405
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
406
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
407
+
408
+ if (mode === 'custom') {
409
+ failureReason = response[responseProperty];
410
+ if (failureReason) {
411
+ if (failureReason.length > maxChars) {
412
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
413
+ }
414
+ }
415
+ else {
416
+ failureReason = this._options.text.failUpload;
417
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
418
+ }
419
+
420
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
421
+
422
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
423
+ this._showTooltip(item, failureReason);
424
+ }
425
+ }
426
+ else if (mode === 'default') {
427
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
428
+ }
429
+ else if (mode !== 'none') {
430
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
431
+ }
432
+ },
433
+ //TODO turn this into a real tooltip, with click trigger (so it is usable on mobile devices). See case #355 for details.
434
+ _showTooltip: function(item, text) {
435
+ item.title = text;
436
+ },
437
+ _showSpinner: function(item) {
438
+ var spinnerEl = this._find(item, 'spinner');
439
+ spinnerEl.style.display = "inline-block";
440
+ },
441
+ _showCancelLink: function(item) {
442
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
443
+ var cancelLink = this._find(item, 'cancel');
444
+ cancelLink.style.display = 'inline';
445
+ }
446
+ },
447
+ _error: function(code, fileName){
448
+ var message = qq.FineUploaderBasic.prototype._error.apply(this, arguments);
449
+ this._options.showMessage(message);
450
+ }
451
+ });