PageSourceSearch

https://www.ville-sussargues.fr/openinfoliveRessources/js/fileuploader/fileuploader.js

js ville-sussargues.fr collected 2026-10-02 20:44:38 UTC 47,635 bytes, 1,432 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.