PageSourceSearch

https://www.larec-skazok.ru/vendor/qqfileuploader.js

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

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.