文件上传队列模块,来至yui3 UploaderQueue

作者:root
注:可用,但是错误处理 errorAction 赋值与取值 机制还没搞明白,以后再搞。

UploaderQueue = function(o={}) {
    for(var i in o){
		if (o.hasOwnProperty(i))
			if(UploaderQueue.ATTRS.hasOwnProperty(i))UploaderQueue.ATTRS[i]=o[i];
	}
    CONTINUE= "continue",
    STOP= "stop",
    RESTART_ASAP= "restartasap",
    RESTART_AFTER= "restartafter",
    STOPPED= "stopped",
    UPLOADING= "uploading",
    NAME= 'uploaderqueue',
    this.queuedFiles = [];
    this.uploadRetries = {};
    this.numberOfUploads = 0;
    this.currentUploadedByteValues = {};
    this.currentFiles = {};
    this.totalBytesUploaded = 0;
    this.totalBytes = 0;
};
UploaderQueue.prototype = {
    _currentState: UploaderQueue.STOPPED,
    uploadCompleteHandler:null,
    uploadProgressHandler:null,
    uploadStartHandler:null,
    uploadCancelHandler:null,
    uploadErrorHandler:null,
    _uploadStartHandler : function (event) {
        var updatedEvent = {};
        updatedEvent.file = event.target;
        updatedEvent.originEvent = event;


        this.fire("uploadstart", updatedEvent);
    },
    each: function(a,f){for(var i=0;i< a.length;i++)f(a[i]);},
    fire:function(e,o={},canBubble=false,cancelable=false){
		var evt = document.createEvent("HTMLEvents");
		evt.initEvent(e, canBubble, cancelable);
		Object.assign(evt,o);
		document.dispatchEvent(evt);
	},
    get:function(s){
		if(UploaderQueue.ATTRS.hasOwnProperty(s))return UploaderQueue.ATTRS[s];
	},
	set:function(s,o){
		if(UploaderQueue.ATTRS.hasOwnProperty(s))return UploaderQueue.ATTRS[s]=o;
	},
    _uploadErrorHandler : function (event) {
        var errorAction = this.get("errorAction"),
            updatedEvent = event,
            fileid,
            retries;


        updatedEvent.file = event.target;
        updatedEvent.originEvent = event;


        this.numberOfUploads-=1;
        delete this.currentFiles[event.target.id];
        this._detachFileEvents(event.target);


        event.target.cancelUpload();


        if (errorAction === UploaderQueue.STOP) {
            this.pauseUpload();
        }


        else if (errorAction === UploaderQueue.RESTART_ASAP) {
            fileid = event.target.id;
            retries = this.uploadRetries[fileid] || 0;


            if (retries < this.get("retryCount")) {
                this.uploadRetries[fileid] = retries + 1;
                this.addToQueueTop(event.target);
            }
            this._startNextFile();
        }
        else if (errorAction === UploaderQueue.RESTART_AFTER) {
            fileid = event.target.id;
            retries = this.uploadRetries[fileid] || 0;


            if (retries < this.get("retryCount")) {
                this.uploadRetries[fileid] = retries + 1;
                this.addToQueueBottom(event.target);
            }
            this._startNextFile();
        }


        this.fire("uploaderror", updatedEvent);
    },
    _startNextFile : function () {
        if (this.queuedFiles.length > 0) {
            var currentFile = this.queuedFiles.shift(),
                fileId = currentFile.id,
                parameters = this.get("perFileParameters"),
                fileParameters = parameters.hasOwnProperty(fileId) ? parameters[fileId] : parameters;


            this.currentUploadedByteValues[fileId] = 0;
            this.uploadCompleteHandler = this._uploadCompleteHandler.bind(this);
            this.uploadProgressHandler = this._uploadProgressHandler.bind(this);
            this.uploadStartHandler = this._uploadStartHandler.bind(this);
            this.uploadCancelHandler = this._uploadCancelHandler.bind(this);
            this.uploadErrorHandler = this._uploadErrorHandler.bind(this);
            currentFile.on("uploadstart", this.uploadStartHandler);
            currentFile.on("uploadprogress", this.uploadProgressHandler);
            currentFile.on("uploadcomplete", this.uploadCompleteHandler);
            currentFile.on("uploaderror", this.uploadErrorHandler);
            currentFile.on("uploadcancel", this.uploadCancelHandler);


            currentFile.xhrHeaders = this.get("uploadHeaders");
            currentFile.xhrWithCredentials = this.get("withCredentials");


            currentFile.startUpload(this.get("uploadURL"), fileParameters, this.get("fileFieldName"));


            this._registerUpload(currentFile);
        }
    },


    /**
    * Register a new upload process.
    *
    * @method _registerUpload
    * @private
    */
    _registerUpload : function (file) {
        this.numberOfUploads += 1;
        this.currentFiles[file.id] = file;
    },


    /**
    * Unregisters a new upload process.
    *
    * @method _unregisterUpload
    * @private
    */
    _unregisterUpload : function (file) {
        if (this.numberOfUploads > 0) {
            this.numberOfUploads -= 1;
        }


        delete this.currentFiles[file.id];
        delete this.uploadRetries[file.id];


        this._detachFileEvents(file);
    },


    _detachFileEvents : function (file) {
        
        file.detach("uploadstart", this.uploadStartHandler);
        file.detach("uploadprogress", this.uploadProgressHandler);
        file.detach("uploadcomplete", this.uploadCompleteHandler);
        file.detach("uploaderror", this.uploadErrorHandler);
        file.detach("uploadcancel", this.uploadCancelHandler);
    },


    /**
    * Handles and retransmits upload complete event.
    *
    * @method _uploadCompleteHandler
    * @param event The event dispatched during the upload process.
    * @private
    */
    _uploadCompleteHandler : function (event) {


        this._unregisterUpload(event.file);


        this.totalBytesUploaded += event.file.size;
        delete this.currentUploadedByteValues[event.file.id];


        if (this.queuedFiles.length > 0 && this._currentState === UploaderQueue.UPLOADING) {
            this._startNextFile();
        }


        var updatedEvent = {},
            uploadedTotal = this.totalBytesUploaded,
            percentLoaded = Math.min(100, Math.round(10000*uploadedTotal/this.totalBytes) / 100);


        updatedEvent.file = event.file;
        updatedEvent.originEvent = event;


        this.each(this.currentUploadedByteValues, function (value) {
            uploadedTotal += value;
        });


        this.fire("totaluploadprogress", {
            bytesLoaded: uploadedTotal,
            bytesTotal: this.totalBytes,
            percentLoaded: percentLoaded
        });


        this.fire("uploadcomplete", updatedEvent);


        if (this.queuedFiles.length === 0 && this.numberOfUploads <= 0) {
            this.fire("alluploadscomplete");
            this._currentState = UploaderQueue.STOPPED;
        }
    },
    
    /**
    * Handles and retransmits upload cancel event.
    *
    * @method _uploadCancelHandler
    * @param event The event dispatched during the upload process.
    * @private
    */
    _uploadCancelHandler : function (event) {


        var updatedEvent = {};
        updatedEvent.originEvent = event;
        updatedEvent.file = event.target;


        this.fire("uploadcancel", updatedEvent);
    },
    _uploadProgressHandler : function (event) {


        this.currentUploadedByteValues[event.file.id] = event.bytesLoaded;


        var updatedEvent = {},
            uploadedTotal = this.totalBytesUploaded,
            percentLoaded = Math.min(100, Math.round(10000*uploadedTotal/this.totalBytes) / 100);


        updatedEvent.originEvent = event;
        updatedEvent.file = event.file;


        this.fire("uploadprogress", updatedEvent);


        this.each(this.currentUploadedByteValues, function (value) {
            uploadedTotal += value;
        });


        this.fire("totaluploadprogress", {
            bytesLoaded: uploadedTotal,
            bytesTotal: this.totalBytes,
            percentLoaded: percentLoaded
        });
    },


    /**
    * Starts uploading the queued up file list.
    *
    * @method startUpload
    */
    startUpload: function() {
        this.queuedFiles = this.get("fileList").slice(0);
        this.numberOfUploads = 0;
        this.currentUploadedByteValues = {};
        this.currentFiles = {};
        this.totalBytesUploaded = 0;


        this._currentState = UploaderQueue.UPLOADING;


        while (this.numberOfUploads < this.get("simUploads") && this.queuedFiles.length > 0) {
            this._startNextFile();
        }
    },
    pauseUpload: function () {this._currentState = UploaderQueue.STOPPED;},
    restartUpload: function () {
        this._currentState = UploaderQueue.UPLOADING;
        while (this.numberOfUploads < this.get("simUploads")) {
             this._startNextFile();
        }
    },
    forceReupload : function (file) {
        var id = file.id;
        if (this.currentFiles.hasOwnProperty(id)) {
            file.cancelUpload();
            this._unregisterUpload(file);
            this.addToQueueTop(file);
            this._startNextFile();
        }
    },
    addToQueueTop: function (file) {this.queuedFiles.unshift(file); },
    addToQueueBottom: function (file) {this.queuedFiles.push(file);},
    cancelUpload: function (file) {
        var id,
            i,
            fid;


        if (file) {
            id = file.id;


            if (this.currentFiles[id]) {
                this.currentFiles[id].cancelUpload();
                this._unregisterUpload(this.currentFiles[id]);
                if (this._currentState === UploaderQueue.UPLOADING) {
                    this._startNextFile();
                }
            }
            else {
                for (i = 0, len = this.queuedFiles.length; i < len; i++) {
                    if (this.queuedFiles[i].id === id) {
                        this.queuedFiles.splice(i, 1);
                        break;
                    }
                }
            }
        }
        else {
            for (fid in this.currentFiles) {
                this.currentFiles[fid].cancelUpload();
                this._unregisterUpload(this.currentFiles[fid]);
            }


            this.currentUploadedByteValues = {};
            this.currentFiles = {};
            this.totalBytesUploaded = 0;
            this.fire("alluploadscancelled");
            this._currentState = UploaderQueue.STOPPED;
        }
    }
};
UploaderQueue.ATTRS = {
    simUploads: 2,
    errorAction: {
            value: "continue",
                validator: function (val) {
                return (
                    val === UploaderQueue.CONTINUE ||
                    val === UploaderQueue.STOP ||
                    val === UploaderQueue.RESTART_ASAP ||
                    val === UploaderQueue.RESTART_AFTER
                );
            }
    },
    bytesUploaded: 0,
    bytesTotal:  0,
    fileList: [],
    fileFieldName: "Filedata",
    uploadURL: '',
    uploadHeaders:{},
    withCredentials: true,
    perFileParameters: {},
    retryCount: 3
}