PageSourceSearch

https://www.mentedigitale.org/wp-content/plugins/accesspress-anonymous-post-pro/js/fileuploader.js?ver=6.4.12

js mentedigitale.org collected 2026-10-02 04:13:52 UTC 48,693 bytes, 1,425 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        // events
284        // return false to cancel submit
285        onSubmit: function(id, fileName){},
286        onProgress: function(id, fileName, loaded, total){},
287        onComplete: function(id, fileName, responseJSON){},
288        onCancel: function(id, fileName){},
289        onUpload: function(id, fileName, xhr){},
290		onError: function(id, fileName, xhr) {},
291        // messages                
292        messages: {
293            typeError: "Unfortunately, the file(s) you selected weren't the type we were expecting.  {file}
293 has invalid extension. Only {extensions} are allowed.",
294            sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
295            minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
296            emptyError: "{file} is empty, please select files again without it.",
297            onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."            
298        },
299        showMessage: function(message){
300            alert(message);
301        },
302        inputName: 'qqfile',
303        extraDropzones : []
304    };
305    qq.extend(this._options, o);
306    qq.extend(this, qq.DisposeSupport);
307
308    // number of files being uploaded
309    this._filesInProgress = 0;
310    this._handler = this._createUploadHandler();
311    
312    if (this._options.button){ 
313        this._button = this._createUploadButton(this._options.button);
314    }
315                        
316    this._preventLeaveInProgress();         
317};
318   
319qq.FileUploaderBasic.prototype = {
320    setParams: function(params){
321        this._options.params = params;
322    },
323    getInProgress: function(){
324        return this._filesInProgress;         
325    },
326    _createUploadButton: function(element){
327        var self = this;
328        
329        var button = new qq.UploadButton({
330            element: element,
331            multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
332            acceptFiles: this._options.acceptFiles,
333            onChange: function(input){
334                self._onInputChange(input);
335            }        
336        });
337
338        this.addDisposer(function() { button.dispose(); });
339        return button;
340    },    
341    _createUploadHandler: function(){
342        var self = this,
343            handlerClass;        
344        
345        if(qq.UploadHandlerXhr.isSupported()){           
346            handlerClass = 'UploadHandlerXhr';                        
347        } else {
348            handlerClass = 'UploadHandlerForm';
349        }
350
351        var handler = new qq[handlerClass]({
352            debug: this._options.debug,
353            action: this._options.action,
354            encoding: this._options.encoding,
355            maxConnections: this._options.maxConnections,   
356            customHeaders: this._options.customHeaders,
357            inputName: this._options.inputName,
358            extraDropzones: this._options.extraDropzones,
359            onProgress: function(id, fileName, loaded, total){                
360                self._onProgress(id, fileName, loaded, total);
361                self._options.onProgress(id, fileName, loaded, total);                    
362            },            
363            onComplete: function(id, fileName, result){
364                self._onComplete(id, fileName, result);
365                self._options.onComplete(id, fileName, result);
366            },
367            onCancel: function(id, fileName){
368                self._onCancel(id, fileName);
369                self._options.onCancel(id, fileName);
370            },
371            onError: self._options.onError,
372            onUpload: function(id, fileName, xhr){
373                self._onUpload(id, fileName, xhr);
374                self._options.onUpload(id, fileName, xhr);
375            }
376        });
377
378        return handler;
379    },    
380    _preventLeaveInProgress: function(){
381        var self = this;
382        
383        this._attach(window, 'beforeunload', function(e){
384            if (!self._filesInProgress){return;}
385            
386            var e = e || window.event;
387            // for ie, ff
388            e.returnValue = self._options.messages.onLeave;
389            // for webkit
390            return self._options.messages.onLeave;             
391        });        
392    },
393    _onSubmit: function(id, fileName){
394        this._filesInProgress++;  
395    },
396    _onProgress: function(id, fileName, loaded, total){        
397    },
398    _onComplete: function(id, fileName, result){
399        this._filesInProgress--;                 
400        if (result.error){
401            this._options.showMessage(result.error);
402        }             
403    },
404    _onCancel: function(id, fileName){
405        this._filesInProgress--;        
406    },
407    _onUpload: function(id, fileName, xhr){     
408    },
409    _onInputChange: function(input){
410        if (this._handler instanceof qq.UploadHandlerXhr){                
411            this._uploadFileList(input.files);                   
412        } else {             
413            if (this._validateFile(input)){                
414                this._uploadFile(input);                                    
415            }                      
416        }               
417        this._button.reset();   
418    },  
419    _uploadFileList: function(files){
420        for (var i=0; i<files.length; i++){
421            if ( !this._validateFile(files[i])){
422                return;
423            }            
424        }
425        
426        for (var i=0; i<files.length; i++){
427            this._uploadFile(files[i]);        
428        }        
429    },       
430    _uploadFile: function(fileContainer){      
431        var id = this._handler.add(fileContainer);
432        var fileName = this._handler.getName(id);
433        
434        if (this._options.onSubmit(id, fileName) !== false){
435            this._onSubmit(id, fileName);
436            this._handler.upload(id, this._options.params);
437        }
438    },      
439    _validateFile: function(file){
440	//	console.dir(file)
441        var name, size;
442        
443        if (file.value){
444            // it is a file input            
445            // get input value and remove path to normalize
446            name = file.value.replace(/.*(\/|\\)/, "");
447        } else {
448            // fix missing properties in Safari 4 and firefox 11.0a2
449            name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
450			size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size;
451        }
452        
453      //  console.log(file.fileSize);
454      //  console.log(file.size);
455      //  console.log(size);
456		
457        if (! this._isAllowedExtension(name)){            
458            this._error('typeError', name);
459            return false;
460            
461        } else if (size === 0){            
462            this._error('emptyError', name);
463            return false;
464                                                     
465        } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){            
466            this._error('sizeError', name);
467            return false;
468                        
469        } else if (size && size < this._options.minSizeLimit){
470            this._error('minSizeError', name);
471            return false;            
472        }
473        
474        return true;                
475    },
476    _error: function(code, fileName){
477        var message = this._options.messages[code];        
478        function r(name, replacement){ message = message.replace(name, replacement); }
479        
480        r('{file}', this._formatFileName(fileName));        
481        r('{extensions}', this._options.allowedExtensions.join(', '));
482        r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
483        r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
484        
485        this._options.showMessage(message);                
486    },
487    _formatFileName: function(name){
488        if (name.length > 33){
489            name = name.slice(0, 19) + '...' + name.slice(-13);    
490        }
491        return name;
492    },
493    _isAllowedExtension: function(fileName){
494        var ext = (-1 !== fileName.indexOf('.')) 
495					? fileName.replace(/.*[.]/, '').toLowerCase() 
496					: '';
497        var allowed = this._options.allowedExtensions;
498        
499        if (!allowed.length){return true;}        
500        
501        for (var i=0; i<allowed.length; i++){
502            if (allowed[i].toLowerCase() == ext){ return true;}    
503        }
504        
505        return false;
506    },    
507    _formatSize: function(bytes){
508        var i = -1;                                    
509        do {
510            bytes = bytes / 1024;
511            i++;  
512        } while (bytes > 99);
513        
514        return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];          
515    }
516};
517    
518       
519/**
520 * Class that creates upload widget with drag-and-drop and file list
521 * @inherits qq.FileUploaderBasic
522 */
523qq.FileUploader = function(o){
524    // call parent constructor
525    qq.FileUploaderBasic.apply(this, arguments);
526    
527    // additional options    
528    qq.extend(this._options, {
529        element: null,
530        // if set, will be used instead of qq-upload-list in template
531        listElement: null,
532        dragText: 'Drop files here to upload',      
533        uploadButtonText: 'Upload a file',        
534        cancelButtonText: 'Cancel',        
535        failUploadText: 'Upload failed',
536                
537        template: '<div class="qq-uploader">' + 
538                '<div class="qq-upload-drop-area"><span>{dragText}</span></div>' +
539                '<div class="qq-upload-button">{uploadButtonText}</div>' +
540                '<ul class="qq-upload-list"></ul>' + 
541             '</div>',
542
543        // template for one item in file list
544        fileTemplate: '<li>' +
545                '<span class="qq-progress-bar"></span>' +
546                '<span class="qq-upload-file"></span>' +
547                '<span class="qq-upload-spinner"></span>' +
548                '<span class="qq-upload-size"></span>' +
549                '<a class="qq-upload-cancel" href="#">{cancelButtonText}</a>' +
550                '<span class="qq-upload-failed-text">{failUploadtext}</span>' +
551            '</li>',        
552        
553        classes: {
554            // used to get elements from templates
555            button: 'qq-upload-button',
556            drop: 'qq-upload-drop-area',
557            dropActive: 'qq-upload-drop-area-active',
558            dropDisabled: 'qq-upload-drop-area-disabled',
559            list: 'qq-upload-list',
560            progressBar: 'qq-progress-bar',
561            file: 'qq-upload-file',
562            spinner: 'qq-upload-spinner',
563            size: 'qq-upload-size',
564            cancel: 'qq-upload-cancel',
565
566            // added to list item <li> when upload completes
567            // used in css to hide progress spinner
568            success: 'qq-upload-success',
569            fail: 'qq-upload-fail'
570        }
571    });
572    // overwrite options with user supplied    
573    qq.extend(this._options, o);
574    
575    // overwrite the upload button text if any
576    // same for the Cancel button and Fail message text
577    this._options.template     = this._options.template.replace(/\{dragText\}/g, this._options.dragText);
578    this._options.template     = this._options.template.replace(/\{uploadButtonText\}/g, this._options.uploadButtonText);
579    this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.cancelButtonText);
580    this._options.fileTemplate = this._options.fileTemplate.replace(/\{failUploadtext\}/g, this._options.failUploadText);
581
582    this._element = this._options.element;
583    this._element.innerHTML = this._options.template;        
584    this._listElement = this._options.listElement || this._find(this._element, 'list');
585    
586    this._classes = this._options.classes;
587        
588    this._button = this._createUploadButton(this._find(this._element, 'button'));        
589    
590    this._bindCancelEvent();
591    this._setupDragDrop();
592};
593
594// inherit from Basic Uploader
595qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
596
597qq.extend(qq.FileUploader.prototype, {
598    addExtraDropzone: function(element){
599        this._setupExtraDropzone(element);
600    },
601    removeExtraDropzone: function(element){
602        var dzs = this._options.extraDropzones;  
603        for(var i in dzs) if (dzs[i] === element) return this._options.extraDropzones.splice(i,1);
604    },
605    _leaving_document_out: function(e){
606        return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
607             || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
608     },
609    /**
610     * Gets one of the elements listed in this._options.classes
611     **/
612    _find: function(parent, type){                                
613        var element = qq.getByClass(parent, this._options.classes[type])[0];        
614        if (!element){
615            throw new Error('element not found ' + type);
616        }
617        
618        return element;
619    },
620    _setupExtraDropzone: function(element){
621        this._options.extraDropzones.push(element); 
622        this._setupDropzone(element);
623    },
624    _setupDropzone: function(dropArea){
625        var self = this;
626        
627        var dz = new qq.UploadDropZone({
628            element: dropArea,
629            onEnter: function(e){
630                qq.addClass(dropArea, self._classes.dropActive);
631                e.stopPropagation();
632            },
633            onLeave: function(e){
634                //e.stopPropagation();
635            },
636            onLeaveNotDescendants: function(e){
637                qq.removeClass(dropArea, self._classes.dropActive);  
638            },
639            onDrop: function(e){
640                dropArea.style.display = 'none';
641                qq.removeClass(dropArea, self._classes.dropActive);
642                self._uploadFileList(e.dataTransfer.files);    
643            }
644        });
645
646		this.addDisposer(function() { dz.dispose(); });
647
648		dropArea.style.display = 'none';
649    },
650    _setupDragDrop: function(){
651        var dropArea = this._find(this._element, 'drop');
652		var self = this;
653        this._options.extraDropzones.push(dropArea); 
654        
655        var dropzones = this._options.extraDropzones;
656        var i;
657        for (i=0; i < dropzones.length; i++){
658            this._setupDropzone(dropzones[i]);
659        }
660        
661		// IE <= 9 does not support the File API used for drag+drop uploads
662		// Any volunteers to enable & test this for IE10?
663		if (!qq.ie()) {
664			this._attach(document, 'dragenter', function(e){
665				// console.log();
666				if (!self._isValidFileDrag(e)) return; // now causing error. Need it be here?
667				if (qq.hasClass(dropArea, self._classes.dropDisabled)) return;
668
669				dropArea.style.display = 'block';        
670				for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'block'; }
671				
672			});
673		}
674        this._attach(document, 'dragleave', function(e){
675            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
676            // only fire when leaving document out
677            if (qq.FileUploader.prototype._leaving_document_out(e)) {        
678                for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'none'; }
679            }
680        }); 
681        qq.attach(document, 'drop', function(e){
682          for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'none'; }
683          e.preventDefault();
684        });               
685    },
686    _onSubmit: function(id, fileName){
687        qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
688        this._addToList(id, fileName);  
689    },
690	// Update the progress bar & percentage as the file is uploaded
691    _onProgress: function(id, fileName, loaded, total){
692        qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
693
694        var item = this._getItemByFileId(id);
695        var size = this._find(item, 'size');
696        size.style.display = 'inline';
697		
698        var text;
699		var percent = Math.round(loaded / total * 100);
700
701        if (loaded != total) {
702			// If still uploading, display percentage
703            text = percent + '% from ' + this._formatSize(total);	
704        } else {
705			// If complete, just display final size
706            text = this._formatSize(total);
707        }          
708		
709		// Update progress bar <span> tag
710		this._find(item, 'progressBar').style.width = percent + '%';
711        
712        qq.setText(size, text);         
713    },
714    _onComplete: function(id, fileName, result){
715        qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
716
717        // mark completed
718        var item = this._getItemByFileId(id);                
719        qq.remove(this._find(item, 'cancel'));
720        qq.remove(this._find(item, 'spinner'));
721        qq.remove(item);
722        
723        if (result.success){
724            qq.addClass(item, this._classes.success);    
725        } else {
726            qq.addClass(item, this._classes.fail);
727        }         
728    },
729    _addToList: function(id, fileName){
730        var item = qq.toElement(this._options.fileTemplate);                
731        item.qqFileId = id;
732
733        var fileElement = this._find(item, 'file');        
734        qq.setText(fileElement, this._formatFileName(fileName));
735        this._find(item, 'size').style.display = 'none';        
736		if (!this._options.multiple) this._clearList();
737        this._listElement.appendChild(item);
738    },
739	_clearList: function(){
740		this._listElement.innerHTML = '';
741	},
742    _getItemByFileId: function(id){
743        var item = this._listElement.firstChild;        
744        
745        // there can't be txt nodes in dynamically created list
746        // and we can  use nextSibling
747        while (item){            
748            if (item.qqFileId == id) return item;            
749            item = item.nextSibling;
750        }          
751    },
752    /**
753     * delegate click event for cancel link 
754     **/
755    _bindCancelEvent: function(){
756        var self = this,
757            list = this._listElement;            
758        
759        this._attach(list, 'click', function(e){
760            e = e || window.event;
761            var target = e.target || e.srcElement;
762            
763            if (qq.hasClass(target, self._classes.cancel)){                
764                qq.preventDefault(e);
765               
766                var item = target.parentNode;
767                self._handler.cancel(item.qqFileId);
768                qq.remove(item);
769            }
770        });
771    }    
772});
773    
774qq.UploadDropZone = function(o){
775    this._options = {
776        element: null,  
777        onEnter: function(e){},
778        onLeave: function(e){},  
779        // is not fired when leaving element by hovering descendants   
780        onLeaveNotDescendants: function(e){},   
781        onDrop: function(e){}                       
782    };
783    qq.extend(this._options, o);
784    qq.extend(this, qq.DisposeSupport);
785
786    this._element = this._options.element;
787    
788    this._disableDropOutside();
789    this._attachEvents();   
790};
791
792qq.UploadDropZone.prototype = {
793    _dragover_should_be_canceled: function(){
794        return qq.safari() || (qq.firefox() && qq.windows());
795    },
796    _disableDropOutside: function(e){
797        // run only once for all instances
798        if (!qq.UploadDropZone.dropOutsideDisabled ){
799
800            // for these cases we need to catch onDrop to reset dropArea
801            if (this._dragover_should_be_canceled){
802            qq.attach(document, 'dragover', function(e){
803                    e.preventDefault();
804                });
805            } else {
806                qq.attach(document, 'dragover', function(e){
807                if (e.dataTransfer){
808                    e.dataTransfer.dropEffect = 'none';
809                    e.preventDefault(); 
810                    }
811            });
812            }
813
814            qq.UploadDropZone.dropOutsideDisabled = true; 
815        }        
816    },
817    _attachEvents: function(){
818        var self = this;              
819                  
820        self._attach(self._element, 'dragover', function(e){
821            if (!self._isValidFileDrag(e)) return;
822            
823            var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
824            if (effect == 'move' || effect == 'linkMove'){
825                e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)    
826            } else {                    
827                e.dataTransfer.dropEffect = 'copy'; // for Chrome
828            }
829                                                     
830            e.stopPropagation();
831            e.preventDefault();                                                                    
832        });
833
834        self._attach(self._element, 'dragenter', function(e){
835            if (!self._isValidFileDrag(e)) return;
836                        
837            self._options.onEnter(e);
838        });
839
840        self._attach(self._element, 'dragleave', function(e){
841            if (!self._isValidFileDrag(e)) return;
842            
843            self._options.onLeave(e);
844            
845            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);                      
846            // do not fire when moving a mouse over a descendant
847            if (qq.contains(this, relatedTarget)) return;
848                        
849            self._options.onLeaveNotDescendants(e); 
850        });
851
852        self._attach(self._element, 'drop', function(e){
853            if (!self._isValidFileDrag(e)) return;
854            
855            e.preventDefault();
856            self._options.onDrop(e);
857        });          
858    },
859    _isValidFileDrag: function(e){
860		// e.dataTransfer currently causing IE errors
861		// IE9 does NOT support file API, so drag-and-drop is not possible
862		// IE10 should work, but currently has not been tested - any volunteers?
863		if (qq.ie()) return false;
864		
865        var dt = e.dataTransfer,
866            // do not check dt.types.contains in webkit, because it crashes safari 4            
867            isSafari = qq.safari();
868		
869        // dt.effectAllowed is none in Safari 5
870        // dt.types.contains check is for firefox            
871        return dt && dt.effectAllowed != 'none' && 
872            (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
873
874    }        
875}; 
876
877qq.UploadButton = function(o){
878    this._options = {
879        element: null,  
880        // if set to true adds multiple attribute to file input      
881        multiple: false,
882        acceptFiles: null,
883        // name attribute of file input
884        name: 'file',
885        onChange: function(input){},
886        hoverClass: 'qq-upload-button-hover',
887        focusClass: 'qq-upload-button-focus'                       
888    };
889    
890    qq.extend(this._options, o);
891    qq.extend(this, qq.DisposeSupport);
892
893    this._element = this._options.element;
894    
895    // make button suitable container for input
896    qq.css(this._element, {
897        position: 'relative',
898        overflow: 'hidden',
899        // Make sure browse button is in the right side
900        // in Internet Explorer
901        direction: 'ltr'
902    });   
903    
904    this._input = this._createInput();
905};
906
907qq.UploadButton.prototype = {
908    /* returns file input element */    
909    getInput: function(){
910        return this._input;
911    },
912    /* cleans/recreates the file input */
913    reset: function(){
914        if (this._input.parentNode){
915            qq.remove(this._input);    
916        }                
917        
918        qq.removeClass(this._element, this._options.focusClass);
919        this._input = this._createInput();
920    },    
921    _createInput: function(){                
922        var input = document.createElement("input");
923        
924        if (this._options.multiple){
925            input.setAttribute("multiple", "multiple");
926        }
927                
928        if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles);
929                
930        input.setAttribute("type", "file");
931        input.setAttribute("name", this._options.name);
932        
933        qq.css(input, {
934            position: 'absolute',
935            // in Opera only 'browse' button
936            // is clickable and it is located at
937            // the right side of the input
938            right: 0,
939            top: 0,
940            fontFamily: 'Arial',
941            // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
942            fontSize: '118px',
943            margin: 0,
944            padding: 0,
945            cursor: 'pointer',
946            opacity: 0
947        });
948        
949        this._element.appendChild(input);
950
951        var self = this;
952        this._attach(input, 'change', function(){
953            self._options.onChange(input);
954        });
955
956        this._attach(input, 'mouseover', function(){
957            qq.addClass(self._element, self._options.hoverClass);
958        });
959        this._attach(input, 'mouseout', function(){
960            qq.removeClass(self._element, self._options.hoverClass);
961        });
962        this._attach(input, 'focus', function(){
963            qq.addClass(self._element, self._options.focusClass);
964        });
965        this._attach(input, 'blur', function(){
966            qq.removeClass(self._element, self._options.focusClass);
967        });
968
969        // IE and Opera, unfortunately have 2 tab stops on file input
970        // which is unacceptable in our case, disable keyboard access
971        if (window.attachEvent){
972            // it is IE or Opera
973            input.setAttribute('tabIndex', "-1");
974        }
975
976        return input;            
977    }        
978};
979
980/**
981 * Class for uploading files, uploading itself is handled by child classes
982 */
983qq.UploadHandlerAbstract = function(o){
984	// Default options, can be overridden by the user
985    this._options = {
986        debug: false,
987        action: '/upload.php',
988        // maximum number of concurrent uploads        
989        maxConnections: 999,
990        onProgress: function(id, fileName, loaded, total){},
991        onComplete: function(id, fileName, response){},
992        onCancel: function(id, fileName){},
993        onUpload: function(id, fileName, xhr){}
994    };
995    qq.extend(this._options, o);    
996    
997    this._queue = [];
998    // params for files in queue
999    this._params = [];
1000};
1001qq.UploadHandlerAbstract.prototype = {
1002    log: function(str){
1003        if (this._options.debug && window.console) console.log('[uploader] ' + str);        
1004    },
1005    /**
1006     * Adds file or file input to the queue
1007     * @returns id
1008     **/    
1009    add: function(file){},
1010    /**
1011     * Sends the file identified by id and additional query params to the server
1012     */
1013    upload: function(id, params){
1014        var len = this._queue.push(id);
1015
1016        var copy = {};        
1017        qq.extend(copy, params);
1018        this._params[id] = copy;        
1019                
1020        // if too many active uploads, wait...
1021        if (len <= this._options.maxConnections){               
1022            this._upload(id, this._params[id]);
1023        }
1024    },
1025    /**
1026     * Cancels file upload by id
1027     */
1028    cancel: function(id){
1029        this._cancel(id);
1030        this._dequeue(id);
1031    },
1032    /**
1033     * Cancells all uploads
1034     */
1035    cancelAll: function(){
1036        for (var i=0; i<this._queue.length; i++){
1037            this._cancel(this._queue[i]);
1038        }
1039        this._queue = [];
1040    },
1041    /**
1042     * Returns name of the file identified by id
1043     */
1044    getName: function(id){},
1045    /**
1046     * Returns size of the file identified by id
1047     */          
1048    getSize: function(id){},
1049    /**
1050     * Returns id of files being uploaded or
1051     * waiting for their turn
1052     */
1053    getQueue: function(){
1054        return this._queue;
1055    },
1056    /**
1057     * Actual upload method
1058     */
1059    _upload: function(id){},
1060    /**
1061     * Actual cancel method
1062     */
1063    _cancel: function(id){},     
1064    /**
1065     * Removes element from queue, starts upload of next
1066     */
1067    _dequeue: function(id){
1068        var i = qq.indexOf(this._queue, id);
1069        this._queue.splice(i, 1);
1070                
1071        var max = this._options.maxConnections;
1072        
1073        if (this._queue.length >= max && i < max){
1074            var nextId = this._queue[max-1];
1075            this._upload(nextId, this._params[nextId]);
1076        }
1077    }        
1078};
1079
1080/**
1081 * Class for uploading files using form and iframe
1082 * @inherits qq.UploadHandlerAbstract
1083 */
1084qq.UploadHandlerForm = function(o){
1085    qq.UploadHandlerAbstract.apply(this, arguments);
1086       
1087    this._inputs = {};
1088};
1089// @inherits qq.UploadHandlerAbstract
1090qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
1091
1092qq.extend(qq.UploadHandlerForm.prototype, {
1093    add: function(fileInput){
1094        fileInput.setAttribute('name', this._options.inputName);
1095        var id = 'qq-upload-handler-iframe' + qq.getUniqueId();       
1096        
1097        this._inputs[id] = fileInput;
1098        
1099        // remove file input from DOM
1100        if (fileInput.parentNode){
1101            qq.remove(fileInput);
1102        }
1103                
1104        return id;
1105    },
1106    getName: function(id){
1107        // get input value and remove path to normalize
1108        return this._inputs[id].value.replace(/.*(\/|\\)/, "");
1109    },    
1110    _cancel: function(id){
1111        this._options.onCancel(id, this.getName(id));
1112        
1113        delete this._inputs[id];        
1114
1115        var iframe = document.getElementById(id);
1116        if (iframe){
1117            // to cancel request set src to something else
1118            // we use src="javascript:false;" because it doesn't
1119            // trigger ie6 prompt on https
1120            iframe.setAttribute('src', 'javascript:false;');
1121
1122            qq.remove(iframe);
1123        }
1124    },     
1125    _upload: function(id, params){
1126        this._options.onUpload(id, this.getName(id), false);                    
1127        var input = this._inputs[id];
1128        
1129        if (!input){
1130            throw new Error('file with passed id was not added, or already uploaded or cancelled');
1131        }                
1132
1133        var fileName = this.getName(id);
1134                
1135        var iframe = this._createIframe(id);
1136        var form = this._createForm(iframe, params);
1137        form.appendChild(input);
1138
1139        var self = this;
1140        this._attachLoadEvent(iframe, function(){                                 
1141            self.log('iframe loaded');
1142            
1143            var response = self._getIframeContentJSON(iframe);
1144
1145            self._options.onComplete(id, fileName, response);
1146            self._dequeue(id);
1147            
1148            delete self._inputs[id];
1149            // timeout added to fix busy state in FF3.6
1150            setTimeout(function(){
1151                self._detach_event();
1152                qq.remove(iframe);
1153            }, 1);
1154        });
1155
1156        form.submit();        
1157        qq.remove(form);        
1158        
1159        return id;
1160    }, 
1161    _attachLoadEvent: function(iframe, callback){
1162        this._detach_event = qq.attach(iframe, 'load', function(){
1163            // when we remove iframe from dom
1164            // the request stops, but in IE load
1165            // event fires
1166            if (!iframe.parentNode){
1167                return;
1168            }
1169
1170            // fixing Opera 10.53
1171            if (iframe.contentDocument &&
1172                iframe.contentDocument.body &&
1173                iframe.contentDocument.body.innerHTML == "false"){
1174                // In Opera event is fired second time
1175                // when body.innerHTML changed from false
1176                // to server response approx. after 1 sec
1177                // when we upload file with iframe
1178                return;
1179            }
1180
1181            callback();
1182        });
1183    },
1184    /**
1185     * Returns json object received by iframe from server.
1186     */
1187    _getIframeContentJSON: function(iframe){
1188        // iframe.contentWindow.document - for IE<7
1189        var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
1190            response;
1191
1192        var innerHTML = doc.body.innerHTML;
1193        this.log("converting iframe's innerHTML to JSON");
1194        this.log("innerHTML = " + innerHTML);
1195        //plain text response may be wrapped in <pre> tag
1196        if (innerHTML.slice(0, 5).toLowerCase() == '<pre>' && innerHTML.slice(-6).toLowerCase() == '</pre>') {
1197          innerHTML = doc.body.firstChild.firstChild.nodeValue;
1198        }
1199
1200        try {
1201            response = eval("(" + innerHTML + ")");
1202        } catch(err){
1203            response = {};
1204        }        
1205
1206        return response;
1207    },
1208    /**
1209     * Creates iframe with unique name
1210     */
1211    _createIframe: function(id){
1212        // We can't use following code as the name attribute
1213        // won't be properly registered in IE6, and new window
1214        // on form submit will open
1215        // var iframe = document.createElement('iframe');
1216        // iframe.setAttribute('name', id);
1217
1218        var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
1219        // src="javascript:false;" removes ie6 prompt on https
1220
1221        iframe.setAttribute('id', id);
1222
1223        iframe.style.display = 'none';
1224        document.body.appendChild(iframe);
1225
1226        return iframe;
1227    },
1228    /**
1229     * Creates form, that will be submitted to iframe
1230     */
1231    _createForm: function(iframe, params){
1232        // We can't use the following code in IE6
1233        // var form = document.createElement('form');
1234        // form.setAttribute('method', 'post');
1235        // form.setAttribute('enctype', 'multipart/form-data');
1236        // Because in this case file won't be attached to request
1237        var form = qq.toElement('<form method="post" enctype="multipart/form-data"></form>');
1238
1239        var queryString = qq.obj2url(params, this._options.action);
1240
1241        form.setAttribute('action', queryString);
1242        form.setAttribute('target', iframe.name);
1243        form.style.display = 'none';
1244        document.body.appendChild(form);
1245
1246        return form;
1247    }
1248});
1249
1250/**
1251 * Class for uploading files using xhr
1252 * @inherits qq.UploadHandlerAbstract
1253 */
1254qq.UploadHandlerXhr = function(o){
1255    qq.UploadHandlerAbstract.apply(this, arguments);
1256
1257    this._files = [];
1258    this._xhrs = [];
1259    
1260    // current loaded size in bytes for each file 
1261    this._loaded = [];
1262};
1263
1264// static method
1265qq.UploadHandlerXhr.isSupported = function(){
1266    var input = document.createElement('input');
1267    input.type = 'file';        
1268    
1269    return (
1270        'multiple' in input &&
1271        typeof File != "undefined" &&
1272        typeof FormData != "undefined" &&
1273        typeof (new XMLHttpRequest()).upload != "undefined" );
1274};
1275
1276// @inherits qq.UploadHandlerAbstract
1277qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
1278
1279qq.extend(qq.UploadHandlerXhr.prototype, {
1280    /**
1281     * Adds file to the queue
1282     * Returns id to use with upload, cancel
1283     **/    
1284    add: function(file){
1285        if (!(file instanceof File)){
1286            throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
1287        }
1288                
1289        return this._files.push(file) - 1;        
1290    },
1291    getName: function(id){        
1292        var file = this._files[id];
1293        // fix missing name in Safari 4
1294        //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
1295        return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
1296    },
1297    getSize: function(id){
1298        var file = this._files[id];
1299        return file.fileSize != null ? file.fileSize : file.size;
1300    },    
1301    /**
1302     * Returns uploaded bytes for file identified by id 
1303     */    
1304    getLoaded: function(id){
1305        return this._loaded[id] || 0; 
1306    },
1307    /**
1308     * Sends the file identified by id and additional query params to the server
1309     * @param {Object} params name-value string pairs
1310     */    
1311    _upload: function(id, params){
1312        this._options.onUpload(id, this.getName(id), true);
1313        
1314        var file = this._files[id],
1315            name = this.getName(id),
1316            size = this.getSize(id);
1317                
1318        this._loaded[id] = 0;
1319                                
1320        var xhr = this._xhrs[id] = new XMLHttpRequest();
1321        var self = this;
1322                                        
1323        xhr.upload.onprogress = function(e){
1324            if (e.lengthComputable){
1325                self._loaded[id] = e.loaded;
1326                self._options.onProgress(id, name, e.loaded, e.total);
1327            }
1328        };
1329
1330        xhr.onreadystatechange = function(){            
1331            if (xhr.readyState == 4){
1332                self._onComplete(id, xhr);                    
1333            }
1334        };
1335
1336        // build query string
1337        params = params || {};
1338        params[this._options.inputName] = name;
1339        var queryString = qq.obj2url(params, this._options.action);
1340
1341        xhr.open("POST", queryString, true);
1342        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
1343        xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
1344        if (this._options.encoding == 'multipart') {
1345            var formData = new FormData();
1346            formData.append(name, file);
1347            file = formData;
1348        } else {
1349            xhr.setRequestHeader("Content-Type", "application/octet-stream");
1350            //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
1351            xhr.setRequestHeader("X-Mime-Type",file.type );
1352        }
1353        for (key in this._options.customHeaders){
1354            xhr.setRequestHeader(key, this._options.customHeaders[key]);
1355        };
1356        xhr.send(file);
1357    },
1358    _onComplete: function(id, xhr){
1359        // the request was aborted/cancelled
1360        if (!this._files[id]) return;
1361        
1362        var name = this.getName(id);
1363        var size = this.getSize(id);
1364        
1365        this._options.onProgress(id, name, size, size);
1366                
1367        if (xhr.status == 200){
1368            this.log("xhr - server response received");
1369            this.log("responseText = " + xhr.responseText);
1370                        
1371            var response;
1372                    
1373            try {
1374                response = eval("(" + xhr.responseText + ")");
1375            } catch(err){
1376                response = {};
1377            }
1378            
1379            this._options.onComplete(id, name, response);
1380                        
1381        } else {
1382            this._options.onError(id, name, xhr);
1383            this._options.onComplete(id, name, {});
1384        }
1385                
1386        this._files[id] = null;
1387        this._xhrs[id] = null;    
1388        this._dequeue(id);                    
1389    },
1390    _cancel: function(id){
1391        this._options.onCancel(id, this.getName(id));
1392        
1393        this._files[id] = null;
1394        
1395        if (this._xhrs[id]){
1396            this._xhrs[id].abort();
1397            this._xhrs[id] = null;                                   
1398        }
1399    }
1400});
1401
1402/**
1403 * A generic module which supports object disposing in dispose() method.
1404 * */
1405qq.DisposeSupport = {
1406  _disposers: [],
1407
1408  /** Run all registered disposers */
1409  dispose: function() {
1410    var disposer;
1411    while (disposer = this._disposers.shift()) {
1412      disposer();
1413    }
1414  },
1415
1416  /** Add disposer to the collection */
1417  addDisposer: function(disposeFunction) {
1418    this._disposers.push(disposeFunction);
1419  },
1420
1421  /** Attach event handler and register de-attacher as a disposer */
1422  _attach: function() {
1423    this.addDisposer(qq.attach.apply(this, arguments));
1424  }
1425};

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.