12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610 |
- /*!
- * Uploader - Uploader library implements html5 file upload and provides multiple simultaneous, stable, fault tolerant and resumable uploads
- * @version v0.5.6
- * @author dolymood <dolymood@gmail.com>
- * @link https://github.com/simple-uploader/Uploader
- * @license MIT
- */
- !function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Uploader=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
- var utils = _dereq_('./utils')
- function Chunk (uploader, file, offset) {
- utils.defineNonEnumerable(this, 'uploader', uploader)
- utils.defineNonEnumerable(this, 'file', file)
- utils.defineNonEnumerable(this, 'bytes', null)
- this.offset = offset
- this.tested = false
- this.retries = 0
- this.pendingRetry = false
- this.preprocessState = 0
- this.readState = 0
- this.loaded = 0
- this.total = 0
- this.chunkSize = this.uploader.opts.chunkSize
- this.startByte = this.offset * this.chunkSize
- this.endByte = this.computeEndByte()
- this.xhr = null
- }
- var STATUS = Chunk.STATUS = {
- PENDING: 'pending',
- UPLOADING: 'uploading',
- READING: 'reading',
- SUCCESS: 'success',
- ERROR: 'error',
- COMPLETE: 'complete',
- PROGRESS: 'progress',
- RETRY: 'retry'
- }
- utils.extend(Chunk.prototype, {
- _event: function (evt, args) {
- args = utils.toArray(arguments)
- args.unshift(this)
- this.file._chunkEvent.apply(this.file, args)
- },
- computeEndByte: function () {
- var endByte = Math.min(this.file.size, (this.offset + 1) * this.chunkSize)
- if (this.file.size - endByte < this.chunkSize && !this.uploader.opts.forceChunkSize) {
- // The last chunk will be bigger than the chunk size,
- // but less than 2 * this.chunkSize
- endByte = this.file.size
- }
- return endByte
- },
- getParams: function () {
- return {
- chunkNumber: this.offset + 1,
- chunkSize: this.uploader.opts.chunkSize,
- currentChunkSize: this.endByte - this.startByte,
- totalSize: this.file.size,
- identifier: this.file.uniqueIdentifier,
- filename: this.file.name,
- relativePath: this.file.relativePath,
- totalChunks: this.file.chunks.length
- }
- },
- getTarget: function (target, params) {
- if (!params.length) {
- return target
- }
- if (target.indexOf('?') < 0) {
- target += '?'
- } else {
- target += '&'
- }
- return target + params.join('&')
- },
- test: function () {
- this.xhr = new XMLHttpRequest()
- this.xhr.addEventListener('load', testHandler, false)
- this.xhr.addEventListener('error', testHandler, false)
- var testMethod = utils.evalOpts(this.uploader.opts.testMethod, this.file, this)
- var data = this.prepareXhrRequest(testMethod, true)
- this.xhr.send(data)
- var $ = this
- function testHandler (event) {
- var status = $.status(true)
- if (status === STATUS.ERROR) {
- $._event(status, $.message())
- $.uploader.uploadNextChunk()
- } else if (status === STATUS.SUCCESS) {
- $._event(status, $.message())
- $.tested = true
- } else if (!$.file.paused) {
- // Error might be caused by file pause method
- // Chunks does not exist on the server side
- $.tested = true
- $.send()
- }
- }
- },
- preprocessFinished: function () {
- // Compute the endByte after the preprocess function to allow an
- // implementer of preprocess to set the fileObj size
- this.endByte = this.computeEndByte()
- this.preprocessState = 2
- this.send()
- },
- readFinished: function (bytes) {
- this.readState = 2
- this.bytes = bytes
- this.send()
- },
- send: function () {
- var preprocess = this.uploader.opts.preprocess
- var read = this.uploader.opts.readFileFn
- if (utils.isFunction(preprocess)) {
- switch (this.preprocessState) {
- case 0:
- this.preprocessState = 1
- preprocess(this)
- return
- case 1:
- return
- }
- }
- switch (this.readState) {
- case 0:
- this.readState = 1
- read(this.file, this.file.fileType, this.startByte, this.endByte, this)
- return
- case 1:
- return
- }
- if (this.uploader.opts.testChunks && !this.tested) {
- this.test()
- return
- }
- this.loaded = 0
- this.total = 0
- this.pendingRetry = false
- // Set up request and listen for event
- this.xhr = new XMLHttpRequest()
- this.xhr.upload.addEventListener('progress', progressHandler, false)
- this.xhr.addEventListener('load', doneHandler, false)
- this.xhr.addEventListener('error', doneHandler, false)
- var uploadMethod = utils.evalOpts(this.uploader.opts.uploadMethod, this.file, this)
- var data = this.prepareXhrRequest(uploadMethod, false, this.uploader.opts.method, this.bytes)
- this.xhr.send(data)
- var $ = this
- function progressHandler (event) {
- if (event.lengthComputable) {
- $.loaded = event.loaded
- $.total = event.total
- }
- $._event(STATUS.PROGRESS, event)
- }
- function doneHandler (event) {
- var msg = $.message()
- $.processingResponse = true
- $.uploader.opts.processResponse(msg, function (err, res) {
- $.processingResponse = false
- if (!$.xhr) {
- return
- }
- $.processedState = {
- err: err,
- res: res
- }
- var status = $.status()
- if (status === STATUS.SUCCESS || status === STATUS.ERROR) {
- // delete this.data
- $._event(status, res)
- status === STATUS.ERROR && $.uploader.uploadNextChunk()
- } else {
- $._event(STATUS.RETRY, res)
- $.pendingRetry = true
- $.abort()
- $.retries++
- var retryInterval = $.uploader.opts.chunkRetryInterval
- if (retryInterval !== null) {
- setTimeout(function () {
- $.send()
- }, retryInterval)
- } else {
- $.send()
- }
- }
- }, $.file, $)
- }
- },
- abort: function () {
- var xhr = this.xhr
- this.xhr = null
- this.processingResponse = false
- this.processedState = null
- if (xhr) {
- xhr.abort()
- }
- },
- status: function (isTest) {
- if (this.readState === 1) {
- return STATUS.READING
- } else if (this.pendingRetry || this.preprocessState === 1) {
- // if pending retry then that's effectively the same as actively uploading,
- // there might just be a slight delay before the retry starts
- return STATUS.UPLOADING
- } else if (!this.xhr) {
- return STATUS.PENDING
- } else if (this.xhr.readyState < 4 || this.processingResponse) {
- // Status is really 'OPENED', 'HEADERS_RECEIVED'
- // or 'LOADING' - meaning that stuff is happening
- return STATUS.UPLOADING
- } else {
- var _status
- if (this.uploader.opts.successStatuses.indexOf(this.xhr.status) > -1) {
- // HTTP 200, perfect
- // HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed.
- _status = STATUS.SUCCESS
- } else if (this.uploader.opts.permanentErrors.indexOf(this.xhr.status) > -1 ||
- !isTest && this.retries >= this.uploader.opts.maxChunkRetries) {
- // HTTP 415/500/501, permanent error
- _status = STATUS.ERROR
- } else {
- // this should never happen, but we'll reset and queue a retry
- // a likely case for this would be 503 service unavailable
- this.abort()
- _status = STATUS.PENDING
- }
- var processedState = this.processedState
- if (processedState && processedState.err) {
- _status = STATUS.ERROR
- }
- return _status
- }
- },
- message: function () {
- return this.xhr ? this.xhr.responseText : ''
- },
- progress: function () {
- if (this.pendingRetry) {
- return 0
- }
- var s = this.status()
- if (s === STATUS.SUCCESS || s === STATUS.ERROR) {
- return 1
- } else if (s === STATUS.PENDING) {
- return 0
- } else {
- return this.total > 0 ? this.loaded / this.total : 0
- }
- },
- sizeUploaded: function () {
- var size = this.endByte - this.startByte
- // can't return only chunk.loaded value, because it is bigger than chunk size
- if (this.status() !== STATUS.SUCCESS) {
- size = this.progress() * size
- }
- return size
- },
- prepareXhrRequest: function (method, isTest, paramsMethod, blob) {
- // Add data from the query options
- var query = utils.evalOpts(this.uploader.opts.query, this.file, this, isTest)
- query = utils.extend(this.getParams(), query)
- // processParams
- query = this.uploader.opts.processParams(query, this.file, this, isTest)
- var target = utils.evalOpts(this.uploader.opts.target, this.file, this, isTest)
- var data = null
- if (method === 'GET' || paramsMethod === 'octet') {
- // Add data from the query options
- var params = []
- utils.each(query, function (v, k) {
- params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='))
- })
- target = this.getTarget(target, params)
- data = blob || null
- } else {
- // Add data from the query options
- data = new FormData()
- utils.each(query, function (v, k) {
- data.append(k, v)
- })
- if (typeof blob !== 'undefined') {
- data.append(this.uploader.opts.fileParameterName, blob, this.file.name)
- }
- }
- this.xhr.open(method, target, true)
- this.xhr.withCredentials = this.uploader.opts.withCredentials
- // Add data from header options
- utils.each(utils.evalOpts(this.uploader.opts.headers, this.file, this, isTest), function (v, k) {
- this.xhr.setRequestHeader(k, v)
- }, this)
- return data
- }
- })
- module.exports = Chunk
- },{"./utils":5}],2:[function(_dereq_,module,exports){
- var each = _dereq_('./utils').each
- var event = {
- _eventData: null,
- on: function (name, func) {
- if (!this._eventData) this._eventData = {}
- if (!this._eventData[name]) this._eventData[name] = []
- var listened = false
- each(this._eventData[name], function (fuc) {
- if (fuc === func) {
- listened = true
- return false
- }
- })
- if (!listened) {
- this._eventData[name].push(func)
- }
- },
- off: function (name, func) {
- if (!this._eventData) this._eventData = {}
- if (!this._eventData[name] || !this._eventData[name].length) return
- if (func) {
- each(this._eventData[name], function (fuc, i) {
- if (fuc === func) {
- this._eventData[name].splice(i, 1)
- return false
- }
- }, this)
- } else {
- this._eventData[name] = []
- }
- },
- trigger: function (name) {
- if (!this._eventData) this._eventData = {}
- if (!this._eventData[name]) return true
- var args = this._eventData[name].slice.call(arguments, 1)
- var preventDefault = false
- each(this._eventData[name], function (fuc) {
- preventDefault = fuc.apply(this, args) === false || preventDefault
- }, this)
- return !preventDefault
- }
- }
- module.exports = event
- },{"./utils":5}],3:[function(_dereq_,module,exports){
- var utils = _dereq_('./utils')
- var event = _dereq_('./event')
- var File = _dereq_('./file')
- var Chunk = _dereq_('./chunk')
- var version = '0.5.6'
- var isServer = typeof window === 'undefined'
- // ie10+
- var ie10plus = isServer ? false : window.navigator.msPointerEnabled
- var support = (function () {
- if (isServer) {
- return false
- }
- var sliceName = 'slice'
- var _support = utils.isDefined(window.File) && utils.isDefined(window.Blob) &&
- utils.isDefined(window.FileList)
- var bproto = null
- if (_support) {
- bproto = window.Blob.prototype
- utils.each(['slice', 'webkitSlice', 'mozSlice'], function (n) {
- if (bproto[n]) {
- sliceName = n
- return false
- }
- })
- _support = !!bproto[sliceName]
- }
- if (_support) Uploader.sliceName = sliceName
- bproto = null
- return _support
- })()
- var supportDirectory = (function () {
- if (isServer) {
- return false
- }
- var input = window.document.createElement('input')
- input.type = 'file'
- var sd = 'webkitdirectory' in input || 'directory' in input
- input = null
- return sd
- })()
- function Uploader (opts) {
- this.support = support
- /* istanbul ignore if */
- if (!this.support) {
- return
- }
- this.supportDirectory = supportDirectory
- utils.defineNonEnumerable(this, 'filePaths', {})
- this.opts = utils.extend({}, Uploader.defaults, opts || {})
- this.preventEvent = utils.bind(this._preventEvent, this)
- File.call(this, this)
- }
- /**
- * Default read function using the webAPI
- *
- * @function webAPIFileRead(fileObj, fileType, startByte, endByte, chunk)
- *
- */
- var webAPIFileRead = function (fileObj, fileType, startByte, endByte, chunk) {
- chunk.readFinished(fileObj.file[Uploader.sliceName](startByte, endByte, fileType))
- }
- Uploader.version = version
- Uploader.defaults = {
- chunkSize: 1024 * 1024,
- forceChunkSize: false,
- simultaneousUploads: 3,
- singleFile: false,
- fileParameterName: 'file',
- progressCallbacksInterval: 500,
- speedSmoothingFactor: 0.1,
- query: {},
- headers: {},
- withCredentials: false,
- preprocess: null,
- method: 'multipart',
- testMethod: 'GET',
- uploadMethod: 'POST',
- prioritizeFirstAndLastChunk: false,
- allowDuplicateUploads: false,
- target: '/',
- testChunks: true,
- generateUniqueIdentifier: null,
- maxChunkRetries: 0,
- chunkRetryInterval: null,
- permanentErrors: [404, 415, 500, 501],
- successStatuses: [200, 201, 202],
- onDropStopPropagation: false,
- initFileFn: null,
- readFileFn: webAPIFileRead,
- checkChunkUploadedByResponse: null,
- initialPaused: false,
- processResponse: function (response, cb) {
- cb(null, response)
- },
- processParams: function (params) {
- return params
- }
- }
- Uploader.utils = utils
- Uploader.event = event
- Uploader.File = File
- Uploader.Chunk = Chunk
- // inherit file
- Uploader.prototype = utils.extend({}, File.prototype)
- // inherit event
- utils.extend(Uploader.prototype, event)
- utils.extend(Uploader.prototype, {
- constructor: Uploader,
- _trigger: function (name) {
- var args = utils.toArray(arguments)
- var preventDefault = !this.trigger.apply(this, arguments)
- if (name !== 'catchAll') {
- args.unshift('catchAll')
- preventDefault = !this.trigger.apply(this, args) || preventDefault
- }
- return !preventDefault
- },
- _triggerAsync: function () {
- var args = arguments
- utils.nextTick(function () {
- this._trigger.apply(this, args)
- }, this)
- },
- addFiles: function (files, evt) {
- var _files = []
- var oldFileListLen = this.fileList.length
- utils.each(files, function (file) {
- // Uploading empty file IE10/IE11 hangs indefinitely
- // Directories have size `0` and name `.`
- // Ignore already added files if opts.allowDuplicateUploads is set to false
- if ((!ie10plus || ie10plus && file.size > 0) && !(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.'))) {
- var uniqueIdentifier = this.generateUniqueIdentifier(file)
- if (this.opts.allowDuplicateUploads || !this.getFromUniqueIdentifier(uniqueIdentifier)) {
- var _file = new File(this, file, this)
- _file.uniqueIdentifier = uniqueIdentifier
- if (this._trigger('fileAdded', _file, evt)) {
- _files.push(_file)
- } else {
- File.prototype.removeFile.call(this, _file)
- }
- }
- }
- }, this)
- // get new fileList
- var newFileList = this.fileList.slice(oldFileListLen)
- if (this._trigger('filesAdded', _files, newFileList, evt)) {
- utils.each(_files, function (file) {
- if (this.opts.singleFile && this.files.length > 0) {
- this.removeFile(this.files[0])
- }
- this.files.push(file)
- }, this)
- this._trigger('filesSubmitted', _files, newFileList, evt)
- } else {
- utils.each(newFileList, function (file) {
- File.prototype.removeFile.call(this, file)
- }, this)
- }
- },
- addFile: function (file, evt) {
- this.addFiles([file], evt)
- },
- cancel: function () {
- for (var i = this.fileList.length - 1; i >= 0; i--) {
- this.fileList[i].cancel()
- }
- },
- removeFile: function (file) {
- File.prototype.removeFile.call(this, file)
- this._trigger('fileRemoved', file)
- },
- generateUniqueIdentifier: function (file) {
- var custom = this.opts.generateUniqueIdentifier
- if (utils.isFunction(custom)) {
- return custom(file)
- }
- /* istanbul ignore next */
- // Some confusion in different versions of Firefox
- var relativePath = file.relativePath || file.webkitRelativePath || file.fileName || file.name
- /* istanbul ignore next */
- return file.size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '')
- },
- getFromUniqueIdentifier: function (uniqueIdentifier) {
- var ret = false
- utils.each(this.files, function (file) {
- if (file.uniqueIdentifier === uniqueIdentifier) {
- ret = file
- return false
- }
- })
- return ret
- },
- uploadNextChunk: function (preventEvents) {
- var found = false
- var pendingStatus = Chunk.STATUS.PENDING
- var checkChunkUploaded = this.uploader.opts.checkChunkUploadedByResponse
- if (this.opts.prioritizeFirstAndLastChunk) {
- utils.each(this.files, function (file) {
- if (file.paused) {
- return
- }
- if (checkChunkUploaded && !file._firstResponse && file.isUploading()) {
- // waiting for current file's first chunk response
- return
- }
- if (file.chunks.length && file.chunks[0].status() === pendingStatus) {
- file.chunks[0].send()
- found = true
- return false
- }
- if (file.chunks.length > 1 && file.chunks[file.chunks.length - 1].status() === pendingStatus) {
- file.chunks[file.chunks.length - 1].send()
- found = true
- return false
- }
- })
- if (found) {
- return found
- }
- }
- // Now, simply look for the next, best thing to upload
- utils.each(this.files, function (file) {
- if (!file.paused) {
- if (checkChunkUploaded && !file._firstResponse && file.isUploading()) {
- // waiting for current file's first chunk response
- return
- }
- utils.each(file.chunks, function (chunk) {
- if (chunk.status() === pendingStatus) {
- chunk.send()
- found = true
- return false
- }
- })
- }
- if (found) {
- return false
- }
- })
- if (found) {
- return true
- }
- // The are no more outstanding chunks to upload, check is everything is done
- var outstanding = false
- utils.each(this.files, function (file) {
- if (!file.isComplete()) {
- outstanding = true
- return false
- }
- })
- // should check files now
- // if now files in list
- // should not trigger complete event
- if (!outstanding && !preventEvents && this.files.length) {
- // All chunks have been uploaded, complete
- this._triggerAsync('complete')
- }
- return outstanding
- },
- upload: function (preventEvents) {
- // Make sure we don't start too many uploads at once
- var ret = this._shouldUploadNext()
- if (ret === false) {
- return
- }
- !preventEvents && this._trigger('uploadStart')
- var started = false
- for (var num = 1; num <= this.opts.simultaneousUploads - ret; num++) {
- started = this.uploadNextChunk(!preventEvents) || started
- if (!started && preventEvents) {
- // completed
- break
- }
- }
- if (!started && !preventEvents) {
- this._triggerAsync('complete')
- }
- },
- /**
- * should upload next chunk
- * @function
- * @returns {Boolean|Number}
- */
- _shouldUploadNext: function () {
- var num = 0
- var should = true
- var simultaneousUploads = this.opts.simultaneousUploads
- var uploadingStatus = Chunk.STATUS.UPLOADING
- utils.each(this.files, function (file) {
- utils.each(file.chunks, function (chunk) {
- if (chunk.status() === uploadingStatus) {
- num++
- if (num >= simultaneousUploads) {
- should = false
- return false
- }
- }
- })
- return should
- })
- // if should is true then return uploading chunks's length
- return should && num
- },
- /**
- * Assign a browse action to one or more DOM nodes.
- * @function
- * @param {Element|Array.<Element>} domNodes
- * @param {boolean} isDirectory Pass in true to allow directories to
- * @param {boolean} singleFile prevent multi file upload
- * @param {Object} attributes set custom attributes:
- * http://www.w3.org/TR/html-markup/input.file.html#input.file-attributes
- * eg: accept: 'image/*'
- * be selected (Chrome only).
- */
- assignBrowse: function (domNodes, isDirectory, singleFile, attributes) {
- if (typeof domNodes.length === 'undefined') {
- domNodes = [domNodes]
- }
- utils.each(domNodes, function (domNode) {
- var input
- if (domNode.tagName === 'INPUT' && domNode.type === 'file') {
- input = domNode
- } else {
- input = document.createElement('input')
- input.setAttribute('type', 'file')
- // display:none - not working in opera 12
- utils.extend(input.style, {
- visibility: 'hidden',
- position: 'absolute',
- width: '1px',
- height: '1px'
- })
- // for opera 12 browser, input must be assigned to a document
- domNode.appendChild(input)
- // https://developer.mozilla.org/en/using_files_from_web_applications)
- // event listener is executed two times
- // first one - original mouse click event
- // second - input.click(), input is inside domNode
- domNode.addEventListener('click', function (e) {
- if (domNode.tagName.toLowerCase() === 'label') {
- return
- }
- input.click()
- }, false)
- }
- if (!this.opts.singleFile && !singleFile) {
- input.setAttribute('multiple', 'multiple')
- }
- if (isDirectory) {
- input.setAttribute('webkitdirectory', 'webkitdirectory')
- }
- attributes && utils.each(attributes, function (value, key) {
- input.setAttribute(key, value)
- })
- // When new files are added, simply append them to the overall list
- var that = this
- input.addEventListener('change', function (e) {
- that._trigger(e.type, e)
- if (e.target.value) {
- that.addFiles(e.target.files, e)
- e.target.value = ''
- }
- }, false)
- }, this)
- },
- onDrop: function (evt) {
- this._trigger(evt.type, evt)
- if (this.opts.onDropStopPropagation) {
- evt.stopPropagation()
- }
- evt.preventDefault()
- this._parseDataTransfer(evt.dataTransfer, evt)
- },
- _parseDataTransfer: function (dataTransfer, evt) {
- if (dataTransfer.items && dataTransfer.items[0] &&
- dataTransfer.items[0].webkitGetAsEntry) {
- this.webkitReadDataTransfer(dataTransfer, evt)
- } else {
- this.addFiles(dataTransfer.files, evt)
- }
- },
- webkitReadDataTransfer: function (dataTransfer, evt) {
- var self = this
- var queue = dataTransfer.items.length
- var files = []
- utils.each(dataTransfer.items, function (item) {
- var entry = item.webkitGetAsEntry()
- if (!entry) {
- decrement()
- return
- }
- if (entry.isFile) {
- // due to a bug in Chrome's File System API impl - #149735
- fileReadSuccess(item.getAsFile(), entry.fullPath)
- } else {
- readDirectory(entry.createReader())
- }
- })
- function readDirectory (reader) {
- reader.readEntries(function (entries) {
- if (entries.length) {
- queue += entries.length
- utils.each(entries, function (entry) {
- if (entry.isFile) {
- var fullPath = entry.fullPath
- entry.file(function (file) {
- fileReadSuccess(file, fullPath)
- }, readError)
- } else if (entry.isDirectory) {
- readDirectory(entry.createReader())
- }
- })
- readDirectory(reader)
- } else {
- decrement()
- }
- }, readError)
- }
- function fileReadSuccess (file, fullPath) {
- // relative path should not start with "/"
- file.relativePath = fullPath.substring(1)
- files.push(file)
- decrement()
- }
- function readError (fileError) {
- throw fileError
- }
- function decrement () {
- if (--queue === 0) {
- self.addFiles(files, evt)
- }
- }
- },
- _assignHelper: function (domNodes, handles, remove) {
- if (typeof domNodes.length === 'undefined') {
- domNodes = [domNodes]
- }
- var evtMethod = remove ? 'removeEventListener' : 'addEventListener'
- utils.each(domNodes, function (domNode) {
- utils.each(handles, function (handler, name) {
- domNode[evtMethod](name, handler, false)
- }, this)
- }, this)
- },
- _preventEvent: function (e) {
- utils.preventEvent(e)
- this._trigger(e.type, e)
- },
- /**
- * Assign one or more DOM nodes as a drop target.
- * @function
- * @param {Element|Array.<Element>} domNodes
- */
- assignDrop: function (domNodes) {
- this._onDrop = utils.bind(this.onDrop, this)
- this._assignHelper(domNodes, {
- dragover: this.preventEvent,
- dragenter: this.preventEvent,
- dragleave: this.preventEvent,
- drop: this._onDrop
- })
- },
- /**
- * Un-assign drop event from DOM nodes
- * @function
- * @param domNodes
- */
- unAssignDrop: function (domNodes) {
- this._assignHelper(domNodes, {
- dragover: this.preventEvent,
- dragenter: this.preventEvent,
- dragleave: this.preventEvent,
- drop: this._onDrop
- }, true)
- this._onDrop = null
- }
- })
- module.exports = Uploader
- },{"./chunk":1,"./event":2,"./file":4,"./utils":5}],4:[function(_dereq_,module,exports){
- var utils = _dereq_('./utils')
- var Chunk = _dereq_('./chunk')
- function File (uploader, file, parent) {
- utils.defineNonEnumerable(this, 'uploader', uploader)
- this.isRoot = this.isFolder = uploader === this
- utils.defineNonEnumerable(this, 'parent', parent || null)
- utils.defineNonEnumerable(this, 'files', [])
- utils.defineNonEnumerable(this, 'fileList', [])
- utils.defineNonEnumerable(this, 'chunks', [])
- utils.defineNonEnumerable(this, '_errorFiles', [])
- utils.defineNonEnumerable(this, 'file', null)
- this.id = utils.uid()
- if (this.isRoot || !file) {
- this.file = null
- } else {
- if (utils.isString(file)) {
- // folder
- this.isFolder = true
- this.file = null
- this.path = file
- if (this.parent.path) {
- file = file.substr(this.parent.path.length)
- }
- this.name = file.charAt(file.length - 1) === '/' ? file.substr(0, file.length - 1) : file
- } else {
- this.file = file
- this.fileType = this.file.type
- this.name = file.fileName || file.name
- this.size = file.size
- this.relativePath = file.relativePath || file.webkitRelativePath || this.name
- this._parseFile()
- }
- }
- this.paused = uploader.opts.initialPaused
- this.error = false
- this.allError = false
- this.aborted = false
- this.completed = false
- this.averageSpeed = 0
- this.currentSpeed = 0
- this._lastProgressCallback = Date.now()
- this._prevUploadedSize = 0
- this._prevProgress = 0
- this.bootstrap()
- }
- utils.extend(File.prototype, {
- _parseFile: function () {
- var ppaths = parsePaths(this.relativePath)
- if (ppaths.length) {
- var filePaths = this.uploader.filePaths
- utils.each(ppaths, function (path, i) {
- var folderFile = filePaths[path]
- if (!folderFile) {
- folderFile = new File(this.uploader, path, this.parent)
- filePaths[path] = folderFile
- this._updateParentFileList(folderFile)
- }
- this.parent = folderFile
- folderFile.files.push(this)
- if (!ppaths[i + 1]) {
- folderFile.fileList.push(this)
- }
- }, this)
- } else {
- this._updateParentFileList()
- }
- },
- _updateParentFileList: function (file) {
- if (!file) {
- file = this
- }
- var p = this.parent
- if (p) {
- p.fileList.push(file)
- }
- },
- _eachAccess: function (eachFn, fileFn) {
- if (this.isFolder) {
- utils.each(this.files, function (f, i) {
- return eachFn.call(this, f, i)
- }, this)
- return
- }
- fileFn.call(this, this)
- },
- bootstrap: function () {
- if (this.isFolder) return
- var opts = this.uploader.opts
- if (utils.isFunction(opts.initFileFn)) {
- opts.initFileFn.call(this, this)
- }
- this.abort(true)
- this._resetError()
- // Rebuild stack of chunks from file
- this._prevProgress = 0
- var round = opts.forceChunkSize ? Math.ceil : Math.floor
- var chunks = Math.max(round(this.size / opts.chunkSize), 1)
- for (var offset = 0; offset < chunks; offset++) {
- this.chunks.push(new Chunk(this.uploader, this, offset))
- }
- },
- _measureSpeed: function () {
- var smoothingFactor = this.uploader.opts.speedSmoothingFactor
- var timeSpan = Date.now() - this._lastProgressCallback
- if (!timeSpan) {
- return
- }
- var uploaded = this.sizeUploaded()
- // Prevent negative upload speed after file upload resume
- this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0)
- this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed
- this._prevUploadedSize = uploaded
- if (this.parent && this.parent._checkProgress()) {
- this.parent._measureSpeed()
- }
- },
- _checkProgress: function (file) {
- return Date.now() - this._lastProgressCallback >= this.uploader.opts.progressCallbacksInterval
- },
- _chunkEvent: function (chunk, evt, message) {
- var uploader = this.uploader
- var STATUS = Chunk.STATUS
- var that = this
- var rootFile = this.getRoot()
- var triggerProgress = function () {
- that._measureSpeed()
- uploader._trigger('fileProgress', rootFile, that, chunk)
- that._lastProgressCallback = Date.now()
- }
- switch (evt) {
- case STATUS.PROGRESS:
- if (this._checkProgress()) {
- triggerProgress()
- }
- break
- case STATUS.ERROR:
- this._error()
- this.abort(true)
- uploader._trigger('fileError', rootFile, this, message, chunk)
- break
- case STATUS.SUCCESS:
- this._updateUploadedChunks(message, chunk)
- if (this.error) {
- return
- }
- clearTimeout(this._progeressId)
- this._progeressId = 0
- var timeDiff = Date.now() - this._lastProgressCallback
- if (timeDiff < uploader.opts.progressCallbacksInterval) {
- this._progeressId = setTimeout(triggerProgress, uploader.opts.progressCallbacksInterval - timeDiff)
- }
- if (this.isComplete()) {
- clearTimeout(this._progeressId)
- triggerProgress()
- this.currentSpeed = 0
- this.averageSpeed = 0
- uploader._trigger('fileSuccess', rootFile, this, message, chunk)
- if (rootFile.isComplete()) {
- uploader._trigger('fileComplete', rootFile, this)
- }
- } else if (!this._progeressId) {
- triggerProgress()
- }
- break
- case STATUS.RETRY:
- uploader._trigger('fileRetry', rootFile, this, chunk)
- break
- }
- },
- _updateUploadedChunks: function (message, chunk) {
- var checkChunkUploaded = this.uploader.opts.checkChunkUploadedByResponse
- if (checkChunkUploaded) {
- var xhr = chunk.xhr
- utils.each(this.chunks, function (_chunk) {
- if (!_chunk.tested) {
- var uploaded = checkChunkUploaded.call(this, _chunk, message)
- if (_chunk === chunk && !uploaded) {
- // fix the first chunk xhr status
- // treated as success but checkChunkUploaded is false
- // so the current chunk should be uploaded again
- _chunk.xhr = null
- }
- if (uploaded) {
- // first success and other chunks are uploaded
- // then set xhr, so the uploaded chunks
- // will be treated as success too
- _chunk.xhr = xhr
- }
- _chunk.tested = true
- }
- }, this)
- if (!this._firstResponse) {
- this._firstResponse = true
- this.uploader.upload(true)
- } else {
- this.uploader.uploadNextChunk()
- }
- } else {
- this.uploader.uploadNextChunk()
- }
- },
- _error: function () {
- this.error = this.allError = true
- var parent = this.parent
- while (parent && parent !== this.uploader) {
- parent._errorFiles.push(this)
- parent.error = true
- if (parent._errorFiles.length === parent.files.length) {
- parent.allError = true
- }
- parent = parent.parent
- }
- },
- _resetError: function () {
- this.error = this.allError = false
- var parent = this.parent
- var index = -1
- while (parent && parent !== this.uploader) {
- index = parent._errorFiles.indexOf(this)
- parent._errorFiles.splice(index, 1)
- parent.allError = false
- if (!parent._errorFiles.length) {
- parent.error = false
- }
- parent = parent.parent
- }
- },
- isComplete: function () {
- if (!this.completed) {
- var outstanding = false
- this._eachAccess(function (file) {
- if (!file.isComplete()) {
- outstanding = true
- return false
- }
- }, function () {
- if (this.error) {
- outstanding = true
- } else {
- var STATUS = Chunk.STATUS
- utils.each(this.chunks, function (chunk) {
- var status = chunk.status()
- if (status === STATUS.ERROR || status === STATUS.PENDING || status === STATUS.UPLOADING || status === STATUS.READING || chunk.preprocessState === 1 || chunk.readState === 1) {
- outstanding = true
- return false
- }
- })
- }
- })
- this.completed = !outstanding
- }
- return this.completed
- },
- isUploading: function () {
- var uploading = false
- this._eachAccess(function (file) {
- if (file.isUploading()) {
- uploading = true
- return false
- }
- }, function () {
- var uploadingStatus = Chunk.STATUS.UPLOADING
- utils.each(this.chunks, function (chunk) {
- if (chunk.status() === uploadingStatus) {
- uploading = true
- return false
- }
- })
- })
- return uploading
- },
- resume: function () {
- this._eachAccess(function (f) {
- f.resume()
- }, function () {
- this.paused = false
- this.aborted = false
- this.uploader.upload()
- })
- this.paused = false
- this.aborted = false
- },
- pause: function () {
- this._eachAccess(function (f) {
- f.pause()
- }, function () {
- this.paused = true
- this.abort()
- })
- this.paused = true
- },
- cancel: function () {
- this.uploader.removeFile(this)
- },
- retry: function (file) {
- var fileRetry = function (file) {
- if (file.error) {
- file.bootstrap()
- }
- }
- if (file) {
- file.bootstrap()
- } else {
- this._eachAccess(fileRetry, function () {
- this.bootstrap()
- })
- }
- this.uploader.upload()
- },
- abort: function (reset) {
- if (this.aborted) {
- return
- }
- this.currentSpeed = 0
- this.averageSpeed = 0
- this.aborted = !reset
- var chunks = this.chunks
- if (reset) {
- this.chunks = []
- }
- var uploadingStatus = Chunk.STATUS.UPLOADING
- utils.each(chunks, function (c) {
- if (c.status() === uploadingStatus) {
- c.abort()
- this.uploader.uploadNextChunk()
- }
- }, this)
- },
- progress: function () {
- var totalDone = 0
- var totalSize = 0
- var ret = 0
- this._eachAccess(function (file, index) {
- totalDone += file.progress() * file.size
- totalSize += file.size
- if (index === this.files.length - 1) {
- ret = totalSize > 0 ? totalDone / totalSize : this.isComplete() ? 1 : 0
- }
- }, function () {
- if (this.error) {
- ret = 1
- return
- }
- if (this.chunks.length === 1) {
- this._prevProgress = Math.max(this._prevProgress, this.chunks[0].progress())
- ret = this._prevProgress
- return
- }
- // Sum up progress across everything
- var bytesLoaded = 0
- utils.each(this.chunks, function (c) {
- // get chunk progress relative to entire file
- bytesLoaded += c.progress() * (c.endByte - c.startByte)
- })
- var percent = bytesLoaded / this.size
- // We don't want to lose percentages when an upload is paused
- this._prevProgress = Math.max(this._prevProgress, percent > 0.9999 ? 1 : percent)
- ret = this._prevProgress
- })
- return ret
- },
- getSize: function () {
- var size = 0
- this._eachAccess(function (file) {
- size += file.size
- }, function () {
- size += this.size
- })
- return size
- },
- getFormatSize: function () {
- var size = this.getSize()
- return utils.formatSize(size)
- },
- getRoot: function () {
- if (this.isRoot) {
- return this
- }
- var parent = this.parent
- while (parent) {
- if (parent.parent === this.uploader) {
- // find it
- return parent
- }
- parent = parent.parent
- }
- return this
- },
- sizeUploaded: function () {
- var size = 0
- this._eachAccess(function (file) {
- size += file.sizeUploaded()
- }, function () {
- utils.each(this.chunks, function (chunk) {
- size += chunk.sizeUploaded()
- })
- })
- return size
- },
- timeRemaining: function () {
- var ret = 0
- var sizeDelta = 0
- var averageSpeed = 0
- this._eachAccess(function (file, i) {
- if (!file.paused && !file.error) {
- sizeDelta += file.size - file.sizeUploaded()
- averageSpeed += file.averageSpeed
- }
- if (i === this.files.length - 1) {
- ret = calRet(sizeDelta, averageSpeed)
- }
- }, function () {
- if (this.paused || this.error) {
- ret = 0
- return
- }
- var delta = this.size - this.sizeUploaded()
- ret = calRet(delta, this.averageSpeed)
- })
- return ret
- function calRet (delta, averageSpeed) {
- if (delta && !averageSpeed) {
- return Number.POSITIVE_INFINITY
- }
- if (!delta && !averageSpeed) {
- return 0
- }
- return Math.floor(delta / averageSpeed)
- }
- },
- removeFile: function (file) {
- if (file.isFolder) {
- while (file.files.length) {
- var f = file.files[file.files.length - 1]
- this._removeFile(f)
- }
- }
- this._removeFile(file)
- },
- _delFilePath: function (file) {
- if (file.path && this.filePaths) {
- delete this.filePaths[file.path]
- }
- utils.each(file.fileList, function (file) {
- this._delFilePath(file)
- }, this)
- },
- _removeFile: function (file) {
- if (!file.isFolder) {
- utils.each(this.files, function (f, i) {
- if (f === file) {
- this.files.splice(i, 1)
- return false
- }
- }, this)
- file.abort()
- var parent = file.parent
- var newParent
- while (parent && parent !== this) {
- newParent = parent.parent
- parent._removeFile(file)
- parent = newParent
- }
- }
- file.parent === this && utils.each(this.fileList, function (f, i) {
- if (f === file) {
- this.fileList.splice(i, 1)
- return false
- }
- }, this)
- if (!this.isRoot && this.isFolder && !this.files.length) {
- this.parent._removeFile(this)
- this.uploader._delFilePath(this)
- }
- file.parent = null
- },
- getType: function () {
- if (this.isFolder) {
- return 'folder'
- }
- return this.file.type && this.file.type.split('/')[1]
- },
- getExtension: function () {
- if (this.isFolder) {
- return ''
- }
- return this.name.substr((~-this.name.lastIndexOf('.') >>> 0) + 2).toLowerCase()
- }
- })
- module.exports = File
- function parsePaths (path) {
- var ret = []
- var paths = path.split('/')
- var len = paths.length
- var i = 1
- paths.splice(len - 1, 1)
- len--
- if (paths.length) {
- while (i <= len) {
- ret.push(paths.slice(0, i++).join('/') + '/')
- }
- }
- return ret
- }
- },{"./chunk":1,"./utils":5}],5:[function(_dereq_,module,exports){
- var oproto = Object.prototype
- var aproto = Array.prototype
- var serialize = oproto.toString
- var isFunction = function (fn) {
- return serialize.call(fn) === '[object Function]'
- }
- var isArray = Array.isArray || /* istanbul ignore next */ function (ary) {
- return serialize.call(ary) === '[object Array]'
- }
- var isPlainObject = function (obj) {
- return serialize.call(obj) === '[object Object]' && Object.getPrototypeOf(obj) === oproto
- }
- var i = 0
- var utils = {
- uid: function () {
- return ++i
- },
- noop: function () {},
- bind: function (fn, context) {
- return function () {
- return fn.apply(context, arguments)
- }
- },
- preventEvent: function (evt) {
- evt.preventDefault()
- },
- stop: function (evt) {
- evt.preventDefault()
- evt.stopPropagation()
- },
- nextTick: function (fn, context) {
- setTimeout(utils.bind(fn, context), 0)
- },
- toArray: function (ary, start, end) {
- if (start === undefined) start = 0
- if (end === undefined) end = ary.length
- return aproto.slice.call(ary, start, end)
- },
- isPlainObject: isPlainObject,
- isFunction: isFunction,
- isArray: isArray,
- isObject: function (obj) {
- return Object(obj) === obj
- },
- isString: function (s) {
- return typeof s === 'string'
- },
- isUndefined: function (a) {
- return typeof a === 'undefined'
- },
- isDefined: function (a) {
- return typeof a !== 'undefined'
- },
- each: function (ary, func, context) {
- if (utils.isDefined(ary.length)) {
- for (var i = 0, len = ary.length; i < len; i++) {
- if (func.call(context, ary[i], i, ary) === false) {
- break
- }
- }
- } else {
- for (var k in ary) {
- if (func.call(context, ary[k], k, ary) === false) {
- break
- }
- }
- }
- },
- /**
- * If option is a function, evaluate it with given params
- * @param {*} data
- * @param {...} args arguments of a callback
- * @returns {*}
- */
- evalOpts: function (data, args) {
- if (utils.isFunction(data)) {
- // `arguments` is an object, not array, in FF, so:
- args = utils.toArray(arguments)
- data = data.apply(null, args.slice(1))
- }
- return data
- },
- extend: function () {
- var options
- var name
- var src
- var copy
- var copyIsArray
- var clone
- var target = arguments[0] || {}
- var i = 1
- var length = arguments.length
- var force = false
- // 如果第一个参数为布尔,判定是否深拷贝
- if (typeof target === 'boolean') {
- force = target
- target = arguments[1] || {}
- i++
- }
- // 确保接受方为一个复杂的数据类型
- if (typeof target !== 'object' && !isFunction(target)) {
- target = {}
- }
- // 如果只有一个参数,那么新成员添加于 extend 所在的对象上
- if (i === length) {
- target = this
- i--
- }
- for (; i < length; i++) {
- // 只处理非空参数
- if ((options = arguments[i]) != null) {
- for (name in options) {
- src = target[name]
- copy = options[name]
- // 防止环引用
- if (target === copy) {
- continue
- }
- if (force && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
- if (copyIsArray) {
- copyIsArray = false
- clone = src && isArray(src) ? src : []
- } else {
- clone = src && isPlainObject(src) ? src : {}
- }
- target[name] = utils.extend(force, clone, copy)
- } else if (copy !== undefined) {
- target[name] = copy
- }
- }
- }
- }
- return target
- },
- formatSize: function (size) {
- if (size < 1024) {
- return size.toFixed(0) + ' bytes'
- } else if (size < 1024 * 1024) {
- return (size / 1024.0).toFixed(0) + ' KB'
- } else if (size < 1024 * 1024 * 1024) {
- return (size / 1024.0 / 1024.0).toFixed(1) + ' MB'
- } else {
- return (size / 1024.0 / 1024.0 / 1024.0).toFixed(1) + ' GB'
- }
- },
- defineNonEnumerable: function (target, key, value) {
- Object.defineProperty(target, key, {
- enumerable: false,
- configurable: true,
- writable: true,
- value: value
- })
- }
- }
- module.exports = utils
- },{}]},{},[3])
- (3)
- });
|