PageSourceSearch

https://caribbeanculinary.community/wp-content/plugins/frontend-po…/assets/js/fpsml-fileuploader.js?ver=1.3.5

js caribbeanculinary.community collected 2026-10-07 16:38:12 UTC 47,499 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        // 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} 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    // Labels are text, including when read from untrusted data-* attributes.
578    // Replace all tokens in one pass so label text cannot introduce new tokens.
579    var options = this._options;
580    var replaceText = function(match, name) {
581        var value = options[name === 'failUploadtext' ? 'failUploadText' : name];
582        var text = document.createElement('span');
583        qq.setText(text, value == null ? '' : String(value));
584        return text.innerHTML;
585    };
586    this._options.template = this._options.template.replace(/\{(
586dragText|uploadButtonText)\}/g, replaceText);
587    this._options.fileTemplate = this._options.fileTemplate.replace(/\{(cancelButtonText|failUploadtext)\}/g, replaceText);
588
589    this._element = this._options.element;
590    this._element.innerHTML = this._options.template;        
591    this._listElement = this._options.listElement || this._find(this._element, 'list');
592    
593    this._classes = this._options.classes;
594        
595    this._button = this._createUploadButton(this._find(this._element, 'button'));        
596    
597    this._bindCancelEvent();
598    this._setupDragDrop();
599};
600
601// inherit from Basic Uploader
602qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
603
604qq.extend(qq.FileUploader.prototype, {
605    addExtraDropzone: function(element){
606        this._setupExtraDropzone(element);
607    },
608    removeExtraDropzone: function(element){
609        var dzs = this._options.extraDropzones;  
610        for(var i in dzs) if (dzs[i] === element) return this._options.extraDropzones.splice(i,1);
611    },
612    _leaving_document_out: function(e){
613        return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
614             || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
615     },
616    /**
617     * Gets one of the elements listed in this._options.classes
618     **/
619    _find: function(parent, type){                                
620        var element = qq.getByClass(parent, this._options.classes[type])[0];        
621        if (!element){
622            throw new Error('element not found ' + type);
623        }
624        
625        return element;
626    },
627    _setupExtraDropzone: function(element){
628        this._options.extraDropzones.push(element); 
629        this._setupDropzone(element);
630    },
631    _setupDropzone: function(dropArea){
632        var self = this;
633        
634        var dz = new qq.UploadDropZone({
635            element: dropArea,
636            onEnter: function(e){
637                qq.addClass(dropArea, self._classes.dropActive);
638                e.stopPropagation();
639            },
640            onLeave: function(e){
641                //e.stopPropagation();
642            },
643            onLeaveNotDescendants: function(e){
644                qq.removeClass(dropArea, self._classes.dropActive);  
645            },
646            onDrop: function(e){
647                dropArea.style.display = 'none';
648                qq.removeClass(dropArea, self._classes.dropActive);
649                self._uploadFileList(e.dataTransfer.files);    
650            }
651        });
652
653		this.addDisposer(function() { dz.dispose(); });
654
655		dropArea.style.display = 'none';
656    },
657    _setupDragDrop: function(){
658        var dropArea = this._find(this._element, 'drop');
659		var self = this;
660        this._options.extraDropzones.push(dropArea); 
661        
662        var dropzones = this._options.extraDropzones;
663        var i;
664        for (i=0; i < dropzones.length; i++){
665            this._setupDropzone(dropzones[i]);
666        }
667        
668		// IE <= 9 does not support the File API used for drag+drop uploads
669		// Any volunteers to enable & test this for IE10?
670		if (!qq.ie()) {
671			this._attach(document, 'dragenter', function(e){
672				// console.log();
673				if (!self._isValidFileDrag(e)) return; // now causing error. Need it be here?
674				if (qq.hasClass(dropArea, self._classes.dropDisabled)) return;
675
676				dropArea.style.display = 'block';        
677				for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'block'; }
678				
679			});
680		}
681        this._attach(document, 'dragleave', function(e){
682            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
683            // only fire when leaving document out
684            if (qq.FileUploader.prototype._leaving_document_out(e)) {        
685                for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'none'; }
686            }
687        }); 
688        qq.attach(document, 'drop', function(e){
689          for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'none'; }
690          e.preventDefault();
691        });               
692    },
693    _onSubmit: function(id, fileName){
694        qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
695        this._addToList(id, fileName);  
696    },
697	// Update the progress bar & percentage as the file is uploaded
698    _onProgress: function(id, fileName, loaded, total){
699        qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
700
701        var item = this._getItemByFileId(id);
702        var size = this._find(item, 'size');
703        size.style.display = 'inline';
704		
705        var text;
706		var percent = Math.round(loaded / total * 100);
707
708        if (loaded != total) {
709			// If still uploading, display percentage
710            text = percent + '% from ' + this._formatSize(total);	
711        } else {
712			// If complete, just display final size
713            text = this._formatSize(total);
714        }          
715		
716		// Update progress bar <span> tag
717		this._find(item, 'progressBar').style.width = percent + '%';
718        
719        qq.setText(size, text);         
720    },
721    _onComplete: function(id, fileName, result){
722        qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
723
724        // mark completed
725        var item = this._getItemByFileId(id);                
726        qq.remove(this._find(item, 'cancel'));
727        qq.remove(this._find(item, 'spinner'));
728        qq.remove(item);
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.