PageSourceSearch

https://wearechange.org/wp-content/plugins/wp-eMember/js/jquery.fileuploader.js

js wearechange.org collected 2026-10-03 21:38:53 UTC 37,724 bytes, 1,282 lines download raw bytes

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

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.