/*! WebUploader 0.1.5 */ /** * @fileOverview 让内部å„个部件的代ç å¯ä»¥ç”¨[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模å—定义方å¼ç»„织起æ¥ã€‚ * * AMD API 内部的简å•ä¸å®Œå…¨å®žçŽ°ï¼Œè¯·å¿½ç•¥ã€‚åªæœ‰å½“WebUploader被åˆå¹¶æˆä¸€ä¸ªæ–‡ä»¶çš„æ—¶å€™æ‰ä¼šå¼•入。 */ (function( root, factory ) { var modules = {}, // 内部require, 简å•ä¸å®Œå…¨å®žçŽ°ã€‚ // https://github.com/amdjs/amdjs-api/wiki/require _require = function( deps, callback ) { var args, len, i; // 如果deps䏿˜¯æ•°ç»„,则直接返回指定module if ( typeof deps === 'string' ) { return getModule( deps ); } else { args = []; for( len = deps.length, i = 0; i < len; i++ ) { args.push( getModule( deps[ i ] ) ); } return callback.apply( null, args ); } }, // 内部defineï¼Œæš‚æ—¶ä¸æ”¯æŒä¸æŒ‡å®šid. _define = function( id, deps, factory ) { if ( arguments.length === 2 ) { factory = deps; deps = null; } _require( deps || [], function() { setModule( id, factory, arguments ); }); }, // 设置module, 兼容CommonJs写法。 setModule = function( id, factory, args ) { var module = { exports: factory }, returned; if ( typeof factory === 'function' ) { args.length || (args = [ _require, module.exports, module ]); returned = factory.apply( null, args ); returned !== undefined && (module.exports = returned); } modules[ id ] = module.exports; }, // æ ¹æ®id获å–module getModule = function( id ) { var module = modules[ id ] || root[ id ]; if ( !module ) { throw new Error( '`' + id + '` is undefined' ); } return module; }, // 将所有modules,将路径idsè£…æ¢æˆå¯¹è±¡ã€‚ exportsTo = function( obj ) { var key, host, parts, part, last, ucFirst; // make the first character upper case. ucFirst = function( str ) { return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); }; for ( key in modules ) { host = obj; if ( !modules.hasOwnProperty( key ) ) { continue; } parts = key.split('/'); last = ucFirst( parts.pop() ); while( (part = ucFirst( parts.shift() )) ) { host[ part ] = host[ part ] || {}; host = host[ part ]; } host[ last ] = modules[ key ]; } return obj; }, makeExport = function( dollar ) { root.__dollar = dollar; // exports every module. return exportsTo( factory( root, _define, _require ) ); }, origin; if ( typeof module === 'object' && typeof module.exports === 'object' ) { // For CommonJS and CommonJS-like environments where a proper window is present, module.exports = makeExport(); } else if ( typeof define === 'function' && define.amd ) { // Allow using this built library as an AMD module // in another project. That other project will only // see this AMD call, not the internal modules in // the closure below. define([ 'jquery' ], makeExport ); } else { // Browser globals case. Just assign the // result to a property on the global. origin = root.WebUploader; root.WebUploader = makeExport(); root.WebUploader.noConflict = function() { root.WebUploader = origin; }; } })( window, function( window, define, require ) { /** * @fileOverview jQuery or Zepto */ define('dollar-third',[],function() { var $ = window.__dollar || window.jQuery || window.Zepto; if ( !$ ) { throw new Error('jQuery or Zepto not found!'); } return $; }); /** * @fileOverview Dom æ“作相关 */ define('dollar',[ 'dollar-third' ], function( _ ) { return _; }); /** * @fileOverview 使用jQueryçš„Promise */ define('promise-third',[ 'dollar' ], function( $ ) { return { Deferred: $.Deferred, when: $.when, isPromise: function( anything ) { return anything && typeof anything.then === 'function'; } }; }); /** * @fileOverview Promise/A+ */ define('promise',[ 'promise-third' ], function( _ ) { return _; }); /** * @fileOverview 基础类方法。 */ /** * Web Uploader内部类的详细说明,以下æåŠçš„功能类,都å¯ä»¥åœ¨`WebUploader`这个å˜é‡ä¸è®¿é—®åˆ°ã€‚ * * As you know, Web Uploaderçš„æ¯ä¸ªæ–‡ä»¶éƒ½æ˜¯ç”¨è¿‡[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范ä¸çš„`define`组织起æ¥çš„, æ¯ä¸ªModule都会有个module id. * 默认module id为该文件的路径,而æ¤è·¯å¾„将会转化æˆåå—ç©ºé—´å˜æ”¾åœ¨WebUploaderä¸ã€‚如: * * * module `base`:WebUploader.Base * * module `file`: WebUploader.File * * module `lib/dnd`: WebUploader.Lib.Dnd * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd * * * 以下文档ä¸å¯¹ç±»çš„使用å¯èƒ½çœç•¥æŽ‰äº†`WebUploader`å‰ç¼€ã€‚ * @module WebUploader * @title WebUploader API文档 */ define('base',[ 'dollar', 'promise' ], function( $, promise ) { var noop = function() {}, call = Function.call; // http://jsperf.com/uncurrythis // å科里化 function uncurryThis( fn ) { return function() { return call.apply( fn, arguments ); }; } function bindFn( fn, context ) { return function() { return fn.apply( context, arguments ); }; } function createObject( proto ) { var f; if ( Object.create ) { return Object.create( proto ); } else { f = function() {}; f.prototype = proto; return new f(); } } /** * 基础类,æä¾›ä¸€äº›ç®€å•常用的方法。 * @class Base */ return { /** * @property {String} version 当å‰ç‰ˆæœ¬å·ã€‚ */ version: '0.1.5', /** * @property {jQuery|Zepto} $ 引用ä¾èµ–çš„jQuery或者Zepto对象。 */ $: $, Deferred: promise.Deferred, isPromise: promise.isPromise, when: promise.when, /** * @description 简å•çš„æµè§ˆå™¨æ£€æŸ¥ç»“果。 * * * `webkit` webkit版本å·ï¼Œå¦‚æžœæµè§ˆå™¨ä¸ºéžwebkitå†…æ ¸ï¼Œæ¤å±žæ€§ä¸º`undefined`。 * * `chrome` chromeæµè§ˆå™¨ç‰ˆæœ¬å·ï¼Œå¦‚æžœæµè§ˆå™¨ä¸ºchrome,æ¤å±žæ€§ä¸º`undefined`。 * * `ie` ieæµè§ˆå™¨ç‰ˆæœ¬å·ï¼Œå¦‚æžœæµè§ˆå™¨ä¸ºéžie,æ¤å±žæ€§ä¸º`undefined`。**æš‚ä¸æ”¯æŒie10+** * * `firefox` firefoxæµè§ˆå™¨ç‰ˆæœ¬å·ï¼Œå¦‚æžœæµè§ˆå™¨ä¸ºéžfirefox,æ¤å±žæ€§ä¸º`undefined`。 * * `safari` safariæµè§ˆå™¨ç‰ˆæœ¬å·ï¼Œå¦‚æžœæµè§ˆå™¨ä¸ºéžsafari,æ¤å±žæ€§ä¸º`undefined`。 * * `opera` operaæµè§ˆå™¨ç‰ˆæœ¬å·ï¼Œå¦‚æžœæµè§ˆå™¨ä¸ºéžopera,æ¤å±žæ€§ä¸º`undefined`。 * * @property {Object} [browser] */ browser: (function( ua ) { var ret = {}, webkit = ua.match( /WebKit\/([\d.]+)/ ), chrome = ua.match( /Chrome\/([\d.]+)/ ) || ua.match( /CriOS\/([\d.]+)/ ), ie = ua.match( /MSIE\s([\d\.]+)/ ) || ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), firefox = ua.match( /Firefox\/([\d.]+)/ ), safari = ua.match( /Safari\/([\d.]+)/ ), opera = ua.match( /OPR\/([\d.]+)/ ); webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); ie && (ret.ie = parseFloat( ie[ 1 ] )); firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); safari && (ret.safari = parseFloat( safari[ 1 ] )); opera && (ret.opera = parseFloat( opera[ 1 ] )); return ret; })( navigator.userAgent ), /** * @description æ“作系统检查结果。 * * * `android` 如果在androidæµè§ˆå™¨çŽ¯å¢ƒä¸‹ï¼Œæ¤å€¼ä¸ºå¯¹åº”çš„android版本å·ï¼Œå¦åˆ™ä¸º`undefined`。 * * `ios` 如果在iosæµè§ˆå™¨çŽ¯å¢ƒä¸‹ï¼Œæ¤å€¼ä¸ºå¯¹åº”çš„ios版本å·ï¼Œå¦åˆ™ä¸º`undefined`。 * @property {Object} [os] */ os: (function( ua ) { var ret = {}, // osx = !!ua.match( /\(Macintosh\; Intel / ), android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); // osx && (ret.osx = true); android && (ret.android = parseFloat( android[ 1 ] )); ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); return ret; })( navigator.userAgent ), /** * 实现类与类之间的继承。 * @method inherits * @grammar Base.inherits( super ) => child * @grammar Base.inherits( super, protos ) => child * @grammar Base.inherits( super, protos, statics ) => child * @param {Class} super 父类 * @param {Object | Function} [protos] å类或者对象。如果对象ä¸åŒ…å«constructor,å类将是用æ¤å±žæ€§å€¼ã€‚ * @param {Function} [protos.constructor] åç±»æž„é€ å™¨ï¼Œä¸æŒ‡å®šçš„è¯å°†åˆ›å»ºä¸ªä¸´æ—¶çš„ç›´æŽ¥æ‰§è¡Œçˆ¶ç±»æž„é€ å™¨çš„æ–¹æ³•ã€‚ * @param {Object} [statics] 陿€å±žæ€§æˆ–方法。 * @return {Class} 返回å类。 * @example * function Person() { * console.log( 'Super' ); * } * Person.prototype.hello = function() { * console.log( 'hello' ); * }; * * var Manager = Base.inherits( Person, { * world: function() { * console.log( 'World' ); * } * }); * * // å› ä¸ºæ²¡æœ‰æŒ‡å®šæž„é€ å™¨ï¼Œçˆ¶ç±»çš„æž„é€ å™¨å°†ä¼šæ‰§è¡Œã€‚ * var instance = new Manager(); // => Super * * // 继承å父类的方法 * instance.hello(); // => hello * instance.world(); // => World * * // å类的__super__属性指å‘父类 * console.log( Manager.__super__ === Person ); // => true */ inherits: function( Super, protos, staticProtos ) { var child; if ( typeof protos === 'function' ) { child = protos; protos = null; } else if ( protos && protos.hasOwnProperty('constructor') ) { child = protos.constructor; } else { child = function() { return Super.apply( this, arguments ); }; } // å¤åˆ¶é™æ€æ–¹æ³• $.extend( true, child, Super, staticProtos || {} ); /* jshint camelcase: false */ // 让å类的__super__属性指å‘父类。 child.__super__ = Super.prototype; // æž„å»ºåŽŸåž‹ï¼Œæ·»åŠ åŽŸåž‹æ–¹æ³•æˆ–å±žæ€§ã€‚ // 暂时用Object.create实现。 child.prototype = createObject( Super.prototype ); protos && $.extend( true, child.prototype, protos ); return child; }, /** * 一个ä¸åšä»»ä½•事情的方法。å¯ä»¥ç”¨æ¥èµ‹å€¼ç»™é»˜è®¤çš„callback. * @method noop */ noop: noop, /** * è¿”å›žä¸€ä¸ªæ–°çš„æ–¹æ³•ï¼Œæ¤æ–¹æ³•将已指定的`context`æ¥æ‰§è¡Œã€‚ * @grammar Base.bindFn( fn, context ) => Function * @method bindFn * @example * var doSomething = function() { * console.log( this.name ); * }, * obj = { * name: 'Object Name' * }, * aliasFn = Base.bind( doSomething, obj ); * * aliasFn(); // => Object Name * */ bindFn: bindFn, /** * 引用Console.log如果å˜åœ¨çš„è¯ï¼Œå¦åˆ™å¼•用一个[空函数noop](#WebUploader:Base.noop)。 * @grammar Base.log( args... ) => undefined * @method log */ log: (function() { if ( window.console ) { return bindFn( console.log, console ); } return noop; })(), nextTick: (function() { return function( cb ) { setTimeout( cb, 1 ); }; // @bug 当æµè§ˆå™¨ä¸åœ¨å½“å‰çª—壿—¶å°±åœäº†ã€‚ // var next = window.requestAnimationFrame || // window.webkitRequestAnimationFrame || // window.mozRequestAnimationFrame || // function( cb ) { // window.setTimeout( cb, 1000 / 60 ); // }; // // fix: Uncaught TypeError: Illegal invocation // return bindFn( next, window ); })(), /** * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 * 将用æ¥å°†éžæ•°ç»„å¯¹è±¡è½¬åŒ–æˆæ•°ç»„对象。 * @grammar Base.slice( target, start[, end] ) => Array * @method slice * @example * function doSomthing() { * var args = Base.slice( arguments, 1 ); * console.log( args ); * } * * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] */ slice: uncurryThis( [].slice ), /** * 生æˆå”¯ä¸€çš„ID * @method guid * @grammar Base.guid() => String * @grammar Base.guid( prefx ) => String */ guid: (function() { var counter = 0; return function( prefix ) { var guid = (+new Date()).toString( 32 ), i = 0; for ( ; i < 5; i++ ) { guid += Math.floor( Math.random() * 65535 ).toString( 32 ); } return (prefix || 'wu_') + guid + (counter++).toString( 32 ); }; })(), /** * æ ¼å¼åŒ–文件大å°, 输出æˆå¸¦å•ä½çš„å—符串 * @method formatSize * @grammar Base.formatSize( size ) => String * @grammar Base.formatSize( size, pointLength ) => String * @grammar Base.formatSize( size, pointLength, units ) => String * @param {Number} size æ–‡ä»¶å¤§å° * @param {Number} [pointLength=2] ç²¾ç¡®åˆ°çš„å°æ•°ç‚¹æ•°ã€‚ * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] å•使•°ç»„。从å—节,到åƒå—节,一直往上指定。如果å•使•°ç»„里é¢åªæŒ‡å®šäº†åˆ°äº†K(åƒå—节)ï¼ŒåŒæ—¶æ–‡ä»¶å¤§å°å¤§äºŽM, æ¤æ–¹æ³•的输出将还是显示æˆå¤šå°‘K. * @example * console.log( Base.formatSize( 100 ) ); // => 100B * console.log( Base.formatSize( 1024 ) ); // => 1.00K * console.log( Base.formatSize( 1024, 0 ) ); // => 1K * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB */ formatSize: function( size, pointLength, units ) { var unit; units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; while ( (unit = units.shift()) && size > 1024 ) { size = size / 1024; } return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + unit; } }; }); /** * 事件处ç†ç±»ï¼Œå¯ä»¥ç‹¬ç«‹ä½¿ç”¨ï¼Œä¹Ÿå¯ä»¥æ‰©å±•给对象使用。 * @fileOverview Mediator */ define('mediator',[ 'base' ], function( Base ) { var $ = Base.$, slice = [].slice, separator = /\s+/, protos; // æ ¹æ®æ¡ä»¶è¿‡æ»¤å‡ºäº‹ä»¶handlers. function findHandlers( arr, name, callback, context ) { return $.grep( arr, function( handler ) { return handler && (!name || handler.e === name) && (!callback || handler.cb === callback || handler.cb._cb === callback) && (!context || handler.ctx === context); }); } function eachEvent( events, callback, iterator ) { // 䏿”¯æŒå¯¹è±¡ï¼Œåªæ”¯æŒå¤šä¸ªeventç”¨ç©ºæ ¼éš”å¼€ $.each( (events || '').split( separator ), function( _, key ) { iterator( key, callback ); }); } function triggerHanders( events, args ) { var stoped = false, i = -1, len = events.length, handler; while ( ++i < len ) { handler = events[ i ]; if ( handler.cb.apply( handler.ctx2, args ) === false ) { stoped = true; break; } } return !stoped; } protos = { /** * 绑定事件。 * * `callback`方法在执行时,argumentså°†ä¼šæ¥æºäºŽtrigger的时候æºå¸¦çš„傿•°ã€‚如 * ```javascript * var obj = {}; * * // 使得obj有事件行为 * Mediator.installTo( obj ); * * obj.on( 'testa', function( arg1, arg2 ) { * console.log( arg1, arg2 ); // => 'arg1', 'arg2' * }); * * obj.trigger( 'testa', 'arg1', 'arg2' ); * ``` * * 如果`callback`ä¸ï¼ŒæŸä¸€ä¸ªæ–¹æ³•`return false`了,则åŽç»çš„å…¶ä»–`callback`都ä¸ä¼šè¢«æ‰§è¡Œåˆ°ã€‚ * 切会影å“到`trigger`方法的返回值,为`false`。 * * `on`还å¯ä»¥ç”¨æ¥æ·»åŠ ä¸€ä¸ªç‰¹æ®Šäº‹ä»¶`all`, è¿™æ ·æ‰€æœ‰çš„äº‹ä»¶è§¦å‘都会å“åº”åˆ°ã€‚åŒæ—¶æ¤ç±»`callback`ä¸çš„arguments有一个ä¸åŒå¤„, * å°±æ˜¯ç¬¬ä¸€ä¸ªå‚æ•°ä¸º`type`ï¼Œè®°å½•å½“å‰æ˜¯ä»€ä¹ˆäº‹ä»¶åœ¨è§¦å‘。æ¤ç±»`callback`çš„ä¼˜å…ˆçº§æ¯”è„šä½Žï¼Œä¼šå†æ£å¸¸`callback`执行完åŽè§¦å‘。 * ```javascript * obj.on( 'all', function( type, arg1, arg2 ) { * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' * }); * ``` * * @method on * @grammar on( name, callback[, context] ) => self * @param {String} name 事件å,支æŒå¤šä¸ªäº‹ä»¶ç”¨ç©ºæ ¼éš”å¼€ * @param {Function} callback 事件处ç†å™¨ * @param {Object} [context] 事件处ç†å™¨çš„上下文。 * @return {self} è¿”å›žè‡ªèº«ï¼Œæ–¹ä¾¿é“¾å¼ * @chainable * @class Mediator */ on: function( name, callback, context ) { var me = this, set; if ( !callback ) { return this; } set = this._events || (this._events = []); eachEvent( name, callback, function( name, callback ) { var handler = { e: name }; handler.cb = callback; handler.ctx = context; handler.ctx2 = context || me; handler.id = set.length; set.push( handler ); }); return this; }, /** * 绑定事件,且当handler执行完åŽï¼Œè‡ªåŠ¨è§£é™¤ç»‘å®šã€‚ * @method once * @grammar once( name, callback[, context] ) => self * @param {String} name 事件å * @param {Function} callback 事件处ç†å™¨ * @param {Object} [context] 事件处ç†å™¨çš„上下文。 * @return {self} è¿”å›žè‡ªèº«ï¼Œæ–¹ä¾¿é“¾å¼ * @chainable */ once: function( name, callback, context ) { var me = this; if ( !callback ) { return me; } eachEvent( name, callback, function( name, callback ) { var once = function() { me.off( name, once ); return callback.apply( context || me, arguments ); }; once._cb = callback; me.on( name, once, context ); }); return me; }, /** * 解除事件绑定 * @method off * @grammar off( [name[, callback[, context] ] ] ) => self * @param {String} [name] 事件å * @param {Function} [callback] 事件处ç†å™¨ * @param {Object} [context] 事件处ç†å™¨çš„上下文。 * @return {self} è¿”å›žè‡ªèº«ï¼Œæ–¹ä¾¿é“¾å¼ * @chainable */ off: function( name, cb, ctx ) { var events = this._events; if ( !events ) { return this; } if ( !name && !cb && !ctx ) { this._events = []; return this; } eachEvent( name, cb, function( name, cb ) { $.each( findHandlers( events, name, cb, ctx ), function() { delete events[ this.id ]; }); }); return this; }, /** * 触å‘事件 * @method trigger * @grammar trigger( name[, args...] ) => self * @param {String} type 事件å * @param {*} [...] ä»»æ„傿•° * @return {Boolean} 如果handlerä¸return false了,则返回false, å¦åˆ™è¿”回true */ trigger: function( type ) { var args, events, allEvents; if ( !this._events || !type ) { return this; } args = slice.call( arguments, 1 ); events = findHandlers( this._events, type ); allEvents = findHandlers( this._events, 'all' ); return triggerHanders( events, args ) && triggerHanders( allEvents, arguments ); } }; /** * ä¸ä»‹è€…,它本身是个å•例,但å¯ä»¥é€šè¿‡[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 * 主è¦ç›®çš„æ˜¯è´Ÿè´£æ¨¡å—与模å—之间的åˆä½œï¼Œé™ä½Žè€¦åˆåº¦ã€‚ * * @class Mediator */ return $.extend({ /** * å¯ä»¥é€šè¿‡è¿™ä¸ªæŽ¥å£ï¼Œä½¿ä»»ä½•对象具备事件功能。 * @method installTo * @param {Object} obj 需è¦å…·å¤‡äº‹ä»¶è¡Œä¸ºçš„对象。 * @return {Object} 返回obj. */ installTo: function( obj ) { return $.extend( obj, protos ); } }, protos ); }); /** * @fileOverview Uploaderä¸Šä¼ ç±» */ define('uploader',[ 'base', 'mediator' ], function( Base, Mediator ) { var $ = Base.$; /** * ä¸Šä¼ å…¥å£ç±»ã€‚ * @class Uploader * @constructor * @grammar new Uploader( opts ) => Uploader * @example * var uploader = WebUploader.Uploader({ * swf: 'path_of_swf/Uploader.swf', * * // å¼€èµ·åˆ†ç‰‡ä¸Šä¼ ã€‚ * chunked: true * }); */ function Uploader( opts ) { this.options = $.extend( true, {}, Uploader.options, opts ); this._init( this.options ); } // default Options // widgets䏿œ‰ç›¸åº”扩展 Uploader.options = {}; Mediator.installTo( Uploader.prototype ); // æ‰¹é‡æ·»åŠ çº¯å‘½ä»¤å¼æ–¹æ³•。 $.each({ upload: 'start-upload', stop: 'stop-upload', getFile: 'get-file', getFiles: 'get-files', addFile: 'add-file', addFiles: 'add-file', sort: 'sort-files', removeFile: 'remove-file', cancelFile: 'cancel-file', skipFile: 'skip-file', retry: 'retry', isInProgress: 'is-in-progress', makeThumb: 'make-thumb', md5File: 'md5-file', getDimension: 'get-dimension', addButton: 'add-btn', predictRuntimeType: 'predict-runtime-type', refresh: 'refresh', disable: 'disable', enable: 'enable', reset: 'reset' }, function( fn, command ) { Uploader.prototype[ fn ] = function() { return this.request( command, arguments ); }; }); $.extend( Uploader.prototype, { state: 'pending', _init: function( opts ) { var me = this; me.request( 'init', opts, function() { me.state = 'ready'; me.trigger('ready'); }); }, /** * èŽ·å–æˆ–者设置Uploaderé…置项。 * @method option * @grammar option( key ) => * * @grammar option( key, val ) => self * @example * * // åˆå§‹çжæ€å›¾ç‰‡ä¸Šä¼ å‰ä¸ä¼šåŽ‹ç¼© * var uploader = new WebUploader.Uploader({ * compress: null; * }); * * // 修改åŽå›¾ç‰‡ä¸Šä¼ å‰ï¼Œå°è¯•将图片压缩到1600 * 1600 * uploader.option( 'compress', { * width: 1600, * height: 1600 * }); */ option: function( key, val ) { var opts = this.options; // setter if ( arguments.length > 1 ) { if ( $.isPlainObject( val ) && $.isPlainObject( opts[ key ] ) ) { $.extend( opts[ key ], val ); } else { opts[ key ] = val; } } else { // getter return key ? opts[ key ] : opts; } }, /** * èŽ·å–æ–‡ä»¶ç»Ÿè®¡ä¿¡æ¯ã€‚返回一个包å«ä¸€ä¸‹ä¿¡æ¯çš„对象。 * * `successNum` ä¸Šä¼ æˆåŠŸçš„æ–‡ä»¶æ•° * * `progressNum` ä¸Šä¼ ä¸çš„æ–‡ä»¶æ•° * * `cancelNum` è¢«åˆ é™¤çš„æ–‡ä»¶æ•° * * `invalidNum` æ— æ•ˆçš„æ–‡ä»¶æ•° * * `uploadFailNum` ä¸Šä¼ å¤±è´¥çš„æ–‡ä»¶æ•° * * `queueNum` 还在队列ä¸çš„æ–‡ä»¶æ•° * * `interruptNum` 被暂åœçš„æ–‡ä»¶æ•° * @method getStats * @grammar getStats() => Object */ getStats: function() { // return this._mgr.getStats.apply( this._mgr, arguments ); var stats = this.request('get-stats'); return stats ? { successNum: stats.numOfSuccess, progressNum: stats.numOfProgress, // who care? // queueFailNum: 0, cancelNum: stats.numOfCancel, invalidNum: stats.numOfInvalid, uploadFailNum: stats.numOfUploadFailed, queueNum: stats.numOfQueue, interruptNum: stats.numofInterrupt } : {}; }, // 需è¦é‡å†™æ¤æ–¹æ³•æ¥æ¥æ”¯æŒopts.onEventå’Œinstance.onEvent的处ç†å™¨ trigger: function( type/*, args...*/ ) { var args = [].slice.call( arguments, 1 ), opts = this.options, name = 'on' + type.substring( 0, 1 ).toUpperCase() + type.substring( 1 ); if ( // 调用通过on方法注册的handler. Mediator.trigger.apply( this, arguments ) === false || // 调用opts.onEvent $.isFunction( opts[ name ] ) && opts[ name ].apply( this, args ) === false || // 调用this.onEvent $.isFunction( this[ name ] ) && this[ name ].apply( this, args ) === false || // å¹¿æ’æ‰€æœ‰uploader的事件。 Mediator.trigger.apply( Mediator, [ this, type ].concat( args ) ) === false ) { return false; } return true; }, /** * é”€æ¯ webuploader 实例 * @method destroy * @grammar destroy() => undefined */ destroy: function() { this.request( 'destroy', arguments ); this.off(); }, // widgets/widget.jså°†è¡¥å……æ¤æ–¹æ³•的详细文档。 request: Base.noop }); /** * 创建Uploader实例,ç‰åŒäºŽnew Uploader( opts ); * @method create * @class Base * @static * @grammar Base.create( opts ) => Uploader */ Base.create = Uploader.create = function( opts ) { return new Uploader( opts ); }; // 暴露Uploader,å¯ä»¥é€šè¿‡å®ƒæ¥æ‰©å±•业务逻辑。 Base.Uploader = Uploader; return Uploader; }); /** * @fileOverview Runtime管ç†å™¨ï¼Œè´Ÿè´£Runtime的选择, 连接 */ define('runtime/runtime',[ 'base', 'mediator' ], function( Base, Mediator ) { var $ = Base.$, factories = {}, // 获å–对象的第一个key getFirstKey = function( obj ) { for ( var key in obj ) { if ( obj.hasOwnProperty( key ) ) { return key; } } return null; }; // 接å£ç±»ã€‚ function Runtime( options ) { this.options = $.extend({ container: document.body }, options ); this.uid = Base.guid('rt_'); } $.extend( Runtime.prototype, { getContainer: function() { var opts = this.options, parent, container; if ( this._container ) { return this._container; } parent = $( opts.container || document.body ); container = $( document.createElement('div') ); container.attr( 'id', 'rt_' + this.uid ); container.css({ position: 'absolute', top: '0px', left: '0px', width: '1px', height: '1px', overflow: 'hidden' }); parent.append( container ); parent.addClass('webuploader-container'); this._container = container; this._parent = parent; return container; }, init: Base.noop, exec: Base.noop, destroy: function() { this._container && this._container.remove(); this._parent && this._parent.removeClass('webuploader-container'); this.off(); } }); Runtime.orders = 'html5,flash'; /** * æ·»åŠ Runtime实现。 * @param {String} type 类型 * @param {Runtime} factory 具体Runtime实现。 */ Runtime.addRuntime = function( type, factory ) { factories[ type ] = factory; }; Runtime.hasRuntime = function( type ) { return !!(type ? factories[ type ] : getFirstKey( factories )); }; Runtime.create = function( opts, orders ) { var type, runtime; orders = orders || Runtime.orders; $.each( orders.split( /\s*,\s*/g ), function() { if ( factories[ this ] ) { type = this; return false; } }); type = type || getFirstKey( factories ); if ( !type ) { throw new Error('Runtime Error'); } runtime = new factories[ type ]( opts ); return runtime; }; Mediator.installTo( Runtime.prototype ); return Runtime; }); /** * @fileOverview Runtime管ç†å™¨ï¼Œè´Ÿè´£Runtime的选择, 连接 */ define('runtime/client',[ 'base', 'mediator', 'runtime/runtime' ], function( Base, Mediator, Runtime ) { var cache; cache = (function() { var obj = {}; return { add: function( runtime ) { obj[ runtime.uid ] = runtime; }, get: function( ruid, standalone ) { var i; if ( ruid ) { return obj[ ruid ]; } for ( i in obj ) { // 有些类型ä¸èƒ½é‡ç”¨ï¼Œæ¯”如filepicker. if ( standalone && obj[ i ].__standalone ) { continue; } return obj[ i ]; } return null; }, remove: function( runtime ) { delete obj[ runtime.uid ]; } }; })(); function RuntimeClient( component, standalone ) { var deferred = Base.Deferred(), runtime; this.uid = Base.guid('client_'); // å…许runtime没有åˆå§‹åŒ–之å‰ï¼Œæ³¨å†Œä¸€äº›æ–¹æ³•在åˆå§‹åŒ–åŽæ‰§è¡Œã€‚ this.runtimeReady = function( cb ) { return deferred.done( cb ); }; this.connectRuntime = function( opts, cb ) { // already connected. if ( runtime ) { throw new Error('already connected!'); } deferred.done( cb ); if ( typeof opts === 'string' && cache.get( opts ) ) { runtime = cache.get( opts ); } // åƒfilePickeråªèƒ½ç‹¬ç«‹å˜åœ¨ï¼Œä¸èƒ½å…¬ç”¨ã€‚ runtime = runtime || cache.get( null, standalone ); // 需è¦åˆ›å»º if ( !runtime ) { runtime = Runtime.create( opts, opts.runtimeOrder ); runtime.__promise = deferred.promise(); runtime.once( 'ready', deferred.resolve ); runtime.init(); cache.add( runtime ); runtime.__client = 1; } else { // æ¥è‡ªcache Base.$.extend( runtime.options, opts ); runtime.__promise.then( deferred.resolve ); runtime.__client++; } standalone && (runtime.__standalone = standalone); return runtime; }; this.getRuntime = function() { return runtime; }; this.disconnectRuntime = function() { if ( !runtime ) { return; } runtime.__client--; if ( runtime.__client <= 0 ) { cache.remove( runtime ); delete runtime.__promise; runtime.destroy(); } runtime = null; }; this.exec = function() { if ( !runtime ) { return; } var args = Base.slice( arguments ); component && args.unshift( component ); return runtime.exec.apply( this, args ); }; this.getRuid = function() { return runtime && runtime.uid; }; this.destroy = (function( destroy ) { return function() { destroy && destroy.apply( this, arguments ); this.trigger('destroy'); this.off(); this.exec('destroy'); this.disconnectRuntime(); }; })( this.destroy ); } Mediator.installTo( RuntimeClient.prototype ); return RuntimeClient; }); /** * @fileOverview é”™è¯¯ä¿¡æ¯ */ define('lib/dnd',[ 'base', 'mediator', 'runtime/client' ], function( Base, Mediator, RuntimeClent ) { var $ = Base.$; function DragAndDrop( opts ) { opts = this.options = $.extend({}, DragAndDrop.options, opts ); opts.container = $( opts.container ); if ( !opts.container.length ) { return; } RuntimeClent.call( this, 'DragAndDrop' ); } DragAndDrop.options = { accept: null, disableGlobalDnd: false }; Base.inherits( RuntimeClent, { constructor: DragAndDrop, init: function() { var me = this; me.connectRuntime( me.options, function() { me.exec('init'); me.trigger('ready'); }); } }); Mediator.installTo( DragAndDrop.prototype ); return DragAndDrop; }); /** * @fileOverview 组件基类。 */ define('widgets/widget',[ 'base', 'uploader' ], function( Base, Uploader ) { var $ = Base.$, _init = Uploader.prototype._init, _destroy = Uploader.prototype.destroy, IGNORE = {}, widgetClass = []; function isArrayLike( obj ) { if ( !obj ) { return false; } var length = obj.length, type = $.type( obj ); if ( obj.nodeType === 1 && length ) { return true; } return type === 'array' || type !== 'function' && type !== 'string' && (length === 0 || typeof length === 'number' && length > 0 && (length - 1) in obj); } function Widget( uploader ) { this.owner = uploader; this.options = uploader.options; } $.extend( Widget.prototype, { init: Base.noop, // ç±»Backbone的事件监å¬å£°æ˜Žï¼Œç›‘å¬uploader实例上的事件 // widgetç›´æŽ¥æ— æ³•ç›‘å¬äº‹ä»¶ï¼Œäº‹ä»¶åªèƒ½é€šè¿‡uploaderæ¥ä¼ 递 invoke: function( apiName, args ) { /* { 'make-thumb': 'makeThumb' } */ var map = this.responseMap; // å¦‚æžœæ— APIå“应声明则忽略 if ( !map || !(apiName in map) || !(map[ apiName ] in this) || !$.isFunction( this[ map[ apiName ] ] ) ) { return IGNORE; } return this[ map[ apiName ] ].apply( this, args ); }, /** * å‘é€å‘½ä»¤ã€‚å½“ä¼ å…¥`callback`或者`handler`ä¸è¿”回`promise`时。返回一个当所有`handler`ä¸çš„promise都完æˆåŽå®Œæˆçš„æ–°`promise`。 * @method request * @grammar request( command, args ) => * | Promise * @grammar request( command, args, callback ) => Promise * @for Uploader */ request: function() { return this.owner.request.apply( this.owner, arguments ); } }); // 扩展Uploader. $.extend( Uploader.prototype, { /** * @property {String | Array} [disableWidgets=undefined] * @namespace options * @for Uploader * @description 默认所有 Uploader.register 了的 widget éƒ½ä¼šè¢«åŠ è½½ï¼Œå¦‚æžœç¦ç”¨æŸä¸€éƒ¨åˆ†ï¼Œè¯·é€šè¿‡æ¤ option 指定黑åå•。 */ // 覆写_init用æ¥åˆå§‹åŒ–widgets _init: function() { var me = this, widgets = me._widgets = [], deactives = me.options.disableWidgets || ''; $.each( widgetClass, function( _, klass ) { (!deactives || !~deactives.indexOf( klass._name )) && widgets.push( new klass( me ) ); }); return _init.apply( me, arguments ); }, request: function( apiName, args, callback ) { var i = 0, widgets = this._widgets, len = widgets && widgets.length, rlts = [], dfds = [], widget, rlt, promise, key; args = isArrayLike( args ) ? args : [ args ]; for ( ; i < len; i++ ) { widget = widgets[ i ]; rlt = widget.invoke( apiName, args ); if ( rlt !== IGNORE ) { // Deferred对象 if ( Base.isPromise( rlt ) ) { dfds.push( rlt ); } else { rlts.push( rlt ); } } } // 如果有callbackï¼Œåˆ™ç”¨å¼‚æ¥æ–¹å¼ã€‚ if ( callback || dfds.length ) { promise = Base.when.apply( Base, dfds ); key = promise.pipe ? 'pipe' : 'then'; // 很é‡è¦ä¸èƒ½åˆ é™¤ã€‚åˆ é™¤äº†ä¼šæ»å¾ªçŽ¯ã€‚ // ä¿è¯æ‰§è¡Œé¡ºåºã€‚让callback总是在下一个 tick 䏿‰§è¡Œã€‚ return promise[ key ](function() { var deferred = Base.Deferred(), args = arguments; if ( args.length === 1 ) { args = args[ 0 ]; } setTimeout(function() { deferred.resolve( args ); }, 1 ); return deferred.promise(); })[ callback ? key : 'done' ]( callback || Base.noop ); } else { return rlts[ 0 ]; } }, destroy: function() { _destroy.apply( this, arguments ); this._widgets = null; } }); /** * æ·»åŠ ç»„ä»¶ * @grammar Uploader.register(proto); * @grammar Uploader.register(map, proto); * @param {object} responseMap API åç§°ä¸Žå‡½æ•°å®žçŽ°çš„æ˜ å°„ * @param {object} proto ç»„ä»¶åŽŸåž‹ï¼Œæž„é€ å‡½æ•°é€šè¿‡ constructor 属性定义 * @method Uploader.register * @for Uploader * @example * Uploader.register({ * 'make-thumb': 'makeThumb' * }, { * init: function( options ) {}, * makeThumb: function() {} * }); * * Uploader.register({ * 'make-thumb': function() { * * } * }); */ Uploader.register = Widget.register = function( responseMap, widgetProto ) { var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, klass; if ( arguments.length === 1 ) { widgetProto = responseMap; // è‡ªåŠ¨ç”Ÿæˆ map 表。 $.each(widgetProto, function(key) { if ( key[0] === '_' || key === 'name' ) { key === 'name' && (map.name = widgetProto.name); return; } map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; }); } else { map = $.extend( map, responseMap ); } widgetProto.responseMap = map; klass = Base.inherits( Widget, widgetProto ); klass._name = map.name; widgetClass.push( klass ); return klass; }; /** * åˆ é™¤æ’ä»¶ï¼Œåªæœ‰åœ¨æ³¨å†Œæ—¶æŒ‡å®šäº†åå—çš„æ‰èƒ½è¢«åˆ 除。 * @grammar Uploader.unRegister(name); * @param {string} name 组件åå— * @method Uploader.unRegister * @for Uploader * @example * * Uploader.register({ * name: 'custom', * * 'make-thumb': function() { * * } * }); * * Uploader.unRegister('custom'); */ Uploader.unRegister = Widget.unRegister = function( name ) { if ( !name || name === 'anonymous' ) { return; } // åˆ é™¤æŒ‡å®šçš„æ’件。 for ( var i = widgetClass.length; i--; ) { if ( widgetClass[i]._name === name ) { widgetClass.splice(i, 1) } } }; return Widget; }); /** * @fileOverview DragAndDrop Widget。 */ define('widgets/filednd',[ 'base', 'uploader', 'lib/dnd', 'widgets/widget' ], function( Base, Uploader, Dnd ) { var $ = Base.$; Uploader.options.dnd = ''; /** * @property {Selector} [dnd=undefined] 指定Drag And Dropæ‹–æ‹½çš„å®¹å™¨ï¼Œå¦‚æžœä¸æŒ‡å®šï¼Œåˆ™ä¸å¯åŠ¨ã€‚ * @namespace options * @for Uploader */ /** * @property {Selector} [disableGlobalDnd=false] 是å¦ç¦æŽ‰æ•´ä¸ªé¡µé¢çš„æ‹–拽功能,如果ä¸ç¦ç”¨ï¼Œå›¾ç‰‡æ‹–è¿›æ¥çš„æ—¶å€™ä¼šé»˜è®¤è¢«æµè§ˆå™¨æ‰“开。 * @namespace options * @for Uploader */ /** * @event dndAccept * @param {DataTransferItemList} items DataTransferItem * @description é˜»æ¢æ¤äº‹ä»¶å¯ä»¥æ‹’ç»æŸäº›ç±»åž‹çš„æ–‡ä»¶æ‹–入进æ¥ã€‚ç›®å‰åªæœ‰ chrome æä¾›è¿™æ ·çš„ API,且åªèƒ½é€šè¿‡ mime-type 验è¯ã€‚ * @for Uploader */ return Uploader.register({ name: 'dnd', init: function( opts ) { if ( !opts.dnd || this.request('predict-runtime-type') !== 'html5' ) { return; } var me = this, deferred = Base.Deferred(), options = $.extend({}, { disableGlobalDnd: opts.disableGlobalDnd, container: opts.dnd, accept: opts.accept }), dnd; this.dnd = dnd = new Dnd( options ); dnd.once( 'ready', deferred.resolve ); dnd.on( 'drop', function( files ) { me.request( 'add-file', [ files ]); }); // 检测文件是å¦å…¨éƒ¨å…è®¸æ·»åŠ ã€‚ dnd.on( 'accept', function( items ) { return me.owner.trigger( 'dndAccept', items ); }); dnd.init(); return deferred.promise(); }, destroy: function() { this.dnd && this.dnd.destroy(); } }); }); /** * @fileOverview é”™è¯¯ä¿¡æ¯ */ define('lib/filepaste',[ 'base', 'mediator', 'runtime/client' ], function( Base, Mediator, RuntimeClent ) { var $ = Base.$; function FilePaste( opts ) { opts = this.options = $.extend({}, opts ); opts.container = $( opts.container || document.body ); RuntimeClent.call( this, 'FilePaste' ); } Base.inherits( RuntimeClent, { constructor: FilePaste, init: function() { var me = this; me.connectRuntime( me.options, function() { me.exec('init'); me.trigger('ready'); }); } }); Mediator.installTo( FilePaste.prototype ); return FilePaste; }); /** * @fileOverview 组件基类。 */ define('widgets/filepaste',[ 'base', 'uploader', 'lib/filepaste', 'widgets/widget' ], function( Base, Uploader, FilePaste ) { var $ = Base.$; /** * @property {Selector} [paste=undefined] 指定监å¬pasteäº‹ä»¶çš„å®¹å™¨ï¼Œå¦‚æžœä¸æŒ‡å®šï¼Œä¸å¯ç”¨æ¤åŠŸèƒ½ã€‚æ¤åŠŸèƒ½ä¸ºé€šè¿‡ç²˜è´´æ¥æ·»åŠ æˆªå±çš„图片。建议设置为`document.body`. * @namespace options * @for Uploader */ return Uploader.register({ name: 'paste', init: function( opts ) { if ( !opts.paste || this.request('predict-runtime-type') !== 'html5' ) { return; } var me = this, deferred = Base.Deferred(), options = $.extend({}, { container: opts.paste, accept: opts.accept }), paste; this.paste = paste = new FilePaste( options ); paste.once( 'ready', deferred.resolve ); paste.on( 'paste', function( files ) { me.owner.request( 'add-file', [ files ]); }); paste.init(); return deferred.promise(); }, destroy: function() { this.paste && this.paste.destroy(); } }); }); /** * @fileOverview Blob */ define('lib/blob',[ 'base', 'runtime/client' ], function( Base, RuntimeClient ) { function Blob( ruid, source ) { var me = this; me.source = source; me.ruid = ruid; this.size = source.size || 0; // 如果没有指定 mimetype, ä½†æ˜¯çŸ¥é“æ–‡ä»¶åŽç¼€ã€‚ if ( !source.type && this.ext && ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); } else { this.type = source.type || 'application/octet-stream'; } RuntimeClient.call( me, 'Blob' ); this.uid = source.uid || this.uid; if ( ruid ) { me.connectRuntime( ruid ); } } Base.inherits( RuntimeClient, { constructor: Blob, slice: function( start, end ) { return this.exec( 'slice', start, end ); }, getSource: function() { return this.source; } }); return Blob; }); /** * 为了统一化Flashçš„Fileå’ŒHTML5çš„File而å˜åœ¨ã€‚ * 以至于è¦è°ƒç”¨Flash里é¢çš„File,也å¯ä»¥åƒè°ƒç”¨HTML5版本的File一下。 * @fileOverview File */ define('lib/file',[ 'base', 'lib/blob' ], function( Base, Blob ) { var uid = 1, rExt = /\.([^.]+)$/; function File( ruid, file ) { var ext; this.name = file.name || ('untitled' + uid++); ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; // todo 支æŒå…¶ä»–类型文件的转æ¢ã€‚ // 如果有 mimetype, 但是文件åé‡Œé¢æ²¡æœ‰æ‰¾å‡ºåŽç¼€è§„律 if ( !ext && file.type ) { ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? RegExp.$1.toLowerCase() : ''; this.name += '.' + ext; } this.ext = ext; this.lastModifiedDate = file.lastModifiedDate || (new Date()).toLocaleString(); Blob.apply( this, arguments ); } return Base.inherits( Blob, File ); }); /** * @fileOverview é”™è¯¯ä¿¡æ¯ */ define('lib/filepicker',[ 'base', 'runtime/client', 'lib/file' ], function( Base, RuntimeClent, File ) { var $ = Base.$; function FilePicker( opts ) { opts = this.options = $.extend({}, FilePicker.options, opts ); opts.container = $( opts.id ); if ( !opts.container.length ) { throw new Error('按钮指定错误'); } opts.innerHTML = opts.innerHTML || opts.label || opts.container.html() || ''; opts.button = $( opts.button || document.createElement('div') ); opts.button.html( opts.innerHTML ); opts.container.html( opts.button ); RuntimeClent.call( this, 'FilePicker', true ); } FilePicker.options = { button: null, container: null, label: null, innerHTML: null, multiple: true, accept: null, name: 'file' }; Base.inherits( RuntimeClent, { constructor: FilePicker, init: function() { var me = this, opts = me.options, button = opts.button; button.addClass('webuploader-pick'); me.on( 'all', function( type ) { var files; switch ( type ) { case 'mouseenter': button.addClass('webuploader-pick-hover'); break; case 'mouseleave': button.removeClass('webuploader-pick-hover'); break; case 'change': files = me.exec('getFiles'); me.trigger( 'select', $.map( files, function( file ) { file = new File( me.getRuid(), file ); // è®°å½•æ¥æºã€‚ file._refer = opts.container; return file; }), opts.container ); break; } }); me.connectRuntime( opts, function() { me.refresh(); me.exec( 'init', opts ); me.trigger('ready'); }); this._resizeHandler = Base.bindFn( this.refresh, this ); $( window ).on( 'resize', this._resizeHandler ); }, refresh: function() { var shimContainer = this.getRuntime().getContainer(), button = this.options.button, width = button.outerWidth ? button.outerWidth() : button.width(), height = button.outerHeight ? button.outerHeight() : button.height(), pos = button.offset(); width && height && shimContainer.css({ bottom: 'auto', right: 'auto', width: width + 'px', height: height + 'px' }).offset( pos ); }, enable: function() { var btn = this.options.button; btn.removeClass('webuploader-pick-disable'); this.refresh(); }, disable: function() { var btn = this.options.button; this.getRuntime().getContainer().css({ top: '-99999px' }); btn.addClass('webuploader-pick-disable'); }, destroy: function() { var btn = this.options.button; $( window ).off( 'resize', this._resizeHandler ); btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + 'webuploader-pick'); } }); return FilePicker; }); /** * @fileOverview 文件选择相关 */ define('widgets/filepicker',[ 'base', 'uploader', 'lib/filepicker', 'widgets/widget' ], function( Base, Uploader, FilePicker ) { var $ = Base.$; $.extend( Uploader.options, { /** * @property {Selector | Object} [pick=undefined] * @namespace options * @for Uploader * @description æŒ‡å®šé€‰æ‹©æ–‡ä»¶çš„æŒ‰é’®å®¹å™¨ï¼Œä¸æŒ‡å®šåˆ™ä¸åˆ›å»ºæŒ‰é’®ã€‚ * * * `id` {Seletor} æŒ‡å®šé€‰æ‹©æ–‡ä»¶çš„æŒ‰é’®å®¹å™¨ï¼Œä¸æŒ‡å®šåˆ™ä¸åˆ›å»ºæŒ‰é’®ã€‚ * * `label` {String} 请采用 `innerHTML` 代替 * * `innerHTML` {String} 指定按钮文å—ã€‚ä¸æŒ‡å®šæ—¶ä¼˜å…ˆä»ŽæŒ‡å®šçš„容器ä¸çœ‹æ˜¯å¦è‡ªå¸¦æ–‡å—。 * * `multiple` {Boolean} 是å¦å¼€èµ·åŒæ—¶é€‰æ‹©å¤šä¸ªæ–‡ä»¶èƒ½åŠ›ã€‚ */ pick: null, /** * @property {Arroy} [accept=null] * @namespace options * @for Uploader * @description 指定接å—哪些类型的文件。 由于目å‰è¿˜æœ‰ext转mimeType表,所以这里需è¦åˆ†å¼€æŒ‡å®šã€‚ * * * `title` {String} æ–‡å—æè¿° * * `extensions` {String} å…许的文件åŽç¼€ï¼Œä¸å¸¦ç‚¹ï¼Œå¤šä¸ªç”¨é€—å·åˆ†å‰²ã€‚ * * `mimeTypes` {String} 多个用逗å·åˆ†å‰²ã€‚ * * 如: * * ``` * { * title: 'Images', * extensions: 'gif,jpg,jpeg,bmp,png', * mimeTypes: 'image/*' * } * ``` */ accept: null/*{ title: 'Images', extensions: 'gif,jpg,jpeg,bmp,png', mimeTypes: 'image/*' }*/ }); return Uploader.register({ name: 'picker', init: function( opts ) { this.pickers = []; return opts.pick && this.addBtn( opts.pick ); }, refresh: function() { $.each( this.pickers, function() { this.refresh(); }); }, /** * @method addButton * @for Uploader * @grammar addButton( pick ) => Promise * @description * æ·»åŠ æ–‡ä»¶é€‰æ‹©æŒ‰é’®ï¼Œå¦‚æžœä¸€ä¸ªæŒ‰é’®ä¸å¤Ÿï¼Œéœ€è¦è°ƒç”¨æ¤æ–¹æ³•æ¥æ·»åŠ ã€‚å‚æ•°è·Ÿ[options.pick](#WebUploader:Uploader:options)一致。 * @example * uploader.addButton({ * id: '#btnContainer', * innerHTML: '选择文件' * }); */ addBtn: function( pick ) { var me = this, opts = me.options, accept = opts.accept, promises = []; if ( !pick ) { return; } $.isPlainObject( pick ) || (pick = { id: pick }); $( pick.id ).each(function() { var options, picker, deferred; deferred = Base.Deferred(); options = $.extend({}, pick, { accept: $.isPlainObject( accept ) ? [ accept ] : accept, swf: opts.swf, runtimeOrder: opts.runtimeOrder, id: this }); picker = new FilePicker( options ); picker.once( 'ready', deferred.resolve ); picker.on( 'select', function( files ) { me.owner.request( 'add-file', [ files ]); }); picker.init(); me.pickers.push( picker ); promises.push( deferred.promise() ); }); return Base.when.apply( Base, promises ); }, disable: function() { $.each( this.pickers, function() { this.disable(); }); }, enable: function() { $.each( this.pickers, function() { this.enable(); }); }, destroy: function() { $.each( this.pickers, function() { this.destroy(); }); this.pickers = null; } }); }); /** * @fileOverview 文件属性å°è£… */ define('file',[ 'base', 'mediator' ], function( Base, Mediator ) { var $ = Base.$, idPrefix = 'WU_FILE_', idSuffix = 0, rExt = /\.([^.]+)$/, statusMap = {}; function gid() { return idPrefix + idSuffix++; } /** * 文件类 * @class File * @constructor æž„é€ å‡½æ•° * @grammar new File( source ) => File * @param {Lib.File} source [lib.File](#Lib.File)实例, æ¤source对象是带有Runtimeä¿¡æ¯çš„。 */ function WUFile( source ) { /** * 文件å,包括扩展å(åŽç¼€ï¼‰ * @property name * @type {string} */ this.name = source.name || 'Untitled'; /** * 文件体积(å—节) * @property size * @type {uint} * @default 0 */ this.size = source.size || 0; /** * 文件MIMETYPE类型,与文件类型的对应关系请å‚考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) * @property type * @type {string} * @default 'application/octet-stream' */ this.type = source.type || 'application/octet-stream'; /** * 文件最åŽä¿®æ”¹æ—¥æœŸ * @property lastModifiedDate * @type {int} * @default 当剿—¶é—´æˆ³ */ this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); /** * 文件ID,æ¯ä¸ªå¯¹è±¡å…·æœ‰å”¯ä¸€IDï¼Œä¸Žæ–‡ä»¶åæ— å…³ * @property id * @type {string} */ this.id = gid(); /** * 文件扩展å,通过文件å获å–,例如test.png的扩展å为png * @property ext * @type {string} */ this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; /** * çŠ¶æ€æ–‡å—说明。在ä¸åŒçš„statusè¯å¢ƒä¸‹æœ‰ä¸åŒçš„用途。 * @property statusText * @type {string} */ this.statusText = ''; // å˜å‚¨æ–‡ä»¶çжæ€ï¼Œé˜²æ¢é€šè¿‡å±žæ€§ç›´æŽ¥ä¿®æ”¹ statusMap[ this.id ] = WUFile.Status.INITED; this.source = source; this.loaded = 0; this.on( 'error', function( msg ) { this.setStatus( WUFile.Status.ERROR, msg ); }); } $.extend( WUFile.prototype, { /** * 设置状æ€ï¼Œçжæ€å˜åŒ–时会触å‘`change`事件。 * @method setStatus * @grammar setStatus( status[, statusText] ); * @param {File.Status|String} status [文件状æ€å€¼](#WebUploader:File:File.Status) * @param {String} [statusText=''] 状æ€è¯´æ˜Žï¼Œå¸¸åœ¨error时使用,用http, abort,serverç‰æ¥æ ‡è®°æ˜¯ç”±äºŽä»€ä¹ˆåŽŸå› å¯¼è‡´æ–‡ä»¶é”™è¯¯ã€‚ */ setStatus: function( status, text ) { var prevStatus = statusMap[ this.id ]; typeof text !== 'undefined' && (this.statusText = text); if ( status !== prevStatus ) { statusMap[ this.id ] = status; /** * 文件状æ€å˜åŒ– * @event statuschange */ this.trigger( 'statuschange', status, prevStatus ); } }, /** * èŽ·å–æ–‡ä»¶çŠ¶æ€ * @return {File.Status} * @example 文件状æ€å…·ä½“åŒ…æ‹¬ä»¥ä¸‹å‡ ç§ç±»åž‹ï¼š { // åˆå§‹åŒ– INITED: 0, // 已入队列 QUEUED: 1, // æ£åœ¨ä¸Šä¼ PROGRESS: 2, // ä¸Šä¼ å‡ºé”™ ERROR: 3, // ä¸Šä¼ æˆåŠŸ COMPLETE: 4, // ä¸Šä¼ å–æ¶ˆ CANCELLED: 5 } */ getStatus: function() { return statusMap[ this.id ]; }, /** * èŽ·å–æ–‡ä»¶åŽŸå§‹ä¿¡æ¯ã€‚ * @return {*} */ getSource: function() { return this.source; }, destroy: function() { this.off(); delete statusMap[ this.id ]; } }); Mediator.installTo( WUFile.prototype ); /** * 文件状æ€å€¼ï¼Œå…·ä½“åŒ…æ‹¬ä»¥ä¸‹å‡ ç§ç±»åž‹ï¼š * * `inited` åˆå§‹çŠ¶æ€ * * `queued` å·²ç»è¿›å…¥é˜Ÿåˆ—, ç‰å¾…ä¸Šä¼ * * `progress` ä¸Šä¼ ä¸ * * `complete` ä¸Šä¼ å®Œæˆã€‚ * * `error` ä¸Šä¼ å‡ºé”™ï¼Œå¯é‡è¯• * * `interrupt` ä¸Šä¼ ä¸æ–,å¯ç»ä¼ 。 * * `invalid` 文件ä¸åˆæ ¼ï¼Œä¸èƒ½é‡è¯•ä¸Šä¼ ã€‚ä¼šè‡ªåŠ¨ä»Žé˜Ÿåˆ—ä¸ç§»é™¤ã€‚ * * `cancelled` 文件被移除。 * @property {Object} Status * @namespace File * @class File * @static */ WUFile.Status = { INITED: 'inited', // åˆå§‹çŠ¶æ€ QUEUED: 'queued', // å·²ç»è¿›å…¥é˜Ÿåˆ—, ç‰å¾…ä¸Šä¼ PROGRESS: 'progress', // ä¸Šä¼ ä¸ ERROR: 'error', // ä¸Šä¼ å‡ºé”™ï¼Œå¯é‡è¯• COMPLETE: 'complete', // ä¸Šä¼ å®Œæˆã€‚ CANCELLED: 'cancelled', // ä¸Šä¼ å–æ¶ˆã€‚ INTERRUPT: 'interrupt', // ä¸Šä¼ ä¸æ–,å¯ç»ä¼ 。 INVALID: 'invalid' // 文件ä¸åˆæ ¼ï¼Œä¸èƒ½é‡è¯•ä¸Šä¼ ã€‚ }; return WUFile; }); /** * @fileOverview 文件队列 */ define('queue',[ 'base', 'mediator', 'file' ], function( Base, Mediator, WUFile ) { var $ = Base.$, STATUS = WUFile.Status; /** * 文件队列, 用æ¥å˜å‚¨å„个状æ€ä¸çš„æ–‡ä»¶ã€‚ * @class Queue * @extends Mediator */ function Queue() { /** * 统计文件数。 * * `numOfQueue` 队列ä¸çš„æ–‡ä»¶æ•°ã€‚ * * `numOfSuccess` ä¸Šä¼ æˆåŠŸçš„æ–‡ä»¶æ•° * * `numOfCancel` è¢«å–æ¶ˆçš„æ–‡ä»¶æ•° * * `numOfProgress` æ£åœ¨ä¸Šä¼ ä¸çš„æ–‡ä»¶æ•° * * `numOfUploadFailed` ä¸Šä¼ é”™è¯¯çš„æ–‡ä»¶æ•°ã€‚ * * `numOfInvalid` æ— æ•ˆçš„æ–‡ä»¶æ•°ã€‚ * * `numofDeleted` 被移除的文件数。 * @property {Object} stats */ this.stats = { numOfQueue: 0, numOfSuccess: 0, numOfCancel: 0, numOfProgress: 0, numOfUploadFailed: 0, numOfInvalid: 0, numofDeleted: 0, numofInterrupt: 0, }; // ä¸Šä¼ é˜Ÿåˆ—ï¼Œä»…åŒ…æ‹¬ç‰å¾…ä¸Šä¼ çš„æ–‡ä»¶ this._queue = []; // å˜å‚¨æ‰€æœ‰æ–‡ä»¶ this._map = {}; } $.extend( Queue.prototype, { /** * å°†æ–°æ–‡ä»¶åŠ å…¥å¯¹é˜Ÿåˆ—å°¾éƒ¨ * * @method append * @param {File} file 文件对象 */ append: function( file ) { this._queue.push( file ); this._fileAdded( file ); return this; }, /** * å°†æ–°æ–‡ä»¶åŠ å…¥å¯¹é˜Ÿåˆ—å¤´éƒ¨ * * @method prepend * @param {File} file 文件对象 */ prepend: function( file ) { this._queue.unshift( file ); this._fileAdded( file ); return this; }, /** * èŽ·å–æ–‡ä»¶å¯¹è±¡ * * @method getFile * @param {String} fileId 文件ID * @return {File} */ getFile: function( fileId ) { if ( typeof fileId !== 'string' ) { return fileId; } return this._map[ fileId ]; }, /** * 从队列ä¸å–出一个指定状æ€çš„æ–‡ä»¶ã€‚ * @grammar fetch( status ) => File * @method fetch * @param {String} status [文件状æ€å€¼](#WebUploader:File:File.Status) * @return {File} [File](#WebUploader:File) */ fetch: function( status ) { var len = this._queue.length, i, file; status = status || STATUS.QUEUED; for ( i = 0; i < len; i++ ) { file = this._queue[ i ]; if ( status === file.getStatus() ) { return file; } } return null; }, /** * 对队列进行排åºï¼Œèƒ½å¤ŸæŽ§åˆ¶æ–‡ä»¶ä¸Šä¼ 顺åºã€‚ * @grammar sort( fn ) => undefined * @method sort * @param {Function} fn æŽ’åºæ–¹æ³• */ sort: function( fn ) { if ( typeof fn === 'function' ) { this._queue.sort( fn ); } }, /** * èŽ·å–æŒ‡å®šç±»åž‹çš„æ–‡ä»¶åˆ—表, åˆ—è¡¨ä¸æ¯ä¸€ä¸ªæˆå‘˜ä¸º[File](#WebUploader:File)对象。 * @grammar getFiles( [status1[, status2 ...]] ) => Array * @method getFiles * @param {String} [status] [文件状æ€å€¼](#WebUploader:File:File.Status) */ getFiles: function() { var sts = [].slice.call( arguments, 0 ), ret = [], i = 0, len = this._queue.length, file; for ( ; i < len; i++ ) { file = this._queue[ i ]; if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { continue; } ret.push( file ); } return ret; }, /** * 在队列ä¸åˆ 除文件。 * @grammar removeFile( file ) => Array * @method removeFile * @param {File} 文件对象。 */ removeFile: function( file ) { var me = this, existing = this._map[ file.id ]; if ( existing ) { delete this._map[ file.id ]; file.destroy(); this.stats.numofDeleted++; } }, _fileAdded: function( file ) { var me = this, existing = this._map[ file.id ]; if ( !existing ) { this._map[ file.id ] = file; file.on( 'statuschange', function( cur, pre ) { me._onFileStatusChange( cur, pre ); }); } }, _onFileStatusChange: function( curStatus, preStatus ) { var stats = this.stats; switch ( preStatus ) { case STATUS.PROGRESS: stats.numOfProgress--; break; case STATUS.QUEUED: stats.numOfQueue --; break; case STATUS.ERROR: stats.numOfUploadFailed--; break; case STATUS.INVALID: stats.numOfInvalid--; break; case STATUS.INTERRUPT: stats.numofInterrupt--; break; } switch ( curStatus ) { case STATUS.QUEUED: stats.numOfQueue++; break; case STATUS.PROGRESS: stats.numOfProgress++; break; case STATUS.ERROR: stats.numOfUploadFailed++; break; case STATUS.COMPLETE: stats.numOfSuccess++; break; case STATUS.CANCELLED: stats.numOfCancel++; break; case STATUS.INVALID: stats.numOfInvalid++; break; case STATUS.INTERRUPT: stats.numofInterrupt++; break; } } }); Mediator.installTo( Queue.prototype ); return Queue; }); /** * @fileOverview 队列 */ define('widgets/queue',[ 'base', 'uploader', 'queue', 'file', 'lib/file', 'runtime/client', 'widgets/widget' ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { var $ = Base.$, rExt = /\.\w+$/, Status = WUFile.Status; return Uploader.register({ name: 'queue', init: function( opts ) { var me = this, deferred, len, i, item, arr, accept, runtime; if ( $.isPlainObject( opts.accept ) ) { opts.accept = [ opts.accept ]; } // acceptä¸çš„ä¸ç”ŸæˆåŒ¹é…æ£åˆ™ã€‚ if ( opts.accept ) { arr = []; for ( i = 0, len = opts.accept.length; i < len; i++ ) { item = opts.accept[ i ].extensions; item && arr.push( item ); } if ( arr.length ) { accept = '\\.' + arr.join(',') .replace( /,/g, '$|\\.' ) .replace( /\*/g, '.*' ) + '$'; } me.accept = new RegExp( accept, 'i' ); } me.queue = new Queue(); me.stats = me.queue.stats; // 如果当å‰ä¸æ˜¯html5è¿è¡Œæ—¶ï¼Œé‚£å°±ç®—了。 // 䏿‰§è¡ŒåŽç»æ“作 if ( this.request('predict-runtime-type') !== 'html5' ) { return; } // 创建一个 html5 è¿è¡Œæ—¶çš„ placeholder // ä»¥è‡³äºŽå¤–éƒ¨æ·»åŠ åŽŸç”Ÿ File 对象的时候能æ£ç¡®åŒ…裹一下供 webuploader 使用。 deferred = Base.Deferred(); this.placeholder = runtime = new RuntimeClient('Placeholder'); runtime.connectRuntime({ runtimeOrder: 'html5' }, function() { me._ruid = runtime.getRuid(); deferred.resolve(); }); return deferred.promise(); }, // 为了支æŒå¤–éƒ¨ç›´æŽ¥æ·»åŠ ä¸€ä¸ªåŽŸç”ŸFile对象。 _wrapFile: function( file ) { if ( !(file instanceof WUFile) ) { if ( !(file instanceof File) ) { if ( !this._ruid ) { throw new Error('Can\'t add external files.'); } file = new File( this._ruid, file ); } file = new WUFile( file ); } return file; }, // åˆ¤æ–æ–‡ä»¶æ˜¯å¦å¯ä»¥è¢«åŠ å…¥é˜Ÿåˆ— acceptFile: function( file ) { var invalid = !file || !file.size || this.accept && // 如果åå—䏿œ‰åŽç¼€ï¼Œæ‰åšåŽç¼€ç™½åå•处ç†ã€‚ rExt.exec( file.name ) && !this.accept.test( file.name ); return !invalid; }, /** * @event beforeFileQueued * @param {File} file File对象 * @description å½“æ–‡ä»¶è¢«åŠ å…¥é˜Ÿåˆ—ä¹‹å‰è§¦å‘,æ¤äº‹ä»¶çš„handler返回值为`false`ï¼Œåˆ™æ¤æ–‡ä»¶ä¸ä¼šè¢«æ·»åŠ è¿›å…¥é˜Ÿåˆ—ã€‚ * @for Uploader */ /** * @event fileQueued * @param {File} file File对象 * @description å½“æ–‡ä»¶è¢«åŠ å…¥é˜Ÿåˆ—ä»¥åŽè§¦å‘。 * @for Uploader */ _addFile: function( file ) { var me = this; file = me._wrapFile( file ); // ä¸è¿‡ç±»åž‹åˆ¤æ–å…许ä¸å…è®¸ï¼Œå…ˆæ´¾é€ `beforeFileQueued` if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { return; } // 类型ä¸åŒ¹é…,则派é€é”™è¯¯äº‹ä»¶ï¼Œå¹¶è¿”回。 if ( !me.acceptFile( file ) ) { me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); return; } me.queue.append( file ); me.owner.trigger( 'fileQueued', file ); return file; }, getFile: function( fileId ) { return this.queue.getFile( fileId ); }, /** * @event filesQueued * @param {File} files 数组,内容为原始File(lib/File)对象。 * @description å½“ä¸€æ‰¹æ–‡ä»¶æ·»åŠ è¿›é˜Ÿåˆ—ä»¥åŽè§¦å‘。 * @for Uploader */ /** * @property {Boolean} [auto=false] * @namespace options * @for Uploader * @description 设置为 true åŽï¼Œä¸éœ€è¦æ‰‹åŠ¨è°ƒç”¨ä¸Šä¼ ï¼Œæœ‰æ–‡ä»¶é€‰æ‹©å³å¼€å§‹ä¸Šä¼ 。 * */ /** * @method addFiles * @grammar addFiles( file ) => undefined * @grammar addFiles( [file1, file2 ...] ) => undefined * @param {Array of File or File} [files] Files 对象 数组 * @description æ·»åŠ æ–‡ä»¶åˆ°é˜Ÿåˆ— * @for Uploader */ addFile: function( files ) { var me = this; if ( !files.length ) { files = [ files ]; } files = $.map( files, function( file ) { return me._addFile( file ); }); me.owner.trigger( 'filesQueued', files ); if ( me.options.auto ) { setTimeout(function() { me.request('start-upload'); }, 20 ); } }, getStats: function() { return this.stats; }, /** * @event fileDequeued * @param {File} file File对象 * @description 当文件被移除队列åŽè§¦å‘。 * @for Uploader */ /** * @method removeFile * @grammar removeFile( file ) => undefined * @grammar removeFile( id ) => undefined * @grammar removeFile( file, true ) => undefined * @grammar removeFile( id, true ) => undefined * @param {File|id} file File对象或这File对象的id * @description 移除æŸä¸€æ–‡ä»¶, 默认åªä¼šæ ‡è®°æ–‡ä»¶çжæ€ä¸ºå·²å–æ¶ˆï¼Œå¦‚æžœç¬¬äºŒä¸ªå‚æ•°ä¸º `true` 则会从 queue ä¸ç§»é™¤ã€‚ * @for Uploader * @example * * $li.on('click', '.remove-this', function() { * uploader.removeFile( file ); * }) */ removeFile: function( file, remove ) { var me = this; file = file.id ? file : me.queue.getFile( file ); this.request( 'cancel-file', file ); if ( remove ) { this.queue.removeFile( file ); } }, /** * @method getFiles * @grammar getFiles() => Array * @grammar getFiles( status1, status2, status... ) => Array * @description 返回指定状æ€çš„æ–‡ä»¶é›†åˆï¼Œä¸ä¼ 傿•°å°†è¿”回所有状æ€çš„æ–‡ä»¶ã€‚ * @for Uploader * @example * console.log( uploader.getFiles() ); // => all files * console.log( uploader.getFiles('error') ) // => all error files. */ getFiles: function() { return this.queue.getFiles.apply( this.queue, arguments ); }, fetchFile: function() { return this.queue.fetch.apply( this.queue, arguments ); }, /** * @method retry * @grammar retry() => undefined * @grammar retry( file ) => undefined * @description é‡è¯•ä¸Šä¼ ï¼Œé‡è¯•æŒ‡å®šæ–‡ä»¶ï¼Œæˆ–è€…ä»Žå‡ºé”™çš„æ–‡ä»¶å¼€å§‹é‡æ–°ä¸Šä¼ 。 * @for Uploader * @example * function retry() { * uploader.retry(); * } */ retry: function( file, noForceStart ) { var me = this, files, i, len; if ( file ) { file = file.id ? file : me.queue.getFile( file ); file.setStatus( Status.QUEUED ); noForceStart || me.request('start-upload'); return; } files = me.queue.getFiles( Status.ERROR ); i = 0; len = files.length; for ( ; i < len; i++ ) { file = files[ i ]; file.setStatus( Status.QUEUED ); } me.request('start-upload'); }, /** * @method sort * @grammar sort( fn ) => undefined * @description 排åºé˜Ÿåˆ—ä¸çš„æ–‡ä»¶ï¼Œåœ¨ä¸Šä¼ 之å‰è°ƒæ•´å¯ä»¥æŽ§åˆ¶ä¸Šä¼ 顺åºã€‚ * @for Uploader */ sortFiles: function() { return this.queue.sort.apply( this.queue, arguments ); }, /** * @event reset * @description 当 uploader 被é‡ç½®çš„æ—¶å€™è§¦å‘。 * @for Uploader */ /** * @method reset * @grammar reset() => undefined * @description é‡ç½®uploader。目å‰åªé‡ç½®äº†é˜Ÿåˆ—。 * @for Uploader * @example * uploader.reset(); */ reset: function() { this.owner.trigger('reset'); this.queue = new Queue(); this.stats = this.queue.stats; }, destroy: function() { this.reset(); this.placeholder && this.placeholder.destroy(); } }); }); /** * @fileOverview æ·»åŠ èŽ·å–Runtime相关信æ¯çš„æ–¹æ³•。 */ define('widgets/runtime',[ 'uploader', 'runtime/runtime', 'widgets/widget' ], function( Uploader, Runtime ) { Uploader.support = function() { return Runtime.hasRuntime.apply( Runtime, arguments ); }; return Uploader.register({ name: 'runtime', init: function() { if ( !this.predictRuntimeType() ) { throw Error('Runtime Error'); } }, /** * 预测Uploader将采用哪个`Runtime` * @grammar predictRuntimeType() => String * @method predictRuntimeType * @for Uploader */ predictRuntimeType: function() { var orders = this.options.runtimeOrder || Runtime.orders, type = this.type, i, len; if ( !type ) { orders = orders.split( /\s*,\s*/g ); for ( i = 0, len = orders.length; i < len; i++ ) { if ( Runtime.hasRuntime( orders[ i ] ) ) { this.type = type = orders[ i ]; break; } } } return type; } }); }); /** * @fileOverview Transport */ define('lib/transport',[ 'base', 'runtime/client', 'mediator' ], function( Base, RuntimeClient, Mediator ) { var $ = Base.$; function Transport( opts ) { var me = this; opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); RuntimeClient.call( this, 'Transport' ); this._blob = null; this._formData = opts.formData || {}; this._headers = opts.headers || {}; this.on( 'progress', this._timeout ); this.on( 'load error', function() { me.trigger( 'progress', 1 ); clearTimeout( me._timer ); }); } Transport.options = { server: '', method: 'POST', // 跨域时,是å¦å…许æºå¸¦cookie, åªæœ‰html5 runtimeæ‰æœ‰æ•ˆ withCredentials: false, fileVal: 'file', timeout: 2 * 60 * 1000, // 2分钟 formData: {}, headers: {}, sendAsBinary: false }; $.extend( Transport.prototype, { // æ·»åŠ Blob, åªèƒ½æ·»åŠ ä¸€æ¬¡ï¼Œæœ€åŽä¸€æ¬¡æœ‰æ•ˆã€‚ appendBlob: function( key, blob, filename ) { var me = this, opts = me.options; if ( me.getRuid() ) { me.disconnectRuntime(); } // 连接到blob归属的åŒä¸€ä¸ªruntime. me.connectRuntime( blob.ruid, function() { me.exec('init'); }); me._blob = blob; opts.fileVal = key || opts.fileVal; opts.filename = filename || opts.filename; }, // æ·»åŠ å…¶ä»–å—æ®µ append: function( key, value ) { if ( typeof key === 'object' ) { $.extend( this._formData, key ); } else { this._formData[ key ] = value; } }, setRequestHeader: function( key, value ) { if ( typeof key === 'object' ) { $.extend( this._headers, key ); } else { this._headers[ key ] = value; } }, send: function( method ) { this.exec( 'send', method ); this._timeout(); }, abort: function() { clearTimeout( this._timer ); return this.exec('abort'); }, destroy: function() { this.trigger('destroy'); this.off(); this.exec('destroy'); this.disconnectRuntime(); }, getResponse: function() { return this.exec('getResponse'); }, getResponseAsJson: function() { return this.exec('getResponseAsJson'); }, getStatus: function() { return this.exec('getStatus'); }, _timeout: function() { var me = this, duration = me.options.timeout; if ( !duration ) { return; } clearTimeout( me._timer ); me._timer = setTimeout(function() { me.abort(); me.trigger( 'error', 'timeout' ); }, duration ); } }); // 让Transport具备事件功能。 Mediator.installTo( Transport.prototype ); return Transport; }); /** * @fileOverview è´Ÿè´£æ–‡ä»¶ä¸Šä¼ ç›¸å…³ã€‚ */ define('widgets/upload',[ 'base', 'uploader', 'file', 'lib/transport', 'widgets/widget' ], function( Base, Uploader, WUFile, Transport ) { var $ = Base.$, isPromise = Base.isPromise, Status = WUFile.Status; // æ·»åŠ é»˜è®¤é…置项 $.extend( Uploader.options, { /** * @property {Boolean} [prepareNextFile=false] * @namespace options * @for Uploader * @description 是å¦å…è®¸åœ¨æ–‡ä»¶ä¼ è¾“æ—¶æå‰æŠŠä¸‹ä¸€ä¸ªæ–‡ä»¶å‡†å¤‡å¥½ã€‚ * 对于一个文件的准备工作比较耗时,比如图片压缩,md5åºåˆ—化。 * 如果能æå‰åœ¨å½“剿–‡ä»¶ä¼ 输期处ç†ï¼Œå¯ä»¥èŠ‚çœæ€»ä½“耗时。 */ prepareNextFile: false, /** * @property {Boolean} [chunked=false] * @namespace options * @for Uploader * @description 是å¦è¦åˆ†ç‰‡å¤„ç†å¤§æ–‡ä»¶ä¸Šä¼ 。 */ chunked: false, /** * @property {Boolean} [chunkSize=5242880] * @namespace options * @for Uploader * @description 如果è¦åˆ†ç‰‡ï¼Œåˆ†å¤šå¤§ä¸€ç‰‡ï¼Ÿ 默认大å°ä¸º5M. */ chunkSize: 5 * 1024 * 1024, /** * @property {Boolean} [chunkRetry=2] * @namespace options * @for Uploader * @description 如果æŸä¸ªåˆ†ç‰‡ç”±äºŽç½‘络问题出错,å…许自动é‡ä¼ 多少次? */ chunkRetry: 2, /** * @property {Boolean} [threads=3] * @namespace options * @for Uploader * @description ä¸Šä¼ å¹¶å‘æ•°ã€‚å…è®¸åŒæ—¶æœ€å¤§ä¸Šä¼ 进程数。 */ threads: 3, /** * @property {Object} [formData={}] * @namespace options * @for Uploader * @description æ–‡ä»¶ä¸Šä¼ è¯·æ±‚çš„å‚æ•°è¡¨ï¼Œæ¯æ¬¡å‘é€éƒ½ä¼šå‘逿¤å¯¹è±¡ä¸çš„傿•°ã€‚ */ formData: {} /** * @property {Object} [fileVal='file'] * @namespace options * @for Uploader * @description è®¾ç½®æ–‡ä»¶ä¸Šä¼ åŸŸçš„name。 */ /** * @property {Object} [method='POST'] * @namespace options * @for Uploader * @description æ–‡ä»¶ä¸Šä¼ æ–¹å¼ï¼Œ`POST`或者`GET`。 */ /** * @property {Object} [sendAsBinary=false] * @namespace options * @for Uploader * @description 是å¦å·²äºŒè¿›åˆ¶çš„æµçš„æ–¹å¼å‘逿–‡ä»¶ï¼Œè¿™æ ·æ•´ä¸ªä¸Šä¼ 内容`php://input`都为文件内容, * 其他傿•°åœ¨$_GET数组ä¸ã€‚ */ }); // 负责将文件切片。 function CuteFile( file, chunkSize ) { var pending = [], blob = file.source, total = blob.size, chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, start = 0, index = 0, len, api; api = { file: file, has: function() { return !!pending.length; }, shift: function() { return pending.shift(); }, unshift: function( block ) { pending.unshift( block ); } }; while ( index < chunks ) { len = Math.min( chunkSize, total - start ); pending.push({ file: file, start: start, end: chunkSize ? (start + len) : total, total: total, chunks: chunks, chunk: index++, cuted: api }); start += len; } file.blocks = pending.concat(); file.remaning = pending.length; return api; } Uploader.register({ name: 'upload', init: function() { var owner = this.owner, me = this; this.runing = false; this.progress = false; owner .on( 'startUpload', function() { me.progress = true; }) .on( 'uploadFinished', function() { me.progress = false; }); // è®°å½•å½“å‰æ£åœ¨ä¼ 的数æ®ï¼Œè·Ÿthreads相关 this.pool = []; // 缓å˜åˆ†å¥½ç‰‡çš„æ–‡ä»¶ã€‚ this.stack = []; // 缓å˜å³å°†ä¸Šä¼ 的文件。 this.pending = []; // è·Ÿè¸ªè¿˜æœ‰å¤šå°‘åˆ†ç‰‡åœ¨ä¸Šä¼ ä¸ä½†æ˜¯æ²¡æœ‰å®Œæˆä¸Šä¼ 。 this.remaning = 0; this.__tick = Base.bindFn( this._tick, this ); owner.on( 'uploadComplete', function( file ) { // 把其他å—å–æ¶ˆäº†ã€‚ file.blocks && $.each( file.blocks, function( _, v ) { v.transport && (v.transport.abort(), v.transport.destroy()); delete v.transport; }); delete file.blocks; delete file.remaning; }); }, reset: function() { this.request( 'stop-upload', true ); this.runing = false; this.pool = []; this.stack = []; this.pending = []; this.remaning = 0; this._trigged = false; this._promise = null; }, /** * @event startUpload * @description å½“å¼€å§‹ä¸Šä¼ æµç¨‹æ—¶è§¦å‘。 * @for Uploader */ /** * å¼€å§‹ä¸Šä¼ ã€‚æ¤æ–¹æ³•å¯ä»¥ä»Žåˆå§‹çжæ€è°ƒç”¨å¼€å§‹ä¸Šä¼ æµç¨‹ï¼Œä¹Ÿå¯ä»¥ä»Žæš‚åœçжæ€è°ƒç”¨ï¼Œç»§ç»ä¸Šä¼ æµç¨‹ã€‚ * * å¯ä»¥æŒ‡å®šå¼€å§‹æŸä¸€ä¸ªæ–‡ä»¶ã€‚ * @grammar upload() => undefined * @grammar upload( file | fileId) => undefined * @method upload * @for Uploader */ startUpload: function(file) { var me = this; // 移出invalid的文件 $.each( me.request( 'get-files', Status.INVALID ), function() { me.request( 'remove-file', this ); }); // 如果指定了开始æŸä¸ªæ–‡ä»¶ï¼Œåˆ™åªå¼€å§‹æŒ‡å®šæ–‡ä»¶ã€‚ if ( file ) { file = file.id ? file : me.request( 'get-file', file ); if (file.getStatus() === Status.INTERRUPT) { $.each( me.pool, function( _, v ) { // 之剿š‚åœè¿‡ã€‚ if (v.file !== file) { return; } v.transport && v.transport.send(); }); file.setStatus( Status.QUEUED ); } else if (file.getStatus() === Status.PROGRESS) { return; } else { file.setStatus( Status.QUEUED ); } } else { $.each( me.request( 'get-files', [ Status.INITED ] ), function() { this.setStatus( Status.QUEUED ); }); } if ( me.runing ) { return; } me.runing = true; // 如果有暂åœçš„,则ç»ä¼ $.each( me.pool, function( _, v ) { var file = v.file; if ( file.getStatus() === Status.INTERRUPT ) { file.setStatus( Status.PROGRESS ); me._trigged = false; v.transport && v.transport.send(); } }); file || $.each( me.request( 'get-files', Status.INTERRUPT ), function() { this.setStatus( Status.PROGRESS ); }); me._trigged = false; Base.nextTick( me.__tick ); me.owner.trigger('startUpload'); }, /** * @event stopUpload * @description å½“å¼€å§‹ä¸Šä¼ æµç¨‹æš‚åœæ—¶è§¦å‘。 * @for Uploader */ /** * æš‚åœä¸Šä¼ ã€‚ç¬¬ä¸€ä¸ªå‚æ•°ä¸ºæ˜¯å¦ä¸æ–ä¸Šä¼ å½“å‰æ£åœ¨ä¸Šä¼ 的文件。 * * å¦‚æžœç¬¬ä¸€ä¸ªå‚æ•°æ˜¯æ–‡ä»¶ï¼Œåˆ™åªæš‚åœæŒ‡å®šæ–‡ä»¶ã€‚ * @grammar stop() => undefined * @grammar stop( true ) => undefined * @grammar stop( file ) => undefined * @method stop * @for Uploader */ stopUpload: function( file, interrupt ) { var me = this; if (file === true) { interrupt = file; file = null; } if ( me.runing === false ) { return; } // å¦‚æžœåªæ˜¯æš‚åœæŸä¸ªæ–‡ä»¶ã€‚ if ( file ) { file = file.id ? file : me.request( 'get-file', file ); if ( file.getStatus() !== Status.PROGRESS && file.getStatus() !== Status.QUEUED ) { return; } file.setStatus( Status.INTERRUPT ); $.each( me.pool, function( _, v ) { // åª abort 指定的文件。 if (v.file !== file) { return; } v.transport && v.transport.abort(); me._putback(v); me._popBlock(v); }); return Base.nextTick( me.__tick ); } me.runing = false; if (this._promise && this._promise.file) { this._promise.file.setStatus( Status.INTERRUPT ); } interrupt && $.each( me.pool, function( _, v ) { v.transport && v.transport.abort(); v.file.setStatus( Status.INTERRUPT ); }); me.owner.trigger('stopUpload'); }, /** * @method cancelFile * @grammar cancelFile( file ) => undefined * @grammar cancelFile( id ) => undefined * @param {File|id} file File对象或这File对象的id * @description æ ‡è®°æ–‡ä»¶çŠ¶æ€ä¸ºå·²å–消, åŒæ—¶å°†ä¸æ–æ–‡ä»¶ä¼ è¾“ã€‚ * @for Uploader * @example * * $li.on('click', '.remove-this', function() { * uploader.cancelFile( file ); * }) */ cancelFile: function( file ) { file = file.id ? file : this.request( 'get-file', file ); // 如果æ£åœ¨ä¸Šä¼ 。 file.blocks && $.each( file.blocks, function( _, v ) { var _tr = v.transport; if ( _tr ) { _tr.abort(); _tr.destroy(); delete v.transport; } }); file.setStatus( Status.CANCELLED ); this.owner.trigger( 'fileDequeued', file ); }, /** * 判æ–`Uplaode`ræ˜¯å¦æ£åœ¨ä¸Šä¼ ä¸ã€‚ * @grammar isInProgress() => Boolean * @method isInProgress * @for Uploader */ isInProgress: function() { return !!this.progress; }, _getStats: function() { return this.request('get-stats'); }, /** * æŽ‰è¿‡ä¸€ä¸ªæ–‡ä»¶ä¸Šä¼ ï¼Œç›´æŽ¥æ ‡è®°æŒ‡å®šæ–‡ä»¶ä¸ºå·²ä¸Šä¼ çŠ¶æ€ã€‚ * @grammar skipFile( file ) => undefined * @method skipFile * @for Uploader */ skipFile: function( file, status ) { file = file.id ? file : this.request( 'get-file', file ); file.setStatus( status || Status.COMPLETE ); file.skipped = true; // 如果æ£åœ¨ä¸Šä¼ 。 file.blocks && $.each( file.blocks, function( _, v ) { var _tr = v.transport; if ( _tr ) { _tr.abort(); _tr.destroy(); delete v.transport; } }); this.owner.trigger( 'uploadSkip', file ); }, /** * @event uploadFinished * @description å½“æ‰€æœ‰æ–‡ä»¶ä¸Šä¼ ç»“æŸæ—¶è§¦å‘。 * @for Uploader */ _tick: function() { var me = this, opts = me.options, fn, val; // 上一个promise还没有结æŸï¼Œåˆ™ç‰å¾…完æˆåŽå†æ‰§è¡Œã€‚ if ( me._promise ) { return me._promise.always( me.__tick ); } // 还有ä½ç½®ï¼Œä¸”还有文件è¦å¤„ç†çš„è¯ã€‚ if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { me._trigged = false; fn = function( val ) { me._promise = null; // 有å¯èƒ½æ˜¯reject过æ¥çš„ï¼Œæ‰€ä»¥è¦æ£€æµ‹val的类型。 val && val.file && me._startSend( val ); Base.nextTick( me.__tick ); }; me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); // 没有è¦ä¸Šä¼ 的了,且没有æ£åœ¨ä¼ 输的了。 } else if ( !me.remaning && !me._getStats().numOfQueue && !me._getStats().numofInterrupt ) { me.runing = false; me._trigged || Base.nextTick(function() { me.owner.trigger('uploadFinished'); }); me._trigged = true; } }, _putback: function(block) { var idx; block.cuted.unshift(block); idx = this.stack.indexOf(block.cuted); if (!~idx) { this.stack.unshift(block.cuted); } }, _getStack: function() { var i = 0, act; while ( (act = this.stack[ i++ ]) ) { if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { return act; } else if (!act.has() || act.file.getStatus() !== Status.PROGRESS && act.file.getStatus() !== Status.INTERRUPT ) { // 把已ç»å¤„ç†å®Œäº†çš„,或者,状æ€ä¸ºéž progressï¼ˆä¸Šä¼ ä¸ï¼‰ã€ // interupt(暂åœä¸ï¼‰ 的移除。 this.stack.splice( --i, 1 ); } } return null; }, _nextBlock: function() { var me = this, opts = me.options, act, next, done, preparing; // å¦‚æžœå½“å‰æ–‡ä»¶è¿˜æœ‰æ²¡æœ‰éœ€è¦ä¼ 输的,则直接返回剩下的。 if ( (act = this._getStack()) ) { // æ˜¯å¦æå‰å‡†å¤‡ä¸‹ä¸€ä¸ªæ–‡ä»¶ if ( opts.prepareNextFile && !me.pending.length ) { me._prepareNextFile(); } return act.shift(); // å¦åˆ™ï¼Œå¦‚æžœæ£åœ¨è¿è¡Œï¼Œåˆ™å‡†å¤‡ä¸‹ä¸€ä¸ªæ–‡ä»¶ï¼Œå¹¶ç‰å¾…完æˆåŽè¿”回下个分片。 } else if ( me.runing ) { // 如果缓å˜ä¸æœ‰ï¼Œåˆ™ç›´æŽ¥åœ¨ç¼“å˜ä¸å–,没有则去queueä¸å–。 if ( !me.pending.length && me._getStats().numOfQueue ) { me._prepareNextFile(); } next = me.pending.shift(); done = function( file ) { if ( !file ) { return null; } act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); me.stack.push(act); return act.shift(); }; // 文件å¯èƒ½è¿˜åœ¨prepareä¸ï¼Œä¹Ÿæœ‰å¯èƒ½å·²ç»å®Œå…¨å‡†å¤‡å¥½äº†ã€‚ if ( isPromise( next) ) { preparing = next.file; next = next[ next.pipe ? 'pipe' : 'then' ]( done ); next.file = preparing; return next; } return done( next ); } }, /** * @event uploadStart * @param {File} file File对象 * @description æŸä¸ªæ–‡ä»¶å¼€å§‹ä¸Šä¼ å‰è§¦å‘,一个文件åªä¼šè§¦å‘一次。 * @for Uploader */ _prepareNextFile: function() { var me = this, file = me.request('fetch-file'), pending = me.pending, promise; if ( file ) { promise = me.request( 'before-send-file', file, function() { // 有å¯èƒ½æ–‡ä»¶è¢«skip掉了。文件被skip掉åŽï¼Œçжæ€å‘å®šä¸æ˜¯Queued. if ( file.getStatus() === Status.PROGRESS || file.getStatus() === Status.INTERRUPT ) { return file; } return me._finishFile( file ); }); me.owner.trigger( 'uploadStart', file ); file.setStatus( Status.PROGRESS ); promise.file = file; // 如果还在pendingä¸ï¼Œåˆ™æ›¿æ¢æˆæ–‡ä»¶æœ¬èº«ã€‚ promise.done(function() { var idx = $.inArray( promise, pending ); ~idx && pending.splice( idx, 1, file ); }); // befeore-send-file的钩å就有错误å‘生。 promise.fail(function( reason ) { file.setStatus( Status.ERROR, reason ); me.owner.trigger( 'uploadError', file, reason ); me.owner.trigger( 'uploadComplete', file ); }); pending.push( promise ); } }, // 让出ä½ç½®äº†ï¼Œå¯ä»¥è®©å…¶ä»–åˆ†ç‰‡å¼€å§‹ä¸Šä¼ _popBlock: function( block ) { var idx = $.inArray( block, this.pool ); this.pool.splice( idx, 1 ); block.file.remaning--; this.remaning--; }, // å¼€å§‹ä¸Šä¼ ï¼Œå¯ä»¥è¢«æŽ‰è¿‡ã€‚如果promise被reject了,则表示跳过æ¤åˆ†ç‰‡ã€‚ _startSend: function( block ) { var me = this, file = block.file, promise; // 有å¯èƒ½åœ¨ before-send-file çš„ promise 期间改å˜äº†æ–‡ä»¶çжæ€ã€‚ // 如:暂åœï¼Œå–消 // 我们ä¸èƒ½ä¸æ– promise, 但是å¯ä»¥åœ¨ promise 完åŽï¼Œä¸åšä¸Šä¼ æ“作。 if ( file.getStatus() !== Status.PROGRESS ) { // å¦‚æžœæ˜¯ä¸æ–ï¼Œåˆ™è¿˜éœ€è¦æ”¾å›žåŽ»ã€‚ if (file.getStatus() === Status.INTERRUPT) { me._putback(block); } return; } me.pool.push( block ); me.remaning++; // 如果没有分片,则直接使用原始的。 // ä¸ä¼šä¸¢å¤±content-typeä¿¡æ¯ã€‚ block.blob = block.chunks === 1 ? file.source : file.source.slice( block.start, block.end ); // hook, æ¯ä¸ªåˆ†ç‰‡å‘é€ä¹‹å‰å¯èƒ½è¦åšäº›å¼‚æ¥çš„事情。 promise = me.request( 'before-send', block, function() { // 有å¯èƒ½æ–‡ä»¶å·²ç»ä¸Šä¼ 出错了,所以ä¸éœ€è¦å†ä¼ 输了。 if ( file.getStatus() === Status.PROGRESS ) { me._doSend( block ); } else { me._popBlock( block ); Base.nextTick( me.__tick ); } }); // 如果为fail了,则跳过æ¤åˆ†ç‰‡ã€‚ promise.fail(function() { if ( file.remaning === 1 ) { me._finishFile( file ).always(function() { block.percentage = 1; me._popBlock( block ); me.owner.trigger( 'uploadComplete', file ); Base.nextTick( me.__tick ); }); } else { block.percentage = 1; me._popBlock( block ); Base.nextTick( me.__tick ); } }); }, /** * @event uploadBeforeSend * @param {Object} object * @param {Object} data é»˜è®¤çš„ä¸Šä¼ å‚æ•°ï¼Œå¯ä»¥æ‰©å±•æ¤å¯¹è±¡æ¥æŽ§åˆ¶ä¸Šä¼ 傿•°ã€‚ * @param {Object} headers å¯ä»¥æ‰©å±•æ¤å¯¹è±¡æ¥æŽ§åˆ¶ä¸Šä¼ 头部。 * @description 当æŸä¸ªæ–‡ä»¶çš„分å—在å‘é€å‰è§¦å‘,主è¦ç”¨æ¥è¯¢é—®æ˜¯å¦è¦æ·»åР附另傿•°ï¼Œå¤§æ–‡ä»¶åœ¨å¼€èµ·åˆ†ç‰‡ä¸Šä¼ çš„å‰æä¸‹æ¤äº‹ä»¶å¯èƒ½ä¼šè§¦å‘多次。 * @for Uploader */ /** * @event uploadAccept * @param {Object} object * @param {Object} ret æœåŠ¡ç«¯çš„è¿”å›žæ•°æ®ï¼Œjsonæ ¼å¼ï¼Œå¦‚æžœæœåŠ¡ç«¯ä¸æ˜¯jsonæ ¼å¼ï¼Œä»Žret._rawä¸å–æ•°æ®ï¼Œè‡ªè¡Œè§£æžã€‚ * @description 当æŸä¸ªæ–‡ä»¶ä¸Šä¼ 到æœåŠ¡ç«¯å“应åŽï¼Œä¼šæ´¾é€æ¤äº‹ä»¶æ¥è¯¢é—®æœåŠ¡ç«¯å“åº”æ˜¯å¦æœ‰æ•ˆã€‚如果æ¤äº‹ä»¶handler返回值为`false`, åˆ™æ¤æ–‡ä»¶å°†æ´¾é€`server`类型的`uploadError`事件。 * @for Uploader */ /** * @event uploadProgress * @param {File} file File对象 * @param {Number} percentage ä¸Šä¼ è¿›åº¦ * @description ä¸Šä¼ è¿‡ç¨‹ä¸è§¦å‘,æºå¸¦ä¸Šä¼ 进度。 * @for Uploader */ /** * @event uploadError * @param {File} file File对象 * @param {String} reason 出错的code * @description å½“æ–‡ä»¶ä¸Šä¼ å‡ºé”™æ—¶è§¦å‘。 * @for Uploader */ /** * @event uploadSuccess * @param {File} file File对象 * @param {Object} response æœåŠ¡ç«¯è¿”å›žçš„æ•°æ® * @description å½“æ–‡ä»¶ä¸Šä¼ æˆåŠŸæ—¶è§¦å‘。 * @for Uploader */ /** * @event uploadComplete * @param {File} [file] File对象 * @description ä¸ç®¡æˆåŠŸæˆ–è€…å¤±è´¥ï¼Œæ–‡ä»¶ä¸Šä¼ å®Œæˆæ—¶è§¦å‘。 * @for Uploader */ // åšä¸Šä¼ æ“作。 _doSend: function( block ) { var me = this, owner = me.owner, opts = me.options, file = block.file, tr = new Transport( opts ), data = $.extend({}, opts.formData ), headers = $.extend({}, opts.headers ), requestAccept, ret; block.transport = tr; tr.on( 'destroy', function() { delete block.transport; me._popBlock( block ); Base.nextTick( me.__tick ); }); // 广æ’ä¸Šä¼ è¿›åº¦ã€‚ä»¥æ–‡ä»¶ä¸ºå•ä½ã€‚ tr.on( 'progress', function( percentage ) { var totalPercent = 0, uploaded = 0; // å¯èƒ½æ²¡æœ‰abort掉,progress还是执行进æ¥äº†ã€‚ // if ( !file.blocks ) { // return; // } totalPercent = block.percentage = percentage; if ( block.chunks > 1 ) { // 计算文件的整体速度。 $.each( file.blocks, function( _, v ) { uploaded += (v.percentage || 0) * (v.end - v.start); }); totalPercent = uploaded / file.size; } owner.trigger( 'uploadProgress', file, totalPercent || 0 ); }); // 用æ¥è¯¢é—®ï¼Œæ˜¯å¦è¿”回的结果是有错误的。 requestAccept = function( reject ) { var fn; ret = tr.getResponseAsJson() || {}; ret._raw = tr.getResponse(); fn = function( value ) { reject = value; }; // æœåŠ¡ç«¯å“应了,ä¸ä»£è¡¨æˆåŠŸäº†ï¼Œè¯¢é—®æ˜¯å¦å“应æ£ç¡®ã€‚ if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { reject = reject || 'server'; } return reject; }; // å°è¯•é‡è¯•,然åŽå¹¿æ’æ–‡ä»¶ä¸Šä¼ å‡ºé”™ã€‚ tr.on( 'error', function( type, flag ) { block.retried = block.retried || 0; // 自动é‡è¯• if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && block.retried < opts.chunkRetry ) { block.retried++; tr.send(); } else { // http status 500 ~ 600 if ( !flag && type === 'server' ) { type = requestAccept( type ); } file.setStatus( Status.ERROR, type ); owner.trigger( 'uploadError', file, type ); owner.trigger( 'uploadComplete', file ); } }); // ä¸Šä¼ æˆåŠŸ tr.on( 'load', function() { var reason; // 如果éžé¢„期,转å‘ä¸Šä¼ å‡ºé”™ã€‚ if ( (reason = requestAccept()) ) { tr.trigger( 'error', reason, true ); return; } // å…¨éƒ¨ä¸Šä¼ å®Œæˆã€‚ if ( file.remaning === 1 ) { me._finishFile( file, ret ); } else { tr.destroy(); } }); // é…ç½®é»˜è®¤çš„ä¸Šä¼ å—æ®µã€‚ data = $.extend( data, { id: file.id, name: file.name, type: file.type, lastModifiedDate: file.lastModifiedDate, size: file.size }); block.chunks > 1 && $.extend( data, { chunks: block.chunks, chunk: block.chunk }); // 在å‘é€ä¹‹é—´å¯ä»¥æ·»åŠ å—æ®µä»€ä¹ˆçš„。。。 // å¦‚æžœé»˜è®¤çš„å—æ®µä¸å¤Ÿä½¿ç”¨ï¼Œå¯ä»¥é€šè¿‡ç›‘嬿¤äº‹ä»¶æ¥æ‰©å±• owner.trigger( 'uploadBeforeSend', block, data, headers ); // 开始å‘é€ã€‚ tr.appendBlob( opts.fileVal, block.blob, file.name ); tr.append( data ); tr.setRequestHeader( headers ); tr.send(); }, // 完æˆä¸Šä¼ 。 _finishFile: function( file, ret, hds ) { var owner = this.owner; return owner .request( 'after-send-file', arguments, function() { file.setStatus( Status.COMPLETE ); owner.trigger( 'uploadSuccess', file, ret, hds ); }) .fail(function( reason ) { // å¦‚æžœå¤–éƒ¨å·²ç»æ ‡è®°ä¸ºinvalid什么的,ä¸å†æ”¹çжæ€ã€‚ if ( file.getStatus() === Status.PROGRESS ) { file.setStatus( Status.ERROR, reason ); } owner.trigger( 'uploadError', file, reason ); }) .always(function() { owner.trigger( 'uploadComplete', file ); }); } }); }); /** * @fileOverview å„ç§éªŒè¯ï¼ŒåŒ…æ‹¬æ–‡ä»¶æ€»å¤§å°æ˜¯å¦è¶…出ã€å•文件是å¦è¶…出和文件是å¦é‡å¤ã€‚ */ define('widgets/validator',[ 'base', 'uploader', 'file', 'widgets/widget' ], function( Base, Uploader, WUFile ) { var $ = Base.$, validators = {}, api; /** * @event error * @param {String} type 错误类型。 * @description 当validateä¸é€šè¿‡æ—¶ï¼Œä¼šä»¥æ´¾é€é”™è¯¯äº‹ä»¶çš„å½¢å¼é€šçŸ¥è°ƒç”¨è€…。通过`upload.on('error', handler)`å¯ä»¥æ•获到æ¤ç±»é”™è¯¯ï¼Œç›®å‰æœ‰ä»¥ä¸‹é”™è¯¯ä¼šåœ¨ç‰¹å®šçš„æƒ…况下派é€é”™æ¥ã€‚ * * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且å°è¯•ç»™`uploader`æ·»åŠ çš„æ–‡ä»¶æ•°é‡è¶…出这个值时派é€ã€‚ * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且å°è¯•ç»™`uploader`æ·»åŠ çš„æ–‡ä»¶æ€»å¤§å°è¶…出这个值时派é€ã€‚ * * `Q_TYPE_DENIED` å½“æ–‡ä»¶ç±»åž‹ä¸æ»¡è¶³æ—¶è§¦å‘。。 * @for Uploader */ // 暴露给外é¢çš„api api = { // æ·»åŠ éªŒè¯å™¨ addValidator: function( type, cb ) { validators[ type ] = cb; }, // 移除验è¯å™¨ removeValidator: function( type ) { delete validators[ type ]; } }; // 在Uploaderåˆå§‹åŒ–的时候å¯åЍValidatorsçš„åˆå§‹åŒ– Uploader.register({ name: 'validator', init: function() { var me = this; Base.nextTick(function() { $.each( validators, function() { this.call( me.owner ); }); }); } }); /** * @property {int} [fileNumLimit=undefined] * @namespace options * @for Uploader * @description éªŒè¯æ–‡ä»¶æ€»æ•°é‡, 超出则ä¸å…è®¸åŠ å…¥é˜Ÿåˆ—ã€‚ */ api.addValidator( 'fileNumLimit', function() { var uploader = this, opts = uploader.options, count = 0, max = parseInt( opts.fileNumLimit, 10 ), flag = true; if ( !max ) { return; } uploader.on( 'beforeFileQueued', function( file ) { if ( count >= max && flag ) { flag = false; this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); setTimeout(function() { flag = true; }, 1 ); } return count >= max ? false : true; }); uploader.on( 'fileQueued', function() { count++; }); uploader.on( 'fileDequeued', function() { count--; }); uploader.on( 'reset', function() { count = 0; }); }); /** * @property {int} [fileSizeLimit=undefined] * @namespace options * @for Uploader * @description éªŒè¯æ–‡ä»¶æ€»å¤§å°æ˜¯å¦è¶…出é™åˆ¶, 超出则ä¸å…è®¸åŠ å…¥é˜Ÿåˆ—ã€‚ */ api.addValidator( 'fileSizeLimit', function() { var uploader = this, opts = uploader.options, count = 0, max = parseInt( opts.fileSizeLimit, 10 ), flag = true; if ( !max ) { return; } uploader.on( 'beforeFileQueued', function( file ) { var invalid = count + file.size > max; if ( invalid && flag ) { flag = false; this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); setTimeout(function() { flag = true; }, 1 ); } return invalid ? false : true; }); uploader.on( 'fileQueued', function( file ) { count += file.size; }); uploader.on( 'fileDequeued', function( file ) { count -= file.size; }); uploader.on( 'reset', function() { count = 0; }); }); /** * @property {int} [fileSingleSizeLimit=undefined] * @namespace options * @for Uploader * @description 验è¯å•ä¸ªæ–‡ä»¶å¤§å°æ˜¯å¦è¶…出é™åˆ¶, 超出则ä¸å…è®¸åŠ å…¥é˜Ÿåˆ—ã€‚ */ api.addValidator( 'fileSingleSizeLimit', function() { var uploader = this, opts = uploader.options, max = opts.fileSingleSizeLimit; if ( !max ) { return; } uploader.on( 'beforeFileQueued', function( file ) { if ( file.size > max ) { file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); return false; } }); }); /** * @property {Boolean} [duplicate=undefined] * @namespace options * @for Uploader * @description 去é‡ï¼Œ æ ¹æ®æ–‡ä»¶åå—ã€æ–‡ä»¶å¤§å°å’Œæœ€åŽä¿®æ”¹æ—¶é—´æ¥ç”Ÿæˆhash Key. */ api.addValidator( 'duplicate', function() { var uploader = this, opts = uploader.options, mapping = {}; if ( opts.duplicate ) { return; } function hashString( str ) { var hash = 0, i = 0, len = str.length, _char; for ( ; i < len; i++ ) { _char = str.charCodeAt( i ); hash = _char + (hash << 6) + (hash << 16) - hash; } return hash; } uploader.on( 'beforeFileQueued', function( file ) { var hash = file.__hash || (file.__hash = hashString( file.name + file.size + file.lastModifiedDate )); // å·²ç»é‡å¤äº† if ( mapping[ hash ] ) { this.trigger( 'error', 'F_DUPLICATE', file ); return false; } }); uploader.on( 'fileQueued', function( file ) { var hash = file.__hash; hash && (mapping[ hash ] = true); }); uploader.on( 'fileDequeued', function( file ) { var hash = file.__hash; hash && (delete mapping[ hash ]); }); uploader.on( 'reset', function() { mapping = {}; }); }); return api; }); /** * @fileOverview Runtime管ç†å™¨ï¼Œè´Ÿè´£Runtime的选择, 连接 */ define('runtime/compbase',[],function() { function CompBase( owner, runtime ) { this.owner = owner; this.options = owner.options; this.getRuntime = function() { return runtime; }; this.getRuid = function() { return runtime.uid; }; this.trigger = function() { return owner.trigger.apply( owner, arguments ); }; } return CompBase; }); /** * @fileOverview Html5Runtime */ define('runtime/html5/runtime',[ 'base', 'runtime/runtime', 'runtime/compbase' ], function( Base, Runtime, CompBase ) { var type = 'html5', components = {}; function Html5Runtime() { var pool = {}, me = this, destroy = this.destroy; Runtime.apply( me, arguments ); me.type = type; // 这个方法的调用者,实际上是RuntimeClient me.exec = function( comp, fn/*, args...*/) { var client = this, uid = client.uid, args = Base.slice( arguments, 2 ), instance; if ( components[ comp ] ) { instance = pool[ uid ] = pool[ uid ] || new components[ comp ]( client, me ); if ( instance[ fn ] ) { return instance[ fn ].apply( instance, args ); } } }; me.destroy = function() { // @todo åˆ é™¤æ± åä¸çš„æ‰€æœ‰å®žä¾‹ return destroy && destroy.apply( this, arguments ); }; } Base.inherits( Runtime, { constructor: Html5Runtime, // ä¸éœ€è¦è¿žæŽ¥å…¶ä»–程åºï¼Œç›´æŽ¥æ‰§è¡Œcallback init: function() { var me = this; setTimeout(function() { me.trigger('ready'); }, 1 ); } }); // 注册Components Html5Runtime.register = function( name, component ) { var klass = components[ name ] = Base.inherits( CompBase, component ); return klass; }; // 注册html5è¿è¡Œæ—¶ã€‚ // åªæœ‰åœ¨æ”¯æŒçš„å‰æä¸‹æ³¨å†Œã€‚ if ( window.Blob && window.FileReader && window.DataView ) { Runtime.addRuntime( type, Html5Runtime ); } return Html5Runtime; }); /** * @fileOverview Blob Html实现 */ define('runtime/html5/blob',[ 'runtime/html5/runtime', 'lib/blob' ], function( Html5Runtime, Blob ) { return Html5Runtime.register( 'Blob', { slice: function( start, end ) { var blob = this.owner.source, slice = blob.slice || blob.webkitSlice || blob.mozSlice; blob = slice.call( blob, start, end ); return new Blob( this.getRuid(), blob ); } }); }); /** * @fileOverview FilePaste */ define('runtime/html5/dnd',[ 'base', 'runtime/html5/runtime', 'lib/file' ], function( Base, Html5Runtime, File ) { var $ = Base.$, prefix = 'webuploader-dnd-'; return Html5Runtime.register( 'DragAndDrop', { init: function() { var elem = this.elem = this.options.container; this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); this.dropHandler = Base.bindFn( this._dropHandler, this ); this.dndOver = false; elem.on( 'dragenter', this.dragEnterHandler ); elem.on( 'dragover', this.dragOverHandler ); elem.on( 'dragleave', this.dragLeaveHandler ); elem.on( 'drop', this.dropHandler ); if ( this.options.disableGlobalDnd ) { $( document ).on( 'dragover', this.dragOverHandler ); $( document ).on( 'drop', this.dropHandler ); } }, _dragEnterHandler: function( e ) { var me = this, denied = me._denied || false, items; e = e.originalEvent || e; if ( !me.dndOver ) { me.dndOver = true; // 注æ„åªæœ‰ chrome 支æŒã€‚ items = e.dataTransfer.items; if ( items && items.length ) { me._denied = denied = !me.trigger( 'accept', items ); } me.elem.addClass( prefix + 'over' ); me.elem[ denied ? 'addClass' : 'removeClass' ]( prefix + 'denied' ); } e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; return false; }, _dragOverHandler: function( e ) { // åªå¤„ç†æ¡†å†…的。 var parentElem = this.elem.parent().get( 0 ); if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { return false; } clearTimeout( this._leaveTimer ); this._dragEnterHandler.call( this, e ); return false; }, _dragLeaveHandler: function() { var me = this, handler; handler = function() { me.dndOver = false; me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); }; clearTimeout( me._leaveTimer ); me._leaveTimer = setTimeout( handler, 100 ); return false; }, _dropHandler: function( e ) { var me = this, ruid = me.getRuid(), parentElem = me.elem.parent().get( 0 ), dataTransfer, data; // åªå¤„ç†æ¡†å†…的。 if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { return false; } e = e.originalEvent || e; dataTransfer = e.dataTransfer; // 如果是页é¢å†…拖拽,还ä¸èƒ½å¤„ç†ï¼Œä¸é˜»æ¢äº‹ä»¶ã€‚ // æ¤å¤„ ie11 ä¸‹ä¼šæŠ¥å‚æ•°é”™è¯¯ï¼Œ try { data = dataTransfer.getData('text/html'); } catch( err ) { } if ( data ) { return; } me._getTansferFiles( dataTransfer, function( results ) { me.trigger( 'drop', $.map( results, function( file ) { return new File( ruid, file ); }) ); }); me.dndOver = false; me.elem.removeClass( prefix + 'over' ); return false; }, // å¦‚æžœä¼ å…¥ callback 则去查看文件夹,å¦åˆ™åªç®¡å½“剿–‡ä»¶å¤¹ã€‚ _getTansferFiles: function( dataTransfer, callback ) { var results = [], promises = [], items, files, file, item, i, len, canAccessFolder; items = dataTransfer.items; files = dataTransfer.files; canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); for ( i = 0, len = files.length; i < len; i++ ) { file = files[ i ]; item = items && items[ i ]; if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { promises.push( this._traverseDirectoryTree( item.webkitGetAsEntry(), results ) ); } else { results.push( file ); } } Base.when.apply( Base, promises ).done(function() { if ( !results.length ) { return; } callback( results ); }); }, _traverseDirectoryTree: function( entry, results ) { var deferred = Base.Deferred(), me = this; if ( entry.isFile ) { entry.file(function( file ) { results.push( file ); deferred.resolve(); }); } else if ( entry.isDirectory ) { entry.createReader().readEntries(function( entries ) { var len = entries.length, promises = [], arr = [], // 为了ä¿è¯é¡ºåºã€‚ i; for ( i = 0; i < len; i++ ) { promises.push( me._traverseDirectoryTree( entries[ i ], arr ) ); } Base.when.apply( Base, promises ).then(function() { results.push.apply( results, arr ); deferred.resolve(); }, deferred.reject ); }); } return deferred.promise(); }, destroy: function() { var elem = this.elem; // 还没 init 就调用 destroy if (!elem) { return; } elem.off( 'dragenter', this.dragEnterHandler ); elem.off( 'dragover', this.dragOverHandler ); elem.off( 'dragleave', this.dragLeaveHandler ); elem.off( 'drop', this.dropHandler ); if ( this.options.disableGlobalDnd ) { $( document ).off( 'dragover', this.dragOverHandler ); $( document ).off( 'drop', this.dropHandler ); } } }); }); /** * @fileOverview FilePaste */ define('runtime/html5/filepaste',[ 'base', 'runtime/html5/runtime', 'lib/file' ], function( Base, Html5Runtime, File ) { return Html5Runtime.register( 'FilePaste', { init: function() { var opts = this.options, elem = this.elem = opts.container, accept = '.*', arr, i, len, item; // accetpçš„mimeTypesä¸ç”ŸæˆåŒ¹é…æ£åˆ™ã€‚ if ( opts.accept ) { arr = []; for ( i = 0, len = opts.accept.length; i < len; i++ ) { item = opts.accept[ i ].mimeTypes; item && arr.push( item ); } if ( arr.length ) { accept = arr.join(','); accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); } } this.accept = accept = new RegExp( accept, 'i' ); this.hander = Base.bindFn( this._pasteHander, this ); elem.on( 'paste', this.hander ); }, _pasteHander: function( e ) { var allowed = [], ruid = this.getRuid(), items, item, blob, i, len; e = e.originalEvent || e; items = e.clipboardData.items; for ( i = 0, len = items.length; i < len; i++ ) { item = items[ i ]; if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { continue; } allowed.push( new File( ruid, blob ) ); } if ( allowed.length ) { // ä¸é˜»æ¢éžæ–‡ä»¶ç²˜è´´ï¼ˆæ–‡å—粘贴)的事件冒泡 e.preventDefault(); e.stopPropagation(); this.trigger( 'paste', allowed ); } }, destroy: function() { this.elem.off( 'paste', this.hander ); } }); }); /** * @fileOverview FilePicker */ define('runtime/html5/filepicker',[ 'base', 'runtime/html5/runtime' ], function( Base, Html5Runtime ) { var $ = Base.$; return Html5Runtime.register( 'FilePicker', { init: function() { var container = this.getRuntime().getContainer(), me = this, owner = me.owner, opts = me.options, label = this.label = $( document.createElement('label') ), input = this.input = $( document.createElement('input') ), arr, i, len, mouseHandler; input.attr( 'type', 'file' ); input.attr( 'name', opts.name ); input.addClass('webuploader-element-invisible'); label.on( 'click', function() { input.trigger('click'); }); label.css({ opacity: 0, width: '100%', height: '100%', display: 'block', cursor: 'pointer', background: '#ffffff' }); if ( opts.multiple ) { input.attr( 'multiple', 'multiple' ); } // @todo Firefox䏿”¯æŒå•独指定åŽç¼€ if ( opts.accept && opts.accept.length > 0 ) { arr = []; for ( i = 0, len = opts.accept.length; i < len; i++ ) { arr.push( opts.accept[ i ].mimeTypes ); } input.attr( 'accept', arr.join(',') ); } container.append( input ); container.append( label ); mouseHandler = function( e ) { owner.trigger( e.type ); }; input.on( 'change', function( e ) { var fn = arguments.callee, clone; me.files = e.target.files; // reset input clone = this.cloneNode( true ); clone.value = null; this.parentNode.replaceChild( clone, this ); input.off(); input = $( clone ).on( 'change', fn ) .on( 'mouseenter mouseleave', mouseHandler ); owner.trigger('change'); }); label.on( 'mouseenter mouseleave', mouseHandler ); }, getFiles: function() { return this.files; }, destroy: function() { this.input.off(); this.label.off(); } }); }); /** * @fileOverview Transport * @todo 支æŒchunkedä¼ è¾“ï¼Œä¼˜åŠ¿ï¼š * å¯ä»¥å°†å¤§æ–‡ä»¶åˆ†æˆå°å—ï¼ŒæŒ¨ä¸ªä¼ è¾“ï¼Œå¯ä»¥æé«˜å¤§æ–‡ä»¶æˆåŠŸçŽ‡ï¼Œå½“å¤±è´¥çš„æ—¶å€™ï¼Œä¹Ÿåªéœ€è¦é‡ä¼ é‚£å°éƒ¨åˆ†ï¼Œ * 而ä¸éœ€è¦é‡å¤´å†ä¼ 一次。å¦å¤–æ–点ç»ä¼ 也需è¦ç”¨chunkedæ–¹å¼ã€‚ */ define('runtime/html5/transport',[ 'base', 'runtime/html5/runtime' ], function( Base, Html5Runtime ) { var noop = Base.noop, $ = Base.$; return Html5Runtime.register( 'Transport', { init: function() { this._status = 0; this._response = null; }, send: function() { var owner = this.owner, opts = this.options, xhr = this._initAjax(), blob = owner._blob, server = opts.server, formData, binary, fr; if ( opts.sendAsBinary ) { server += (/\?/.test( server ) ? '&' : '?') + $.param( owner._formData ); binary = blob.getSource(); } else { formData = new FormData(); $.each( owner._formData, function( k, v ) { formData.append( k, v ); }); formData.append( opts.fileVal, blob.getSource(), opts.filename || owner._formData.name || '' ); } if ( opts.withCredentials && 'withCredentials' in xhr ) { xhr.open( opts.method, server, true ); xhr.withCredentials = true; } else { xhr.open( opts.method, server ); } this._setRequestHeader( xhr, opts.headers ); if ( binary ) { // å¼ºåˆ¶è®¾ç½®æˆ content-type 为文件æµã€‚ xhr.overrideMimeType && xhr.overrideMimeType('application/octet-stream'); // android直接å‘é€blob会导致æœåŠ¡ç«¯æŽ¥æ”¶åˆ°çš„æ˜¯ç©ºæ–‡ä»¶ã€‚ // bug详情。 // https://code.google.com/p/android/issues/detail?id=39882 // 所以先用fileReader读å–出æ¥å†é€šè¿‡arraybuffer的方å¼å‘é€ã€‚ if ( Base.os.android ) { fr = new FileReader(); fr.onload = function() { xhr.send( this.result ); fr = fr.onload = null; }; fr.readAsArrayBuffer( binary ); } else { xhr.send( binary ); } } else { xhr.send( formData ); } }, getResponse: function() { return this._response; }, getResponseAsJson: function() { return this._parseJson( this._response ); }, getStatus: function() { return this._status; }, abort: function() { var xhr = this._xhr; if ( xhr ) { xhr.upload.onprogress = noop; xhr.onreadystatechange = noop; xhr.abort(); this._xhr = xhr = null; } }, destroy: function() { this.abort(); }, _initAjax: function() { var me = this, xhr = new XMLHttpRequest(), opts = this.options; if ( opts.withCredentials && !('withCredentials' in xhr) && typeof XDomainRequest !== 'undefined' ) { xhr = new XDomainRequest(); } xhr.upload.onprogress = function( e ) { var percentage = 0; if ( e.lengthComputable ) { percentage = e.loaded / e.total; } return me.trigger( 'progress', percentage ); }; xhr.onreadystatechange = function() { if ( xhr.readyState !== 4 ) { return; } xhr.upload.onprogress = noop; xhr.onreadystatechange = noop; me._xhr = null; me._status = xhr.status; if ( xhr.status >= 200 && xhr.status < 300 ) { me._response = xhr.responseText; return me.trigger('load'); } else if ( xhr.status >= 500 && xhr.status < 600 ) { me._response = xhr.responseText; return me.trigger( 'error', 'server' ); } return me.trigger( 'error', me._status ? 'http' : 'abort' ); }; me._xhr = xhr; return xhr; }, _setRequestHeader: function( xhr, headers ) { $.each( headers, function( key, val ) { xhr.setRequestHeader( key, val ); }); }, _parseJson: function( str ) { var json; try { json = JSON.parse( str ); } catch ( ex ) { json = {}; } return json; } }); }); /** * @fileOverview FlashRuntime */ define('runtime/flash/runtime',[ 'base', 'runtime/runtime', 'runtime/compbase' ], function( Base, Runtime, CompBase ) { var $ = Base.$, type = 'flash', components = {}; function getFlashVersion() { var version; try { version = navigator.plugins[ 'Shockwave Flash' ]; version = version.description; } catch ( ex ) { try { version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') .GetVariable('$version'); } catch ( ex2 ) { version = '0.0'; } } version = version.match( /\d+/g ); return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); } function FlashRuntime() { var pool = {}, clients = {}, destroy = this.destroy, me = this, jsreciver = Base.guid('webuploader_'); Runtime.apply( me, arguments ); me.type = type; // 这个方法的调用者,实际上是RuntimeClient me.exec = function( comp, fn/*, args...*/ ) { var client = this, uid = client.uid, args = Base.slice( arguments, 2 ), instance; clients[ uid ] = client; if ( components[ comp ] ) { if ( !pool[ uid ] ) { pool[ uid ] = new components[ comp ]( client, me ); } instance = pool[ uid ]; if ( instance[ fn ] ) { return instance[ fn ].apply( instance, args ); } } return me.flashExec.apply( client, arguments ); }; function handler( evt, obj ) { var type = evt.type || evt, parts, uid; parts = type.split('::'); uid = parts[ 0 ]; type = parts[ 1 ]; // console.log.apply( console, arguments ); if ( type === 'Ready' && uid === me.uid ) { me.trigger('ready'); } else if ( clients[ uid ] ) { clients[ uid ].trigger( type.toLowerCase(), evt, obj ); } // Base.log( evt, obj ); } // flash的接å—器。 window[ jsreciver ] = function() { var args = arguments; // 为了能æ•获得到。 setTimeout(function() { handler.apply( null, args ); }, 1 ); }; this.jsreciver = jsreciver; this.destroy = function() { // @todo åˆ é™¤æ± åä¸çš„æ‰€æœ‰å®žä¾‹ return destroy && destroy.apply( this, arguments ); }; this.flashExec = function( comp, fn ) { var flash = me.getFlash(), args = Base.slice( arguments, 2 ); return flash.exec( this.uid, comp, fn, args ); }; // @todo } Base.inherits( Runtime, { constructor: FlashRuntime, init: function() { var container = this.getContainer(), opts = this.options, html; // if not the minimal height, shims are not initialized // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) container.css({ position: 'absolute', top: '-8px', left: '-8px', width: '9px', height: '9px', overflow: 'hidden' }); // insert flash object html = '<object id="' + this.uid + '" type="application/' + 'x-shockwave-flash" data="' + opts.swf + '" '; if ( Base.browser.ie ) { html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" '; } html += 'width="100%" height="100%" style="outline:0">' + '<param name="movie" value="' + opts.swf + '" />' + '<param name="flashvars" value="uid=' + this.uid + '&jsreciver=' + this.jsreciver + '" />' + '<param name="wmode" value="transparent" />' + '<param name="allowscriptaccess" value="always" />' + '</object>'; container.html( html ); }, getFlash: function() { if ( this._flash ) { return this._flash; } this._flash = $( '#' + this.uid ).get( 0 ); return this._flash; } }); FlashRuntime.register = function( name, component ) { component = components[ name ] = Base.inherits( CompBase, $.extend({ // @todo fix this later flashExec: function() { var owner = this.owner, runtime = this.getRuntime(); return runtime.flashExec.apply( owner, arguments ); } }, component ) ); return component; }; if ( getFlashVersion() >= 11.4 ) { Runtime.addRuntime( type, FlashRuntime ); } return FlashRuntime; }); /** * @fileOverview FilePicker */ define('runtime/flash/filepicker',[ 'base', 'runtime/flash/runtime' ], function( Base, FlashRuntime ) { var $ = Base.$; return FlashRuntime.register( 'FilePicker', { init: function( opts ) { var copy = $.extend({}, opts ), len, i; // ä¿®å¤Flash冿²¡æœ‰è®¾ç½®titleçš„æƒ…å†µä¸‹æ— æ³•å¼¹å‡ºflash文件选择框的bug. len = copy.accept && copy.accept.length; for ( i = 0; i < len; i++ ) { if ( !copy.accept[ i ].title ) { copy.accept[ i ].title = 'Files'; } } delete copy.button; delete copy.id; delete copy.container; this.flashExec( 'FilePicker', 'init', copy ); }, destroy: function() { this.flashExec( 'FilePicker', 'destroy' ); } }); }); /** * @fileOverview Transport flash实现 */ define('runtime/flash/transport',[ 'base', 'runtime/flash/runtime', 'runtime/client' ], function( Base, FlashRuntime, RuntimeClient ) { var $ = Base.$; return FlashRuntime.register( 'Transport', { init: function() { this._status = 0; this._response = null; this._responseJson = null; }, send: function() { var owner = this.owner, opts = this.options, xhr = this._initAjax(), blob = owner._blob, server = opts.server, binary; xhr.connectRuntime( blob.ruid ); if ( opts.sendAsBinary ) { server += (/\?/.test( server ) ? '&' : '?') + $.param( owner._formData ); binary = blob.uid; } else { $.each( owner._formData, function( k, v ) { xhr.exec( 'append', k, v ); }); xhr.exec( 'appendBlob', opts.fileVal, blob.uid, opts.filename || owner._formData.name || '' ); } this._setRequestHeader( xhr, opts.headers ); xhr.exec( 'send', { method: opts.method, url: server, forceURLStream: opts.forceURLStream, mimeType: 'application/octet-stream' }, binary ); }, getStatus: function() { return this._status; }, getResponse: function() { return this._response || ''; }, getResponseAsJson: function() { return this._responseJson; }, abort: function() { var xhr = this._xhr; if ( xhr ) { xhr.exec('abort'); xhr.destroy(); this._xhr = xhr = null; } }, destroy: function() { this.abort(); }, _initAjax: function() { var me = this, xhr = new RuntimeClient('XMLHttpRequest'); xhr.on( 'uploadprogress progress', function( e ) { var percent = e.loaded / e.total; percent = Math.min( 1, Math.max( 0, percent ) ); return me.trigger( 'progress', percent ); }); xhr.on( 'load', function() { var status = xhr.exec('getStatus'), readBody = false, err = '', p; xhr.off(); me._xhr = null; if ( status >= 200 && status < 300 ) { readBody = true; } else if ( status >= 500 && status < 600 ) { readBody = true; err = 'server'; } else { err = 'http'; } if ( readBody ) { me._response = xhr.exec('getResponse'); me._response = decodeURIComponent( me._response ); // flash 处ç†å¯èƒ½å˜åœ¨ bug, 没辙åªèƒ½é js 了 // try { // me._responseJson = xhr.exec('getResponseAsJson'); // } catch ( error ) { p = window.JSON && window.JSON.parse || function( s ) { try { return new Function('return ' + s).call(); } catch ( err ) { return {}; } }; me._responseJson = me._response ? p(me._response) : {}; // } } xhr.destroy(); xhr = null; return err ? me.trigger( 'error', err ) : me.trigger('load'); }); xhr.on( 'error', function() { xhr.off(); me._xhr = null; me.trigger( 'error', 'http' ); }); me._xhr = xhr; return xhr; }, _setRequestHeader: function( xhr, headers ) { $.each( headers, function( key, val ) { xhr.exec( 'setRequestHeader', key, val ); }); } }); }); /** * @fileOverview 没有图åƒå¤„ç†çš„版本。 */ define('preset/withoutimage',[ 'base', // widgets 'widgets/filednd', 'widgets/filepaste', 'widgets/filepicker', 'widgets/queue', 'widgets/runtime', 'widgets/upload', 'widgets/validator', // runtimes // html5 'runtime/html5/blob', 'runtime/html5/dnd', 'runtime/html5/filepaste', 'runtime/html5/filepicker', 'runtime/html5/transport', // flash 'runtime/flash/filepicker', 'runtime/flash/transport' ], function( Base ) { return Base; }); define('webuploader',[ 'preset/withoutimage' ], function( preset ) { return preset; }); return require('webuploader'); });