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.
@@ -1,1634 +1,10 @@
1
- /**
2
- * http://github.com/Valums-File-Uploader/file-uploader
3
- *
4
- * Multiple file upload component with progress-bar, drag-and-drop.
5
- *
6
- * Have ideas for improving this JS for the general community?
7
- * Submit your changes at: https://github.com/Valums-File-Uploader/file-uploader
8
- * Readme at https://github.com/valums/file-uploader/blob/2.1.1/readme.md
9
- *
10
- * VERSION 2.1.1
11
- * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
12
- * Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
13
- *
14
- * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
15
- */
16
-
17
- //
18
- // Helper functions
19
- //
20
-
21
- var qq = qq || {};
22
-
23
- /**
24
- * Adds all missing properties from second obj to first obj
25
- */
26
- qq.extend = function(first, second){
27
- for (var prop in second){
28
- first[prop] = second[prop];
29
- }
30
- };
31
-
32
- /**
33
- * Searches for a given element in the array, returns -1 if it is not present.
34
- * @param {Number} [from] The index at which to begin the search
35
- */
36
- qq.indexOf = function(arr, elt, from){
37
- if (arr.indexOf) return arr.indexOf(elt, from);
38
-
39
- from = from || 0;
40
- var len = arr.length;
41
-
42
- if (from < 0) from += len;
43
-
44
- for (; from < len; from++){
45
- if (from in arr && arr[from] === elt){
46
- return from;
47
- }
48
- }
49
- return -1;
50
- };
51
-
52
- qq.getUniqueId = (function(){
53
- var id = 0;
54
- return function(){ return id++; };
55
- })();
56
-
57
- //
58
- // Browsers and platforms detection
59
-
60
- qq.ie = function(){ return navigator.userAgent.indexOf('MSIE') != -1; }
61
- qq.safari = function(){ return navigator.vendor != undefined && navigator.vendor.indexOf("Apple") != -1; }
62
- qq.chrome = function(){ return navigator.vendor != undefined && navigator.vendor.indexOf('Google') != -1; }
63
- qq.firefox = function(){ return (navigator.userAgent.indexOf('Mozilla') != -1 && navigator.vendor != undefined && navigator.vendor == ''); }
64
- qq.windows = function(){ return navigator.platform == "Win32"; }
65
-
66
- //
67
- // Events
68
-
69
- /** Returns the function which detaches attached event */
70
- qq.attach = function(element, type, fn){
71
- if (element.addEventListener){
72
- element.addEventListener(type, fn, false);
73
- } else if (element.attachEvent){
74
- element.attachEvent('on' + type, fn);
75
- }
76
- return function() {
77
- qq.detach(element, type, fn)
78
- }
79
- };
80
- qq.detach = function(element, type, fn){
81
- if (element.removeEventListener){
82
- element.removeEventListener(type, fn, false);
83
- } else if (element.attachEvent){
84
- element.detachEvent('on' + type, fn);
85
- }
86
- };
87
-
88
- qq.preventDefault = function(e){
89
- if (e.preventDefault){
90
- e.preventDefault();
91
- } else{
92
- e.returnValue = false;
93
- }
94
- };
95
-
96
- //
97
- // Node manipulations
98
-
99
- /**
100
- * Insert node a before node b.
101
- */
102
- qq.insertBefore = function(a, b){
103
- b.parentNode.insertBefore(a, b);
104
- };
105
- qq.remove = function(element){
106
- element.parentNode.removeChild(element);
107
- };
108
-
109
- qq.contains = function(parent, descendant){
110
- // compareposition returns false in this case
111
- if (parent == descendant) return true;
112
-
113
- if (parent.contains){
114
- return parent.contains(descendant);
115
- } else {
116
- return !!(descendant.compareDocumentPosition(parent) & 8);
117
- }
118
- };
119
-
120
- /**
121
- * Creates and returns element from html string
122
- * Uses innerHTML to create an element
123
- */
124
- qq.toElement = (function(){
125
- var div = document.createElement('div');
126
- return function(html){
127
- div.innerHTML = html;
128
- var element = div.firstChild;
129
- div.removeChild(element);
130
- return element;
131
- };
132
- })();
133
-
134
- //
135
- // Node properties and attributes
136
-
137
- /**
138
- * Sets styles for an element.
139
- * Fixes opacity in IE6-8.
140
- */
141
- qq.css = function(element, styles){
142
- if (styles.opacity != null){
143
- if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
144
- styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
145
- }
146
- }
147
- qq.extend(element.style, styles);
148
- };
149
- qq.hasClass = function(element, name){
150
- var re = new RegExp('(^| )' + name + '( |$)');
151
- return re.test(element.className);
152
- };
153
- qq.addClass = function(element, name){
154
- if (!qq.hasClass(element, name)){
155
- element.className += ' ' + name;
156
- }
157
- };
158
- qq.removeClass = function(element, name){
159
- var re = new RegExp('(^| )' + name + '( |$)');
160
- element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
161
- };
162
- qq.setText = function(element, text){
163
- element.innerText = text;
164
- element.textContent = text;
165
- };
166
-
167
- //
168
- // Selecting elements
169
-
170
- qq.children = function(element){
171
- var children = [],
172
- child = element.firstChild;
173
-
174
- while (child){
175
- if (child.nodeType == 1){
176
- children.push(child);
177
- }
178
- child = child.nextSibling;
179
- }
180
-
181
- return children;
182
- };
183
-
184
- qq.getByClass = function(element, className){
185
- if (element.querySelectorAll){
186
- return element.querySelectorAll('.' + className);
187
- }
188
-
189
- var result = [];
190
- var candidates = element.getElementsByTagName("*");
191
- var len = candidates.length;
192
-
193
- for (var i = 0; i < len; i++){
194
- if (qq.hasClass(candidates[i], className)){
195
- result.push(candidates[i]);
196
- }
197
- }
198
- return result;
199
- };
200
-
201
- /**
202
- * obj2url() takes a json-object as argument and generates
203
- * a querystring. pretty much like jQuery.param()
204
- *
205
- * how to use:
206
- *
207
- * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
208
- *
209
- * will result in:
210
- *
211
- * `http://any.url/upload?otherParam=value&a=b&c=d`
212
- *
213
- * @param Object JSON-Object
214
- * @param String current querystring-part
215
- * @return String encoded querystring
216
- */
217
- qq.obj2url = function(obj, temp, prefixDone){
218
- var uristrings = [],
219
- prefix = '&',
220
- add = function(nextObj, i){
221
- var nextTemp = temp
222
- ? (/\[\]$/.test(temp)) // prevent double-encoding
223
- ? temp
224
- : temp+'['+i+']'
225
- : i;
226
- if ((nextTemp != 'undefined') && (i != 'undefined')) {
227
- uristrings.push(
228
- (typeof nextObj === 'object')
229
- ? qq.obj2url(nextObj, nextTemp, true)
230
- : (Object.prototype.toString.call(nextObj) === '[object Function]')
231
- ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
232
- : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
233
- );
234
- }
235
- };
236
-
237
- if (!prefixDone && temp) {
238
- prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
239
- uristrings.push(temp);
240
- uristrings.push(qq.obj2url(obj));
241
- } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
242
- // we wont use a for-in-loop on an array (performance)
243
- for (var i = 0, len = obj.length; i < len; ++i){
244
- add(obj[i], i);
245
- }
246
- } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
247
- // for anything else but a scalar, we will use for-in-loop
248
- for (var i in obj){
249
- add(obj[i], i);
250
- }
251
- } else {
252
- uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
253
- }
254
-
255
- return uristrings.join(prefix)
256
- .replace(/^&/, '')
257
- .replace(/%20/g, '+');
258
- };
259
-
260
- //
261
- //
262
- // Uploader Classes
263
- //
264
- //
265
-
266
- var qq = qq || {};
267
-
268
- /**
269
- * Creates upload button, validates upload, but doesn't create file list or dd.
270
- */
271
- qq.FileUploaderBasic = function(o){
272
- var that = this;
273
- this._options = {
274
- // set to true to see the server response
275
- debug: false,
276
- action: '/server/upload',
277
- params: {},
278
- customHeaders: {},
279
- button: null,
280
- multiple: true,
281
- maxConnections: 3,
282
- disableCancelForFormUploads: false,
283
- autoUpload: true,
284
- forceMultipart: false,
285
- // validation
286
- allowedExtensions: [],
287
- acceptFiles: null, // comma separated string of mime-types for browser to display in browse dialog
288
- sizeLimit: 0,
289
- minSizeLimit: 0,
290
- stopOnFirstInvalidFile: true,
291
- // events
292
- // return false to cancel submit
293
- onSubmit: function(id, fileName){},
294
- onComplete: function(id, fileName, responseJSON){},
295
- onCancel: function(id, fileName){},
296
- onUpload: function(id, fileName, xhr){},
297
- onProgress: function(id, fileName, loaded, total){},
298
- onError: function(id, fileName, reason) {},
299
- // messages
300
- messages: {
301
- typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
302
- sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
303
- minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
304
- emptyError: "{file} is empty, please select files again without it.",
305
- noFilesError: "No files to upload.",
306
- onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
307
- },
308
- showMessage: function(message){
309
- alert(message);
310
- },
311
- inputName: 'qqfile'
312
- };
313
- qq.extend(this._options, o);
314
- this._wrapCallbacks();
315
- qq.extend(this, qq.DisposeSupport);
316
-
317
- // number of files being uploaded
318
- this._filesInProgress = 0;
319
-
320
- this._storedFileIds = [];
321
-
322
- this._handler = this._createUploadHandler();
323
-
324
- if (this._options.button){
325
- this._button = this._createUploadButton(this._options.button);
326
- }
327
-
328
- this._preventLeaveInProgress();
329
- };
330
-
331
- qq.FileUploaderBasic.prototype = {
332
- log: function(str){
333
- if (this._options.debug && window.console) console.log('[uploader] ' + str);
334
- },
335
- setParams: function(params){
336
- this._options.params = params;
337
- },
338
- getInProgress: function(){
339
- return this._filesInProgress;
340
- },
341
- uploadStoredFiles: function(){
342
- while(this._storedFileIds.length) {
343
- this._filesInProgress++;
344
- this._handler.upload(this._storedFileIds.shift(), this._options.params);
345
- }
346
- },
347
- clearStoredFiles: function(){
348
- this._storedFileIds = [];
349
- },
350
- _createUploadButton: function(element){
351
- var self = this;
352
-
353
- var button = new qq.UploadButton({
354
- element: element,
355
- multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
356
- acceptFiles: this._options.acceptFiles,
357
- onChange: function(input){
358
- self._onInputChange(input);
359
- }
360
- });
361
-
362
- this.addDisposer(function() { button.dispose(); });
363
- return button;
364
- },
365
- _createUploadHandler: function(){
366
- var self = this,
367
- handlerClass;
368
-
369
- if(qq.UploadHandlerXhr.isSupported()){
370
- handlerClass = 'UploadHandlerXhr';
371
- } else {
372
- handlerClass = 'UploadHandlerForm';
373
- }
374
-
375
- var handler = new qq[handlerClass]({
376
- debug: this._options.debug,
377
- action: this._options.action,
378
- forceMultipart: this._options.forceMultipart,
379
- maxConnections: this._options.maxConnections,
380
- customHeaders: this._options.customHeaders,
381
- inputName: this._options.inputName,
382
- demoMode: this._options.demoMode,
383
- onProgress: function(id, fileName, loaded, total){
384
- self._onProgress(id, fileName, loaded, total);
385
- self._options.onProgress(id, fileName, loaded, total);
386
- },
387
- onComplete: function(id, fileName, result){
388
- self._onComplete(id, fileName, result);
389
- self._options.onComplete(id, fileName, result);
390
- },
391
- onCancel: function(id, fileName){
392
- self._onCancel(id, fileName);
393
- self._options.onCancel(id, fileName);
394
- },
395
- onError: self._options.onError,
396
- onUpload: function(id, fileName, xhr){
397
- self._onUpload(id, fileName, xhr);
398
- self._options.onUpload(id, fileName, xhr);
399
- }
400
- });
401
-
402
- return handler;
403
- },
404
- _preventLeaveInProgress: function(){
405
- var self = this;
406
-
407
- this._attach(window, 'beforeunload', function(e){
408
- if (!self._filesInProgress){return;}
409
-
410
- var e = e || window.event;
411
- // for ie, ff
412
- e.returnValue = self._options.messages.onLeave;
413
- // for webkit
414
- return self._options.messages.onLeave;
415
- });
416
- },
417
- _onSubmit: function(id, fileName){
418
- if (this._options.autoUpload) {
419
- this._filesInProgress++;
420
- }
421
- },
422
- _onProgress: function(id, fileName, loaded, total){
423
- },
424
- _onComplete: function(id, fileName, result){
425
- this._filesInProgress--;
426
-
427
- if (!result.success){
428
- var errorReason = result.error ? result.error : "Upload failure reason unknown";
429
- this._options.onError(id, fileName, errorReason);
430
- }
431
- },
432
- _onCancel: function(id, fileName){
433
- var storedFileIndex = qq.indexOf(this._storedFileIds, id);
434
- if (this._options.autoUpload || storedFileIndex < 0) {
435
- this._filesInProgress--;
436
- }
437
- else if (!this._options.autoUpload) {
438
- this._storedFileIds.splice(storedFileIndex, 1);
439
- }
440
- },
441
- _onUpload: function(id, fileName, xhr){
442
- },
443
- _onInputChange: function(input){
444
- if (this._handler instanceof qq.UploadHandlerXhr){
445
- this._uploadFileList(input.files);
446
- } else {
447
- if (this._validateFile(input)){
448
- this._uploadFile(input);
449
- }
450
- }
451
- this._button.reset();
452
- },
453
- _uploadFileList: function(files){
454
- if (files.length > 0) {
455
- for (var i=0; i<files.length; i++){
456
- if (this._validateFile(files[i])){
457
- this._uploadFile(files[i]);
458
- } else {
459
- if (this._options.stopOnFirstInvalidFile){
460
- return;
461
- }
462
- }
463
- }
464
- }
465
- else {
466
- this._error('noFilesError', "");
467
- }
468
- },
469
- _uploadFile: function(fileContainer){
470
- var id = this._handler.add(fileContainer);
471
- var fileName = this._handler.getName(id);
472
-
473
- if (this._options.onSubmit(id, fileName) !== false){
474
- this._onSubmit(id, fileName);
475
- if (this._options.autoUpload) {
476
- this._handler.upload(id, this._options.params);
477
- }
478
- else {
479
- this._storeFileForLater(id);
480
- }
481
- }
482
- },
483
- _storeFileForLater: function(id) {
484
- this._storedFileIds.push(id);
485
- },
486
- _validateFile: function(file){
487
- var name, size;
488
-
489
- if (file.value){
490
- // it is a file input
491
- // get input value and remove path to normalize
492
- name = file.value.replace(/.*(\/|\\)/, "");
493
- } else {
494
- // fix missing properties in Safari 4 and firefox 11.0a2
495
- name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
496
- size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size;
497
- }
498
-
499
- if (! this._isAllowedExtension(name)){
500
- this._error('typeError', name);
501
- return false;
502
-
503
- } else if (size === 0){
504
- this._error('emptyError', name);
505
- return false;
506
-
507
- } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){
508
- this._error('sizeError', name);
509
- return false;
510
-
511
- } else if (size && size < this._options.minSizeLimit){
512
- this._error('minSizeError', name);
513
- return false;
514
- }
515
-
516
- return true;
517
- },
518
- _error: function(code, fileName){
519
- var message = this._options.messages[code];
520
- function r(name, replacement){ message = message.replace(name, replacement); }
521
-
522
- var extensions = this._options.allowedExtensions.join(', ');
523
-
524
- r('{file}', this._formatFileName(fileName));
525
- r('{extensions}', extensions);
526
- r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
527
- r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
528
-
529
- this._options.onError(null, fileName, message);
530
- this._options.showMessage(message);
531
- },
532
- _formatFileName: function(name){
533
- if (name.length > 33){
534
- name = name.slice(0, 19) + '...' + name.slice(-13);
535
- }
536
- return name;
537
- },
538
- _isAllowedExtension: function(fileName){
539
- var ext = (-1 !== fileName.indexOf('.'))
540
- ? fileName.replace(/.*[.]/, '').toLowerCase()
541
- : '';
542
- var allowed = this._options.allowedExtensions;
543
-
544
- if (!allowed.length){return true;}
545
-
546
- for (var i=0; i<allowed.length; i++){
547
- if (allowed[i].toLowerCase() == ext){ return true;}
548
- }
549
-
550
- return false;
551
- },
552
- _formatSize: function(bytes){
553
- var i = -1;
554
- do {
555
- bytes = bytes / 1024;
556
- i++;
557
- } while (bytes > 99);
558
-
559
- return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
560
- },
561
- _wrapCallbacks: function() {
562
- var self, safeCallback;
563
-
564
- self = this;
565
-
566
- safeCallback = function(callback, args) {
567
- try {
568
- return callback.apply(self, args);
569
- }
570
- catch (exception) {
571
- self.log("Caught " + exception + " in callback: " + callback);
572
- }
573
- }
574
-
575
- for (var prop in this._options) {
576
- if (/^on[A-Z]/.test(prop)) {
577
- (function() {
578
- var oldCallback = self._options[prop];
579
- self._options[prop] = function() {
580
- return safeCallback(oldCallback, arguments);
581
- }
582
- }());
583
- }
584
- }
585
- }
586
- };
587
-
588
-
589
- /**
590
- * Class that creates upload widget with drag-and-drop and file list
591
- * @inherits qq.FileUploaderBasic
592
- */
593
- qq.FileUploader = function(o){
594
- // call parent constructor
595
- qq.FileUploaderBasic.apply(this, arguments);
596
-
597
- // additional options
598
- qq.extend(this._options, {
599
- element: null,
600
- // if set, will be used instead of qq-upload-list in template
601
- listElement: null,
602
- dragText: 'Drop files here to upload',
603
- extraDropzones : [],
604
- hideDropzones : true,
605
- disableDefaultDropzone: false,
606
- uploadButtonText: 'Upload a file',
607
- cancelButtonText: 'Cancel',
608
- failUploadText: 'Upload failed',
609
-
610
- template: '<div class="qq-uploader">' +
611
- (!this._options.disableDefaultDropzone ? '<div class="qq-upload-drop-area"><span>{dragText}</span></div>' : '') +
612
- (!this._options.button ? '<div class="qq-upload-button">{uploadButtonText}</div>' : '') +
613
- (!this._options.listElement ? '<ul class="qq-upload-list"></ul>' : '') +
614
- '</div>',
615
-
616
- // template for one item in file list
617
- fileTemplate: '<li>' +
618
- '<div class="qq-progress-bar"></div>' +
619
- '<span class="qq-upload-spinner"></span>' +
620
- '<span class="qq-upload-finished"></span>' +
621
- '<span class="qq-upload-file"></span>' +
622
- '<span class="qq-upload-size"></span>' +
623
- '<a class="qq-upload-cancel" href="#">{cancelButtonText}</a>' +
624
- '<span class="qq-upload-failed-text">{failUploadtext}</span>' +
625
- '</li>',
626
-
627
- classes: {
628
- // used to get elements from templates
629
- button: 'qq-upload-button',
630
- drop: 'qq-upload-drop-area',
631
- dropActive: 'qq-upload-drop-area-active',
632
- dropDisabled: 'qq-upload-drop-area-disabled',
633
- list: 'qq-upload-list',
634
- progressBar: 'qq-progress-bar',
635
- file: 'qq-upload-file',
636
- spinner: 'qq-upload-spinner',
637
- finished: 'qq-upload-finished',
638
- size: 'qq-upload-size',
639
- cancel: 'qq-upload-cancel',
640
- failText: 'qq-upload-failed-text',
641
-
642
- // added to list item <li> when upload completes
643
- // used in css to hide progress spinner
644
- success: 'qq-upload-success',
645
- fail: 'qq-upload-fail',
646
-
647
- successIcon: null,
648
- failIcon: null
649
- },
650
- extraMessages: {
651
- formatProgress: "{percent}% of {total_size}",
652
- tooManyFilesError: "You may only drop one file"
653
- },
654
- failedUploadTextDisplay: {
655
- mode: 'default', //default, custom, or none
656
- maxChars: 50,
657
- responseProperty: 'error',
658
- enableTooltip: true
659
- }
660
- });
661
- // overwrite options with user supplied
662
- qq.extend(this._options, o);
663
- this._wrapCallbacks();
664
-
665
- qq.extend(this._options.messages, this._options.extraMessages);
666
-
667
- // overwrite the upload button text if any
668
- // same for the Cancel button and Fail message text
669
- this._options.template = this._options.template.replace(/\{dragText\}/g, this._options.dragText);
670
- this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.uploadButtonText);
671
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.cancelButtonText);
672
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{failUploadtext\}/g, this._options.failUploadText);
673
-
674
- this._element = this._options.element;
675
- this._element.innerHTML = this._options.template;
676
- this._listElement = this._options.listElement || this._find(this._element, 'list');
677
-
678
- this._classes = this._options.classes;
679
-
680
- if (!this._button) {
681
- this._button = this._createUploadButton(this._find(this._element, 'button'));
682
- }
683
-
684
- this._bindCancelEvent();
685
- this._setupDragDrop();
686
- };
687
-
688
- // inherit from Basic Uploader
689
- qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
690
-
691
- qq.extend(qq.FileUploader.prototype, {
692
- clearStoredFiles: function() {
693
- qq.FileUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
694
- this._listElement.innerHTML = "";
695
- },
696
- addExtraDropzone: function(element){
697
- this._setupExtraDropzone(element);
698
- },
699
- removeExtraDropzone: function(element){
700
- var dzs = this._options.extraDropzones;
701
- for(var i in dzs) if (dzs[i] === element) return this._options.extraDropzones.splice(i,1);
702
- },
703
- _leaving_document_out: function(e){
704
- return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
705
- || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
706
- },
707
- _storeFileForLater: function(id) {
708
- qq.FileUploaderBasic.prototype._storeFileForLater.apply(this, arguments);
709
- var item = this._getItemByFileId(id);
710
- this._find(item, 'spinner').style.display = "none";
711
- },
712
- /**
713
- * Gets one of the elements listed in this._options.classes
714
- **/
715
- _find: function(parent, type){
716
- var element = qq.getByClass(parent, this._options.classes[type])[0];
717
- if (!element){
718
- throw new Error('element not found ' + type);
719
- }
720
-
721
- return element;
722
- },
723
- _setupExtraDropzone: function(element){
724
- this._options.extraDropzones.push(element);
725
- this._setupDropzone(element);
726
- },
727
- _setupDropzone: function(dropArea){
728
- var self = this;
729
-
730
- var dz = new qq.UploadDropZone({
731
- element: dropArea,
732
- onEnter: function(e){
733
- qq.addClass(dropArea, self._classes.dropActive);
734
- e.stopPropagation();
735
- },
736
- onLeave: function(e){
737
- //e.stopPropagation();
738
- },
739
- onLeaveNotDescendants: function(e){
740
- qq.removeClass(dropArea, self._classes.dropActive);
741
- },
742
- onDrop: function(e){
743
- if (self._options.hideDropzones) {
744
- dropArea.style.display = 'none';
745
- }
746
- qq.removeClass(dropArea, self._classes.dropActive);
747
- if (e.dataTransfer.files.length > 1 && !self._options.multiple) {
748
- self._error('tooManyFilesError', "");
749
- }
750
- else {
751
- self._uploadFileList(e.dataTransfer.files);
752
- }
753
- }
754
- });
755
-
756
- this.addDisposer(function() { dz.dispose(); });
757
-
758
- if (this._options.hideDropzones) {
759
- dropArea.style.display = 'none';
760
- }
761
- },
762
- _setupDragDrop: function(){
763
- var self = this;
764
-
765
- if (!this._options.disableDefaultDropzone) {
766
- var dropArea = this._find(this._element, 'drop');
767
- this._options.extraDropzones.push(dropArea);
768
- }
769
-
770
- var dropzones = this._options.extraDropzones;
771
- var i;
772
- for (i=0; i < dropzones.length; i++){
773
- this._setupDropzone(dropzones[i]);
774
- }
775
-
776
- // IE <= 9 does not support the File API used for drag+drop uploads
777
- // Any volunteers to enable & test this for IE10?
778
- if (!this._options.disableDefaultDropzone && !qq.ie()) {
779
- this._attach(document, 'dragenter', function(e){
780
- if (qq.hasClass(dropArea, self._classes.dropDisabled)) return;
781
-
782
- dropArea.style.display = 'block';
783
- for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'block'; }
784
-
785
- });
786
- }
787
- this._attach(document, 'dragleave', function(e){
788
- // only fire when leaving document out
789
- if (self._options.hideDropzones && qq.FileUploader.prototype._leaving_document_out(e)) {
790
- for (i=0; i < dropzones.length; i++) {
791
- dropzones[i].style.display = 'none';
792
- }
793
- }
794
- });
795
- qq.attach(document, 'drop', function(e){
796
- if (self._options.hideDropzones) {
797
- for (i=0; i < dropzones.length; i++){
798
- dropzones[i].style.display = 'none';
799
- }
800
- }
801
- e.preventDefault();
802
- });
803
- },
804
- _onSubmit: function(id, fileName){
805
- qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
806
- this._addToList(id, fileName);
807
- },
808
- // Update the progress bar & percentage as the file is uploaded
809
- _onProgress: function(id, fileName, loaded, total){
810
- qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
811
-
812
- var item = this._getItemByFileId(id);
813
-
814
- if (loaded === total) {
815
- var cancelLink = this._find(item, 'cancel');
816
- cancelLink.style.display = 'none';
817
- }
818
-
819
- var size = this._find(item, 'size');
820
- size.style.display = 'inline';
821
-
822
- var text;
823
- var percent = Math.round(loaded / total * 100);
824
-
825
- if (loaded != total) {
826
- // If still uploading, display percentage
827
- text = this._formatProgress(loaded, total);
828
- } else {
829
- // If complete, just display final size
830
- text = this._formatSize(total);
831
- }
832
-
833
- // Update progress bar <span> tag
834
- this._find(item, 'progressBar').style.width = percent + '%';
835
-
836
- qq.setText(size, text);
837
- },
838
- _onComplete: function(id, fileName, result){
839
- qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
840
-
841
- var item = this._getItemByFileId(id);
842
-
843
- qq.remove(this._find(item, 'progressBar'));
844
-
845
- if (!this._options.disableCancelForFormUploads || qq.UploadHandlerXhr.isSupported()) {
846
- qq.remove(this._find(item, 'cancel'));
847
- }
848
- qq.remove(this._find(item, 'spinner'));
849
-
850
- if (result.success){
851
- qq.addClass(item, this._classes.success);
852
- if (this._classes.successIcon) {
853
- this._find(item, 'finished').style.display = "inline-block";
854
- qq.addClass(item, this._classes.successIcon)
855
- }
856
- } else {
857
- qq.addClass(item, this._classes.fail);
858
- if (this._classes.failIcon) {
859
- this._find(item, 'finished').style.display = "inline-block";
860
- qq.addClass(item, this._classes.failIcon)
861
- }
862
- this._controlFailureTextDisplay(item, result);
863
- }
864
- },
865
- _onUpload: function(id, fileName, xhr){
866
- qq.FileUploaderBasic.prototype._onUpload.apply(this, arguments);
867
-
868
- var item = this._getItemByFileId(id);
869
-
870
- if (qq.UploadHandlerXhr.isSupported()) {
871
- this._find(item, 'progressBar').style.display = "block";
872
- }
873
-
874
- var spinnerEl = this._find(item, 'spinner');
875
- if (spinnerEl.style.display == "none") {
876
- spinnerEl.style.display = "inline-block";
877
- }
878
- },
879
- _addToList: function(id, fileName){
880
- var item = qq.toElement(this._options.fileTemplate);
881
- if (this._options.disableCancelForFormUploads && !qq.UploadHandlerXhr.isSupported()) {
882
- var cancelLink = this._find(item, 'cancel');
883
- qq.remove(cancelLink);
884
- }
885
-
886
- item.qqFileId = id;
887
-
888
- var fileElement = this._find(item, 'file');
889
- qq.setText(fileElement, this._formatFileName(fileName));
890
- this._find(item, 'size').style.display = 'none';
891
- if (!this._options.multiple) this._clearList();
892
- this._listElement.appendChild(item);
893
- },
894
- _clearList: function(){
895
- this._listElement.innerHTML = '';
896
- this.clearStoredFiles();
897
- },
898
- _getItemByFileId: function(id){
899
- var item = this._listElement.firstChild;
900
-
901
- // there can't be txt nodes in dynamically created list
902
- // and we can use nextSibling
903
- while (item){
904
- if (item.qqFileId == id) return item;
905
- item = item.nextSibling;
906
- }
907
- },
908
- /**
909
- * delegate click event for cancel link
910
- **/
911
- _bindCancelEvent: function(){
912
- var self = this,
913
- list = this._listElement;
914
-
915
- this._attach(list, 'click', function(e){
916
- e = e || window.event;
917
- var target = e.target || e.srcElement;
918
-
919
- if (qq.hasClass(target, self._classes.cancel)){
920
- qq.preventDefault(e);
921
-
922
- var item = target.parentNode;
923
- self._handler.cancel(item.qqFileId);
924
- qq.remove(item);
925
- }
926
- });
927
- },
928
- _formatProgress: function (uploadedSize, totalSize) {
929
- var message = this._options.messages.formatProgress;
930
- function r(name, replacement) { message = message.replace(name, replacement); }
931
-
932
- r('{percent}', Math.round(uploadedSize / totalSize * 100));
933
- r('{total_size}', this._formatSize(totalSize));
934
- return message;
935
- },
936
- _controlFailureTextDisplay: function(item, response) {
937
- var mode, maxChars, responseProperty, failureReason, shortFailureReason;
938
-
939
- mode = this._options.failedUploadTextDisplay.mode;
940
- maxChars = this._options.failedUploadTextDisplay.maxChars;
941
- responseProperty = this._options.failedUploadTextDisplay.responseProperty;
942
-
943
- if (mode === 'custom') {
944
- var failureReason = response[responseProperty];
945
- if (failureReason) {
946
- if (failureReason.length > maxChars) {
947
- shortFailureReason = failureReason.substring(0, maxChars) + '...';
948
- }
949
- qq.setText(this._find(item, 'failText'), shortFailureReason || failureReason);
950
-
951
- if (this._options.failedUploadTextDisplay.enableTooltip) {
952
- this._showTooltip(item, failureReason);
953
- }
954
- }
955
- else {
956
- this.log("'" + responseProperty + "' is not a valid property on the server response.");
957
- }
958
- }
959
- else if (mode === 'none') {
960
- qq.remove(this._find(item, 'failText'));
961
- }
962
- else if (mode !== 'default') {
963
- this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid");
964
- }
965
- },
966
- //TODO turn this into a real tooltip, with click trigger (so it is usable on mobile devices). See case #355 for details.
967
- _showTooltip: function(item, text) {
968
- item.title = text;
969
- }
970
- });
971
-
972
- qq.UploadDropZone = function(o){
973
- this._options = {
974
- element: null,
975
- onEnter: function(e){},
976
- onLeave: function(e){},
977
- // is not fired when leaving element by hovering descendants
978
- onLeaveNotDescendants: function(e){},
979
- onDrop: function(e){}
980
- };
981
- qq.extend(this._options, o);
982
- qq.extend(this, qq.DisposeSupport);
983
-
984
- this._element = this._options.element;
985
-
986
- this._disableDropOutside();
987
- this._attachEvents();
988
- };
989
-
990
- qq.UploadDropZone.prototype = {
991
- _dragover_should_be_canceled: function(){
992
- return qq.safari() || (qq.firefox() && qq.windows());
993
- },
994
- _disableDropOutside: function(e){
995
- // run only once for all instances
996
- if (!qq.UploadDropZone.dropOutsideDisabled ){
997
-
998
- // for these cases we need to catch onDrop to reset dropArea
999
- if (this._dragover_should_be_canceled){
1000
- qq.attach(document, 'dragover', function(e){
1001
- e.preventDefault();
1002
- });
1003
- } else {
1004
- qq.attach(document, 'dragover', function(e){
1005
- if (e.dataTransfer){
1006
- e.dataTransfer.dropEffect = 'none';
1007
- e.preventDefault();
1008
- }
1009
- });
1010
- }
1011
-
1012
- qq.UploadDropZone.dropOutsideDisabled = true;
1013
- }
1014
- },
1015
- _attachEvents: function(){
1016
- var self = this;
1017
-
1018
- self._attach(self._element, 'dragover', function(e){
1019
- if (!self._isValidFileDrag(e)) return;
1020
-
1021
- var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
1022
- if (effect == 'move' || effect == 'linkMove'){
1023
- e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
1024
- } else {
1025
- e.dataTransfer.dropEffect = 'copy'; // for Chrome
1026
- }
1027
-
1028
- e.stopPropagation();
1029
- e.preventDefault();
1030
- });
1031
-
1032
- self._attach(self._element, 'dragenter', function(e){
1033
- if (!self._isValidFileDrag(e)) return;
1034
-
1035
- self._options.onEnter(e);
1036
- });
1037
-
1038
- self._attach(self._element, 'dragleave', function(e){
1039
- if (!self._isValidFileDrag(e)) return;
1040
-
1041
- self._options.onLeave(e);
1042
-
1043
- var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
1044
- // do not fire when moving a mouse over a descendant
1045
- if (qq.contains(this, relatedTarget)) return;
1046
-
1047
- self._options.onLeaveNotDescendants(e);
1048
- });
1049
-
1050
- self._attach(self._element, 'drop', function(e){
1051
- if (!self._isValidFileDrag(e)) return;
1052
-
1053
- e.preventDefault();
1054
- self._options.onDrop(e);
1055
- });
1056
- },
1057
- _isValidFileDrag: function(e){
1058
- // e.dataTransfer currently causing IE errors
1059
- // IE9 does NOT support file API, so drag-and-drop is not possible
1060
- // IE10 should work, but currently has not been tested - any volunteers?
1061
- if (qq.ie()) return false;
1062
-
1063
- var dt = e.dataTransfer,
1064
- // do not check dt.types.contains in webkit, because it crashes safari 4
1065
- isSafari = qq.safari();
1066
-
1067
- // dt.effectAllowed is none in Safari 5
1068
- // dt.types.contains check is for firefox
1069
- return dt && dt.effectAllowed != 'none' &&
1070
- (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
1071
-
1072
- }
1073
- };
1074
-
1075
- qq.UploadButton = function(o){
1076
- this._options = {
1077
- element: null,
1078
- // if set to true adds multiple attribute to file input
1079
- multiple: false,
1080
- acceptFiles: null,
1081
- // name attribute of file input
1082
- name: 'file',
1083
- onChange: function(input){},
1084
- hoverClass: 'qq-upload-button-hover',
1085
- focusClass: 'qq-upload-button-focus'
1086
- };
1087
-
1088
- qq.extend(this._options, o);
1089
- qq.extend(this, qq.DisposeSupport);
1090
-
1091
- this._element = this._options.element;
1092
-
1093
- // make button suitable container for input
1094
- qq.css(this._element, {
1095
- position: 'relative',
1096
- overflow: 'hidden',
1097
- // Make sure browse button is in the right side
1098
- // in Internet Explorer
1099
- direction: 'ltr'
1100
- });
1101
-
1102
- this._input = this._createInput();
1103
- };
1104
-
1105
- qq.UploadButton.prototype = {
1106
- /* returns file input element */
1107
- getInput: function(){
1108
- return this._input;
1109
- },
1110
- /* cleans/recreates the file input */
1111
- reset: function(){
1112
- if (this._input.parentNode){
1113
- qq.remove(this._input);
1114
- }
1115
-
1116
- qq.removeClass(this._element, this._options.focusClass);
1117
- this._input = this._createInput();
1118
- },
1119
- _createInput: function(){
1120
- var input = document.createElement("input");
1121
-
1122
- if (this._options.multiple){
1123
- input.setAttribute("multiple", "multiple");
1124
- }
1125
-
1126
- if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles);
1127
-
1128
- input.setAttribute("type", "file");
1129
- input.setAttribute("name", this._options.name);
1130
-
1131
- qq.css(input, {
1132
- position: 'absolute',
1133
- // in Opera only 'browse' button
1134
- // is clickable and it is located at
1135
- // the right side of the input
1136
- right: 0,
1137
- top: 0,
1138
- fontFamily: 'Arial',
1139
- // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
1140
- fontSize: '118px',
1141
- margin: 0,
1142
- padding: 0,
1143
- cursor: 'pointer',
1144
- opacity: 0
1145
- });
1146
-
1147
- this._element.appendChild(input);
1148
-
1149
- var self = this;
1150
- this._attach(input, 'change', function(){
1151
- self._options.onChange(input);
1152
- });
1153
-
1154
- this._attach(input, 'mouseover', function(){
1155
- qq.addClass(self._element, self._options.hoverClass);
1156
- });
1157
- this._attach(input, 'mouseout', function(){
1158
- qq.removeClass(self._element, self._options.hoverClass);
1159
- });
1160
- this._attach(input, 'focus', function(){
1161
- qq.addClass(self._element, self._options.focusClass);
1162
- });
1163
- this._attach(input, 'blur', function(){
1164
- qq.removeClass(self._element, self._options.focusClass);
1165
- });
1166
-
1167
- // IE and Opera, unfortunately have 2 tab stops on file input
1168
- // which is unacceptable in our case, disable keyboard access
1169
- if (window.attachEvent){
1170
- // it is IE or Opera
1171
- input.setAttribute('tabIndex', "-1");
1172
- }
1173
-
1174
- return input;
1175
- }
1176
- };
1177
-
1178
- /**
1179
- * Class for uploading files, uploading itself is handled by child classes
1180
- */
1181
- qq.UploadHandlerAbstract = function(o){
1182
- // Default options, can be overridden by the user
1183
- this._options = {
1184
- debug: false,
1185
- action: '/upload.php',
1186
- // maximum number of concurrent uploads
1187
- maxConnections: 999,
1188
- onProgress: function(id, fileName, loaded, total){},
1189
- onComplete: function(id, fileName, response){},
1190
- onCancel: function(id, fileName){},
1191
- onUpload: function(id, fileName, xhr){}
1192
- };
1193
- qq.extend(this._options, o);
1194
-
1195
- this._queue = [];
1196
- // params for files in queue
1197
- this._params = [];
1198
- };
1199
- qq.UploadHandlerAbstract.prototype = {
1200
- log: function(str){
1201
- if (this._options.debug && window.console) console.log('[uploader] ' + str);
1202
- },
1203
- /**
1204
- * Adds file or file input to the queue
1205
- * @returns id
1206
- **/
1207
- add: function(file){},
1208
- /**
1209
- * Sends the file identified by id and additional query params to the server
1210
- */
1211
- upload: function(id, params){
1212
- var len = this._queue.push(id);
1213
-
1214
- var copy = {};
1215
- qq.extend(copy, params);
1216
- this._params[id] = copy;
1217
-
1218
- // if too many active uploads, wait...
1219
- if (len <= this._options.maxConnections){
1220
- this._upload(id, this._params[id]);
1221
- }
1222
- },
1223
- /**
1224
- * Cancels file upload by id
1225
- */
1226
- cancel: function(id){
1227
- this._cancel(id);
1228
- this._dequeue(id);
1229
- },
1230
- /**
1231
- * Cancells all uploads
1232
- */
1233
- cancelAll: function(){
1234
- for (var i=0; i<this._queue.length; i++){
1235
- this._cancel(this._queue[i]);
1236
- }
1237
- this._queue = [];
1238
- },
1239
- /**
1240
- * Returns name of the file identified by id
1241
- */
1242
- getName: function(id){},
1243
- /**
1244
- * Returns size of the file identified by id
1245
- */
1246
- getSize: function(id){},
1247
- /**
1248
- * Returns id of files being uploaded or
1249
- * waiting for their turn
1250
- */
1251
- getQueue: function(){
1252
- return this._queue;
1253
- },
1254
- /**
1255
- * Actual upload method
1256
- */
1257
- _upload: function(id){},
1258
- /**
1259
- * Actual cancel method
1260
- */
1261
- _cancel: function(id){},
1262
- /**
1263
- * Removes element from queue, starts upload of next
1264
- */
1265
- _dequeue: function(id){
1266
- var i = qq.indexOf(this._queue, id);
1267
- this._queue.splice(i, 1);
1268
-
1269
- var max = this._options.maxConnections;
1270
-
1271
- if (this._queue.length >= max && i < max){
1272
- var nextId = this._queue[max-1];
1273
- this._upload(nextId, this._params[nextId]);
1274
- }
1275
- }
1276
- };
1277
-
1278
- /**
1279
- * Class for uploading files using form and iframe
1280
- * @inherits qq.UploadHandlerAbstract
1281
- */
1282
- qq.UploadHandlerForm = function(o){
1283
- qq.UploadHandlerAbstract.apply(this, arguments);
1284
-
1285
- this._inputs = {};
1286
- this._detach_load_events = {};
1287
- };
1288
- // @inherits qq.UploadHandlerAbstract
1289
- qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
1290
-
1291
- qq.extend(qq.UploadHandlerForm.prototype, {
1292
- add: function(fileInput){
1293
- fileInput.setAttribute('name', this._options.inputName);
1294
- var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
1295
-
1296
- this._inputs[id] = fileInput;
1297
-
1298
- // remove file input from DOM
1299
- if (fileInput.parentNode){
1300
- qq.remove(fileInput);
1301
- }
1302
-
1303
- return id;
1304
- },
1305
- getName: function(id){
1306
- // get input value and remove path to normalize
1307
- return this._inputs[id].value.replace(/.*(\/|\\)/, "");
1308
- },
1309
- _cancel: function(id){
1310
- this._options.onCancel(id, this.getName(id));
1311
-
1312
- delete this._inputs[id];
1313
- delete this._detach_load_events[id];
1314
-
1315
- var iframe = document.getElementById(id);
1316
- if (iframe){
1317
- // to cancel request set src to something else
1318
- // we use src="javascript:false;" because it doesn't
1319
- // trigger ie6 prompt on https
1320
- iframe.setAttribute('src', 'javascript:false;');
1321
-
1322
- qq.remove(iframe);
1323
- }
1324
- },
1325
- _upload: function(id, params){
1326
- this._options.onUpload(id, this.getName(id), false);
1327
- var input = this._inputs[id];
1328
-
1329
- if (!input){
1330
- throw new Error('file with passed id was not added, or already uploaded or cancelled');
1331
- }
1332
-
1333
- var fileName = this.getName(id);
1334
- params[this._options.inputName] = fileName;
1335
-
1336
- var iframe = this._createIframe(id);
1337
- var form = this._createForm(iframe, params);
1338
- form.appendChild(input);
1339
-
1340
- var self = this;
1341
- this._attachLoadEvent(iframe, function(){
1342
- self.log('iframe loaded');
1343
-
1344
- var response = self._getIframeContentJSON(iframe);
1345
-
1346
- self._options.onComplete(id, fileName, response);
1347
- self._dequeue(id);
1348
-
1349
- delete self._inputs[id];
1350
- // timeout added to fix busy state in FF3.6
1351
- setTimeout(function(){
1352
- self._detach_load_events[id]();
1353
- delete self._detach_load_events[id];
1354
- qq.remove(iframe);
1355
- }, 1);
1356
- });
1357
-
1358
- form.submit();
1359
- qq.remove(form);
1360
-
1361
- return id;
1362
- },
1363
- _attachLoadEvent: function(iframe, callback){
1364
- this._detach_load_events[iframe.id] = qq.attach(iframe, 'load', function(){
1365
- // when we remove iframe from dom
1366
- // the request stops, but in IE load
1367
- // event fires
1368
- if (!iframe.parentNode){
1369
- return;
1370
- }
1371
-
1372
- try {
1373
- // fixing Opera 10.53
1374
- if (iframe.contentDocument &&
1375
- iframe.contentDocument.body &&
1376
- iframe.contentDocument.body.innerHTML == "false"){
1377
- // In Opera event is fired second time
1378
- // when body.innerHTML changed from false
1379
- // to server response approx. after 1 sec
1380
- // when we upload file with iframe
1381
- return;
1382
- }
1383
- }
1384
- catch (error) {
1385
- //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
1386
- }
1387
-
1388
- callback();
1389
- });
1390
- },
1391
- /**
1392
- * Returns json object received by iframe from server.
1393
- */
1394
- _getIframeContentJSON: function(iframe){
1395
- //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
1396
- try {
1397
- // iframe.contentWindow.document - for IE<7
1398
- var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
1399
- response;
1400
-
1401
- var innerHTML = doc.body.innerHTML;
1402
- this.log("converting iframe's innerHTML to JSON");
1403
- this.log("innerHTML = " + innerHTML);
1404
- //plain text response may be wrapped in <pre> tag
1405
- if (innerHTML.slice(0, 5).toLowerCase() == '<pre>' && innerHTML.slice(-6).toLowerCase() == '</pre>') {
1406
- innerHTML = doc.body.firstChild.firstChild.nodeValue;
1407
- }
1408
- response = eval("(" + innerHTML + ")");
1409
- } catch(err){
1410
- response = {success: false};
1411
- }
1412
-
1413
- return response;
1414
- },
1415
- /**
1416
- * Creates iframe with unique name
1417
- */
1418
- _createIframe: function(id){
1419
- // We can't use following code as the name attribute
1420
- // won't be properly registered in IE6, and new window
1421
- // on form submit will open
1422
- // var iframe = document.createElement('iframe');
1423
- // iframe.setAttribute('name', id);
1424
-
1425
- var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
1426
- // src="javascript:false;" removes ie6 prompt on https
1427
-
1428
- iframe.setAttribute('id', id);
1429
-
1430
- iframe.style.display = 'none';
1431
- document.body.appendChild(iframe);
1432
-
1433
- return iframe;
1434
- },
1435
- /**
1436
- * Creates form, that will be submitted to iframe
1437
- */
1438
- _createForm: function(iframe, params){
1439
- // We can't use the following code in IE6
1440
- // var form = document.createElement('form');
1441
- // form.setAttribute('method', 'post');
1442
- // form.setAttribute('enctype', 'multipart/form-data');
1443
- // Because in this case file won't be attached to request
1444
- var protocol = this._options.demoMode ? "GET" : "POST"
1445
- var form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>');
1446
-
1447
- var queryString = qq.obj2url(params, this._options.action);
1448
-
1449
- form.setAttribute('action', queryString);
1450
- form.setAttribute('target', iframe.name);
1451
- form.style.display = 'none';
1452
- document.body.appendChild(form);
1453
-
1454
- return form;
1455
- }
1456
- });
1457
-
1458
- /**
1459
- * Class for uploading files using xhr
1460
- * @inherits qq.UploadHandlerAbstract
1461
- */
1462
- qq.UploadHandlerXhr = function(o){
1463
- qq.UploadHandlerAbstract.apply(this, arguments);
1464
-
1465
- this._files = [];
1466
- this._xhrs = [];
1467
-
1468
- // current loaded size in bytes for each file
1469
- this._loaded = [];
1470
- };
1471
-
1472
- // static method
1473
- qq.UploadHandlerXhr.isSupported = function(){
1474
- var input = document.createElement('input');
1475
- input.type = 'file';
1476
-
1477
- return (
1478
- 'multiple' in input &&
1479
- typeof File != "undefined" &&
1480
- typeof FormData != "undefined" &&
1481
- typeof (new XMLHttpRequest()).upload != "undefined" );
1482
- };
1483
-
1484
- // @inherits qq.UploadHandlerAbstract
1485
- qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
1486
-
1487
- qq.extend(qq.UploadHandlerXhr.prototype, {
1488
- /**
1489
- * Adds file to the queue
1490
- * Returns id to use with upload, cancel
1491
- **/
1492
- add: function(file){
1493
- if (!(file instanceof File)){
1494
- throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
1495
- }
1496
-
1497
- return this._files.push(file) - 1;
1498
- },
1499
- getName: function(id){
1500
- var file = this._files[id];
1501
- // fix missing name in Safari 4
1502
- //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
1503
- return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
1504
- },
1505
- getSize: function(id){
1506
- var file = this._files[id];
1507
- return file.fileSize != null ? file.fileSize : file.size;
1508
- },
1509
- /**
1510
- * Returns uploaded bytes for file identified by id
1511
- */
1512
- getLoaded: function(id){
1513
- return this._loaded[id] || 0;
1514
- },
1515
- /**
1516
- * Sends the file identified by id and additional query params to the server
1517
- * @param {Object} params name-value string pairs
1518
- */
1519
- _upload: function(id, params){
1520
- this._options.onUpload(id, this.getName(id), true);
1521
-
1522
- var file = this._files[id],
1523
- name = this.getName(id),
1524
- size = this.getSize(id);
1525
-
1526
- this._loaded[id] = 0;
1527
-
1528
- var xhr = this._xhrs[id] = new XMLHttpRequest();
1529
- var self = this;
1530
-
1531
- xhr.upload.onprogress = function(e){
1532
- if (e.lengthComputable){
1533
- self._loaded[id] = e.loaded;
1534
- self._options.onProgress(id, name, e.loaded, e.total);
1535
- }
1536
- };
1537
-
1538
- xhr.onreadystatechange = function(){
1539
- if (xhr.readyState == 4){
1540
- self._onComplete(id, xhr);
1541
- }
1542
- };
1543
-
1544
- // build query string
1545
- params = params || {};
1546
- params[this._options.inputName] = name;
1547
- var queryString = qq.obj2url(params, this._options.action);
1548
-
1549
- var protocol = this._options.demoMode ? "GET" : "POST";
1550
- xhr.open(protocol, queryString, true);
1551
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
1552
- xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
1553
- xhr.setRequestHeader("Cache-Control", "no-cache");
1554
- if (this._options.forceMultipart) {
1555
- var formData = new FormData();
1556
- formData.append(this._options.inputName, file);
1557
- file = formData;
1558
- } else {
1559
- xhr.setRequestHeader("Content-Type", "application/octet-stream");
1560
- //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
1561
- xhr.setRequestHeader("X-Mime-Type",file.type );
1562
- }
1563
- for (key in this._options.customHeaders){
1564
- xhr.setRequestHeader(key, this._options.customHeaders[key]);
1565
- };
1566
- xhr.send(file);
1567
- },
1568
- _onComplete: function(id, xhr){
1569
- "use strict";
1570
- // the request was aborted/cancelled
1571
- if (!this._files[id]) { return; }
1572
-
1573
- var name = this.getName(id);
1574
- var size = this.getSize(id);
1575
- var response; //the parsed JSON response from the server, or the empty object if parsing failed.
1576
-
1577
- this._options.onProgress(id, name, size, size);
1578
-
1579
- this.log("xhr - server response received");
1580
- this.log("responseText = " + xhr.responseText);
1581
-
1582
- try {
1583
- if (typeof JSON.parse === "function") {
1584
- response = JSON.parse(xhr.responseText);
1585
- } else {
1586
- response = eval("(" + xhr.responseText + ")");
1587
- }
1588
- } catch(err){
1589
- response = {};
1590
- }
1591
- if (xhr.status !== 200){
1592
- this._options.onError(id, name, "XHR returned response code " + xhr.status);
1593
- }
1594
- this._options.onComplete(id, name, response);
1595
-
1596
- this._xhrs[id] = null;
1597
- this._dequeue(id);
1598
- },
1599
- _cancel: function(id){
1600
- this._options.onCancel(id, this.getName(id));
1601
-
1602
- this._files[id] = null;
1603
-
1604
- if (this._xhrs[id]){
1605
- this._xhrs[id].abort();
1606
- this._xhrs[id] = null;
1607
- }
1608
- }
1609
- });
1610
-
1611
- /**
1612
- * A generic module which supports object disposing in dispose() method.
1613
- * */
1614
- qq.DisposeSupport = {
1615
- _disposers: [],
1616
-
1617
- /** Run all registered disposers */
1618
- dispose: function() {
1619
- var disposer;
1620
- while (disposer = this._disposers.shift()) {
1621
- disposer();
1622
- }
1623
- },
1624
-
1625
- /** Add disposer to the collection */
1626
- addDisposer: function(disposeFunction) {
1627
- this._disposers.push(disposeFunction);
1628
- },
1629
-
1630
- /** Attach event handler and register de-attacher as a disposer */
1631
- _attach: function() {
1632
- this.addDisposer(qq.attach.apply(this, arguments));
1633
- }
1634
- };
1
+ //= require fine_uploader/header
2
+ //= require fine_uploader/util
3
+ //= require fine_uploader/button
4
+ //= require fine_uploader/handler.base
5
+ //= require fine_uploader/handler.form
6
+ //= require fine_uploader/handler.xhr
7
+ //= require fine_uploader/uploader.basic
8
+ //= require fine_uploader/dnd.js
9
+ //= require fine_uploader/uploader
10
+ //= require fine_uploader/jquery-plugin