PageSourceSearch

https://visitgreenland.com/wp-content/plugins/admin-site-enhanceme…/assets/js/file-uploader.js?ver=9.1.4

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

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.