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,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
+ });