hjg
2023-11-17 3780c5e65b05bf23020810798babc6d20311fa79
提交 | 用户 | 时间
58d006 1 /*! WebUploader 0.1.5 */
A 2
3
4 /**
5  * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。
6  *
7  * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。
8  */
9 (function( root, factory ) {
10     var modules = {},
11
12         // 内部require, 简单不完全实现。
13         // https://github.com/amdjs/amdjs-api/wiki/require
14         _require = function( deps, callback ) {
15             var args, len, i;
16
17             // 如果deps不是数组,则直接返回指定module
18             if ( typeof deps === 'string' ) {
19                 return getModule( deps );
20             } else {
21                 args = [];
22                 for( len = deps.length, i = 0; i < len; i++ ) {
23                     args.push( getModule( deps[ i ] ) );
24                 }
25
26                 return callback.apply( null, args );
27             }
28         },
29
30         // 内部define,暂时不支持不指定id.
31         _define = function( id, deps, factory ) {
32             if ( arguments.length === 2 ) {
33                 factory = deps;
34                 deps = null;
35             }
36
37             _require( deps || [], function() {
38                 setModule( id, factory, arguments );
39             });
40         },
41
42         // 设置module, 兼容CommonJs写法。
43         setModule = function( id, factory, args ) {
44             var module = {
45                     exports: factory
46                 },
47                 returned;
48
49             if ( typeof factory === 'function' ) {
50                 args.length || (args = [ _require, module.exports, module ]);
51                 returned = factory.apply( null, args );
52                 returned !== undefined && (module.exports = returned);
53             }
54
55             modules[ id ] = module.exports;
56         },
57
58         // 根据id获取module
59         getModule = function( id ) {
60             var module = modules[ id ] || root[ id ];
61
62             if ( !module ) {
63                 throw new Error( '`' + id + '` is undefined' );
64             }
65
66             return module;
67         },
68
69         // 将所有modules,将路径ids装换成对象。
70         exportsTo = function( obj ) {
71             var key, host, parts, part, last, ucFirst;
72
73             // make the first character upper case.
74             ucFirst = function( str ) {
75                 return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));
76             };
77
78             for ( key in modules ) {
79                 host = obj;
80
81                 if ( !modules.hasOwnProperty( key ) ) {
82                     continue;
83                 }
84
85                 parts = key.split('/');
86                 last = ucFirst( parts.pop() );
87
88                 while( (part = ucFirst( parts.shift() )) ) {
89                     host[ part ] = host[ part ] || {};
90                     host = host[ part ];
91                 }
92
93                 host[ last ] = modules[ key ];
94             }
95
96             return obj;
97         },
98
99         makeExport = function( dollar ) {
100             root.__dollar = dollar;
101
102             // exports every module.
103             return exportsTo( factory( root, _define, _require ) );
104         },
105
106         origin;
107
108     if ( typeof module === 'object' && typeof module.exports === 'object' ) {
109
110         // For CommonJS and CommonJS-like environments where a proper window is present,
111         module.exports = makeExport();
112     } else if ( typeof define === 'function' && define.amd ) {
113
114         // Allow using this built library as an AMD module
115         // in another project. That other project will only
116         // see this AMD call, not the internal modules in
117         // the closure below.
118         define([ 'jquery' ], makeExport );
119     } else {
120
121         // Browser globals case. Just assign the
122         // result to a property on the global.
123         origin = root.WebUploader;
124         root.WebUploader = makeExport();
125         root.WebUploader.noConflict = function() {
126             root.WebUploader = origin;
127         };
128     }
129 })( window, function( window, define, require ) {
130
131
132     /**
133      * @fileOverview jQuery or Zepto
134      */
135     define('dollar-third',[],function() {
136         var $ = window.__dollar || window.jQuery || window.Zepto;
137     
138         if ( !$ ) {
139             throw new Error('jQuery or Zepto not found!');
140         }
141     
142         return $;
143     });
144     /**
145      * @fileOverview Dom 操作相关
146      */
147     define('dollar',[
148         'dollar-third'
149     ], function( _ ) {
150         return _;
151     });
152     /**
153      * @fileOverview 使用jQuery的Promise
154      */
155     define('promise-third',[
156         'dollar'
157     ], function( $ ) {
158         return {
159             Deferred: $.Deferred,
160             when: $.when,
161     
162             isPromise: function( anything ) {
163                 return anything && typeof anything.then === 'function';
164             }
165         };
166     });
167     /**
168      * @fileOverview Promise/A+
169      */
170     define('promise',[
171         'promise-third'
172     ], function( _ ) {
173         return _;
174     });
175     /**
176      * @fileOverview 基础类方法。
177      */
178     
179     /**
180      * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。
181      *
182      * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.
183      * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如:
184      *
185      * * module `base`:WebUploader.Base
186      * * module `file`: WebUploader.File
187      * * module `lib/dnd`: WebUploader.Lib.Dnd
188      * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd
189      *
190      *
191      * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。
192      * @module WebUploader
193      * @title WebUploader API文档
194      */
195     define('base',[
196         'dollar',
197         'promise'
198     ], function( $, promise ) {
199     
200         var noop = function() {},
201             call = Function.call;
202     
203         // http://jsperf.com/uncurrythis
204         // 反科里化
205         function uncurryThis( fn ) {
206             return function() {
207                 return call.apply( fn, arguments );
208             };
209         }
210     
211         function bindFn( fn, context ) {
212             return function() {
213                 return fn.apply( context, arguments );
214             };
215         }
216     
217         function createObject( proto ) {
218             var f;
219     
220             if ( Object.create ) {
221                 return Object.create( proto );
222             } else {
223                 f = function() {};
224                 f.prototype = proto;
225                 return new f();
226             }
227         }
228     
229     
230         /**
231          * 基础类,提供一些简单常用的方法。
232          * @class Base
233          */
234         return {
235     
236             /**
237              * @property {String} version 当前版本号。
238              */
239             version: '0.1.5',
240     
241             /**
242              * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。
243              */
244             $: $,
245     
246             Deferred: promise.Deferred,
247     
248             isPromise: promise.isPromise,
249     
250             when: promise.when,
251     
252             /**
253              * @description  简单的浏览器检查结果。
254              *
255              * * `webkit`  webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。
256              * * `chrome`  chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。
257              * * `ie`  ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+**
258              * * `firefox`  firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。
259              * * `safari`  safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。
260              * * `opera`  opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。
261              *
262              * @property {Object} [browser]
263              */
264             browser: (function( ua ) {
265                 var ret = {},
266                     webkit = ua.match( /WebKit\/([\d.]+)/ ),
267                     chrome = ua.match( /Chrome\/([\d.]+)/ ) ||
268                         ua.match( /CriOS\/([\d.]+)/ ),
269     
270                     ie = ua.match( /MSIE\s([\d\.]+)/ ) ||
271                         ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ),
272                     firefox = ua.match( /Firefox\/([\d.]+)/ ),
273                     safari = ua.match( /Safari\/([\d.]+)/ ),
274                     opera = ua.match( /OPR\/([\d.]+)/ );
275     
276                 webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));
277                 chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));
278                 ie && (ret.ie = parseFloat( ie[ 1 ] ));
279                 firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));
280                 safari && (ret.safari = parseFloat( safari[ 1 ] ));
281                 opera && (ret.opera = parseFloat( opera[ 1 ] ));
282     
283                 return ret;
284             })( navigator.userAgent ),
285     
286             /**
287              * @description  操作系统检查结果。
288              *
289              * * `android`  如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。
290              * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。
291              * @property {Object} [os]
292              */
293             os: (function( ua ) {
294                 var ret = {},
295     
296                     // osx = !!ua.match( /\(Macintosh\; Intel / ),
297                     android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ),
298                     ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ );
299     
300                 // osx && (ret.osx = true);
301                 android && (ret.android = parseFloat( android[ 1 ] ));
302                 ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));
303     
304                 return ret;
305             })( navigator.userAgent ),
306     
307             /**
308              * 实现类与类之间的继承。
309              * @method inherits
310              * @grammar Base.inherits( super ) => child
311              * @grammar Base.inherits( super, protos ) => child
312              * @grammar Base.inherits( super, protos, statics ) => child
313              * @param  {Class} super 父类
314              * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。
315              * @param  {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。
316              * @param  {Object} [statics] 静态属性或方法。
317              * @return {Class} 返回子类。
318              * @example
319              * function Person() {
320              *     console.log( 'Super' );
321              * }
322              * Person.prototype.hello = function() {
323              *     console.log( 'hello' );
324              * };
325              *
326              * var Manager = Base.inherits( Person, {
327              *     world: function() {
328              *         console.log( 'World' );
329              *     }
330              * });
331              *
332              * // 因为没有指定构造器,父类的构造器将会执行。
333              * var instance = new Manager();    // => Super
334              *
335              * // 继承子父类的方法
336              * instance.hello();    // => hello
337              * instance.world();    // => World
338              *
339              * // 子类的__super__属性指向父类
340              * console.log( Manager.__super__ === Person );    // => true
341              */
342             inherits: function( Super, protos, staticProtos ) {
343                 var child;
344     
345                 if ( typeof protos === 'function' ) {
346                     child = protos;
347                     protos = null;
348                 } else if ( protos && protos.hasOwnProperty('constructor') ) {
349                     child = protos.constructor;
350                 } else {
351                     child = function() {
352                         return Super.apply( this, arguments );
353                     };
354                 }
355     
356                 // 复制静态方法
357                 $.extend( true, child, Super, staticProtos || {} );
358     
359                 /* jshint camelcase: false */
360     
361                 // 让子类的__super__属性指向父类。
362                 child.__super__ = Super.prototype;
363     
364                 // 构建原型,添加原型方法或属性。
365                 // 暂时用Object.create实现。
366                 child.prototype = createObject( Super.prototype );
367                 protos && $.extend( true, child.prototype, protos );
368     
369                 return child;
370             },
371     
372             /**
373              * 一个不做任何事情的方法。可以用来赋值给默认的callback.
374              * @method noop
375              */
376             noop: noop,
377     
378             /**
379              * 返回一个新的方法,此方法将已指定的`context`来执行。
380              * @grammar Base.bindFn( fn, context ) => Function
381              * @method bindFn
382              * @example
383              * var doSomething = function() {
384              *         console.log( this.name );
385              *     },
386              *     obj = {
387              *         name: 'Object Name'
388              *     },
389              *     aliasFn = Base.bind( doSomething, obj );
390              *
391              *  aliasFn();    // => Object Name
392              *
393              */
394             bindFn: bindFn,
395     
396             /**
397              * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。
398              * @grammar Base.log( args... ) => undefined
399              * @method log
400              */
401             log: (function() {
402                 if ( window.console ) {
403                     return bindFn( console.log, console );
404                 }
405                 return noop;
406             })(),
407     
408             nextTick: (function() {
409     
410                 return function( cb ) {
411                     setTimeout( cb, 1 );
412                 };
413     
414                 // @bug 当浏览器不在当前窗口时就停了。
415                 // var next = window.requestAnimationFrame ||
416                 //     window.webkitRequestAnimationFrame ||
417                 //     window.mozRequestAnimationFrame ||
418                 //     function( cb ) {
419                 //         window.setTimeout( cb, 1000 / 60 );
420                 //     };
421     
422                 // // fix: Uncaught TypeError: Illegal invocation
423                 // return bindFn( next, window );
424             })(),
425     
426             /**
427              * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。
428              * 将用来将非数组对象转化成数组对象。
429              * @grammar Base.slice( target, start[, end] ) => Array
430              * @method slice
431              * @example
432              * function doSomthing() {
433              *     var args = Base.slice( arguments, 1 );
434              *     console.log( args );
435              * }
436              *
437              * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array ["arg2", "arg3"]
438              */
439             slice: uncurryThis( [].slice ),
440     
441             /**
442              * 生成唯一的ID
443              * @method guid
444              * @grammar Base.guid() => String
445              * @grammar Base.guid( prefx ) => String
446              */
447             guid: (function() {
448                 var counter = 0;
449     
450                 return function( prefix ) {
451                     var guid = (+new Date()).toString( 32 ),
452                         i = 0;
453     
454                     for ( ; i < 5; i++ ) {
455                         guid += Math.floor( Math.random() * 65535 ).toString( 32 );
456                     }
457     
458                     return (prefix || 'wu_') + guid + (counter++).toString( 32 );
459                 };
460             })(),
461     
462             /**
463              * 格式化文件大小, 输出成带单位的字符串
464              * @method formatSize
465              * @grammar Base.formatSize( size ) => String
466              * @grammar Base.formatSize( size, pointLength ) => String
467              * @grammar Base.formatSize( size, pointLength, units ) => String
468              * @param {Number} size 文件大小
469              * @param {Number} [pointLength=2] 精确到的小数点数。
470              * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K.
471              * @example
472              * console.log( Base.formatSize( 100 ) );    // => 100B
473              * console.log( Base.formatSize( 1024 ) );    // => 1.00K
474              * console.log( Base.formatSize( 1024, 0 ) );    // => 1K
475              * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M
476              * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G
477              * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB
478              */
479             formatSize: function( size, pointLength, units ) {
480                 var unit;
481     
482                 units = units || [ 'B', 'K', 'M', 'G', 'TB' ];
483     
484                 while ( (unit = units.shift()) && size > 1024 ) {
485                     size = size / 1024;
486                 }
487     
488                 return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +
489                         unit;
490             }
491         };
492     });
493     /**
494      * 事件处理类,可以独立使用,也可以扩展给对象使用。
495      * @fileOverview Mediator
496      */
497     define('mediator',[
498         'base'
499     ], function( Base ) {
500         var $ = Base.$,
501             slice = [].slice,
502             separator = /\s+/,
503             protos;
504     
505         // 根据条件过滤出事件handlers.
506         function findHandlers( arr, name, callback, context ) {
507             return $.grep( arr, function( handler ) {
508                 return handler &&
509                         (!name || handler.e === name) &&
510                         (!callback || handler.cb === callback ||
511                         handler.cb._cb === callback) &&
512                         (!context || handler.ctx === context);
513             });
514         }
515     
516         function eachEvent( events, callback, iterator ) {
517             // 不支持对象,只支持多个event用空格隔开
518             $.each( (events || '').split( separator ), function( _, key ) {
519                 iterator( key, callback );
520             });
521         }
522     
523         function triggerHanders( events, args ) {
524             var stoped = false,
525                 i = -1,
526                 len = events.length,
527                 handler;
528     
529             while ( ++i < len ) {
530                 handler = events[ i ];
531     
532                 if ( handler.cb.apply( handler.ctx2, args ) === false ) {
533                     stoped = true;
534                     break;
535                 }
536             }
537     
538             return !stoped;
539         }
540     
541         protos = {
542     
543             /**
544              * 绑定事件。
545              *
546              * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如
547              * ```javascript
548              * var obj = {};
549              *
550              * // 使得obj有事件行为
551              * Mediator.installTo( obj );
552              *
553              * obj.on( 'testa', function( arg1, arg2 ) {
554              *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'
555              * });
556              *
557              * obj.trigger( 'testa', 'arg1', 'arg2' );
558              * ```
559              *
560              * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。
561              * 切会影响到`trigger`方法的返回值,为`false`。
562              *
563              * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处,
564              * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。
565              * ```javascript
566              * obj.on( 'all', function( type, arg1, arg2 ) {
567              *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'
568              * });
569              * ```
570              *
571              * @method on
572              * @grammar on( name, callback[, context] ) => self
573              * @param  {String}   name     事件名,支持多个事件用空格隔开
574              * @param  {Function} callback 事件处理器
575              * @param  {Object}   [context]  事件处理器的上下文。
576              * @return {self} 返回自身,方便链式
577              * @chainable
578              * @class Mediator
579              */
580             on: function( name, callback, context ) {
581                 var me = this,
582                     set;
583     
584                 if ( !callback ) {
585                     return this;
586                 }
587     
588                 set = this._events || (this._events = []);
589     
590                 eachEvent( name, callback, function( name, callback ) {
591                     var handler = { e: name };
592     
593                     handler.cb = callback;
594                     handler.ctx = context;
595                     handler.ctx2 = context || me;
596                     handler.id = set.length;
597     
598                     set.push( handler );
599                 });
600     
601                 return this;
602             },
603     
604             /**
605              * 绑定事件,且当handler执行完后,自动解除绑定。
606              * @method once
607              * @grammar once( name, callback[, context] ) => self
608              * @param  {String}   name     事件名
609              * @param  {Function} callback 事件处理器
610              * @param  {Object}   [context]  事件处理器的上下文。
611              * @return {self} 返回自身,方便链式
612              * @chainable
613              */
614             once: function( name, callback, context ) {
615                 var me = this;
616     
617                 if ( !callback ) {
618                     return me;
619                 }
620     
621                 eachEvent( name, callback, function( name, callback ) {
622                     var once = function() {
623                             me.off( name, once );
624                             return callback.apply( context || me, arguments );
625                         };
626     
627                     once._cb = callback;
628                     me.on( name, once, context );
629                 });
630     
631                 return me;
632             },
633     
634             /**
635              * 解除事件绑定
636              * @method off
637              * @grammar off( [name[, callback[, context] ] ] ) => self
638              * @param  {String}   [name]     事件名
639              * @param  {Function} [callback] 事件处理器
640              * @param  {Object}   [context]  事件处理器的上下文。
641              * @return {self} 返回自身,方便链式
642              * @chainable
643              */
644             off: function( name, cb, ctx ) {
645                 var events = this._events;
646     
647                 if ( !events ) {
648                     return this;
649                 }
650     
651                 if ( !name && !cb && !ctx ) {
652                     this._events = [];
653                     return this;
654                 }
655     
656                 eachEvent( name, cb, function( name, cb ) {
657                     $.each( findHandlers( events, name, cb, ctx ), function() {
658                         delete events[ this.id ];
659                     });
660                 });
661     
662                 return this;
663             },
664     
665             /**
666              * 触发事件
667              * @method trigger
668              * @grammar trigger( name[, args...] ) => self
669              * @param  {String}   type     事件名
670              * @param  {*} [...] 任意参数
671              * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true
672              */
673             trigger: function( type ) {
674                 var args, events, allEvents;
675     
676                 if ( !this._events || !type ) {
677                     return this;
678                 }
679     
680                 args = slice.call( arguments, 1 );
681                 events = findHandlers( this._events, type );
682                 allEvents = findHandlers( this._events, 'all' );
683     
684                 return triggerHanders( events, args ) &&
685                         triggerHanders( allEvents, arguments );
686             }
687         };
688     
689         /**
690          * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。
691          * 主要目的是负责模块与模块之间的合作,降低耦合度。
692          *
693          * @class Mediator
694          */
695         return $.extend({
696     
697             /**
698              * 可以通过这个接口,使任何对象具备事件功能。
699              * @method installTo
700              * @param  {Object} obj 需要具备事件行为的对象。
701              * @return {Object} 返回obj.
702              */
703             installTo: function( obj ) {
704                 return $.extend( obj, protos );
705             }
706     
707         }, protos );
708     });
709     /**
710      * @fileOverview Uploader上传类
711      */
712     define('uploader',[
713         'base',
714         'mediator'
715     ], function( Base, Mediator ) {
716     
717         var $ = Base.$;
718     
719         /**
720          * 上传入口类。
721          * @class Uploader
722          * @constructor
723          * @grammar new Uploader( opts ) => Uploader
724          * @example
725          * var uploader = WebUploader.Uploader({
726          *     swf: 'path_of_swf/Uploader.swf',
727          *
728          *     // 开起分片上传。
729          *     chunked: true
730          * });
731          */
732         function Uploader( opts ) {
733             this.options = $.extend( true, {}, Uploader.options, opts );
734             this._init( this.options );
735         }
736     
737         // default Options
738         // widgets中有相应扩展
739         Uploader.options = {};
740         Mediator.installTo( Uploader.prototype );
741     
742         // 批量添加纯命令式方法。
743         $.each({
744             upload: 'start-upload',
745             stop: 'stop-upload',
746             getFile: 'get-file',
747             getFiles: 'get-files',
748             addFile: 'add-file',
749             addFiles: 'add-file',
750             sort: 'sort-files',
751             removeFile: 'remove-file',
752             cancelFile: 'cancel-file',
753             skipFile: 'skip-file',
754             retry: 'retry',
755             isInProgress: 'is-in-progress',
756             makeThumb: 'make-thumb',
757             md5File: 'md5-file',
758             getDimension: 'get-dimension',
759             addButton: 'add-btn',
760             predictRuntimeType: 'predict-runtime-type',
761             refresh: 'refresh',
762             disable: 'disable',
763             enable: 'enable',
764             reset: 'reset'
765         }, function( fn, command ) {
766             Uploader.prototype[ fn ] = function() {
767                 return this.request( command, arguments );
768             };
769         });
770     
771         $.extend( Uploader.prototype, {
772             state: 'pending',
773     
774             _init: function( opts ) {
775                 var me = this;
776     
777                 me.request( 'init', opts, function() {
778                     me.state = 'ready';
779                     me.trigger('ready');
780                 });
781             },
782     
783             /**
784              * 获取或者设置Uploader配置项。
785              * @method option
786              * @grammar option( key ) => *
787              * @grammar option( key, val ) => self
788              * @example
789              *
790              * // 初始状态图片上传前不会压缩
791              * var uploader = new WebUploader.Uploader({
792              *     compress: null;
793              * });
794              *
795              * // 修改后图片上传前,尝试将图片压缩到1600 * 1600
796              * uploader.option( 'compress', {
797              *     width: 1600,
798              *     height: 1600
799              * });
800              */
801             option: function( key, val ) {
802                 var opts = this.options;
803     
804                 // setter
805                 if ( arguments.length > 1 ) {
806     
807                     if ( $.isPlainObject( val ) &&
808                             $.isPlainObject( opts[ key ] ) ) {
809                         $.extend( opts[ key ], val );
810                     } else {
811                         opts[ key ] = val;
812                     }
813     
814                 } else {    // getter
815                     return key ? opts[ key ] : opts;
816                 }
817             },
818     
819             /**
820              * 获取文件统计信息。返回一个包含一下信息的对象。
821              * * `successNum` 上传成功的文件数
822              * * `progressNum` 上传中的文件数
823              * * `cancelNum` 被删除的文件数
824              * * `invalidNum` 无效的文件数
825              * * `uploadFailNum` 上传失败的文件数
826              * * `queueNum` 还在队列中的文件数
827              * * `interruptNum` 被暂停的文件数
828              * @method getStats
829              * @grammar getStats() => Object
830              */
831             getStats: function() {
832                 // return this._mgr.getStats.apply( this._mgr, arguments );
833                 var stats = this.request('get-stats');
834     
835                 return stats ? {
836                     successNum: stats.numOfSuccess,
837                     progressNum: stats.numOfProgress,
838     
839                     // who care?
840                     // queueFailNum: 0,
841                     cancelNum: stats.numOfCancel,
842                     invalidNum: stats.numOfInvalid,
843                     uploadFailNum: stats.numOfUploadFailed,
844                     queueNum: stats.numOfQueue,
845                     interruptNum: stats.numofInterrupt
846                 } : {};
847             },
848     
849             // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器
850             trigger: function( type/*, args...*/ ) {
851                 var args = [].slice.call( arguments, 1 ),
852                     opts = this.options,
853                     name = 'on' + type.substring( 0, 1 ).toUpperCase() +
854                         type.substring( 1 );
855     
856                 if (
857                         // 调用通过on方法注册的handler.
858                         Mediator.trigger.apply( this, arguments ) === false ||
859     
860                         // 调用opts.onEvent
861                         $.isFunction( opts[ name ] ) &&
862                         opts[ name ].apply( this, args ) === false ||
863     
864                         // 调用this.onEvent
865                         $.isFunction( this[ name ] ) &&
866                         this[ name ].apply( this, args ) === false ||
867     
868                         // 广播所有uploader的事件。
869                         Mediator.trigger.apply( Mediator,
870                         [ this, type ].concat( args ) ) === false ) {
871     
872                     return false;
873                 }
874     
875                 return true;
876             },
877     
878             /**
879              * 销毁 webuploader 实例
880              * @method destroy
881              * @grammar destroy() => undefined
882              */
883             destroy: function() {
884                 this.request( 'destroy', arguments );
885                 this.off();
886             },
887     
888             // widgets/widget.js将补充此方法的详细文档。
889             request: Base.noop
890         });
891     
892         /**
893          * 创建Uploader实例,等同于new Uploader( opts );
894          * @method create
895          * @class Base
896          * @static
897          * @grammar Base.create( opts ) => Uploader
898          */
899         Base.create = Uploader.create = function( opts ) {
900             return new Uploader( opts );
901         };
902     
903         // 暴露Uploader,可以通过它来扩展业务逻辑。
904         Base.Uploader = Uploader;
905     
906         return Uploader;
907     });
908     /**
909      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
910      */
911     define('runtime/runtime',[
912         'base',
913         'mediator'
914     ], function( Base, Mediator ) {
915     
916         var $ = Base.$,
917             factories = {},
918     
919             // 获取对象的第一个key
920             getFirstKey = function( obj ) {
921                 for ( var key in obj ) {
922                     if ( obj.hasOwnProperty( key ) ) {
923                         return key;
924                     }
925                 }
926                 return null;
927             };
928     
929         // 接口类。
930         function Runtime( options ) {
931             this.options = $.extend({
932                 container: document.body
933             }, options );
934             this.uid = Base.guid('rt_');
935         }
936     
937         $.extend( Runtime.prototype, {
938     
939             getContainer: function() {
940                 var opts = this.options,
941                     parent, container;
942     
943                 if ( this._container ) {
944                     return this._container;
945                 }
946     
947                 parent = $( opts.container || document.body );
948                 container = $( document.createElement('div') );
949     
950                 container.attr( 'id', 'rt_' + this.uid );
951                 container.css({
952                     position: 'absolute',
953                     top: '0px',
954                     left: '0px',
955                     width: '1px',
956                     height: '1px',
957                     overflow: 'hidden'
958                 });
959     
960                 parent.append( container );
961                 parent.addClass('webuploader-container');
962                 this._container = container;
963                 this._parent = parent;
964                 return container;
965             },
966     
967             init: Base.noop,
968             exec: Base.noop,
969     
970             destroy: function() {
971                 this._container && this._container.remove();
972                 this._parent && this._parent.removeClass('webuploader-container');
973                 this.off();
974             }
975         });
976     
977         Runtime.orders = 'html5,flash';
978     
979     
980         /**
981          * 添加Runtime实现。
982          * @param {String} type    类型
983          * @param {Runtime} factory 具体Runtime实现。
984          */
985         Runtime.addRuntime = function( type, factory ) {
986             factories[ type ] = factory;
987         };
988     
989         Runtime.hasRuntime = function( type ) {
990             return !!(type ? factories[ type ] : getFirstKey( factories ));
991         };
992     
993         Runtime.create = function( opts, orders ) {
994             var type, runtime;
995     
996             orders = orders || Runtime.orders;
997             $.each( orders.split( /\s*,\s*/g ), function() {
998                 if ( factories[ this ] ) {
999                     type = this;
1000                     return false;
1001                 }
1002             });
1003     
1004             type = type || getFirstKey( factories );
1005     
1006             if ( !type ) {
1007                 throw new Error('Runtime Error');
1008             }
1009     
1010             runtime = new factories[ type ]( opts );
1011             return runtime;
1012         };
1013     
1014         Mediator.installTo( Runtime.prototype );
1015         return Runtime;
1016     });
1017     
1018     /**
1019      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
1020      */
1021     define('runtime/client',[
1022         'base',
1023         'mediator',
1024         'runtime/runtime'
1025     ], function( Base, Mediator, Runtime ) {
1026     
1027         var cache;
1028     
1029         cache = (function() {
1030             var obj = {};
1031     
1032             return {
1033                 add: function( runtime ) {
1034                     obj[ runtime.uid ] = runtime;
1035                 },
1036     
1037                 get: function( ruid, standalone ) {
1038                     var i;
1039     
1040                     if ( ruid ) {
1041                         return obj[ ruid ];
1042                     }
1043     
1044                     for ( i in obj ) {
1045                         // 有些类型不能重用,比如filepicker.
1046                         if ( standalone && obj[ i ].__standalone ) {
1047                             continue;
1048                         }
1049     
1050                         return obj[ i ];
1051                     }
1052     
1053                     return null;
1054                 },
1055     
1056                 remove: function( runtime ) {
1057                     delete obj[ runtime.uid ];
1058                 }
1059             };
1060         })();
1061     
1062         function RuntimeClient( component, standalone ) {
1063             var deferred = Base.Deferred(),
1064                 runtime;
1065     
1066             this.uid = Base.guid('client_');
1067     
1068             // 允许runtime没有初始化之前,注册一些方法在初始化后执行。
1069             this.runtimeReady = function( cb ) {
1070                 return deferred.done( cb );
1071             };
1072     
1073             this.connectRuntime = function( opts, cb ) {
1074     
1075                 // already connected.
1076                 if ( runtime ) {
1077                     throw new Error('already connected!');
1078                 }
1079     
1080                 deferred.done( cb );
1081     
1082                 if ( typeof opts === 'string' && cache.get( opts ) ) {
1083                     runtime = cache.get( opts );
1084                 }
1085     
1086                 // 像filePicker只能独立存在,不能公用。
1087                 runtime = runtime || cache.get( null, standalone );
1088     
1089                 // 需要创建
1090                 if ( !runtime ) {
1091                     runtime = Runtime.create( opts, opts.runtimeOrder );
1092                     runtime.__promise = deferred.promise();
1093                     runtime.once( 'ready', deferred.resolve );
1094                     runtime.init();
1095                     cache.add( runtime );
1096                     runtime.__client = 1;
1097                 } else {
1098                     // 来自cache
1099                     Base.$.extend( runtime.options, opts );
1100                     runtime.__promise.then( deferred.resolve );
1101                     runtime.__client++;
1102                 }
1103     
1104                 standalone && (runtime.__standalone = standalone);
1105                 return runtime;
1106             };
1107     
1108             this.getRuntime = function() {
1109                 return runtime;
1110             };
1111     
1112             this.disconnectRuntime = function() {
1113                 if ( !runtime ) {
1114                     return;
1115                 }
1116     
1117                 runtime.__client--;
1118     
1119                 if ( runtime.__client <= 0 ) {
1120                     cache.remove( runtime );
1121                     delete runtime.__promise;
1122                     runtime.destroy();
1123                 }
1124     
1125                 runtime = null;
1126             };
1127     
1128             this.exec = function() {
1129                 if ( !runtime ) {
1130                     return;
1131                 }
1132     
1133                 var args = Base.slice( arguments );
1134                 component && args.unshift( component );
1135     
1136                 return runtime.exec.apply( this, args );
1137             };
1138     
1139             this.getRuid = function() {
1140                 return runtime && runtime.uid;
1141             };
1142     
1143             this.destroy = (function( destroy ) {
1144                 return function() {
1145                     destroy && destroy.apply( this, arguments );
1146                     this.trigger('destroy');
1147                     this.off();
1148                     this.exec('destroy');
1149                     this.disconnectRuntime();
1150                 };
1151             })( this.destroy );
1152         }
1153     
1154         Mediator.installTo( RuntimeClient.prototype );
1155         return RuntimeClient;
1156     });
1157     /**
1158      * @fileOverview 错误信息
1159      */
1160     define('lib/dnd',[
1161         'base',
1162         'mediator',
1163         'runtime/client'
1164     ], function( Base, Mediator, RuntimeClent ) {
1165     
1166         var $ = Base.$;
1167     
1168         function DragAndDrop( opts ) {
1169             opts = this.options = $.extend({}, DragAndDrop.options, opts );
1170     
1171             opts.container = $( opts.container );
1172     
1173             if ( !opts.container.length ) {
1174                 return;
1175             }
1176     
1177             RuntimeClent.call( this, 'DragAndDrop' );
1178         }
1179     
1180         DragAndDrop.options = {
1181             accept: null,
1182             disableGlobalDnd: false
1183         };
1184     
1185         Base.inherits( RuntimeClent, {
1186             constructor: DragAndDrop,
1187     
1188             init: function() {
1189                 var me = this;
1190     
1191                 me.connectRuntime( me.options, function() {
1192                     me.exec('init');
1193                     me.trigger('ready');
1194                 });
1195             }
1196         });
1197     
1198         Mediator.installTo( DragAndDrop.prototype );
1199     
1200         return DragAndDrop;
1201     });
1202     /**
1203      * @fileOverview 组件基类。
1204      */
1205     define('widgets/widget',[
1206         'base',
1207         'uploader'
1208     ], function( Base, Uploader ) {
1209     
1210         var $ = Base.$,
1211             _init = Uploader.prototype._init,
1212             _destroy = Uploader.prototype.destroy,
1213             IGNORE = {},
1214             widgetClass = [];
1215     
1216         function isArrayLike( obj ) {
1217             if ( !obj ) {
1218                 return false;
1219             }
1220     
1221             var length = obj.length,
1222                 type = $.type( obj );
1223     
1224             if ( obj.nodeType === 1 && length ) {
1225                 return true;
1226             }
1227     
1228             return type === 'array' || type !== 'function' && type !== 'string' &&
1229                     (length === 0 || typeof length === 'number' && length > 0 &&
1230                     (length - 1) in obj);
1231         }
1232     
1233         function Widget( uploader ) {
1234             this.owner = uploader;
1235             this.options = uploader.options;
1236         }
1237     
1238         $.extend( Widget.prototype, {
1239     
1240             init: Base.noop,
1241     
1242             // 类Backbone的事件监听声明,监听uploader实例上的事件
1243             // widget直接无法监听事件,事件只能通过uploader来传递
1244             invoke: function( apiName, args ) {
1245     
1246                 /*
1247                     {
1248                         'make-thumb': 'makeThumb'
1249                     }
1250                  */
1251                 var map = this.responseMap;
1252     
1253                 // 如果无API响应声明则忽略
1254                 if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||
1255                         !$.isFunction( this[ map[ apiName ] ] ) ) {
1256     
1257                     return IGNORE;
1258                 }
1259     
1260                 return this[ map[ apiName ] ].apply( this, args );
1261     
1262             },
1263     
1264             /**
1265              * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。
1266              * @method request
1267              * @grammar request( command, args ) => * | Promise
1268              * @grammar request( command, args, callback ) => Promise
1269              * @for  Uploader
1270              */
1271             request: function() {
1272                 return this.owner.request.apply( this.owner, arguments );
1273             }
1274         });
1275     
1276         // 扩展Uploader.
1277         $.extend( Uploader.prototype, {
1278     
1279             /**
1280              * @property {String | Array} [disableWidgets=undefined]
1281              * @namespace options
1282              * @for Uploader
1283              * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。
1284              */
1285     
1286             // 覆写_init用来初始化widgets
1287             _init: function() {
1288                 var me = this,
1289                     widgets = me._widgets = [],
1290                     deactives = me.options.disableWidgets || '';
1291     
1292                 $.each( widgetClass, function( _, klass ) {
1293                     (!deactives || !~deactives.indexOf( klass._name )) &&
1294                         widgets.push( new klass( me ) );
1295                 });
1296     
1297                 return _init.apply( me, arguments );
1298             },
1299     
1300             request: function( apiName, args, callback ) {
1301                 var i = 0,
1302                     widgets = this._widgets,
1303                     len = widgets && widgets.length,
1304                     rlts = [],
1305                     dfds = [],
1306                     widget, rlt, promise, key;
1307     
1308                 args = isArrayLike( args ) ? args : [ args ];
1309     
1310                 for ( ; i < len; i++ ) {
1311                     widget = widgets[ i ];
1312                     rlt = widget.invoke( apiName, args );
1313     
1314                     if ( rlt !== IGNORE ) {
1315     
1316                         // Deferred对象
1317                         if ( Base.isPromise( rlt ) ) {
1318                             dfds.push( rlt );
1319                         } else {
1320                             rlts.push( rlt );
1321                         }
1322                     }
1323                 }
1324     
1325                 // 如果有callback,则用异步方式。
1326                 if ( callback || dfds.length ) {
1327                     promise = Base.when.apply( Base, dfds );
1328                     key = promise.pipe ? 'pipe' : 'then';
1329     
1330                     // 很重要不能删除。删除了会死循环。
1331                     // 保证执行顺序。让callback总是在下一个 tick 中执行。
1332                     return promise[ key ](function() {
1333                                 var deferred = Base.Deferred(),
1334                                     args = arguments;
1335     
1336                                 if ( args.length === 1 ) {
1337                                     args = args[ 0 ];
1338                                 }
1339     
1340                                 setTimeout(function() {
1341                                     deferred.resolve( args );
1342                                 }, 1 );
1343     
1344                                 return deferred.promise();
1345                             })[ callback ? key : 'done' ]( callback || Base.noop );
1346                 } else {
1347                     return rlts[ 0 ];
1348                 }
1349             },
1350     
1351             destroy: function() {
1352                 _destroy.apply( this, arguments );
1353                 this._widgets = null;
1354             }
1355         });
1356     
1357         /**
1358          * 添加组件
1359          * @grammar Uploader.register(proto);
1360          * @grammar Uploader.register(map, proto);
1361          * @param  {object} responseMap API 名称与函数实现的映射
1362          * @param  {object} proto 组件原型,构造函数通过 constructor 属性定义
1363          * @method Uploader.register
1364          * @for Uploader
1365          * @example
1366          * Uploader.register({
1367          *     'make-thumb': 'makeThumb'
1368          * }, {
1369          *     init: function( options ) {},
1370          *     makeThumb: function() {}
1371          * });
1372          *
1373          * Uploader.register({
1374          *     'make-thumb': function() {
1375          *         
1376          *     }
1377          * });
1378          */
1379         Uploader.register = Widget.register = function( responseMap, widgetProto ) {
1380             var map = { init: 'init', destroy: 'destroy', name: 'anonymous' },
1381                 klass;
1382     
1383             if ( arguments.length === 1 ) {
1384                 widgetProto = responseMap;
1385     
1386                 // 自动生成 map 表。
1387                 $.each(widgetProto, function(key) {
1388                     if ( key[0] === '_' || key === 'name' ) {
1389                         key === 'name' && (map.name = widgetProto.name);
1390                         return;
1391                     }
1392     
1393                     map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key;
1394                 });
1395     
1396             } else {
1397                 map = $.extend( map, responseMap );
1398             }
1399     
1400             widgetProto.responseMap = map;
1401             klass = Base.inherits( Widget, widgetProto );
1402             klass._name = map.name;
1403             widgetClass.push( klass );
1404     
1405             return klass;
1406         };
1407     
1408         /**
1409          * 删除插件,只有在注册时指定了名字的才能被删除。
1410          * @grammar Uploader.unRegister(name);
1411          * @param  {string} name 组件名字
1412          * @method Uploader.unRegister
1413          * @for Uploader
1414          * @example
1415          *
1416          * Uploader.register({
1417          *     name: 'custom',
1418          *     
1419          *     'make-thumb': function() {
1420          *         
1421          *     }
1422          * });
1423          *
1424          * Uploader.unRegister('custom');
1425          */
1426         Uploader.unRegister = Widget.unRegister = function( name ) {
1427             if ( !name || name === 'anonymous' ) {
1428                 return;
1429             }
1430             
1431             // 删除指定的插件。
1432             for ( var i = widgetClass.length; i--; ) {
1433                 if ( widgetClass[i]._name === name ) {
1434                     widgetClass.splice(i, 1)
1435                 }
1436             }
1437         };
1438     
1439         return Widget;
1440     });
1441     /**
1442      * @fileOverview DragAndDrop Widget。
1443      */
1444     define('widgets/filednd',[
1445         'base',
1446         'uploader',
1447         'lib/dnd',
1448         'widgets/widget'
1449     ], function( Base, Uploader, Dnd ) {
1450         var $ = Base.$;
1451     
1452         Uploader.options.dnd = '';
1453     
1454         /**
1455          * @property {Selector} [dnd=undefined]  指定Drag And Drop拖拽的容器,如果不指定,则不启动。
1456          * @namespace options
1457          * @for Uploader
1458          */
1459         
1460         /**
1461          * @property {Selector} [disableGlobalDnd=false]  是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。
1462          * @namespace options
1463          * @for Uploader
1464          */
1465     
1466         /**
1467          * @event dndAccept
1468          * @param {DataTransferItemList} items DataTransferItem
1469          * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。
1470          * @for  Uploader
1471          */
1472         return Uploader.register({
1473             name: 'dnd',
1474             
1475             init: function( opts ) {
1476     
1477                 if ( !opts.dnd ||
1478                         this.request('predict-runtime-type') !== 'html5' ) {
1479                     return;
1480                 }
1481     
1482                 var me = this,
1483                     deferred = Base.Deferred(),
1484                     options = $.extend({}, {
1485                         disableGlobalDnd: opts.disableGlobalDnd,
1486                         container: opts.dnd,
1487                         accept: opts.accept
1488                     }),
1489                     dnd;
1490     
1491                 this.dnd = dnd = new Dnd( options );
1492     
1493                 dnd.once( 'ready', deferred.resolve );
1494                 dnd.on( 'drop', function( files ) {
1495                     me.request( 'add-file', [ files ]);
1496                 });
1497     
1498                 // 检测文件是否全部允许添加。
1499                 dnd.on( 'accept', function( items ) {
1500                     return me.owner.trigger( 'dndAccept', items );
1501                 });
1502     
1503                 dnd.init();
1504     
1505                 return deferred.promise();
1506             },
1507     
1508             destroy: function() {
1509                 this.dnd && this.dnd.destroy();
1510             }
1511         });
1512     });
1513     
1514     /**
1515      * @fileOverview 错误信息
1516      */
1517     define('lib/filepaste',[
1518         'base',
1519         'mediator',
1520         'runtime/client'
1521     ], function( Base, Mediator, RuntimeClent ) {
1522     
1523         var $ = Base.$;
1524     
1525         function FilePaste( opts ) {
1526             opts = this.options = $.extend({}, opts );
1527             opts.container = $( opts.container || document.body );
1528             RuntimeClent.call( this, 'FilePaste' );
1529         }
1530     
1531         Base.inherits( RuntimeClent, {
1532             constructor: FilePaste,
1533     
1534             init: function() {
1535                 var me = this;
1536     
1537                 me.connectRuntime( me.options, function() {
1538                     me.exec('init');
1539                     me.trigger('ready');
1540                 });
1541             }
1542         });
1543     
1544         Mediator.installTo( FilePaste.prototype );
1545     
1546         return FilePaste;
1547     });
1548     /**
1549      * @fileOverview 组件基类。
1550      */
1551     define('widgets/filepaste',[
1552         'base',
1553         'uploader',
1554         'lib/filepaste',
1555         'widgets/widget'
1556     ], function( Base, Uploader, FilePaste ) {
1557         var $ = Base.$;
1558     
1559         /**
1560          * @property {Selector} [paste=undefined]  指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.
1561          * @namespace options
1562          * @for Uploader
1563          */
1564         return Uploader.register({
1565             name: 'paste',
1566             
1567             init: function( opts ) {
1568     
1569                 if ( !opts.paste ||
1570                         this.request('predict-runtime-type') !== 'html5' ) {
1571                     return;
1572                 }
1573     
1574                 var me = this,
1575                     deferred = Base.Deferred(),
1576                     options = $.extend({}, {
1577                         container: opts.paste,
1578                         accept: opts.accept
1579                     }),
1580                     paste;
1581     
1582                 this.paste = paste = new FilePaste( options );
1583     
1584                 paste.once( 'ready', deferred.resolve );
1585                 paste.on( 'paste', function( files ) {
1586                     me.owner.request( 'add-file', [ files ]);
1587                 });
1588                 paste.init();
1589     
1590                 return deferred.promise();
1591             },
1592     
1593             destroy: function() {
1594                 this.paste && this.paste.destroy();
1595             }
1596         });
1597     });
1598     /**
1599      * @fileOverview Blob
1600      */
1601     define('lib/blob',[
1602         'base',
1603         'runtime/client'
1604     ], function( Base, RuntimeClient ) {
1605     
1606         function Blob( ruid, source ) {
1607             var me = this;
1608     
1609             me.source = source;
1610             me.ruid = ruid;
1611             this.size = source.size || 0;
1612     
1613             // 如果没有指定 mimetype, 但是知道文件后缀。
1614             if ( !source.type && this.ext &&
1615                     ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) {
1616                 this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext);
1617             } else {
1618                 this.type = source.type || 'application/octet-stream';
1619             }
1620     
1621             RuntimeClient.call( me, 'Blob' );
1622             this.uid = source.uid || this.uid;
1623     
1624             if ( ruid ) {
1625                 me.connectRuntime( ruid );
1626             }
1627         }
1628     
1629         Base.inherits( RuntimeClient, {
1630             constructor: Blob,
1631     
1632             slice: function( start, end ) {
1633                 return this.exec( 'slice', start, end );
1634             },
1635     
1636             getSource: function() {
1637                 return this.source;
1638             }
1639         });
1640     
1641         return Blob;
1642     });
1643     /**
1644      * 为了统一化Flash的File和HTML5的File而存在。
1645      * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。
1646      * @fileOverview File
1647      */
1648     define('lib/file',[
1649         'base',
1650         'lib/blob'
1651     ], function( Base, Blob ) {
1652     
1653         var uid = 1,
1654             rExt = /\.([^.]+)$/;
1655     
1656         function File( ruid, file ) {
1657             var ext;
1658     
1659             this.name = file.name || ('untitled' + uid++);
1660             ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';
1661     
1662             // todo 支持其他类型文件的转换。
1663             // 如果有 mimetype, 但是文件名里面没有找出后缀规律
1664             if ( !ext && file.type ) {
1665                 ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ?
1666                         RegExp.$1.toLowerCase() : '';
1667                 this.name += '.' + ext;
1668             }
1669     
1670             this.ext = ext;
1671             this.lastModifiedDate = file.lastModifiedDate ||
1672                     (new Date()).toLocaleString();
1673     
1674             Blob.apply( this, arguments );
1675         }
1676     
1677         return Base.inherits( Blob, File );
1678     });
1679     
1680     /**
1681      * @fileOverview 错误信息
1682      */
1683     define('lib/filepicker',[
1684         'base',
1685         'runtime/client',
1686         'lib/file'
1687     ], function( Base, RuntimeClent, File ) {
1688     
1689         var $ = Base.$;
1690     
1691         function FilePicker( opts ) {
1692             opts = this.options = $.extend({}, FilePicker.options, opts );
1693     
1694             opts.container = $( opts.id );
1695     
1696             if ( !opts.container.length ) {
1697                 throw new Error('按钮指定错误');
1698             }
1699     
1700             opts.innerHTML = opts.innerHTML || opts.label ||
1701                     opts.container.html() || '';
1702     
1703             opts.button = $( opts.button || document.createElement('div') );
1704             opts.button.html( opts.innerHTML );
1705             opts.container.html( opts.button );
1706     
1707             RuntimeClent.call( this, 'FilePicker', true );
1708         }
1709     
1710         FilePicker.options = {
1711             button: null,
1712             container: null,
1713             label: null,
1714             innerHTML: null,
1715             multiple: true,
1716             accept: null,
1717             name: 'file'
1718         };
1719     
1720         Base.inherits( RuntimeClent, {
1721             constructor: FilePicker,
1722     
1723             init: function() {
1724                 var me = this,
1725                     opts = me.options,
1726                     button = opts.button;
1727     
1728                 button.addClass('webuploader-pick');
1729     
1730                 me.on( 'all', function( type ) {
1731                     var files;
1732     
1733                     switch ( type ) {
1734                         case 'mouseenter':
1735                             button.addClass('webuploader-pick-hover');
1736                             break;
1737     
1738                         case 'mouseleave':
1739                             button.removeClass('webuploader-pick-hover');
1740                             break;
1741     
1742                         case 'change':
1743                             files = me.exec('getFiles');
1744                             me.trigger( 'select', $.map( files, function( file ) {
1745                                 file = new File( me.getRuid(), file );
1746     
1747                                 // 记录来源。
1748                                 file._refer = opts.container;
1749                                 return file;
1750                             }), opts.container );
1751                             break;
1752                     }
1753                 });
1754     
1755                 me.connectRuntime( opts, function() {
1756                     me.refresh();
1757                     me.exec( 'init', opts );
1758                     me.trigger('ready');
1759                 });
1760     
1761                 this._resizeHandler = Base.bindFn( this.refresh, this );
1762                 $( window ).on( 'resize', this._resizeHandler );
1763             },
1764     
1765             refresh: function() {
1766                 var shimContainer = this.getRuntime().getContainer(),
1767                     button = this.options.button,
1768                     width = button.outerWidth ?
1769                             button.outerWidth() : button.width(),
1770     
1771                     height = button.outerHeight ?
1772                             button.outerHeight() : button.height(),
1773     
1774                     pos = button.offset();
1775     
1776                 width && height && shimContainer.css({
1777                     bottom: 'auto',
1778                     right: 'auto',
1779                     width: width + 'px',
1780                     height: height + 'px'
1781                 }).offset( pos );
1782             },
1783     
1784             enable: function() {
1785                 var btn = this.options.button;
1786     
1787                 btn.removeClass('webuploader-pick-disable');
1788                 this.refresh();
1789             },
1790     
1791             disable: function() {
1792                 var btn = this.options.button;
1793     
1794                 this.getRuntime().getContainer().css({
1795                     top: '-99999px'
1796                 });
1797     
1798                 btn.addClass('webuploader-pick-disable');
1799             },
1800     
1801             destroy: function() {
1802                 var btn = this.options.button;
1803                 $( window ).off( 'resize', this._resizeHandler );
1804                 btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' +
1805                     'webuploader-pick');
1806             }
1807         });
1808     
1809         return FilePicker;
1810     });
1811     
1812     /**
1813      * @fileOverview 文件选择相关
1814      */
1815     define('widgets/filepicker',[
1816         'base',
1817         'uploader',
1818         'lib/filepicker',
1819         'widgets/widget'
1820     ], function( Base, Uploader, FilePicker ) {
1821         var $ = Base.$;
1822     
1823         $.extend( Uploader.options, {
1824     
1825             /**
1826              * @property {Selector | Object} [pick=undefined]
1827              * @namespace options
1828              * @for Uploader
1829              * @description 指定选择文件的按钮容器,不指定则不创建按钮。
1830              *
1831              * * `id` {Seletor} 指定选择文件的按钮容器,不指定则不创建按钮。
1832              * * `label` {String} 请采用 `innerHTML` 代替
1833              * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。
1834              * * `multiple` {Boolean} 是否开起同时选择多个文件能力。
1835              */
1836             pick: null,
1837     
1838             /**
1839              * @property {Arroy} [accept=null]
1840              * @namespace options
1841              * @for Uploader
1842              * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。
1843              *
1844              * * `title` {String} 文字描述
1845              * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。
1846              * * `mimeTypes` {String} 多个用逗号分割。
1847              *
1848              * 如:
1849              *
1850              * ```
1851              * {
1852              *     title: 'Images',
1853              *     extensions: 'gif,jpg,jpeg,bmp,png',
1854              *     mimeTypes: 'image/*'
1855              * }
1856              * ```
1857              */
1858             accept: null/*{
1859                 title: 'Images',
1860                 extensions: 'gif,jpg,jpeg,bmp,png',
1861                 mimeTypes: 'image/*'
1862             }*/
1863         });
1864     
1865         return Uploader.register({
1866             name: 'picker',
1867     
1868             init: function( opts ) {
1869                 this.pickers = [];
1870                 return opts.pick && this.addBtn( opts.pick );
1871             },
1872     
1873             refresh: function() {
1874                 $.each( this.pickers, function() {
1875                     this.refresh();
1876                 });
1877             },
1878     
1879             /**
1880              * @method addButton
1881              * @for Uploader
1882              * @grammar addButton( pick ) => Promise
1883              * @description
1884              * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。
1885              * @example
1886              * uploader.addButton({
1887              *     id: '#btnContainer',
1888              *     innerHTML: '选择文件'
1889              * });
1890              */
1891             addBtn: function( pick ) {
1892                 var me = this,
1893                     opts = me.options,
1894                     accept = opts.accept,
1895                     promises = [];
1896     
1897                 if ( !pick ) {
1898                     return;
1899                 }
1900                 
1901                 $.isPlainObject( pick ) || (pick = {
1902                     id: pick
1903                 });
1904     
1905                 $( pick.id ).each(function() {
1906                     var options, picker, deferred;
1907     
1908                     deferred = Base.Deferred();
1909     
1910                     options = $.extend({}, pick, {
1911                         accept: $.isPlainObject( accept ) ? [ accept ] : accept,
1912                         swf: opts.swf,
1913                         runtimeOrder: opts.runtimeOrder,
1914                         id: this
1915                     });
1916     
1917                     picker = new FilePicker( options );
1918     
1919                     picker.once( 'ready', deferred.resolve );
1920                     picker.on( 'select', function( files ) {
1921                         me.owner.request( 'add-file', [ files ]);
1922                     });
1923                     picker.init();
1924     
1925                     me.pickers.push( picker );
1926     
1927                     promises.push( deferred.promise() );
1928                 });
1929     
1930                 return Base.when.apply( Base, promises );
1931             },
1932     
1933             disable: function() {
1934                 $.each( this.pickers, function() {
1935                     this.disable();
1936                 });
1937             },
1938     
1939             enable: function() {
1940                 $.each( this.pickers, function() {
1941                     this.enable();
1942                 });
1943             },
1944     
1945             destroy: function() {
1946                 $.each( this.pickers, function() {
1947                     this.destroy();
1948                 });
1949                 this.pickers = null;
1950             }
1951         });
1952     });
1953     /**
1954      * @fileOverview 文件属性封装
1955      */
1956     define('file',[
1957         'base',
1958         'mediator'
1959     ], function( Base, Mediator ) {
1960     
1961         var $ = Base.$,
1962             idPrefix = 'WU_FILE_',
1963             idSuffix = 0,
1964             rExt = /\.([^.]+)$/,
1965             statusMap = {};
1966     
1967         function gid() {
1968             return idPrefix + idSuffix++;
1969         }
1970     
1971         /**
1972          * 文件类
1973          * @class File
1974          * @constructor 构造函数
1975          * @grammar new File( source ) => File
1976          * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。
1977          */
1978         function WUFile( source ) {
1979     
1980             /**
1981              * 文件名,包括扩展名(后缀)
1982              * @property name
1983              * @type {string}
1984              */
1985             this.name = source.name || 'Untitled';
1986     
1987             /**
1988              * 文件体积(字节)
1989              * @property size
1990              * @type {uint}
1991              * @default 0
1992              */
1993             this.size = source.size || 0;
1994     
1995             /**
1996              * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)
1997              * @property type
1998              * @type {string}
1999              * @default 'application/octet-stream'
2000              */
2001             this.type = source.type || 'application/octet-stream';
2002     
2003             /**
2004              * 文件最后修改日期
2005              * @property lastModifiedDate
2006              * @type {int}
2007              * @default 当前时间戳
2008              */
2009             this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);
2010     
2011             /**
2012              * 文件ID,每个对象具有唯一ID,与文件名无关
2013              * @property id
2014              * @type {string}
2015              */
2016             this.id = gid();
2017     
2018             /**
2019              * 文件扩展名,通过文件名获取,例如test.png的扩展名为png
2020              * @property ext
2021              * @type {string}
2022              */
2023             this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';
2024     
2025     
2026             /**
2027              * 状态文字说明。在不同的status语境下有不同的用途。
2028              * @property statusText
2029              * @type {string}
2030              */
2031             this.statusText = '';
2032     
2033             // 存储文件状态,防止通过属性直接修改
2034             statusMap[ this.id ] = WUFile.Status.INITED;
2035     
2036             this.source = source;
2037             this.loaded = 0;
2038     
2039             this.on( 'error', function( msg ) {
2040                 this.setStatus( WUFile.Status.ERROR, msg );
2041             });
2042         }
2043     
2044         $.extend( WUFile.prototype, {
2045     
2046             /**
2047              * 设置状态,状态变化时会触发`change`事件。
2048              * @method setStatus
2049              * @grammar setStatus( status[, statusText] );
2050              * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)
2051              * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。
2052              */
2053             setStatus: function( status, text ) {
2054     
2055                 var prevStatus = statusMap[ this.id ];
2056     
2057                 typeof text !== 'undefined' && (this.statusText = text);
2058     
2059                 if ( status !== prevStatus ) {
2060                     statusMap[ this.id ] = status;
2061                     /**
2062                      * 文件状态变化
2063                      * @event statuschange
2064                      */
2065                     this.trigger( 'statuschange', status, prevStatus );
2066                 }
2067     
2068             },
2069     
2070             /**
2071              * 获取文件状态
2072              * @return {File.Status}
2073              * @example
2074                      文件状态具体包括以下几种类型:
2075                      {
2076                          // 初始化
2077                         INITED:     0,
2078                         // 已入队列
2079                         QUEUED:     1,
2080                         // 正在上传
2081                         PROGRESS:     2,
2082                         // 上传出错
2083                         ERROR:         3,
2084                         // 上传成功
2085                         COMPLETE:     4,
2086                         // 上传取消
2087                         CANCELLED:     5
2088                     }
2089              */
2090             getStatus: function() {
2091                 return statusMap[ this.id ];
2092             },
2093     
2094             /**
2095              * 获取文件原始信息。
2096              * @return {*}
2097              */
2098             getSource: function() {
2099                 return this.source;
2100             },
2101     
2102             destroy: function() {
2103                 this.off();
2104                 delete statusMap[ this.id ];
2105             }
2106         });
2107     
2108         Mediator.installTo( WUFile.prototype );
2109     
2110         /**
2111          * 文件状态值,具体包括以下几种类型:
2112          * * `inited` 初始状态
2113          * * `queued` 已经进入队列, 等待上传
2114          * * `progress` 上传中
2115          * * `complete` 上传完成。
2116          * * `error` 上传出错,可重试
2117          * * `interrupt` 上传中断,可续传。
2118          * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。
2119          * * `cancelled` 文件被移除。
2120          * @property {Object} Status
2121          * @namespace File
2122          * @class File
2123          * @static
2124          */
2125         WUFile.Status = {
2126             INITED:     'inited',    // 初始状态
2127             QUEUED:     'queued',    // 已经进入队列, 等待上传
2128             PROGRESS:   'progress',    // 上传中
2129             ERROR:      'error',    // 上传出错,可重试
2130             COMPLETE:   'complete',    // 上传完成。
2131             CANCELLED:  'cancelled',    // 上传取消。
2132             INTERRUPT:  'interrupt',    // 上传中断,可续传。
2133             INVALID:    'invalid'    // 文件不合格,不能重试上传。
2134         };
2135     
2136         return WUFile;
2137     });
2138     
2139     /**
2140      * @fileOverview 文件队列
2141      */
2142     define('queue',[
2143         'base',
2144         'mediator',
2145         'file'
2146     ], function( Base, Mediator, WUFile ) {
2147     
2148         var $ = Base.$,
2149             STATUS = WUFile.Status;
2150     
2151         /**
2152          * 文件队列, 用来存储各个状态中的文件。
2153          * @class Queue
2154          * @extends Mediator
2155          */
2156         function Queue() {
2157     
2158             /**
2159              * 统计文件数。
2160              * * `numOfQueue` 队列中的文件数。
2161              * * `numOfSuccess` 上传成功的文件数
2162              * * `numOfCancel` 被取消的文件数
2163              * * `numOfProgress` 正在上传中的文件数
2164              * * `numOfUploadFailed` 上传错误的文件数。
2165              * * `numOfInvalid` 无效的文件数。
2166              * * `numofDeleted` 被移除的文件数。
2167              * @property {Object} stats
2168              */
2169             this.stats = {
2170                 numOfQueue: 0,
2171                 numOfSuccess: 0,
2172                 numOfCancel: 0,
2173                 numOfProgress: 0,
2174                 numOfUploadFailed: 0,
2175                 numOfInvalid: 0,
2176                 numofDeleted: 0,
2177                 numofInterrupt: 0,
2178             };
2179     
2180             // 上传队列,仅包括等待上传的文件
2181             this._queue = [];
2182     
2183             // 存储所有文件
2184             this._map = {};
2185         }
2186     
2187         $.extend( Queue.prototype, {
2188     
2189             /**
2190              * 将新文件加入对队列尾部
2191              *
2192              * @method append
2193              * @param  {File} file   文件对象
2194              */
2195             append: function( file ) {
2196                 this._queue.push( file );
2197                 this._fileAdded( file );
2198                 return this;
2199             },
2200     
2201             /**
2202              * 将新文件加入对队列头部
2203              *
2204              * @method prepend
2205              * @param  {File} file   文件对象
2206              */
2207             prepend: function( file ) {
2208                 this._queue.unshift( file );
2209                 this._fileAdded( file );
2210                 return this;
2211             },
2212     
2213             /**
2214              * 获取文件对象
2215              *
2216              * @method getFile
2217              * @param  {String} fileId   文件ID
2218              * @return {File}
2219              */
2220             getFile: function( fileId ) {
2221                 if ( typeof fileId !== 'string' ) {
2222                     return fileId;
2223                 }
2224                 return this._map[ fileId ];
2225             },
2226     
2227             /**
2228              * 从队列中取出一个指定状态的文件。
2229              * @grammar fetch( status ) => File
2230              * @method fetch
2231              * @param {String} status [文件状态值](#WebUploader:File:File.Status)
2232              * @return {File} [File](#WebUploader:File)
2233              */
2234             fetch: function( status ) {
2235                 var len = this._queue.length,
2236                     i, file;
2237     
2238                 status = status || STATUS.QUEUED;
2239     
2240                 for ( i = 0; i < len; i++ ) {
2241                     file = this._queue[ i ];
2242     
2243                     if ( status === file.getStatus() ) {
2244                         return file;
2245                     }
2246                 }
2247     
2248                 return null;
2249             },
2250     
2251             /**
2252              * 对队列进行排序,能够控制文件上传顺序。
2253              * @grammar sort( fn ) => undefined
2254              * @method sort
2255              * @param {Function} fn 排序方法
2256              */
2257             sort: function( fn ) {
2258                 if ( typeof fn === 'function' ) {
2259                     this._queue.sort( fn );
2260                 }
2261             },
2262     
2263             /**
2264              * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。
2265              * @grammar getFiles( [status1[, status2 ...]] ) => Array
2266              * @method getFiles
2267              * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)
2268              */
2269             getFiles: function() {
2270                 var sts = [].slice.call( arguments, 0 ),
2271                     ret = [],
2272                     i = 0,
2273                     len = this._queue.length,
2274                     file;
2275     
2276                 for ( ; i < len; i++ ) {
2277                     file = this._queue[ i ];
2278     
2279                     if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {
2280                         continue;
2281                     }
2282     
2283                     ret.push( file );
2284                 }
2285     
2286                 return ret;
2287             },
2288     
2289             /**
2290              * 在队列中删除文件。
2291              * @grammar removeFile( file ) => Array
2292              * @method removeFile
2293              * @param {File} 文件对象。
2294              */
2295             removeFile: function( file ) {
2296                 var me = this,
2297                     existing = this._map[ file.id ];
2298     
2299                 if ( existing ) {
2300                     delete this._map[ file.id ];
2301                     file.destroy();
2302                     this.stats.numofDeleted++;
2303                 }
2304             },
2305     
2306             _fileAdded: function( file ) {
2307                 var me = this,
2308                     existing = this._map[ file.id ];
2309     
2310                 if ( !existing ) {
2311                     this._map[ file.id ] = file;
2312     
2313                     file.on( 'statuschange', function( cur, pre ) {
2314                         me._onFileStatusChange( cur, pre );
2315                     });
2316                 }
2317             },
2318     
2319             _onFileStatusChange: function( curStatus, preStatus ) {
2320                 var stats = this.stats;
2321     
2322                 switch ( preStatus ) {
2323                     case STATUS.PROGRESS:
2324                         stats.numOfProgress--;
2325                         break;
2326     
2327                     case STATUS.QUEUED:
2328                         stats.numOfQueue --;
2329                         break;
2330     
2331                     case STATUS.ERROR:
2332                         stats.numOfUploadFailed--;
2333                         break;
2334     
2335                     case STATUS.INVALID:
2336                         stats.numOfInvalid--;
2337                         break;
2338     
2339                     case STATUS.INTERRUPT:
2340                         stats.numofInterrupt--;
2341                         break;
2342                 }
2343     
2344                 switch ( curStatus ) {
2345                     case STATUS.QUEUED:
2346                         stats.numOfQueue++;
2347                         break;
2348     
2349                     case STATUS.PROGRESS:
2350                         stats.numOfProgress++;
2351                         break;
2352     
2353                     case STATUS.ERROR:
2354                         stats.numOfUploadFailed++;
2355                         break;
2356     
2357                     case STATUS.COMPLETE:
2358                         stats.numOfSuccess++;
2359                         break;
2360     
2361                     case STATUS.CANCELLED:
2362                         stats.numOfCancel++;
2363                         break;
2364     
2365     
2366                     case STATUS.INVALID:
2367                         stats.numOfInvalid++;
2368                         break;
2369     
2370                     case STATUS.INTERRUPT:
2371                         stats.numofInterrupt++;
2372                         break;
2373                 }
2374             }
2375     
2376         });
2377     
2378         Mediator.installTo( Queue.prototype );
2379     
2380         return Queue;
2381     });
2382     /**
2383      * @fileOverview 队列
2384      */
2385     define('widgets/queue',[
2386         'base',
2387         'uploader',
2388         'queue',
2389         'file',
2390         'lib/file',
2391         'runtime/client',
2392         'widgets/widget'
2393     ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {
2394     
2395         var $ = Base.$,
2396             rExt = /\.\w+$/,
2397             Status = WUFile.Status;
2398     
2399         return Uploader.register({
2400             name: 'queue',
2401     
2402             init: function( opts ) {
2403                 var me = this,
2404                     deferred, len, i, item, arr, accept, runtime;
2405     
2406                 if ( $.isPlainObject( opts.accept ) ) {
2407                     opts.accept = [ opts.accept ];
2408                 }
2409     
2410                 // accept中的中生成匹配正则。
2411                 if ( opts.accept ) {
2412                     arr = [];
2413     
2414                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
2415                         item = opts.accept[ i ].extensions;
2416                         item && arr.push( item );
2417                     }
2418     
2419                     if ( arr.length ) {
2420                         accept = '\\.' + arr.join(',')
2421                                 .replace( /,/g, '$|\\.' )
2422                                 .replace( /\*/g, '.*' ) + '$';
2423                     }
2424     
2425                     me.accept = new RegExp( accept, 'i' );
2426                 }
2427     
2428                 me.queue = new Queue();
2429                 me.stats = me.queue.stats;
2430     
2431                 // 如果当前不是html5运行时,那就算了。
2432                 // 不执行后续操作
2433                 if ( this.request('predict-runtime-type') !== 'html5' ) {
2434                     return;
2435                 }
2436     
2437                 // 创建一个 html5 运行时的 placeholder
2438                 // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。
2439                 deferred = Base.Deferred();
2440                 this.placeholder = runtime = new RuntimeClient('Placeholder');
2441                 runtime.connectRuntime({
2442                     runtimeOrder: 'html5'
2443                 }, function() {
2444                     me._ruid = runtime.getRuid();
2445                     deferred.resolve();
2446                 });
2447                 return deferred.promise();
2448             },
2449     
2450     
2451             // 为了支持外部直接添加一个原生File对象。
2452             _wrapFile: function( file ) {
2453                 if ( !(file instanceof WUFile) ) {
2454     
2455                     if ( !(file instanceof File) ) {
2456                         if ( !this._ruid ) {
2457                             throw new Error('Can\'t add external files.');
2458                         }
2459                         file = new File( this._ruid, file );
2460                     }
2461     
2462                     file = new WUFile( file );
2463                 }
2464     
2465                 return file;
2466             },
2467     
2468             // 判断文件是否可以被加入队列
2469             acceptFile: function( file ) {
2470                 var invalid = !file || !file.size || this.accept &&
2471     
2472                         // 如果名字中有后缀,才做后缀白名单处理。
2473                         rExt.exec( file.name ) && !this.accept.test( file.name );
2474     
2475                 return !invalid;
2476             },
2477     
2478     
2479             /**
2480              * @event beforeFileQueued
2481              * @param {File} file File对象
2482              * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。
2483              * @for  Uploader
2484              */
2485     
2486             /**
2487              * @event fileQueued
2488              * @param {File} file File对象
2489              * @description 当文件被加入队列以后触发。
2490              * @for  Uploader
2491              */
2492     
2493             _addFile: function( file ) {
2494                 var me = this;
2495     
2496                 file = me._wrapFile( file );
2497     
2498                 // 不过类型判断允许不允许,先派送 `beforeFileQueued`
2499                 if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {
2500                     return;
2501                 }
2502     
2503                 // 类型不匹配,则派送错误事件,并返回。
2504                 if ( !me.acceptFile( file ) ) {
2505                     me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );
2506                     return;
2507                 }
2508     
2509                 me.queue.append( file );
2510                 me.owner.trigger( 'fileQueued', file );
2511                 return file;
2512             },
2513     
2514             getFile: function( fileId ) {
2515                 return this.queue.getFile( fileId );
2516             },
2517     
2518             /**
2519              * @event filesQueued
2520              * @param {File} files 数组,内容为原始File(lib/File)对象。
2521              * @description 当一批文件添加进队列以后触发。
2522              * @for  Uploader
2523              */
2524             
2525             /**
2526              * @property {Boolean} [auto=false]
2527              * @namespace options
2528              * @for Uploader
2529              * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。
2530              * 
2531              */
2532     
2533             /**
2534              * @method addFiles
2535              * @grammar addFiles( file ) => undefined
2536              * @grammar addFiles( [file1, file2 ...] ) => undefined
2537              * @param {Array of File or File} [files] Files 对象 数组
2538              * @description 添加文件到队列
2539              * @for  Uploader
2540              */
2541             addFile: function( files ) {
2542                 var me = this;
2543     
2544                 if ( !files.length ) {
2545                     files = [ files ];
2546                 }
2547     
2548                 files = $.map( files, function( file ) {
2549                     return me._addFile( file );
2550                 });
2551     
2552                 me.owner.trigger( 'filesQueued', files );
2553     
2554                 if ( me.options.auto ) {
2555                     setTimeout(function() {
2556                         me.request('start-upload');
2557                     }, 20 );
2558                 }
2559             },
2560     
2561             getStats: function() {
2562                 return this.stats;
2563             },
2564     
2565             /**
2566              * @event fileDequeued
2567              * @param {File} file File对象
2568              * @description 当文件被移除队列后触发。
2569              * @for  Uploader
2570              */
2571     
2572              /**
2573              * @method removeFile
2574              * @grammar removeFile( file ) => undefined
2575              * @grammar removeFile( id ) => undefined
2576              * @grammar removeFile( file, true ) => undefined
2577              * @grammar removeFile( id, true ) => undefined
2578              * @param {File|id} file File对象或这File对象的id
2579              * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。
2580              * @for  Uploader
2581              * @example
2582              *
2583              * $li.on('click', '.remove-this', function() {
2584              *     uploader.removeFile( file );
2585              * })
2586              */
2587             removeFile: function( file, remove ) {
2588                 var me = this;
2589     
2590                 file = file.id ? file : me.queue.getFile( file );
2591     
2592                 this.request( 'cancel-file', file );
2593     
2594                 if ( remove ) {
2595                     this.queue.removeFile( file );
2596                 }
2597             },
2598     
2599             /**
2600              * @method getFiles
2601              * @grammar getFiles() => Array
2602              * @grammar getFiles( status1, status2, status... ) => Array
2603              * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。
2604              * @for  Uploader
2605              * @example
2606              * console.log( uploader.getFiles() );    // => all files
2607              * console.log( uploader.getFiles('error') )    // => all error files.
2608              */
2609             getFiles: function() {
2610                 return this.queue.getFiles.apply( this.queue, arguments );
2611             },
2612     
2613             fetchFile: function() {
2614                 return this.queue.fetch.apply( this.queue, arguments );
2615             },
2616     
2617             /**
2618              * @method retry
2619              * @grammar retry() => undefined
2620              * @grammar retry( file ) => undefined
2621              * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。
2622              * @for  Uploader
2623              * @example
2624              * function retry() {
2625              *     uploader.retry();
2626              * }
2627              */
2628             retry: function( file, noForceStart ) {
2629                 var me = this,
2630                     files, i, len;
2631     
2632                 if ( file ) {
2633                     file = file.id ? file : me.queue.getFile( file );
2634                     file.setStatus( Status.QUEUED );
2635                     noForceStart || me.request('start-upload');
2636                     return;
2637                 }
2638     
2639                 files = me.queue.getFiles( Status.ERROR );
2640                 i = 0;
2641                 len = files.length;
2642     
2643                 for ( ; i < len; i++ ) {
2644                     file = files[ i ];
2645                     file.setStatus( Status.QUEUED );
2646                 }
2647     
2648                 me.request('start-upload');
2649             },
2650     
2651             /**
2652              * @method sort
2653              * @grammar sort( fn ) => undefined
2654              * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。
2655              * @for  Uploader
2656              */
2657             sortFiles: function() {
2658                 return this.queue.sort.apply( this.queue, arguments );
2659             },
2660     
2661             /**
2662              * @event reset
2663              * @description 当 uploader 被重置的时候触发。
2664              * @for  Uploader
2665              */
2666     
2667             /**
2668              * @method reset
2669              * @grammar reset() => undefined
2670              * @description 重置uploader。目前只重置了队列。
2671              * @for  Uploader
2672              * @example
2673              * uploader.reset();
2674              */
2675             reset: function() {
2676                 this.owner.trigger('reset');
2677                 this.queue = new Queue();
2678                 this.stats = this.queue.stats;
2679             },
2680     
2681             destroy: function() {
2682                 this.reset();
2683                 this.placeholder && this.placeholder.destroy();
2684             }
2685         });
2686     
2687     });
2688     /**
2689      * @fileOverview 添加获取Runtime相关信息的方法。
2690      */
2691     define('widgets/runtime',[
2692         'uploader',
2693         'runtime/runtime',
2694         'widgets/widget'
2695     ], function( Uploader, Runtime ) {
2696     
2697         Uploader.support = function() {
2698             return Runtime.hasRuntime.apply( Runtime, arguments );
2699         };
2700     
2701         return Uploader.register({
2702             name: 'runtime',
2703     
2704             init: function() {
2705                 if ( !this.predictRuntimeType() ) {
2706                     throw Error('Runtime Error');
2707                 }
2708             },
2709     
2710             /**
2711              * 预测Uploader将采用哪个`Runtime`
2712              * @grammar predictRuntimeType() => String
2713              * @method predictRuntimeType
2714              * @for  Uploader
2715              */
2716             predictRuntimeType: function() {
2717                 var orders = this.options.runtimeOrder || Runtime.orders,
2718                     type = this.type,
2719                     i, len;
2720     
2721                 if ( !type ) {
2722                     orders = orders.split( /\s*,\s*/g );
2723     
2724                     for ( i = 0, len = orders.length; i < len; i++ ) {
2725                         if ( Runtime.hasRuntime( orders[ i ] ) ) {
2726                             this.type = type = orders[ i ];
2727                             break;
2728                         }
2729                     }
2730                 }
2731     
2732                 return type;
2733             }
2734         });
2735     });
2736     /**
2737      * @fileOverview Transport
2738      */
2739     define('lib/transport',[
2740         'base',
2741         'runtime/client',
2742         'mediator'
2743     ], function( Base, RuntimeClient, Mediator ) {
2744     
2745         var $ = Base.$;
2746     
2747         function Transport( opts ) {
2748             var me = this;
2749     
2750             opts = me.options = $.extend( true, {}, Transport.options, opts || {} );
2751             RuntimeClient.call( this, 'Transport' );
2752     
2753             this._blob = null;
2754             this._formData = opts.formData || {};
2755             this._headers = opts.headers || {};
2756     
2757             this.on( 'progress', this._timeout );
2758             this.on( 'load error', function() {
2759                 me.trigger( 'progress', 1 );
2760                 clearTimeout( me._timer );
2761             });
2762         }
2763     
2764         Transport.options = {
2765             server: '',
2766             method: 'POST',
2767     
2768             // 跨域时,是否允许携带cookie, 只有html5 runtime才有效
2769             withCredentials: false,
2770             fileVal: 'file',
2771             timeout: 2 * 60 * 1000,    // 2分钟
2772             formData: {},
2773             headers: {},
2774             sendAsBinary: false
2775         };
2776     
2777         $.extend( Transport.prototype, {
2778     
2779             // 添加Blob, 只能添加一次,最后一次有效。
2780             appendBlob: function( key, blob, filename ) {
2781                 var me = this,
2782                     opts = me.options;
2783     
2784                 if ( me.getRuid() ) {
2785                     me.disconnectRuntime();
2786                 }
2787     
2788                 // 连接到blob归属的同一个runtime.
2789                 me.connectRuntime( blob.ruid, function() {
2790                     me.exec('init');
2791                 });
2792     
2793                 me._blob = blob;
2794                 opts.fileVal = key || opts.fileVal;
2795                 opts.filename = filename || opts.filename;
2796             },
2797     
2798             // 添加其他字段
2799             append: function( key, value ) {
2800                 if ( typeof key === 'object' ) {
2801                     $.extend( this._formData, key );
2802                 } else {
2803                     this._formData[ key ] = value;
2804                 }
2805             },
2806     
2807             setRequestHeader: function( key, value ) {
2808                 if ( typeof key === 'object' ) {
2809                     $.extend( this._headers, key );
2810                 } else {
2811                     this._headers[ key ] = value;
2812                 }
2813             },
2814     
2815             send: function( method ) {
2816                 this.exec( 'send', method );
2817                 this._timeout();
2818             },
2819     
2820             abort: function() {
2821                 clearTimeout( this._timer );
2822                 return this.exec('abort');
2823             },
2824     
2825             destroy: function() {
2826                 this.trigger('destroy');
2827                 this.off();
2828                 this.exec('destroy');
2829                 this.disconnectRuntime();
2830             },
2831     
2832             getResponse: function() {
2833                 return this.exec('getResponse');
2834             },
2835     
2836             getResponseAsJson: function() {
2837                 return this.exec('getResponseAsJson');
2838             },
2839     
2840             getStatus: function() {
2841                 return this.exec('getStatus');
2842             },
2843     
2844             _timeout: function() {
2845                 var me = this,
2846                     duration = me.options.timeout;
2847     
2848                 if ( !duration ) {
2849                     return;
2850                 }
2851     
2852                 clearTimeout( me._timer );
2853                 me._timer = setTimeout(function() {
2854                     me.abort();
2855                     me.trigger( 'error', 'timeout' );
2856                 }, duration );
2857             }
2858     
2859         });
2860     
2861         // 让Transport具备事件功能。
2862         Mediator.installTo( Transport.prototype );
2863     
2864         return Transport;
2865     });
2866     /**
2867      * @fileOverview 负责文件上传相关。
2868      */
2869     define('widgets/upload',[
2870         'base',
2871         'uploader',
2872         'file',
2873         'lib/transport',
2874         'widgets/widget'
2875     ], function( Base, Uploader, WUFile, Transport ) {
2876     
2877         var $ = Base.$,
2878             isPromise = Base.isPromise,
2879             Status = WUFile.Status;
2880     
2881         // 添加默认配置项
2882         $.extend( Uploader.options, {
2883     
2884     
2885             /**
2886              * @property {Boolean} [prepareNextFile=false]
2887              * @namespace options
2888              * @for Uploader
2889              * @description 是否允许在文件传输时提前把下一个文件准备好。
2890              * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。
2891              * 如果能提前在当前文件传输期处理,可以节省总体耗时。
2892              */
2893             prepareNextFile: false,
2894     
2895             /**
2896              * @property {Boolean} [chunked=false]
2897              * @namespace options
2898              * @for Uploader
2899              * @description 是否要分片处理大文件上传。
2900              */
2901             chunked: false,
2902     
2903             /**
2904              * @property {Boolean} [chunkSize=5242880]
2905              * @namespace options
2906              * @for Uploader
2907              * @description 如果要分片,分多大一片? 默认大小为5M.
2908              */
2909             chunkSize: 5 * 1024 * 1024,
2910     
2911             /**
2912              * @property {Boolean} [chunkRetry=2]
2913              * @namespace options
2914              * @for Uploader
2915              * @description 如果某个分片由于网络问题出错,允许自动重传多少次?
2916              */
2917             chunkRetry: 2,
2918     
2919             /**
2920              * @property {Boolean} [threads=3]
2921              * @namespace options
2922              * @for Uploader
2923              * @description 上传并发数。允许同时最大上传进程数。
2924              */
2925             threads: 3,
2926     
2927     
2928             /**
2929              * @property {Object} [formData={}]
2930              * @namespace options
2931              * @for Uploader
2932              * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。
2933              */
2934             formData: {}
2935     
2936             /**
2937              * @property {Object} [fileVal='file']
2938              * @namespace options
2939              * @for Uploader
2940              * @description 设置文件上传域的name。
2941              */
2942     
2943             /**
2944              * @property {Object} [method='POST']
2945              * @namespace options
2946              * @for Uploader
2947              * @description 文件上传方式,`POST`或者`GET`。
2948              */
2949     
2950             /**
2951              * @property {Object} [sendAsBinary=false]
2952              * @namespace options
2953              * @for Uploader
2954              * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容,
2955              * 其他参数在$_GET数组中。
2956              */
2957         });
2958     
2959         // 负责将文件切片。
2960         function CuteFile( file, chunkSize ) {
2961             var pending = [],
2962                 blob = file.source,
2963                 total = blob.size,
2964                 chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,
2965                 start = 0,
2966                 index = 0,
2967                 len, api;
2968     
2969             api = {
2970                 file: file,
2971     
2972                 has: function() {
2973                     return !!pending.length;
2974                 },
2975     
2976                 shift: function() {
2977                     return pending.shift();
2978                 },
2979     
2980                 unshift: function( block ) {
2981                     pending.unshift( block );
2982                 }
2983             };
2984     
2985             while ( index < chunks ) {
2986                 len = Math.min( chunkSize, total - start );
2987     
2988                 pending.push({
2989                     file: file,
2990                     start: start,
2991                     end: chunkSize ? (start + len) : total,
2992                     total: total,
2993                     chunks: chunks,
2994                     chunk: index++,
2995                     cuted: api
2996                 });
2997                 start += len;
2998             }
2999     
3000             file.blocks = pending.concat();
3001             file.remaning = pending.length;
3002     
3003             return api;
3004         }
3005     
3006         Uploader.register({
3007             name: 'upload',
3008     
3009             init: function() {
3010                 var owner = this.owner,
3011                     me = this;
3012     
3013                 this.runing = false;
3014                 this.progress = false;
3015     
3016                 owner
3017                     .on( 'startUpload', function() {
3018                         me.progress = true;
3019                     })
3020                     .on( 'uploadFinished', function() {
3021                         me.progress = false;
3022                     });
3023     
3024                 // 记录当前正在传的数据,跟threads相关
3025                 this.pool = [];
3026     
3027                 // 缓存分好片的文件。
3028                 this.stack = [];
3029     
3030                 // 缓存即将上传的文件。
3031                 this.pending = [];
3032     
3033                 // 跟踪还有多少分片在上传中但是没有完成上传。
3034                 this.remaning = 0;
3035                 this.__tick = Base.bindFn( this._tick, this );
3036     
3037                 owner.on( 'uploadComplete', function( file ) {
3038                     
3039                     // 把其他块取消了。
3040                     file.blocks && $.each( file.blocks, function( _, v ) {
3041                         v.transport && (v.transport.abort(), v.transport.destroy());
3042                         delete v.transport;
3043                     });
3044     
3045                     delete file.blocks;
3046                     delete file.remaning;
3047                 });
3048             },
3049     
3050             reset: function() {
3051                 this.request( 'stop-upload', true );
3052                 this.runing = false;
3053                 this.pool = [];
3054                 this.stack = [];
3055                 this.pending = [];
3056                 this.remaning = 0;
3057                 this._trigged = false;
3058                 this._promise = null;
3059             },
3060     
3061             /**
3062              * @event startUpload
3063              * @description 当开始上传流程时触发。
3064              * @for  Uploader
3065              */
3066     
3067             /**
3068              * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。
3069              *
3070              * 可以指定开始某一个文件。
3071              * @grammar upload() => undefined
3072              * @grammar upload( file | fileId) => undefined
3073              * @method upload
3074              * @for  Uploader
3075              */
3076             startUpload: function(file) {
3077                 var me = this;
3078     
3079                 // 移出invalid的文件
3080                 $.each( me.request( 'get-files', Status.INVALID ), function() {
3081                     me.request( 'remove-file', this );
3082                 });
3083     
3084                 // 如果指定了开始某个文件,则只开始指定文件。
3085                 if ( file ) {
3086                     file = file.id ? file : me.request( 'get-file', file );
3087     
3088                     if (file.getStatus() === Status.INTERRUPT) {
3089                         $.each( me.pool, function( _, v ) {
3090                         
3091                             // 之前暂停过。
3092                             if (v.file !== file) {
3093                                 return;
3094                             }
3095     
3096                             v.transport && v.transport.send();
3097                         });
3098                         
3099                         file.setStatus( Status.QUEUED );
3100                     } else if (file.getStatus() === Status.PROGRESS) {
3101                         return;
3102                     } else {
3103                         file.setStatus( Status.QUEUED );
3104                     }
3105                 } else {
3106                     $.each( me.request( 'get-files', [ Status.INITED ] ), function() {
3107                         this.setStatus( Status.QUEUED );
3108                     });
3109                 }
3110     
3111                 if ( me.runing ) {
3112                     return;
3113                 }
3114     
3115                 me.runing = true;
3116     
3117                 // 如果有暂停的,则续传
3118                 $.each( me.pool, function( _, v ) {
3119                     var file = v.file;
3120     
3121                     if ( file.getStatus() === Status.INTERRUPT ) {
3122                         file.setStatus( Status.PROGRESS );
3123                         me._trigged = false;
3124                         v.transport && v.transport.send();
3125                     }
3126                 });
3127     
3128                 file || $.each( me.request( 'get-files',
3129                         Status.INTERRUPT ), function() {
3130                     this.setStatus( Status.PROGRESS );
3131                 });
3132     
3133                 me._trigged = false;
3134                 Base.nextTick( me.__tick );
3135                 me.owner.trigger('startUpload');
3136             },
3137     
3138             /**
3139              * @event stopUpload
3140              * @description 当开始上传流程暂停时触发。
3141              * @for  Uploader
3142              */
3143     
3144             /**
3145              * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。
3146              *
3147              * 如果第一个参数是文件,则只暂停指定文件。
3148              * @grammar stop() => undefined
3149              * @grammar stop( true ) => undefined
3150              * @grammar stop( file ) => undefined
3151              * @method stop
3152              * @for  Uploader
3153              */
3154             stopUpload: function( file, interrupt ) {
3155                 var me = this;
3156     
3157                 if (file === true) {
3158                     interrupt = file;
3159                     file = null;
3160                 }
3161     
3162                 if ( me.runing === false ) {
3163                     return;
3164                 }
3165     
3166                 // 如果只是暂停某个文件。
3167                 if ( file ) {
3168                     file = file.id ? file : me.request( 'get-file', file );
3169     
3170                     if ( file.getStatus() !== Status.PROGRESS &&
3171                             file.getStatus() !== Status.QUEUED ) {
3172                         return;
3173                     }
3174     
3175                     file.setStatus( Status.INTERRUPT );
3176                     $.each( me.pool, function( _, v ) {
3177                         
3178                         // 只 abort 指定的文件。
3179                         if (v.file !== file) {
3180                             return;
3181                         }
3182     
3183                         v.transport && v.transport.abort();
3184                         me._putback(v);
3185                         me._popBlock(v);
3186                     });
3187     
3188                     return Base.nextTick( me.__tick );
3189                 }
3190     
3191                 me.runing = false;
3192     
3193                 if (this._promise && this._promise.file) {
3194                     this._promise.file.setStatus( Status.INTERRUPT );
3195                 }
3196     
3197                 interrupt && $.each( me.pool, function( _, v ) {
3198                     v.transport && v.transport.abort();
3199                     v.file.setStatus( Status.INTERRUPT );
3200                 });
3201     
3202                 me.owner.trigger('stopUpload');
3203             },
3204     
3205             /**
3206              * @method cancelFile
3207              * @grammar cancelFile( file ) => undefined
3208              * @grammar cancelFile( id ) => undefined
3209              * @param {File|id} file File对象或这File对象的id
3210              * @description 标记文件状态为已取消, 同时将中断文件传输。
3211              * @for  Uploader
3212              * @example
3213              *
3214              * $li.on('click', '.remove-this', function() {
3215              *     uploader.cancelFile( file );
3216              * })
3217              */
3218             cancelFile: function( file ) {
3219                 file = file.id ? file : this.request( 'get-file', file );
3220     
3221                 // 如果正在上传。
3222                 file.blocks && $.each( file.blocks, function( _, v ) {
3223                     var _tr = v.transport;
3224     
3225                     if ( _tr ) {
3226                         _tr.abort();
3227                         _tr.destroy();
3228                         delete v.transport;
3229                     }
3230                 });
3231     
3232                 file.setStatus( Status.CANCELLED );
3233                 this.owner.trigger( 'fileDequeued', file );
3234             },
3235     
3236             /**
3237              * 判断`Uplaode`r是否正在上传中。
3238              * @grammar isInProgress() => Boolean
3239              * @method isInProgress
3240              * @for  Uploader
3241              */
3242             isInProgress: function() {
3243                 return !!this.progress;
3244             },
3245     
3246             _getStats: function() {
3247                 return this.request('get-stats');
3248             },
3249     
3250             /**
3251              * 掉过一个文件上传,直接标记指定文件为已上传状态。
3252              * @grammar skipFile( file ) => undefined
3253              * @method skipFile
3254              * @for  Uploader
3255              */
3256             skipFile: function( file, status ) {
3257                 file = file.id ? file : this.request( 'get-file', file );
3258     
3259                 file.setStatus( status || Status.COMPLETE );
3260                 file.skipped = true;
3261     
3262                 // 如果正在上传。
3263                 file.blocks && $.each( file.blocks, function( _, v ) {
3264                     var _tr = v.transport;
3265     
3266                     if ( _tr ) {
3267                         _tr.abort();
3268                         _tr.destroy();
3269                         delete v.transport;
3270                     }
3271                 });
3272     
3273                 this.owner.trigger( 'uploadSkip', file );
3274             },
3275     
3276             /**
3277              * @event uploadFinished
3278              * @description 当所有文件上传结束时触发。
3279              * @for  Uploader
3280              */
3281             _tick: function() {
3282                 var me = this,
3283                     opts = me.options,
3284                     fn, val;
3285     
3286                 // 上一个promise还没有结束,则等待完成后再执行。
3287                 if ( me._promise ) {
3288                     return me._promise.always( me.__tick );
3289                 }
3290     
3291                 // 还有位置,且还有文件要处理的话。
3292                 if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {
3293                     me._trigged = false;
3294     
3295                     fn = function( val ) {
3296                         me._promise = null;
3297     
3298                         // 有可能是reject过来的,所以要检测val的类型。
3299                         val && val.file && me._startSend( val );
3300                         Base.nextTick( me.__tick );
3301                     };
3302     
3303                     me._promise = isPromise( val ) ? val.always( fn ) : fn( val );
3304     
3305                 // 没有要上传的了,且没有正在传输的了。
3306                 } else if ( !me.remaning && !me._getStats().numOfQueue &&
3307                     !me._getStats().numofInterrupt ) {
3308                     me.runing = false;
3309     
3310                     me._trigged || Base.nextTick(function() {
3311                         me.owner.trigger('uploadFinished');
3312                     });
3313                     me._trigged = true;
3314                 }
3315             },
3316     
3317             _putback: function(block) {
3318                 var idx;
3319     
3320                 block.cuted.unshift(block);
3321                 idx = this.stack.indexOf(block.cuted);
3322     
3323                 if (!~idx) {
3324                     this.stack.unshift(block.cuted);
3325                 }
3326             },
3327     
3328             _getStack: function() {
3329                 var i = 0,
3330                     act;
3331     
3332                 while ( (act = this.stack[ i++ ]) ) {
3333                     if ( act.has() && act.file.getStatus() === Status.PROGRESS ) {
3334                         return act;
3335                     } else if (!act.has() ||
3336                             act.file.getStatus() !== Status.PROGRESS &&
3337                             act.file.getStatus() !== Status.INTERRUPT ) {
3338     
3339                         // 把已经处理完了的,或者,状态为非 progress(上传中)、
3340                         // interupt(暂停中) 的移除。
3341                         this.stack.splice( --i, 1 );
3342                     }
3343                 }
3344     
3345                 return null;
3346             },
3347     
3348             _nextBlock: function() {
3349                 var me = this,
3350                     opts = me.options,
3351                     act, next, done, preparing;
3352     
3353                 // 如果当前文件还有没有需要传输的,则直接返回剩下的。
3354                 if ( (act = this._getStack()) ) {
3355     
3356                     // 是否提前准备下一个文件
3357                     if ( opts.prepareNextFile && !me.pending.length ) {
3358                         me._prepareNextFile();
3359                     }
3360     
3361                     return act.shift();
3362     
3363                 // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。
3364                 } else if ( me.runing ) {
3365     
3366                     // 如果缓存中有,则直接在缓存中取,没有则去queue中取。
3367                     if ( !me.pending.length && me._getStats().numOfQueue ) {
3368                         me._prepareNextFile();
3369                     }
3370     
3371                     next = me.pending.shift();
3372                     done = function( file ) {
3373                         if ( !file ) {
3374                             return null;
3375                         }
3376                             
3377                         act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );
3378                         me.stack.push(act);
3379                         return act.shift();
3380                     };
3381     
3382                     // 文件可能还在prepare中,也有可能已经完全准备好了。
3383                     if ( isPromise( next) ) {
3384                         preparing = next.file;
3385                         next = next[ next.pipe ? 'pipe' : 'then' ]( done );
3386                         next.file = preparing;
3387                         return next;
3388                     }
3389     
3390                     return done( next );
3391                 }
3392             },
3393     
3394     
3395             /**
3396              * @event uploadStart
3397              * @param {File} file File对象
3398              * @description 某个文件开始上传前触发,一个文件只会触发一次。
3399              * @for  Uploader
3400              */
3401             _prepareNextFile: function() {
3402                 var me = this,
3403                     file = me.request('fetch-file'),
3404                     pending = me.pending,
3405                     promise;
3406     
3407                 if ( file ) {
3408                     promise = me.request( 'before-send-file', file, function() {
3409     
3410                         // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued.
3411                         if ( file.getStatus() === Status.PROGRESS || 
3412                             file.getStatus() === Status.INTERRUPT ) {
3413                             return file;
3414                         }
3415     
3416                         return me._finishFile( file );
3417                     });
3418     
3419                     me.owner.trigger( 'uploadStart', file );
3420                     file.setStatus( Status.PROGRESS );
3421     
3422                     promise.file = file;
3423     
3424                     // 如果还在pending中,则替换成文件本身。
3425                     promise.done(function() {
3426                         var idx = $.inArray( promise, pending );
3427     
3428                         ~idx && pending.splice( idx, 1, file );
3429                     });
3430     
3431                     // befeore-send-file的钩子就有错误发生。
3432                     promise.fail(function( reason ) {
3433                         file.setStatus( Status.ERROR, reason );
3434                         me.owner.trigger( 'uploadError', file, reason );
3435                         me.owner.trigger( 'uploadComplete', file );
3436                     });
3437     
3438                     pending.push( promise );
3439                 }
3440             },
3441     
3442             // 让出位置了,可以让其他分片开始上传
3443             _popBlock: function( block ) {
3444                 var idx = $.inArray( block, this.pool );
3445     
3446                 this.pool.splice( idx, 1 );
3447                 block.file.remaning--;
3448                 this.remaning--;
3449             },
3450     
3451             // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。
3452             _startSend: function( block ) {
3453                 var me = this,
3454                     file = block.file,
3455                     promise;
3456     
3457                 // 有可能在 before-send-file 的 promise 期间改变了文件状态。
3458                 // 如:暂停,取消
3459                 // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。
3460                 if ( file.getStatus() !== Status.PROGRESS ) {
3461                     
3462                     // 如果是中断,则还需要放回去。
3463                     if (file.getStatus() === Status.INTERRUPT) {
3464                         me._putback(block);
3465                     }
3466     
3467                     return;
3468                 }
3469     
3470                 me.pool.push( block );
3471                 me.remaning++;
3472     
3473                 // 如果没有分片,则直接使用原始的。
3474                 // 不会丢失content-type信息。
3475                 block.blob = block.chunks === 1 ? file.source :
3476                         file.source.slice( block.start, block.end );
3477     
3478                 // hook, 每个分片发送之前可能要做些异步的事情。
3479                 promise = me.request( 'before-send', block, function() {
3480     
3481                     // 有可能文件已经上传出错了,所以不需要再传输了。
3482                     if ( file.getStatus() === Status.PROGRESS ) {
3483                         me._doSend( block );
3484                     } else {
3485                         me._popBlock( block );
3486                         Base.nextTick( me.__tick );
3487                     }
3488                 });
3489     
3490                 // 如果为fail了,则跳过此分片。
3491                 promise.fail(function() {
3492                     if ( file.remaning === 1 ) {
3493                         me._finishFile( file ).always(function() {
3494                             block.percentage = 1;
3495                             me._popBlock( block );
3496                             me.owner.trigger( 'uploadComplete', file );
3497                             Base.nextTick( me.__tick );
3498                         });
3499                     } else {
3500                         block.percentage = 1;
3501                         me._popBlock( block );
3502                         Base.nextTick( me.__tick );
3503                     }
3504                 });
3505             },
3506     
3507     
3508             /**
3509              * @event uploadBeforeSend
3510              * @param {Object} object
3511              * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。
3512              * @param {Object} headers 可以扩展此对象来控制上传头部。
3513              * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。
3514              * @for  Uploader
3515              */
3516     
3517             /**
3518              * @event uploadAccept
3519              * @param {Object} object
3520              * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。
3521              * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。
3522              * @for  Uploader
3523              */
3524     
3525             /**
3526              * @event uploadProgress
3527              * @param {File} file File对象
3528              * @param {Number} percentage 上传进度
3529              * @description 上传过程中触发,携带上传进度。
3530              * @for  Uploader
3531              */
3532     
3533     
3534             /**
3535              * @event uploadError
3536              * @param {File} file File对象
3537              * @param {String} reason 出错的code
3538              * @description 当文件上传出错时触发。
3539              * @for  Uploader
3540              */
3541     
3542             /**
3543              * @event uploadSuccess
3544              * @param {File} file File对象
3545              * @param {Object} response 服务端返回的数据
3546              * @description 当文件上传成功时触发。
3547              * @for  Uploader
3548              */
3549     
3550             /**
3551              * @event uploadComplete
3552              * @param {File} [file] File对象
3553              * @description 不管成功或者失败,文件上传完成时触发。
3554              * @for  Uploader
3555              */
3556     
3557             // 做上传操作。
3558             _doSend: function( block ) {
3559                 var me = this,
3560                     owner = me.owner,
3561                     opts = me.options,
3562                     file = block.file,
3563                     tr = new Transport( opts ),
3564                     data = $.extend({}, opts.formData ),
3565                     headers = $.extend({}, opts.headers ),
3566                     requestAccept, ret;
3567     
3568                 block.transport = tr;
3569     
3570                 tr.on( 'destroy', function() {
3571                     delete block.transport;
3572                     me._popBlock( block );
3573                     Base.nextTick( me.__tick );
3574                 });
3575     
3576                 // 广播上传进度。以文件为单位。
3577                 tr.on( 'progress', function( percentage ) {
3578                     var totalPercent = 0,
3579                         uploaded = 0;
3580     
3581                     // 可能没有abort掉,progress还是执行进来了。
3582                     // if ( !file.blocks ) {
3583                     //     return;
3584                     // }
3585     
3586                     totalPercent = block.percentage = percentage;
3587     
3588                     if ( block.chunks > 1 ) {    // 计算文件的整体速度。
3589                         $.each( file.blocks, function( _, v ) {
3590                             uploaded += (v.percentage || 0) * (v.end - v.start);
3591                         });
3592     
3593                         totalPercent = uploaded / file.size;
3594                     }
3595     
3596                     owner.trigger( 'uploadProgress', file, totalPercent || 0 );
3597                 });
3598     
3599                 // 用来询问,是否返回的结果是有错误的。
3600                 requestAccept = function( reject ) {
3601                     var fn;
3602     
3603                     ret = tr.getResponseAsJson() || {};
3604                     ret._raw = tr.getResponse();
3605                     fn = function( value ) {
3606                         reject = value;
3607                     };
3608     
3609                     // 服务端响应了,不代表成功了,询问是否响应正确。
3610                     if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {
3611                         reject = reject || 'server';
3612                     }
3613     
3614                     return reject;
3615                 };
3616     
3617                 // 尝试重试,然后广播文件上传出错。
3618                 tr.on( 'error', function( type, flag ) {
3619                     block.retried = block.retried || 0;
3620     
3621                     // 自动重试
3622                     if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&
3623                             block.retried < opts.chunkRetry ) {
3624     
3625                         block.retried++;
3626                         tr.send();
3627     
3628                     } else {
3629     
3630                         // http status 500 ~ 600
3631                         if ( !flag && type === 'server' ) {
3632                             type = requestAccept( type );
3633                         }
3634     
3635                         file.setStatus( Status.ERROR, type );
3636                         owner.trigger( 'uploadError', file, type );
3637                         owner.trigger( 'uploadComplete', file );
3638                     }
3639                 });
3640     
3641                 // 上传成功
3642                 tr.on( 'load', function() {
3643                     var reason;
3644     
3645                     // 如果非预期,转向上传出错。
3646                     if ( (reason = requestAccept()) ) {
3647                         tr.trigger( 'error', reason, true );
3648                         return;
3649                     }
3650     
3651                     // 全部上传完成。
3652                     if ( file.remaning === 1 ) {
3653                         me._finishFile( file, ret );
3654                     } else {
3655                         tr.destroy();
3656                     }
3657                 });
3658     
3659                 // 配置默认的上传字段。
3660                 data = $.extend( data, {
3661                     id: file.id,
3662                     name: file.name,
3663                     type: file.type,
3664                     lastModifiedDate: file.lastModifiedDate,
3665                     size: file.size
3666                 });
3667     
3668                 block.chunks > 1 && $.extend( data, {
3669                     chunks: block.chunks,
3670                     chunk: block.chunk
3671                 });
3672     
3673                 // 在发送之间可以添加字段什么的。。。
3674                 // 如果默认的字段不够使用,可以通过监听此事件来扩展
3675                 owner.trigger( 'uploadBeforeSend', block, data, headers );
3676     
3677                 // 开始发送。
3678                 tr.appendBlob( opts.fileVal, block.blob, file.name );
3679                 tr.append( data );
3680                 tr.setRequestHeader( headers );
3681                 tr.send();
3682             },
3683     
3684             // 完成上传。
3685             _finishFile: function( file, ret, hds ) {
3686                 var owner = this.owner;
3687     
3688                 return owner
3689                         .request( 'after-send-file', arguments, function() {
3690                             file.setStatus( Status.COMPLETE );
3691                             owner.trigger( 'uploadSuccess', file, ret, hds );
3692                         })
3693                         .fail(function( reason ) {
3694     
3695                             // 如果外部已经标记为invalid什么的,不再改状态。
3696                             if ( file.getStatus() === Status.PROGRESS ) {
3697                                 file.setStatus( Status.ERROR, reason );
3698                             }
3699     
3700                             owner.trigger( 'uploadError', file, reason );
3701                         })
3702                         .always(function() {
3703                             owner.trigger( 'uploadComplete', file );
3704                         });
3705             }
3706     
3707         });
3708     });
3709     /**
3710      * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。
3711      */
3712     
3713     define('widgets/validator',[
3714         'base',
3715         'uploader',
3716         'file',
3717         'widgets/widget'
3718     ], function( Base, Uploader, WUFile ) {
3719     
3720         var $ = Base.$,
3721             validators = {},
3722             api;
3723     
3724         /**
3725          * @event error
3726          * @param {String} type 错误类型。
3727          * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。
3728          *
3729          * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。
3730          * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。
3731          * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。
3732          * @for  Uploader
3733          */
3734     
3735         // 暴露给外面的api
3736         api = {
3737     
3738             // 添加验证器
3739             addValidator: function( type, cb ) {
3740                 validators[ type ] = cb;
3741             },
3742     
3743             // 移除验证器
3744             removeValidator: function( type ) {
3745                 delete validators[ type ];
3746             }
3747         };
3748     
3749         // 在Uploader初始化的时候启动Validators的初始化
3750         Uploader.register({
3751             name: 'validator',
3752     
3753             init: function() {
3754                 var me = this;
3755                 Base.nextTick(function() {
3756                     $.each( validators, function() {
3757                         this.call( me.owner );
3758                     });
3759                 });
3760             }
3761         });
3762     
3763         /**
3764          * @property {int} [fileNumLimit=undefined]
3765          * @namespace options
3766          * @for Uploader
3767          * @description 验证文件总数量, 超出则不允许加入队列。
3768          */
3769         api.addValidator( 'fileNumLimit', function() {
3770             var uploader = this,
3771                 opts = uploader.options,
3772                 count = 0,
3773                 max = parseInt( opts.fileNumLimit, 10 ),
3774                 flag = true;
3775     
3776             if ( !max ) {
3777                 return;
3778             }
3779     
3780             uploader.on( 'beforeFileQueued', function( file ) {
3781     
3782                 if ( count >= max && flag ) {
3783                     flag = false;
3784                     this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );
3785                     setTimeout(function() {
3786                         flag = true;
3787                     }, 1 );
3788                 }
3789     
3790                 return count >= max ? false : true;
3791             });
3792     
3793             uploader.on( 'fileQueued', function() {
3794                 count++;
3795             });
3796     
3797             uploader.on( 'fileDequeued', function() {
3798                 count--;
3799             });
3800     
3801             uploader.on( 'reset', function() {
3802                 count = 0;
3803             });
3804         });
3805     
3806     
3807         /**
3808          * @property {int} [fileSizeLimit=undefined]
3809          * @namespace options
3810          * @for Uploader
3811          * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。
3812          */
3813         api.addValidator( 'fileSizeLimit', function() {
3814             var uploader = this,
3815                 opts = uploader.options,
3816                 count = 0,
3817                 max = parseInt( opts.fileSizeLimit, 10 ),
3818                 flag = true;
3819     
3820             if ( !max ) {
3821                 return;
3822             }
3823     
3824             uploader.on( 'beforeFileQueued', function( file ) {
3825                 var invalid = count + file.size > max;
3826     
3827                 if ( invalid && flag ) {
3828                     flag = false;
3829                     this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );
3830                     setTimeout(function() {
3831                         flag = true;
3832                     }, 1 );
3833                 }
3834     
3835                 return invalid ? false : true;
3836             });
3837     
3838             uploader.on( 'fileQueued', function( file ) {
3839                 count += file.size;
3840             });
3841     
3842             uploader.on( 'fileDequeued', function( file ) {
3843                 count -= file.size;
3844             });
3845     
3846             uploader.on( 'reset', function() {
3847                 count = 0;
3848             });
3849         });
3850     
3851         /**
3852          * @property {int} [fileSingleSizeLimit=undefined]
3853          * @namespace options
3854          * @for Uploader
3855          * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。
3856          */
3857         api.addValidator( 'fileSingleSizeLimit', function() {
3858             var uploader = this,
3859                 opts = uploader.options,
3860                 max = opts.fileSingleSizeLimit;
3861     
3862             if ( !max ) {
3863                 return;
3864             }
3865     
3866             uploader.on( 'beforeFileQueued', function( file ) {
3867     
3868                 if ( file.size > max ) {
3869                     file.setStatus( WUFile.Status.INVALID, 'exceed_size' );
3870                     this.trigger( 'error', 'F_EXCEED_SIZE', max, file );
3871                     return false;
3872                 }
3873     
3874             });
3875     
3876         });
3877     
3878         /**
3879          * @property {Boolean} [duplicate=undefined]
3880          * @namespace options
3881          * @for Uploader
3882          * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key.
3883          */
3884         api.addValidator( 'duplicate', function() {
3885             var uploader = this,
3886                 opts = uploader.options,
3887                 mapping = {};
3888     
3889             if ( opts.duplicate ) {
3890                 return;
3891             }
3892     
3893             function hashString( str ) {
3894                 var hash = 0,
3895                     i = 0,
3896                     len = str.length,
3897                     _char;
3898     
3899                 for ( ; i < len; i++ ) {
3900                     _char = str.charCodeAt( i );
3901                     hash = _char + (hash << 6) + (hash << 16) - hash;
3902                 }
3903     
3904                 return hash;
3905             }
3906     
3907             uploader.on( 'beforeFileQueued', function( file ) {
3908                 var hash = file.__hash || (file.__hash = hashString( file.name +
3909                         file.size + file.lastModifiedDate ));
3910     
3911                 // 已经重复了
3912                 if ( mapping[ hash ] ) {
3913                     this.trigger( 'error', 'F_DUPLICATE', file );
3914                     return false;
3915                 }
3916             });
3917     
3918             uploader.on( 'fileQueued', function( file ) {
3919                 var hash = file.__hash;
3920     
3921                 hash && (mapping[ hash ] = true);
3922             });
3923     
3924             uploader.on( 'fileDequeued', function( file ) {
3925                 var hash = file.__hash;
3926     
3927                 hash && (delete mapping[ hash ]);
3928             });
3929     
3930             uploader.on( 'reset', function() {
3931                 mapping = {};
3932             });
3933         });
3934     
3935         return api;
3936     });
3937     
3938     /**
3939      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
3940      */
3941     define('runtime/compbase',[],function() {
3942     
3943         function CompBase( owner, runtime ) {
3944     
3945             this.owner = owner;
3946             this.options = owner.options;
3947     
3948             this.getRuntime = function() {
3949                 return runtime;
3950             };
3951     
3952             this.getRuid = function() {
3953                 return runtime.uid;
3954             };
3955     
3956             this.trigger = function() {
3957                 return owner.trigger.apply( owner, arguments );
3958             };
3959         }
3960     
3961         return CompBase;
3962     });
3963     /**
3964      * @fileOverview Html5Runtime
3965      */
3966     define('runtime/html5/runtime',[
3967         'base',
3968         'runtime/runtime',
3969         'runtime/compbase'
3970     ], function( Base, Runtime, CompBase ) {
3971     
3972         var type = 'html5',
3973             components = {};
3974     
3975         function Html5Runtime() {
3976             var pool = {},
3977                 me = this,
3978                 destroy = this.destroy;
3979     
3980             Runtime.apply( me, arguments );
3981             me.type = type;
3982     
3983     
3984             // 这个方法的调用者,实际上是RuntimeClient
3985             me.exec = function( comp, fn/*, args...*/) {
3986                 var client = this,
3987                     uid = client.uid,
3988                     args = Base.slice( arguments, 2 ),
3989                     instance;
3990     
3991                 if ( components[ comp ] ) {
3992                     instance = pool[ uid ] = pool[ uid ] ||
3993                             new components[ comp ]( client, me );
3994     
3995                     if ( instance[ fn ] ) {
3996                         return instance[ fn ].apply( instance, args );
3997                     }
3998                 }
3999             };
4000     
4001             me.destroy = function() {
4002                 // @todo 删除池子中的所有实例
4003                 return destroy && destroy.apply( this, arguments );
4004             };
4005         }
4006     
4007         Base.inherits( Runtime, {
4008             constructor: Html5Runtime,
4009     
4010             // 不需要连接其他程序,直接执行callback
4011             init: function() {
4012                 var me = this;
4013                 setTimeout(function() {
4014                     me.trigger('ready');
4015                 }, 1 );
4016             }
4017     
4018         });
4019     
4020         // 注册Components
4021         Html5Runtime.register = function( name, component ) {
4022             var klass = components[ name ] = Base.inherits( CompBase, component );
4023             return klass;
4024         };
4025     
4026         // 注册html5运行时。
4027         // 只有在支持的前提下注册。
4028         if ( window.Blob && window.FileReader && window.DataView ) {
4029             Runtime.addRuntime( type, Html5Runtime );
4030         }
4031     
4032         return Html5Runtime;
4033     });
4034     /**
4035      * @fileOverview Blob Html实现
4036      */
4037     define('runtime/html5/blob',[
4038         'runtime/html5/runtime',
4039         'lib/blob'
4040     ], function( Html5Runtime, Blob ) {
4041     
4042         return Html5Runtime.register( 'Blob', {
4043             slice: function( start, end ) {
4044                 var blob = this.owner.source,
4045                     slice = blob.slice || blob.webkitSlice || blob.mozSlice;
4046     
4047                 blob = slice.call( blob, start, end );
4048     
4049                 return new Blob( this.getRuid(), blob );
4050             }
4051         });
4052     });
4053     /**
4054      * @fileOverview FilePaste
4055      */
4056     define('runtime/html5/dnd',[
4057         'base',
4058         'runtime/html5/runtime',
4059         'lib/file'
4060     ], function( Base, Html5Runtime, File ) {
4061     
4062         var $ = Base.$,
4063             prefix = 'webuploader-dnd-';
4064     
4065         return Html5Runtime.register( 'DragAndDrop', {
4066             init: function() {
4067                 var elem = this.elem = this.options.container;
4068     
4069                 this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );
4070                 this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );
4071                 this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );
4072                 this.dropHandler = Base.bindFn( this._dropHandler, this );
4073                 this.dndOver = false;
4074     
4075                 elem.on( 'dragenter', this.dragEnterHandler );
4076                 elem.on( 'dragover', this.dragOverHandler );
4077                 elem.on( 'dragleave', this.dragLeaveHandler );
4078                 elem.on( 'drop', this.dropHandler );
4079     
4080                 if ( this.options.disableGlobalDnd ) {
4081                     $( document ).on( 'dragover', this.dragOverHandler );
4082                     $( document ).on( 'drop', this.dropHandler );
4083                 }
4084             },
4085     
4086             _dragEnterHandler: function( e ) {
4087                 var me = this,
4088                     denied = me._denied || false,
4089                     items;
4090     
4091                 e = e.originalEvent || e;
4092     
4093                 if ( !me.dndOver ) {
4094                     me.dndOver = true;
4095     
4096                     // 注意只有 chrome 支持。
4097                     items = e.dataTransfer.items;
4098     
4099                     if ( items && items.length ) {
4100                         me._denied = denied = !me.trigger( 'accept', items );
4101                     }
4102     
4103                     me.elem.addClass( prefix + 'over' );
4104                     me.elem[ denied ? 'addClass' :
4105                             'removeClass' ]( prefix + 'denied' );
4106                 }
4107     
4108                 e.dataTransfer.dropEffect = denied ? 'none' : 'copy';
4109     
4110                 return false;
4111             },
4112     
4113             _dragOverHandler: function( e ) {
4114                 // 只处理框内的。
4115                 var parentElem = this.elem.parent().get( 0 );
4116                 if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
4117                     return false;
4118                 }
4119     
4120                 clearTimeout( this._leaveTimer );
4121                 this._dragEnterHandler.call( this, e );
4122     
4123                 return false;
4124             },
4125     
4126             _dragLeaveHandler: function() {
4127                 var me = this,
4128                     handler;
4129     
4130                 handler = function() {
4131                     me.dndOver = false;
4132                     me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );
4133                 };
4134     
4135                 clearTimeout( me._leaveTimer );
4136                 me._leaveTimer = setTimeout( handler, 100 );
4137                 return false;
4138             },
4139     
4140             _dropHandler: function( e ) {
4141                 var me = this,
4142                     ruid = me.getRuid(),
4143                     parentElem = me.elem.parent().get( 0 ),
4144                     dataTransfer, data;
4145     
4146                 // 只处理框内的。
4147                 if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
4148                     return false;
4149                 }
4150     
4151                 e = e.originalEvent || e;
4152                 dataTransfer = e.dataTransfer;
4153     
4154                 // 如果是页面内拖拽,还不能处理,不阻止事件。
4155                 // 此处 ie11 下会报参数错误,
4156                 try {
4157                     data = dataTransfer.getData('text/html');
4158                 } catch( err ) {
4159                 }
4160     
4161                 if ( data ) {
4162                     return;
4163                 }
4164     
4165                 me._getTansferFiles( dataTransfer, function( results ) {
4166                     me.trigger( 'drop', $.map( results, function( file ) {
4167                         return new File( ruid, file );
4168                     }) );
4169                 });
4170     
4171                 me.dndOver = false;
4172                 me.elem.removeClass( prefix + 'over' );
4173                 return false;
4174             },
4175     
4176             // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。
4177             _getTansferFiles: function( dataTransfer, callback ) {
4178                 var results  = [],
4179                     promises = [],
4180                     items, files, file, item, i, len, canAccessFolder;
4181     
4182                 items = dataTransfer.items;
4183                 files = dataTransfer.files;
4184     
4185                 canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);
4186     
4187                 for ( i = 0, len = files.length; i < len; i++ ) {
4188                     file = files[ i ];
4189                     item = items && items[ i ];
4190     
4191                     if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {
4192     
4193                         promises.push( this._traverseDirectoryTree(
4194                                 item.webkitGetAsEntry(), results ) );
4195                     } else {
4196                         results.push( file );
4197                     }
4198                 }
4199     
4200                 Base.when.apply( Base, promises ).done(function() {
4201     
4202                     if ( !results.length ) {
4203                         return;
4204                     }
4205     
4206                     callback( results );
4207                 });
4208             },
4209     
4210             _traverseDirectoryTree: function( entry, results ) {
4211                 var deferred = Base.Deferred(),
4212                     me = this;
4213     
4214                 if ( entry.isFile ) {
4215                     entry.file(function( file ) {
4216                         results.push( file );
4217                         deferred.resolve();
4218                     });
4219                 } else if ( entry.isDirectory ) {
4220                     entry.createReader().readEntries(function( entries ) {
4221                         var len = entries.length,
4222                             promises = [],
4223                             arr = [],    // 为了保证顺序。
4224                             i;
4225     
4226                         for ( i = 0; i < len; i++ ) {
4227                             promises.push( me._traverseDirectoryTree(
4228                                     entries[ i ], arr ) );
4229                         }
4230     
4231                         Base.when.apply( Base, promises ).then(function() {
4232                             results.push.apply( results, arr );
4233                             deferred.resolve();
4234                         }, deferred.reject );
4235                     });
4236                 }
4237     
4238                 return deferred.promise();
4239             },
4240     
4241             destroy: function() {
4242                 var elem = this.elem;
4243     
4244                 // 还没 init 就调用 destroy
4245                 if (!elem) {
4246                     return;
4247                 }
4248                 
4249                 elem.off( 'dragenter', this.dragEnterHandler );
4250                 elem.off( 'dragover', this.dragOverHandler );
4251                 elem.off( 'dragleave', this.dragLeaveHandler );
4252                 elem.off( 'drop', this.dropHandler );
4253     
4254                 if ( this.options.disableGlobalDnd ) {
4255                     $( document ).off( 'dragover', this.dragOverHandler );
4256                     $( document ).off( 'drop', this.dropHandler );
4257                 }
4258             }
4259         });
4260     });
4261     
4262     /**
4263      * @fileOverview FilePaste
4264      */
4265     define('runtime/html5/filepaste',[
4266         'base',
4267         'runtime/html5/runtime',
4268         'lib/file'
4269     ], function( Base, Html5Runtime, File ) {
4270     
4271         return Html5Runtime.register( 'FilePaste', {
4272             init: function() {
4273                 var opts = this.options,
4274                     elem = this.elem = opts.container,
4275                     accept = '.*',
4276                     arr, i, len, item;
4277     
4278                 // accetp的mimeTypes中生成匹配正则。
4279                 if ( opts.accept ) {
4280                     arr = [];
4281     
4282                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
4283                         item = opts.accept[ i ].mimeTypes;
4284                         item && arr.push( item );
4285                     }
4286     
4287                     if ( arr.length ) {
4288                         accept = arr.join(',');
4289                         accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' );
4290                     }
4291                 }
4292                 this.accept = accept = new RegExp( accept, 'i' );
4293                 this.hander = Base.bindFn( this._pasteHander, this );
4294                 elem.on( 'paste', this.hander );
4295             },
4296     
4297             _pasteHander: function( e ) {
4298                 var allowed = [],
4299                     ruid = this.getRuid(),
4300                     items, item, blob, i, len;
4301     
4302                 e = e.originalEvent || e;
4303                 items = e.clipboardData.items;
4304     
4305                 for ( i = 0, len = items.length; i < len; i++ ) {
4306                     item = items[ i ];
4307     
4308                     if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {
4309                         continue;
4310                     }
4311     
4312                     allowed.push( new File( ruid, blob ) );
4313                 }
4314     
4315                 if ( allowed.length ) {
4316                     // 不阻止非文件粘贴(文字粘贴)的事件冒泡
4317                     e.preventDefault();
4318                     e.stopPropagation();
4319                     this.trigger( 'paste', allowed );
4320                 }
4321             },
4322     
4323             destroy: function() {
4324                 this.elem.off( 'paste', this.hander );
4325             }
4326         });
4327     });
4328     
4329     /**
4330      * @fileOverview FilePicker
4331      */
4332     define('runtime/html5/filepicker',[
4333         'base',
4334         'runtime/html5/runtime'
4335     ], function( Base, Html5Runtime ) {
4336     
4337         var $ = Base.$;
4338     
4339         return Html5Runtime.register( 'FilePicker', {
4340             init: function() {
4341                 var container = this.getRuntime().getContainer(),
4342                     me = this,
4343                     owner = me.owner,
4344                     opts = me.options,
4345                     label = this.label = $( document.createElement('label') ),
4346                     input =  this.input = $( document.createElement('input') ),
4347                     arr, i, len, mouseHandler;
4348     
4349                 input.attr( 'type', 'file' );
4350                 input.attr( 'name', opts.name );
4351                 input.addClass('webuploader-element-invisible');
4352     
4353                 label.on( 'click', function() {
4354                     input.trigger('click');
4355                 });
4356     
4357                 label.css({
4358                     opacity: 0,
4359                     width: '100%',
4360                     height: '100%',
4361                     display: 'block',
4362                     cursor: 'pointer',
4363                     background: '#ffffff'
4364                 });
4365     
4366                 if ( opts.multiple ) {
4367                     input.attr( 'multiple', 'multiple' );
4368                 }
4369     
4370                 // @todo Firefox不支持单独指定后缀
4371                 if ( opts.accept && opts.accept.length > 0 ) {
4372                     arr = [];
4373     
4374                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
4375                         arr.push( opts.accept[ i ].mimeTypes );
4376                     }
4377     
4378                     input.attr( 'accept', arr.join(',') );
4379                 }
4380     
4381                 container.append( input );
4382                 container.append( label );
4383     
4384                 mouseHandler = function( e ) {
4385                     owner.trigger( e.type );
4386                 };
4387     
4388                 input.on( 'change', function( e ) {
4389                     var fn = arguments.callee,
4390                         clone;
4391     
4392                     me.files = e.target.files;
4393     
4394                     // reset input
4395                     clone = this.cloneNode( true );
4396                     clone.value = null;
4397                     this.parentNode.replaceChild( clone, this );
4398     
4399                     input.off();
4400                     input = $( clone ).on( 'change', fn )
4401                             .on( 'mouseenter mouseleave', mouseHandler );
4402     
4403                     owner.trigger('change');
4404                 });
4405     
4406                 label.on( 'mouseenter mouseleave', mouseHandler );
4407     
4408             },
4409     
4410     
4411             getFiles: function() {
4412                 return this.files;
4413             },
4414     
4415             destroy: function() {
4416                 this.input.off();
4417                 this.label.off();
4418             }
4419         });
4420     });
4421     /**
4422      * @fileOverview Transport
4423      * @todo 支持chunked传输,优势:
4424      * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分,
4425      * 而不需要重头再传一次。另外断点续传也需要用chunked方式。
4426      */
4427     define('runtime/html5/transport',[
4428         'base',
4429         'runtime/html5/runtime'
4430     ], function( Base, Html5Runtime ) {
4431     
4432         var noop = Base.noop,
4433             $ = Base.$;
4434     
4435         return Html5Runtime.register( 'Transport', {
4436             init: function() {
4437                 this._status = 0;
4438                 this._response = null;
4439             },
4440     
4441             send: function() {
4442                 var owner = this.owner,
4443                     opts = this.options,
4444                     xhr = this._initAjax(),
4445                     blob = owner._blob,
4446                     server = opts.server,
4447                     formData, binary, fr;
4448     
4449                 if ( opts.sendAsBinary ) {
4450                     server += (/\?/.test( server ) ? '&' : '?') +
4451                             $.param( owner._formData );
4452     
4453                     binary = blob.getSource();
4454                 } else {
4455                     formData = new FormData();
4456                     $.each( owner._formData, function( k, v ) {
4457                         formData.append( k, v );
4458                     });
4459     
4460                     formData.append( opts.fileVal, blob.getSource(),
4461                             opts.filename || owner._formData.name || '' );
4462                 }
4463     
4464                 if ( opts.withCredentials && 'withCredentials' in xhr ) {
4465                     xhr.open( opts.method, server, true );
4466                     xhr.withCredentials = true;
4467                 } else {
4468                     xhr.open( opts.method, server );
4469                 }
4470     
4471                 this._setRequestHeader( xhr, opts.headers );
4472     
4473                 if ( binary ) {
4474                     // 强制设置成 content-type 为文件流。
4475                     xhr.overrideMimeType &&
4476                             xhr.overrideMimeType('application/octet-stream');
4477     
4478                     // android直接发送blob会导致服务端接收到的是空文件。
4479                     // bug详情。
4480                     // https://code.google.com/p/android/issues/detail?id=39882
4481                     // 所以先用fileReader读取出来再通过arraybuffer的方式发送。
4482                     if ( Base.os.android ) {
4483                         fr = new FileReader();
4484     
4485                         fr.onload = function() {
4486                             xhr.send( this.result );
4487                             fr = fr.onload = null;
4488                         };
4489     
4490                         fr.readAsArrayBuffer( binary );
4491                     } else {
4492                         xhr.send( binary );
4493                     }
4494                 } else {
4495                     xhr.send( formData );
4496                 }
4497             },
4498     
4499             getResponse: function() {
4500                 return this._response;
4501             },
4502     
4503             getResponseAsJson: function() {
4504                 return this._parseJson( this._response );
4505             },
4506     
4507             getStatus: function() {
4508                 return this._status;
4509             },
4510     
4511             abort: function() {
4512                 var xhr = this._xhr;
4513     
4514                 if ( xhr ) {
4515                     xhr.upload.onprogress = noop;
4516                     xhr.onreadystatechange = noop;
4517                     xhr.abort();
4518     
4519                     this._xhr = xhr = null;
4520                 }
4521             },
4522     
4523             destroy: function() {
4524                 this.abort();
4525             },
4526     
4527             _initAjax: function() {
4528                 var me = this,
4529                     xhr = new XMLHttpRequest(),
4530                     opts = this.options;
4531     
4532                 if ( opts.withCredentials && !('withCredentials' in xhr) &&
4533                         typeof XDomainRequest !== 'undefined' ) {
4534                     xhr = new XDomainRequest();
4535                 }
4536     
4537                 xhr.upload.onprogress = function( e ) {
4538                     var percentage = 0;
4539     
4540                     if ( e.lengthComputable ) {
4541                         percentage = e.loaded / e.total;
4542                     }
4543     
4544                     return me.trigger( 'progress', percentage );
4545                 };
4546     
4547                 xhr.onreadystatechange = function() {
4548     
4549                     if ( xhr.readyState !== 4 ) {
4550                         return;
4551                     }
4552     
4553                     xhr.upload.onprogress = noop;
4554                     xhr.onreadystatechange = noop;
4555                     me._xhr = null;
4556                     me._status = xhr.status;
4557     
4558                     if ( xhr.status >= 200 && xhr.status < 300 ) {
4559                         me._response = xhr.responseText;
4560                         return me.trigger('load');
4561                     } else if ( xhr.status >= 500 && xhr.status < 600 ) {
4562                         me._response = xhr.responseText;
4563                         return me.trigger( 'error', 'server' );
4564                     }
4565     
4566     
4567                     return me.trigger( 'error', me._status ? 'http' : 'abort' );
4568                 };
4569     
4570                 me._xhr = xhr;
4571                 return xhr;
4572             },
4573     
4574             _setRequestHeader: function( xhr, headers ) {
4575                 $.each( headers, function( key, val ) {
4576                     xhr.setRequestHeader( key, val );
4577                 });
4578             },
4579     
4580             _parseJson: function( str ) {
4581                 var json;
4582     
4583                 try {
4584                     json = JSON.parse( str );
4585                 } catch ( ex ) {
4586                     json = {};
4587                 }
4588     
4589                 return json;
4590             }
4591         });
4592     });
4593     /**
4594      * @fileOverview FlashRuntime
4595      */
4596     define('runtime/flash/runtime',[
4597         'base',
4598         'runtime/runtime',
4599         'runtime/compbase'
4600     ], function( Base, Runtime, CompBase ) {
4601     
4602         var $ = Base.$,
4603             type = 'flash',
4604             components = {};
4605     
4606     
4607         function getFlashVersion() {
4608             var version;
4609     
4610             try {
4611                 version = navigator.plugins[ 'Shockwave Flash' ];
4612                 version = version.description;
4613             } catch ( ex ) {
4614                 try {
4615                     version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')
4616                             .GetVariable('$version');
4617                 } catch ( ex2 ) {
4618                     version = '0.0';
4619                 }
4620             }
4621             version = version.match( /\d+/g );
4622             return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );
4623         }
4624     
4625         function FlashRuntime() {
4626             var pool = {},
4627                 clients = {},
4628                 destroy = this.destroy,
4629                 me = this,
4630                 jsreciver = Base.guid('webuploader_');
4631     
4632             Runtime.apply( me, arguments );
4633             me.type = type;
4634     
4635     
4636             // 这个方法的调用者,实际上是RuntimeClient
4637             me.exec = function( comp, fn/*, args...*/ ) {
4638                 var client = this,
4639                     uid = client.uid,
4640                     args = Base.slice( arguments, 2 ),
4641                     instance;
4642     
4643                 clients[ uid ] = client;
4644     
4645                 if ( components[ comp ] ) {
4646                     if ( !pool[ uid ] ) {
4647                         pool[ uid ] = new components[ comp ]( client, me );
4648                     }
4649     
4650                     instance = pool[ uid ];
4651     
4652                     if ( instance[ fn ] ) {
4653                         return instance[ fn ].apply( instance, args );
4654                     }
4655                 }
4656     
4657                 return me.flashExec.apply( client, arguments );
4658             };
4659     
4660             function handler( evt, obj ) {
4661                 var type = evt.type || evt,
4662                     parts, uid;
4663     
4664                 parts = type.split('::');
4665                 uid = parts[ 0 ];
4666                 type = parts[ 1 ];
4667     
4668                 // console.log.apply( console, arguments );
4669     
4670                 if ( type === 'Ready' && uid === me.uid ) {
4671                     me.trigger('ready');
4672                 } else if ( clients[ uid ] ) {
4673                     clients[ uid ].trigger( type.toLowerCase(), evt, obj );
4674                 }
4675     
4676                 // Base.log( evt, obj );
4677             }
4678     
4679             // flash的接受器。
4680             window[ jsreciver ] = function() {
4681                 var args = arguments;
4682     
4683                 // 为了能捕获得到。
4684                 setTimeout(function() {
4685                     handler.apply( null, args );
4686                 }, 1 );
4687             };
4688     
4689             this.jsreciver = jsreciver;
4690     
4691             this.destroy = function() {
4692                 // @todo 删除池子中的所有实例
4693                 return destroy && destroy.apply( this, arguments );
4694             };
4695     
4696             this.flashExec = function( comp, fn ) {
4697                 var flash = me.getFlash(),
4698                     args = Base.slice( arguments, 2 );
4699     
4700                 return flash.exec( this.uid, comp, fn, args );
4701             };
4702     
4703             // @todo
4704         }
4705     
4706         Base.inherits( Runtime, {
4707             constructor: FlashRuntime,
4708     
4709             init: function() {
4710                 var container = this.getContainer(),
4711                     opts = this.options,
4712                     html;
4713     
4714                 // if not the minimal height, shims are not initialized
4715                 // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)
4716                 container.css({
4717                     position: 'absolute',
4718                     top: '-8px',
4719                     left: '-8px',
4720                     width: '9px',
4721                     height: '9px',
4722                     overflow: 'hidden'
4723                 });
4724     
4725                 // insert flash object
4726                 html = '<object id="' + this.uid + '" type="application/' +
4727                         'x-shockwave-flash" data="' +  opts.swf + '" ';
4728     
4729                 if ( Base.browser.ie ) {
4730                     html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
4731                 }
4732     
4733                 html += 'width="100%" height="100%" style="outline:0">'  +
4734                     '<param name="movie" value="' + opts.swf + '" />' +
4735                     '<param name="flashvars" value="uid=' + this.uid +
4736                     '&jsreciver=' + this.jsreciver + '" />' +
4737                     '<param name="wmode" value="transparent" />' +
4738                     '<param name="allowscriptaccess" value="always" />' +
4739                 '</object>';
4740     
4741                 container.html( html );
4742             },
4743     
4744             getFlash: function() {
4745                 if ( this._flash ) {
4746                     return this._flash;
4747                 }
4748     
4749                 this._flash = $( '#' + this.uid ).get( 0 );
4750                 return this._flash;
4751             }
4752     
4753         });
4754     
4755         FlashRuntime.register = function( name, component ) {
4756             component = components[ name ] = Base.inherits( CompBase, $.extend({
4757     
4758                 // @todo fix this later
4759                 flashExec: function() {
4760                     var owner = this.owner,
4761                         runtime = this.getRuntime();
4762     
4763                     return runtime.flashExec.apply( owner, arguments );
4764                 }
4765             }, component ) );
4766     
4767             return component;
4768         };
4769     
4770         if ( getFlashVersion() >= 11.4 ) {
4771             Runtime.addRuntime( type, FlashRuntime );
4772         }
4773     
4774         return FlashRuntime;
4775     });
4776     /**
4777      * @fileOverview FilePicker
4778      */
4779     define('runtime/flash/filepicker',[
4780         'base',
4781         'runtime/flash/runtime'
4782     ], function( Base, FlashRuntime ) {
4783         var $ = Base.$;
4784     
4785         return FlashRuntime.register( 'FilePicker', {
4786             init: function( opts ) {
4787                 var copy = $.extend({}, opts ),
4788                     len, i;
4789     
4790                 // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.
4791                 len = copy.accept && copy.accept.length;
4792                 for (  i = 0; i < len; i++ ) {
4793                     if ( !copy.accept[ i ].title ) {
4794                         copy.accept[ i ].title = 'Files';
4795                     }
4796                 }
4797     
4798                 delete copy.button;
4799                 delete copy.id;
4800                 delete copy.container;
4801     
4802                 this.flashExec( 'FilePicker', 'init', copy );
4803             },
4804     
4805             destroy: function() {
4806                 this.flashExec( 'FilePicker', 'destroy' );
4807             }
4808         });
4809     });
4810     /**
4811      * @fileOverview  Transport flash实现
4812      */
4813     define('runtime/flash/transport',[
4814         'base',
4815         'runtime/flash/runtime',
4816         'runtime/client'
4817     ], function( Base, FlashRuntime, RuntimeClient ) {
4818         var $ = Base.$;
4819     
4820         return FlashRuntime.register( 'Transport', {
4821             init: function() {
4822                 this._status = 0;
4823                 this._response = null;
4824                 this._responseJson = null;
4825             },
4826     
4827             send: function() {
4828                 var owner = this.owner,
4829                     opts = this.options,
4830                     xhr = this._initAjax(),
4831                     blob = owner._blob,
4832                     server = opts.server,
4833                     binary;
4834     
4835                 xhr.connectRuntime( blob.ruid );
4836     
4837                 if ( opts.sendAsBinary ) {
4838                     server += (/\?/.test( server ) ? '&' : '?') +
4839                             $.param( owner._formData );
4840     
4841                     binary = blob.uid;
4842                 } else {
4843                     $.each( owner._formData, function( k, v ) {
4844                         xhr.exec( 'append', k, v );
4845                     });
4846     
4847                     xhr.exec( 'appendBlob', opts.fileVal, blob.uid,
4848                             opts.filename || owner._formData.name || '' );
4849                 }
4850     
4851                 this._setRequestHeader( xhr, opts.headers );
4852                 xhr.exec( 'send', {
4853                     method: opts.method,
4854                     url: server,
4855                     forceURLStream: opts.forceURLStream,
4856                     mimeType: 'application/octet-stream'
4857                 }, binary );
4858             },
4859     
4860             getStatus: function() {
4861                 return this._status;
4862             },
4863     
4864             getResponse: function() {
4865                 return this._response || '';
4866             },
4867     
4868             getResponseAsJson: function() {
4869                 return this._responseJson;
4870             },
4871     
4872             abort: function() {
4873                 var xhr = this._xhr;
4874     
4875                 if ( xhr ) {
4876                     xhr.exec('abort');
4877                     xhr.destroy();
4878                     this._xhr = xhr = null;
4879                 }
4880             },
4881     
4882             destroy: function() {
4883                 this.abort();
4884             },
4885     
4886             _initAjax: function() {
4887                 var me = this,
4888                     xhr = new RuntimeClient('XMLHttpRequest');
4889     
4890                 xhr.on( 'uploadprogress progress', function( e ) {
4891                     var percent = e.loaded / e.total;
4892                     percent = Math.min( 1, Math.max( 0, percent ) );
4893                     return me.trigger( 'progress', percent );
4894                 });
4895     
4896                 xhr.on( 'load', function() {
4897                     var status = xhr.exec('getStatus'),
4898                         readBody = false,
4899                         err = '',
4900                         p;
4901     
4902                     xhr.off();
4903                     me._xhr = null;
4904     
4905                     if ( status >= 200 && status < 300 ) {
4906                         readBody = true;
4907                     } else if ( status >= 500 && status < 600 ) {
4908                         readBody = true;
4909                         err = 'server';
4910                     } else {
4911                         err = 'http';
4912                     }
4913     
4914                     if ( readBody ) {
4915                         me._response = xhr.exec('getResponse');
4916                         me._response = decodeURIComponent( me._response );
4917     
4918                         // flash 处理可能存在 bug, 没辙只能靠 js 了
4919                         // try {
4920                         //     me._responseJson = xhr.exec('getResponseAsJson');
4921                         // } catch ( error ) {
4922                             
4923                         p = window.JSON && window.JSON.parse || function( s ) {
4924                             try {
4925                                 return new Function('return ' + s).call();
4926                             } catch ( err ) {
4927                                 return {};
4928                             }
4929                         };
4930                         me._responseJson  = me._response ? p(me._response) : {};
4931                             
4932                         // }
4933                     }
4934                     
4935                     xhr.destroy();
4936                     xhr = null;
4937     
4938                     return err ? me.trigger( 'error', err ) : me.trigger('load');
4939                 });
4940     
4941                 xhr.on( 'error', function() {
4942                     xhr.off();
4943                     me._xhr = null;
4944                     me.trigger( 'error', 'http' );
4945                 });
4946     
4947                 me._xhr = xhr;
4948                 return xhr;
4949             },
4950     
4951             _setRequestHeader: function( xhr, headers ) {
4952                 $.each( headers, function( key, val ) {
4953                     xhr.exec( 'setRequestHeader', key, val );
4954                 });
4955             }
4956         });
4957     });
4958     /**
4959      * @fileOverview 没有图像处理的版本。
4960      */
4961     define('preset/withoutimage',[
4962         'base',
4963     
4964         // widgets
4965         'widgets/filednd',
4966         'widgets/filepaste',
4967         'widgets/filepicker',
4968         'widgets/queue',
4969         'widgets/runtime',
4970         'widgets/upload',
4971         'widgets/validator',
4972     
4973         // runtimes
4974         // html5
4975         'runtime/html5/blob',
4976         'runtime/html5/dnd',
4977         'runtime/html5/filepaste',
4978         'runtime/html5/filepicker',
4979         'runtime/html5/transport',
4980     
4981         // flash
4982         'runtime/flash/filepicker',
4983         'runtime/flash/transport'
4984     ], function( Base ) {
4985         return Base;
4986     });
4987     define('webuploader',[
4988         'preset/withoutimage'
4989     ], function( preset ) {
4990         return preset;
4991     });
4992     return require('webuploader');
4993 });