Skip to content
Extraits de code Groupes Projets
fileuploader-custom.js 36,8 ko
Newer Older
  • Learn to ignore specific revisions
  • /**
     * http://github.com/valums/file-uploader
    
     *
     * Multiple file upload component with progress-bar, drag-and-drop.
     * © 2010 Andrew Valums ( andrew(at)valums.com )
     *
    
     * Licensed under GNU GPL 2 or later, see license.txt.
    
     * Modified by Diaspora
    
    
    //
    // Helper functions
    //
    
    var qq = qq || {};
    
    /**
     * Adds all missing properties from second obj to first obj
    
    qq.extend = function(first, second){
        for (var prop in second){
            first[prop] = second[prop];
        }
    
    
    /**
     * Searches for a given element in the array, returns -1 if it is not present.
     * @param {Number} [from] The index at which to begin the search
     */
    qq.indexOf = function(arr, elt, from){
    
    Raphael Sofaer's avatar
    Raphael Sofaer a validé
        if (arr.indexOf) { return arr.indexOf(elt, from); }
    
        from = from || 0;
    
    Raphael Sofaer's avatar
    Raphael Sofaer a validé
        if (from < 0) { from += len; }
    
    
        for (; from < len; from++){
            if (from in arr && arr[from] === elt){
    
                return from;
            }
    
    qq.getUniqueId = (function(){
        var id = 0;
        return function(){ return id++; };
    })();
    
    //
    // Events
    
    qq.attach = function(element, type, fn){
        if (element.addEventListener){
            element.addEventListener(type, fn, false);
        } else if (element.attachEvent){
            element.attachEvent('on' + type, fn);
        }
    };
    qq.detach = function(element, type, fn){
        if (element.removeEventListener){
            element.removeEventListener(type, fn, false);
        } else if (element.attachEvent){
            element.detachEvent('on' + type, fn);
        }
    };
    
    qq.preventDefault = function(e){
        if (e.preventDefault){
            e.preventDefault();
        } else{
            e.returnValue = false;
        }
    };
    
    //
    // Node manipulations
    
    /**
     * Insert node a before node b.
     */
    qq.insertBefore = function(a, b){
        b.parentNode.insertBefore(a, b);
    };
    qq.remove = function(element){
        element.parentNode.removeChild(element);
    };
    
    
    qq.contains = function(parent, descendant){
    
        // compareposition returns false in this case
    
    Raphael Sofaer's avatar
    Raphael Sofaer a validé
        if (parent == descendant) { return true; }
    
        if (parent.contains){
            return parent.contains(descendant);
        } else {
            return !!(descendant.compareDocumentPosition(parent) & 8);
        }
    };
    
    /**
     * Creates and returns element from html string
     * Uses innerHTML to create an element
     */
    qq.toElement = (function(){
        var div = document.createElement('div');
        return function(html){
            div.innerHTML = html;
            var element = div.firstChild;
            div.removeChild(element);
            return element;
        };
    })();
    
    //
    // Node properties and attributes
    
    /**
     * Sets styles for an element.
     * Fixes opacity in IE6-8.
     */
    qq.css = function(element, styles){
    
    Raphael Sofaer's avatar
    Raphael Sofaer a validé
        if (styles.opacity !== null){
    
            if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
                styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
            }
        }
        qq.extend(element.style, styles);
    };
    qq.hasClass = function(element, name){
        var re = new RegExp('(^| )' + name + '( |$)');
        return re.test(element.className);
    };
    qq.addClass = function(element, name){
        if (!qq.hasClass(element, name)){
            element.className += ' ' + name;
        }
    };
    qq.removeClass = function(element, name){
        var re = new RegExp('(^| )' + name + '( |$)');
        element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
    };
    qq.setText = function(element, text){
        element.innerText = text;
        element.textContent = text;
    };
    
    //
    // Selecting elements
    
    qq.children = function(element){
        var children = [],
        child = element.firstChild;
    
        while (child){
            if (child.nodeType == 1){
                children.push(child);
            }
            child = child.nextSibling;
        }
    
        return children;
    };
    
    qq.getByClass = function(element, className){
        if (element.querySelectorAll){
            return element.querySelectorAll('.' + className);
        }
    
        var result = [];
        var candidates = element.getElementsByTagName("*");
        var len = candidates.length;
    
        for (var i = 0; i < len; i++){
            if (qq.hasClass(candidates[i], className)){
                result.push(candidates[i]);
            }
        }
        return result;
    };
    
    /**
     * obj2url() takes a json-object as argument and generates
     * a querystring. pretty much like jQuery.param()
    
     * how to use:
     *
     *    `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
     *
     * will result in:
     *
     *    `http://any.url/upload?otherParam=value&a=b&c=d`
     *
     * @param  Object JSON-Object
     * @param  String current querystring-part
     * @return String encoded querystring
     */
    qq.obj2url = function(obj, temp, prefixDone){
        var uristrings = [],
            prefix = '&',
            add = function(nextObj, i){
    
                    ? (/\[\]$/.test(temp)) // prevent double-encoding
                       ? temp
                       : temp+'['+i+']'
                    : i;
    
                if ((nextTemp != 'undefined') && (i != 'undefined')) {
    
                    uristrings.push(
    
                            ? qq.obj2url(nextObj, nextTemp, true)
                            : (Object.prototype.toString.call(nextObj) === '[object Function]')
                                ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
    
                                : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
    
    
        if (!prefixDone && temp) {
          prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
          uristrings.push(temp);
          uristrings.push(qq.obj2url(obj));
        } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
            // we wont use a for-in-loop on an array (performance)
            for (var i = 0, len = obj.length; i < len; ++i){
                add(obj[i], i);
            }
        } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
            // for anything else but a scalar, we will use for-in-loop
            for (var i in obj){
                add(obj[i], i);
            }
        } else {
            uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
        }
    
        return uristrings.join(prefix)
                         .replace(/^&/, '')
    
    };
    
    //
    //
    // Uploader Classes
    //
    //
    
    var qq = qq || {};
    
     * Creates upload button, validates upload, but doesn't create file list or dd.
    
     */
    qq.FileUploaderBasic = function(o){
        this._options = {
            // set to true to see the server response
            debug: false,
            action: '/server/upload',
            params: {},
            button: null,
            multiple: true,
            maxConnections: 3,
    
            // validation
            allowedExtensions: [],
            sizeLimit: 0,
            minSizeLimit: 0,
    
            // events
            // return false to cancel submit
            onSubmit: function(id, fileName){},
            onProgress: function(id, fileName, loaded, total){},
            onComplete: function(id, fileName, responseJSON){},
    
            onAllComplete: function(completed_files){},
    
            onCancel: function(id, fileName){},
    
            messages: {
                typeError: "{file} has invalid extension. Only {extensions} are allowed.",
                sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
                minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
                emptyError: "{file} is empty, please select files again without it.",
    
                onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
    
            },
            showMessage: function(message){
                alert(message);
    
        };
        qq.extend(this._options, o);
    
        // number of files being uploaded
        this._filesInProgress = 0;
    
        this._handler = this._createUploadHandler();
    
        if (this._options.button){
    
            this._button = this._createUploadButton(this._options.button);
        }
    
    qq.FileUploaderBasic.prototype = {
        setParams: function(params){
            this._options.params = params;
        },
        getInProgress: function(){
    
        },
        _createUploadButton: function(element){
            var self = this;
    
            return new qq.UploadButton({
                element: element,
                multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
                onChange: function(input){
                    self._onInputChange(input);
    
        _createUploadHandler: function(){
            var self = this,
    
                handlerClass;
    
            if(qq.UploadHandlerXhr.isSupported()){
                handlerClass = 'UploadHandlerXhr';
    
            } else {
                handlerClass = 'UploadHandlerForm';
            }
    
            var handler = new qq[handlerClass]({
                debug: this._options.debug,
    
                action: this._options.action,
                maxConnections: this._options.maxConnections,
                onProgress: function(id, fileName, loaded, total){
    
                    self._onProgress(id, fileName, loaded, total);
    
                    self._options.onProgress(id, fileName, loaded, total);
                },
    
                onComplete: function(id, fileName, result){
                    self._onComplete(id, fileName, result);
                    self._options.onComplete(id, fileName, result);
                },
    
                onAllComplete: function(completed_files){
                    self._options.onAllComplete(completed_files);
                },
    
                onCancel: function(id, fileName){
                    self._onCancel(id, fileName);
                    self._options.onCancel(id, fileName);
                }
            });
    
            return handler;
    
        _preventLeaveInProgress: function(){
            var self = this;
    
            qq.attach(window, 'beforeunload', function(e){
                if (!self._filesInProgress){return;}
    
                var e = e || window.event;
                // for ie, ff
                e.returnValue = self._options.messages.onLeave;
                // for webkit
    
                return self._options.messages.onLeave;
            });
        },
    
        _onSubmit: function(id, fileName){
    
        _onProgress: function(id, fileName, loaded, total){
    
        },
        _onComplete: function(id, fileName, result){
    
            if (result.error){
                this._options.showMessage(result.error);
    
        },
        _onCancel: function(id, fileName){
    
        },
        _onInputChange: function(input){
    
            if (this._handler instanceof qq.UploadHandlerXhr){
                this._uploadFileList(input.files);
            } else {
                if (this._validateFile(input)){
                    this._uploadFile(input);
                }
            }
            this._button.reset();
        },
    
        _uploadFileList: function(files){
            for (var i=0; i<files.length; i++){
                if ( !this._validateFile(files[i])){
                    return;
    
            for (var i=0; i<files.length; i++){
    
                this._uploadFile(files[i]);
            }
        },
        _uploadFile: function(fileContainer){
    
            var id = this._handler.add(fileContainer);
            var fileName = this._handler.getName(id);
    
            if (this._options.onSubmit(id, fileName) !== false){
                this._onSubmit(id, fileName);
                this._handler.upload(id, this._options.params);
            }
    
        _validateFile: function(file){
            var name, size;
    
            if (file.value){
    
                // get input value and remove path to normalize
                name = file.value.replace(/.*(\/|\\)/, "");
            } else {
                // fix missing properties in Safari
                name = file.fileName != null ? file.fileName : file.name;
                size = file.fileSize != null ? file.fileSize : file.size;
            }
    
                this._error('typeError', name);
                return false;
    
                this._error('emptyError', name);
                return false;
    
    
            } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){
    
                this._error('sizeError', name);
                return false;
    
            } else if (size && size < this._options.minSizeLimit){
                this._error('minSizeError', name);
    
        },
        _error: function(code, fileName){
    
            var message = this._options.messages[code];
    
            function r(name, replacement){ message = message.replace(name, replacement); }
    
    
            r('{file}', this._formatFileName(fileName));
    
            r('{extensions}', this._options.allowedExtensions.join(', '));
            r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
            r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
    
        },
        _formatFileName: function(name){
            if (name.length > 33){
    
                name = name.slice(0, 19) + '...' + name.slice(-13);
    
            }
            return name;
        },
        _isAllowedExtension: function(fileName){
            var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
            var allowed = this._options.allowedExtensions;
    
            for (var i=0; i<allowed.length; i++){
    
                if (allowed[i].toLowerCase() == ext){ return true;}
    
            return false;
    
        _formatSize: function(bytes){
    
            do {
                bytes = bytes / 1024;
    
            } while (bytes > 99);
    
    
            return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
    
     * Class that creates upload widget with drag-and-drop and file list
     * @inherits qq.FileUploaderBasic
    
        // call parent constructor
        qq.FileUploaderBasic.apply(this, arguments);
    
        qq.extend(this._options, {
    
            // if set, will be used instead of qq-upload-list in template
            listElement: null,
    
                    '<div class="qq-upload-drop-area"><span>Drop files here to upload</span></div>' +
                    '<div class="qq-upload-button">Upload a file</div>' +
    
                    '<ul class="qq-upload-list"></ul>' +
    
                 '</div>',
    
            // template for one item in file list
            fileTemplate: '<li>' +
                    '<span class="qq-upload-file"></span>' +
                    '<span class="qq-upload-spinner"></span>' +
                    '<span class="qq-upload-size"></span>' +
                    '<a class="qq-upload-cancel" href="#">Cancel</a>' +
                    '<span class="qq-upload-failed-text">Failed</span>' +
    
            classes: {
                // used to get elements from templates
                button: 'qq-upload-button',
                drop: 'qq-upload-drop-area',
                dropActive: 'qq-upload-drop-area-active',
                list: 'qq-upload-list',
    
                file: 'qq-upload-file',
                spinner: 'qq-upload-spinner',
                size: 'qq-upload-size',
                cancel: 'qq-upload-cancel',
    
                // added to list item when upload completes
                // used in css to hide progress spinner
                success: 'qq-upload-success',
                fail: 'qq-upload-fail'
            }
    
        // overwrite options with user supplied
        qq.extend(this._options, o);
    
        this._element = this._options.element;
    
        this._element.innerHTML = this._options.template;
    
        this._listElement = this._options.listElement || this._find(this._element, 'list');
    
    
        this._button = this._createUploadButton(this._find(this._element, 'button'));
    
    
    // inherit from Basic Uploader
    qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
    
    qq.extend(qq.FileUploader.prototype, {
    
        /**
         * Gets one of the elements listed in this._options.classes
         **/
    
        _find: function(parent, type){
            var element = qq.getByClass(parent, this._options.classes[type])[0];
    
            if (!element){
                throw new Error('element not found ' + type);
            }
    
            return element;
        },
        _setupDragDrop: function(){
            var self = this,
    
                dropArea = this._find(this._element, 'drop');
    
    
            var dz = new qq.UploadDropZone({
                element: dropArea,
                onEnter: function(e){
                    qq.addClass(dropArea, self._classes.dropActive);
                    e.stopPropagation();
                },
                onLeave: function(e){
                    e.stopPropagation();
                },
                onLeaveNotDescendants: function(e){
    
                    qq.removeClass(dropArea, self._classes.dropActive);
    
                },
                onDrop: function(e){
                    dropArea.style.display = 'none';
                    qq.removeClass(dropArea, self._classes.dropActive);
    
                    self._uploadFileList(e.dataTransfer.files);
    
            qq.attach(document, 'dragenter', function(e){
                if (!dz._isValidFileDrag(e)) return;
    
                dropArea.style.display = 'block';
            });
    
            qq.attach(document, 'dragleave', function(e){
    
                var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
                // only fire when leaving document out
    
                if ( ! relatedTarget || relatedTarget.nodeName == "HTML"){
                    dropArea.style.display = 'none';
    
        _onSubmit: function(id, fileName){
            qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
    
        _onProgress: function(id, fileName, loaded, total){
            qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
    
            var size = this._find(item, 'size');
    
            if (loaded != total){
                text = Math.round(loaded / total * 100) + '% from ' + this._formatSize(total);
    
        _onComplete: function(id, fileName, result){
            qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
    
            // mark completed
    
            var item = this._getItemByFileId(id);
    
            qq.remove(this._find(item, 'cancel'));
            qq.remove(this._find(item, 'spinner'));
    
            if (result.success){
    
                qq.addClass(item, this._classes.success);
    
            } else {
                qq.addClass(item, this._classes.fail);
    
        },
        _addToList: function(id, fileName){
    
            var item = qq.toElement(this._options.fileTemplate);
    
            item.qqFileId = id;
    
    
            var fileElement = this._find(item, 'file');
    
            qq.setText(fileElement, this._formatFileName(fileName));
    
            this._find(item, 'size').style.display = 'none';
    
    
            this._listElement.appendChild(item);
    
            var item = this._listElement.firstChild;
    
    
            // there can't be txt nodes in dynamically created list
            // and we can  use nextSibling
    
            while (item){
                if (item.qqFileId == id) return item;
    
         * delegate click event for cancel link
    
         **/
        _bindCancelEvent: function(){
            var self = this,
    
                list = this._listElement;
    
            qq.attach(list, 'click', function(e){
    
                e = e || window.event;
                var target = e.target || e.srcElement;
    
    
                if (qq.hasClass(target, self._classes.cancel)){
    
                    var item = target.parentNode;
                    self._handler.cancel(item.qqFileId);
                    qq.remove(item);
                }
            });
    
    qq.UploadDropZone = function(o){
        this._options = {
    
            onLeave: function(e){},
            // is not fired when leaving element by hovering descendants
            onLeaveNotDescendants: function(e){},
            onDrop: function(e){}
    
    };
    
    qq.UploadDropZone.prototype = {
        _disableDropOutside: function(e){
            // run only once for all instances
            if (!qq.UploadDropZone.dropOutsideDisabled ){
    
                qq.attach(document, 'dragover', function(e){
    
                    if (e.dataTransfer){
                        e.dataTransfer.dropEffect = 'none';
    
    
                qq.UploadDropZone.dropOutsideDisabled = true;
            }
    
            qq.attach(self._element, 'dragover', function(e){
                if (!self._isValidFileDrag(e)) return;
    
                var effect = e.dataTransfer.effectAllowed;
                if (effect == 'move' || effect == 'linkMove'){
    
                    e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
                } else {
    
                    e.dataTransfer.dropEffect = 'copy'; // for Chrome
                }
    
            qq.attach(self._element, 'dragenter', function(e){
                if (!self._isValidFileDrag(e)) return;
    
            qq.attach(self._element, 'dragleave', function(e){
                if (!self._isValidFileDrag(e)) return;
    
    
                var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
    
                // do not fire when moving a mouse over a descendant
                if (qq.contains(this, relatedTarget)) return;
    
            qq.attach(self._element, 'drop', function(e){
                if (!self._isValidFileDrag(e)) return;
    
        },
        _isValidFileDrag: function(e){
            var dt = e.dataTransfer,
    
                // do not check dt.types.contains in webkit, because it crashes safari 4
                isWebkit = navigator.userAgent.indexOf("AppleWebKit") > -1;
    
            // dt.types.contains check is for firefox
            return dt && dt.effectAllowed != 'none' &&
    
                (dt.files || (!isWebkit && dt.types.contains && dt.types.contains('Files')));
    
            element: null,
            // if set to true adds multiple attribute to file input
    
            multiple: false,
            // name attribute of file input
            name: 'file',
            onChange: function(input){},
            hoverClass: 'qq-upload-button-hover',
    
            focusClass: 'qq-upload-button-focus'
    
        // make button suitable container for input
        qq.css(this._element, {
            position: 'relative',
            overflow: 'hidden',
            // Make sure browse button is in the right side
            // in Internet Explorer
            direction: 'ltr'
    
        this._input = this._createInput();
    };
    
    qq.UploadButton.prototype = {
    
        getInput: function(){
            return this._input;
        },
        /* cleans/recreates the file input */
        reset: function(){
            if (this._input.parentNode){
    
            qq.removeClass(this._element, this._options.focusClass);
            this._input = this._createInput();
    
            var input = document.createElement("input");
    
            if (this._options.multiple){
                input.setAttribute("multiple", "multiple");
            }
    
            input.setAttribute("type", "file");
            input.setAttribute("name", this._options.name);
    
            qq.css(input, {
                position: 'absolute',
                // in Opera only 'browse' button
                // is clickable and it is located at
                // the right side of the input
                right: 0,
                top: 0,
    
                // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
    
            this._element.appendChild(input);
    
            var self = this;
            qq.attach(input, 'change', function(){
                self._options.onChange(input);
            });
    
            qq.attach(input, 'mouseover', function(){
                qq.addClass(self._element, self._options.hoverClass);
            });
            qq.attach(input, 'mouseout', function(){
                qq.removeClass(self._element, self._options.hoverClass);
            });
            qq.attach(input, 'focus', function(){
                qq.addClass(self._element, self._options.focusClass);
            });
            qq.attach(input, 'blur', function(){
                qq.removeClass(self._element, self._options.focusClass);
            });
    
            // IE and Opera, unfortunately have 2 tab stops on file input
            // which is unacceptable in our case, disable keyboard access
            if (window.attachEvent){
                // it is IE or Opera
                input.setAttribute('tabIndex', "-1");
            }
    
    
     * Class for uploading files, uploading itself is handled by child classes
    
    qq.UploadHandlerAbstract = function(o){
    
            debug: false,
            action: '/upload.php',
    
            // maximum number of concurrent uploads
    
            maxConnections: 999,
            onProgress: function(id, fileName, loaded, total){},
            onComplete: function(id, fileName, response){},
    
            onAllComplete: function(completed_files){},
    
            onCancel: function(id, fileName){}
    
        this._queue = [];
        // params for files in queue
        this._params = [];
    
    qq.UploadHandlerAbstract.prototype = {
        log: function(str){
    
            if (this._options.debug && window.console) console.log('[uploader] ' + str);
    
         * Adds file or file input to the queue
         * @returns id
    
        add: function(file){},
        /**
         * Sends the file identified by id and additional query params to the server
         */
        upload: function(id, params){
            var len = this._queue.push(id);
    
    
            qq.extend(copy, params);
    
            // if too many active uploads, wait...
    
            if (len <= this._options.maxConnections){
    
                this._upload(id, this._params[id]);
            }
        },
        /**
         * Cancels file upload by id
         */
        cancel: function(id){
            this._cancel(id);
            this._dequeue(id);
        },
        /**
         * Cancells all uploads
         */
        cancelAll: function(){
            for (var i=0; i<this._queue.length; i++){
                this._cancel(this._queue[i]);
            }
            this._queue = [];
        },
        /**
         * Returns name of the file identified by id
         */
        getName: function(id){},
        /**
         * Returns size of the file identified by id
    
        getSize: function(id){},
        /**
         * Returns id of files being uploaded or
         * waiting for their turn
         */
        getQueue: function(){
            return this._queue;
        },
        /**
         * Actual upload method
         */
        _upload: function(id){},
        /**
         * Actual cancel method
         */
    
        /**
         * Removes element from queue, starts upload of next
         */
        _dequeue: function(id){
            var i = qq.indexOf(this._queue, id);
            this._queue.splice(i, 1);
    
            var max = this._options.maxConnections;
    
            if (this._queue.length >= max){
                var nextId = this._queue[max-1];
                this._upload(nextId, this._params[nextId]);
            }
    
    
            if (this._queue.length == 0){
                this._onAllComplete();
            }
        },
        _onAllComplete: function(){
            this._options.onAllComplete(this._completed_files);
        }
    
    };
    
    /**
     * Class for uploading files using form and iframe
     * @inherits qq.UploadHandlerAbstract
     */
    qq.UploadHandlerForm = function(o){
        qq.UploadHandlerAbstract.apply(this, arguments);
    
        this._inputs = {};
    };
    // @inherits qq.UploadHandlerAbstract
    qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
    
    qq.extend(qq.UploadHandlerForm.prototype, {
    
        add: function(fileInput){
            fileInput.setAttribute('name', 'qqfile');
    
            var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
    
    
            // remove file input from DOM
            if (fileInput.parentNode){
                qq.remove(fileInput);
            }
    
        getName: function(id){
            // get input value and remove path to normalize
            return this._inputs[id].value.replace(/.*(\/|\\)/, "");
    
        _cancel: function(id){
            this._options.onCancel(id, this.getName(id));
    
    
            var iframe = document.getElementById(id);
            if (iframe){
                // to cancel request set src to something else