PageSourceSearch

https://khushiipropertiies.com/wp-content/plugins/hash-form/js/file-uploader.js?ver=1.4.4

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

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.