hjg
2024-10-30 8cf23534166c07e711aac2a25911ada317ba01f0
提交 | 用户 | 时间
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|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。
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         /**
2702          * @property {Object} [runtimeOrder=html5,flash]
2703          * @namespace options
2704          * @for Uploader
2705          * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash.
2706          *
2707          * 可以将此值设置成 `flash`,来强制使用 flash 运行时。
2708          */
2709     
2710         return Uploader.register({
2711             name: 'runtime',
2712     
2713             init: function() {
2714                 if ( !this.predictRuntimeType() ) {
2715                     throw Error('Runtime Error');
2716                 }
2717             },
2718     
2719             /**
2720              * 预测Uploader将采用哪个`Runtime`
2721              * @grammar predictRuntimeType() => String
2722              * @method predictRuntimeType
2723              * @for  Uploader
2724              */
2725             predictRuntimeType: function() {
2726                 var orders = this.options.runtimeOrder || Runtime.orders,
2727                     type = this.type,
2728                     i, len;
2729     
2730                 if ( !type ) {
2731                     orders = orders.split( /\s*,\s*/g );
2732     
2733                     for ( i = 0, len = orders.length; i < len; i++ ) {
2734                         if ( Runtime.hasRuntime( orders[ i ] ) ) {
2735                             this.type = type = orders[ i ];
2736                             break;
2737                         }
2738                     }
2739                 }
2740     
2741                 return type;
2742             }
2743         });
2744     });
2745     /**
2746      * @fileOverview Transport
2747      */
2748     define('lib/transport',[
2749         'base',
2750         'runtime/client',
2751         'mediator'
2752     ], function( Base, RuntimeClient, Mediator ) {
2753     
2754         var $ = Base.$;
2755     
2756         function Transport( opts ) {
2757             var me = this;
2758     
2759             opts = me.options = $.extend( true, {}, Transport.options, opts || {} );
2760             RuntimeClient.call( this, 'Transport' );
2761     
2762             this._blob = null;
2763             this._formData = opts.formData || {};
2764             this._headers = opts.headers || {};
2765     
2766             this.on( 'progress', this._timeout );
2767             this.on( 'load error', function() {
2768                 me.trigger( 'progress', 1 );
2769                 clearTimeout( me._timer );
2770             });
2771         }
2772     
2773         Transport.options = {
2774             server: '',
2775             method: 'POST',
2776     
2777             // 跨域时,是否允许携带cookie, 只有html5 runtime才有效
2778             withCredentials: false,
2779             fileVal: 'file',
2780             timeout: 2 * 60 * 1000,    // 2分钟
2781             formData: {},
2782             headers: {},
2783             sendAsBinary: false
2784         };
2785     
2786         $.extend( Transport.prototype, {
2787     
2788             // 添加Blob, 只能添加一次,最后一次有效。
2789             appendBlob: function( key, blob, filename ) {
2790                 var me = this,
2791                     opts = me.options;
2792     
2793                 if ( me.getRuid() ) {
2794                     me.disconnectRuntime();
2795                 }
2796     
2797                 // 连接到blob归属的同一个runtime.
2798                 me.connectRuntime( blob.ruid, function() {
2799                     me.exec('init');
2800                 });
2801     
2802                 me._blob = blob;
2803                 opts.fileVal = key || opts.fileVal;
2804                 opts.filename = filename || opts.filename;
2805             },
2806     
2807             // 添加其他字段
2808             append: function( key, value ) {
2809                 if ( typeof key === 'object' ) {
2810                     $.extend( this._formData, key );
2811                 } else {
2812                     this._formData[ key ] = value;
2813                 }
2814             },
2815     
2816             setRequestHeader: function( key, value ) {
2817                 if ( typeof key === 'object' ) {
2818                     $.extend( this._headers, key );
2819                 } else {
2820                     this._headers[ key ] = value;
2821                 }
2822             },
2823     
2824             send: function( method ) {
2825                 this.exec( 'send', method );
2826                 this._timeout();
2827             },
2828     
2829             abort: function() {
2830                 clearTimeout( this._timer );
2831                 return this.exec('abort');
2832             },
2833     
2834             destroy: function() {
2835                 this.trigger('destroy');
2836                 this.off();
2837                 this.exec('destroy');
2838                 this.disconnectRuntime();
2839             },
2840     
2841             getResponse: function() {
2842                 return this.exec('getResponse');
2843             },
2844     
2845             getResponseAsJson: function() {
2846                 return this.exec('getResponseAsJson');
2847             },
2848     
2849             getStatus: function() {
2850                 return this.exec('getStatus');
2851             },
2852     
2853             _timeout: function() {
2854                 var me = this,
2855                     duration = me.options.timeout;
2856     
2857                 if ( !duration ) {
2858                     return;
2859                 }
2860     
2861                 clearTimeout( me._timer );
2862                 me._timer = setTimeout(function() {
2863                     me.abort();
2864                     me.trigger( 'error', 'timeout' );
2865                 }, duration );
2866             }
2867     
2868         });
2869     
2870         // 让Transport具备事件功能。
2871         Mediator.installTo( Transport.prototype );
2872     
2873         return Transport;
2874     });
2875     /**
2876      * @fileOverview 负责文件上传相关。
2877      */
2878     define('widgets/upload',[
2879         'base',
2880         'uploader',
2881         'file',
2882         'lib/transport',
2883         'widgets/widget'
2884     ], function( Base, Uploader, WUFile, Transport ) {
2885     
2886         var $ = Base.$,
2887             isPromise = Base.isPromise,
2888             Status = WUFile.Status;
2889     
2890         // 添加默认配置项
2891         $.extend( Uploader.options, {
2892     
2893     
2894             /**
2895              * @property {Boolean} [prepareNextFile=false]
2896              * @namespace options
2897              * @for Uploader
2898              * @description 是否允许在文件传输时提前把下一个文件准备好。
2899              * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。
2900              * 如果能提前在当前文件传输期处理,可以节省总体耗时。
2901              */
2902             prepareNextFile: false,
2903     
2904             /**
2905              * @property {Boolean} [chunked=false]
2906              * @namespace options
2907              * @for Uploader
2908              * @description 是否要分片处理大文件上传。
2909              */
2910             chunked: false,
2911     
2912             /**
2913              * @property {Boolean} [chunkSize=5242880]
2914              * @namespace options
2915              * @for Uploader
2916              * @description 如果要分片,分多大一片? 默认大小为5M.
2917              */
2918             chunkSize: 5 * 1024 * 1024,
2919     
2920             /**
2921              * @property {Boolean} [chunkRetry=2]
2922              * @namespace options
2923              * @for Uploader
2924              * @description 如果某个分片由于网络问题出错,允许自动重传多少次?
2925              */
2926             chunkRetry: 2,
2927     
2928             /**
2929              * @property {Boolean} [threads=3]
2930              * @namespace options
2931              * @for Uploader
2932              * @description 上传并发数。允许同时最大上传进程数。
2933              */
2934             threads: 3,
2935     
2936     
2937             /**
2938              * @property {Object} [formData={}]
2939              * @namespace options
2940              * @for Uploader
2941              * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。
2942              */
2943             formData: {}
2944     
2945             /**
2946              * @property {Object} [fileVal='file']
2947              * @namespace options
2948              * @for Uploader
2949              * @description 设置文件上传域的name。
2950              */
2951     
2952             /**
2953              * @property {Object} [method='POST']
2954              * @namespace options
2955              * @for Uploader
2956              * @description 文件上传方式,`POST`或者`GET`。
2957              */
2958     
2959             /**
2960              * @property {Object} [sendAsBinary=false]
2961              * @namespace options
2962              * @for Uploader
2963              * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容,
2964              * 其他参数在$_GET数组中。
2965              */
2966         });
2967     
2968         // 负责将文件切片。
2969         function CuteFile( file, chunkSize ) {
2970             var pending = [],
2971                 blob = file.source,
2972                 total = blob.size,
2973                 chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,
2974                 start = 0,
2975                 index = 0,
2976                 len, api;
2977     
2978             api = {
2979                 file: file,
2980     
2981                 has: function() {
2982                     return !!pending.length;
2983                 },
2984     
2985                 shift: function() {
2986                     return pending.shift();
2987                 },
2988     
2989                 unshift: function( block ) {
2990                     pending.unshift( block );
2991                 }
2992             };
2993     
2994             while ( index < chunks ) {
2995                 len = Math.min( chunkSize, total - start );
2996     
2997                 pending.push({
2998                     file: file,
2999                     start: start,
3000                     end: chunkSize ? (start + len) : total,
3001                     total: total,
3002                     chunks: chunks,
3003                     chunk: index++,
3004                     cuted: api
3005                 });
3006                 start += len;
3007             }
3008     
3009             file.blocks = pending.concat();
3010             file.remaning = pending.length;
3011     
3012             return api;
3013         }
3014     
3015         Uploader.register({
3016             name: 'upload',
3017     
3018             init: function() {
3019                 var owner = this.owner,
3020                     me = this;
3021     
3022                 this.runing = false;
3023                 this.progress = false;
3024     
3025                 owner
3026                     .on( 'startUpload', function() {
3027                         me.progress = true;
3028                     })
3029                     .on( 'uploadFinished', function() {
3030                         me.progress = false;
3031                     });
3032     
3033                 // 记录当前正在传的数据,跟threads相关
3034                 this.pool = [];
3035     
3036                 // 缓存分好片的文件。
3037                 this.stack = [];
3038     
3039                 // 缓存即将上传的文件。
3040                 this.pending = [];
3041     
3042                 // 跟踪还有多少分片在上传中但是没有完成上传。
3043                 this.remaning = 0;
3044                 this.__tick = Base.bindFn( this._tick, this );
3045     
3046                 owner.on( 'uploadComplete', function( file ) {
3047     
3048                     // 把其他块取消了。
3049                     file.blocks && $.each( file.blocks, function( _, v ) {
3050                         v.transport && (v.transport.abort(), v.transport.destroy());
3051                         delete v.transport;
3052                     });
3053     
3054                     delete file.blocks;
3055                     delete file.remaning;
3056                 });
3057             },
3058     
3059             reset: function() {
3060                 this.request( 'stop-upload', true );
3061                 this.runing = false;
3062                 this.pool = [];
3063                 this.stack = [];
3064                 this.pending = [];
3065                 this.remaning = 0;
3066                 this._trigged = false;
3067                 this._promise = null;
3068             },
3069     
3070             /**
3071              * @event startUpload
3072              * @description 当开始上传流程时触发。
3073              * @for  Uploader
3074              */
3075     
3076             /**
3077              * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。
3078              *
3079              * 可以指定开始某一个文件。
3080              * @grammar upload() => undefined
3081              * @grammar upload( file | fileId) => undefined
3082              * @method upload
3083              * @for  Uploader
3084              */
3085             startUpload: function(file) {
3086                 var me = this;
3087     
3088                 // 移出invalid的文件
3089                 $.each( me.request( 'get-files', Status.INVALID ), function() {
3090                     me.request( 'remove-file', this );
3091                 });
3092     
3093                 // 如果指定了开始某个文件,则只开始指定文件。
3094                 if ( file ) {
3095                     file = file.id ? file : me.request( 'get-file', file );
3096     
3097                     if (file.getStatus() === Status.INTERRUPT) {
3098                         $.each( me.pool, function( _, v ) {
3099     
3100                             // 之前暂停过。
3101                             if (v.file !== file) {
3102                                 return;
3103                             }
3104     
3105                             v.transport && v.transport.send();
3106                         });
3107     
3108                         file.setStatus( Status.QUEUED );
3109                     } else if (file.getStatus() === Status.PROGRESS) {
3110                         return;
3111                     } else {
3112                         file.setStatus( Status.QUEUED );
3113                     }
3114                 } else {
3115                     $.each( me.request( 'get-files', [ Status.INITED ] ), function() {
3116                         this.setStatus( Status.QUEUED );
3117                     });
3118                 }
3119     
3120                 if ( me.runing ) {
3121                     return;
3122                 }
3123     
3124                 me.runing = true;
3125     
3126                 var files = [];
3127     
3128                 // 如果有暂停的,则续传
3129                 $.each( me.pool, function( _, v ) {
3130                     var file = v.file;
3131     
3132                     if ( file.getStatus() === Status.INTERRUPT ) {
3133                         files.push(file);
3134                         me._trigged = false;
3135                         v.transport && v.transport.send();
3136                     }
3137                 });
3138     
3139                 var file;
3140                 while ( (file = files.shift()) ) {
3141                     file.setStatus( Status.PROGRESS );
3142                 }
3143     
3144                 file || $.each( me.request( 'get-files',
3145                         Status.INTERRUPT ), function() {
3146                     this.setStatus( Status.PROGRESS );
3147                 });
3148     
3149                 me._trigged = false;
3150                 Base.nextTick( me.__tick );
3151                 me.owner.trigger('startUpload');
3152             },
3153     
3154             /**
3155              * @event stopUpload
3156              * @description 当开始上传流程暂停时触发。
3157              * @for  Uploader
3158              */
3159     
3160             /**
3161              * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。
3162              *
3163              * 如果第一个参数是文件,则只暂停指定文件。
3164              * @grammar stop() => undefined
3165              * @grammar stop( true ) => undefined
3166              * @grammar stop( file ) => undefined
3167              * @method stop
3168              * @for  Uploader
3169              */
3170             stopUpload: function( file, interrupt ) {
3171                 var me = this;
3172     
3173                 if (file === true) {
3174                     interrupt = file;
3175                     file = null;
3176                 }
3177     
3178                 if ( me.runing === false ) {
3179                     return;
3180                 }
3181     
3182                 // 如果只是暂停某个文件。
3183                 if ( file ) {
3184                     file = file.id ? file : me.request( 'get-file', file );
3185     
3186                     if ( file.getStatus() !== Status.PROGRESS &&
3187                             file.getStatus() !== Status.QUEUED ) {
3188                         return;
3189                     }
3190     
3191                     file.setStatus( Status.INTERRUPT );
3192                     $.each( me.pool, function( _, v ) {
3193     
3194                         // 只 abort 指定的文件。
3195                         if (v.file !== file) {
3196                             return;
3197                         }
3198     
3199                         v.transport && v.transport.abort();
3200                         me._putback(v);
3201                         me._popBlock(v);
3202                     });
3203     
3204                     return Base.nextTick( me.__tick );
3205                 }
3206     
3207                 me.runing = false;
3208     
3209                 if (this._promise && this._promise.file) {
3210                     this._promise.file.setStatus( Status.INTERRUPT );
3211                 }
3212     
3213                 interrupt && $.each( me.pool, function( _, v ) {
3214                     v.transport && v.transport.abort();
3215                     v.file.setStatus( Status.INTERRUPT );
3216                 });
3217     
3218                 me.owner.trigger('stopUpload');
3219             },
3220     
3221             /**
3222              * @method cancelFile
3223              * @grammar cancelFile( file ) => undefined
3224              * @grammar cancelFile( id ) => undefined
3225              * @param {File|id} file File对象或这File对象的id
3226              * @description 标记文件状态为已取消, 同时将中断文件传输。
3227              * @for  Uploader
3228              * @example
3229              *
3230              * $li.on('click', '.remove-this', function() {
3231              *     uploader.cancelFile( file );
3232              * })
3233              */
3234             cancelFile: function( file ) {
3235                 file = file.id ? file : this.request( 'get-file', file );
3236     
3237                 // 如果正在上传。
3238                 file.blocks && $.each( file.blocks, function( _, v ) {
3239                     var _tr = v.transport;
3240     
3241                     if ( _tr ) {
3242                         _tr.abort();
3243                         _tr.destroy();
3244                         delete v.transport;
3245                     }
3246                 });
3247     
3248                 file.setStatus( Status.CANCELLED );
3249                 this.owner.trigger( 'fileDequeued', file );
3250             },
3251     
3252             /**
3253              * 判断`Uplaode`r是否正在上传中。
3254              * @grammar isInProgress() => Boolean
3255              * @method isInProgress
3256              * @for  Uploader
3257              */
3258             isInProgress: function() {
3259                 return !!this.progress;
3260             },
3261     
3262             _getStats: function() {
3263                 return this.request('get-stats');
3264             },
3265     
3266             /**
3267              * 掉过一个文件上传,直接标记指定文件为已上传状态。
3268              * @grammar skipFile( file ) => undefined
3269              * @method skipFile
3270              * @for  Uploader
3271              */
3272             skipFile: function( file, status ) {
3273                 file = file.id ? file : this.request( 'get-file', file );
3274     
3275                 file.setStatus( status || Status.COMPLETE );
3276                 file.skipped = true;
3277     
3278                 // 如果正在上传。
3279                 file.blocks && $.each( file.blocks, function( _, v ) {
3280                     var _tr = v.transport;
3281     
3282                     if ( _tr ) {
3283                         _tr.abort();
3284                         _tr.destroy();
3285                         delete v.transport;
3286                     }
3287                 });
3288     
3289                 this.owner.trigger( 'uploadSkip', file );
3290             },
3291     
3292             /**
3293              * @event uploadFinished
3294              * @description 当所有文件上传结束时触发。
3295              * @for  Uploader
3296              */
3297             _tick: function() {
3298                 var me = this,
3299                     opts = me.options,
3300                     fn, val;
3301     
3302                 // 上一个promise还没有结束,则等待完成后再执行。
3303                 if ( me._promise ) {
3304                     return me._promise.always( me.__tick );
3305                 }
3306     
3307                 // 还有位置,且还有文件要处理的话。
3308                 if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {
3309                     me._trigged = false;
3310     
3311                     fn = function( val ) {
3312                         me._promise = null;
3313     
3314                         // 有可能是reject过来的,所以要检测val的类型。
3315                         val && val.file && me._startSend( val );
3316                         Base.nextTick( me.__tick );
3317                     };
3318     
3319                     me._promise = isPromise( val ) ? val.always( fn ) : fn( val );
3320     
3321                 // 没有要上传的了,且没有正在传输的了。
3322                 } else if ( !me.remaning && !me._getStats().numOfQueue &&
3323                     !me._getStats().numofInterrupt ) {
3324                     me.runing = false;
3325     
3326                     me._trigged || Base.nextTick(function() {
3327                         me.owner.trigger('uploadFinished');
3328                     });
3329                     me._trigged = true;
3330                 }
3331             },
3332     
3333             _putback: function(block) {
3334                 var idx;
3335     
3336                 block.cuted.unshift(block);
3337                 idx = this.stack.indexOf(block.cuted);
3338     
3339                 if (!~idx) {
3340                     this.stack.unshift(block.cuted);
3341                 }
3342             },
3343     
3344             _getStack: function() {
3345                 var i = 0,
3346                     act;
3347     
3348                 while ( (act = this.stack[ i++ ]) ) {
3349                     if ( act.has() && act.file.getStatus() === Status.PROGRESS ) {
3350                         return act;
3351                     } else if (!act.has() ||
3352                             act.file.getStatus() !== Status.PROGRESS &&
3353                             act.file.getStatus() !== Status.INTERRUPT ) {
3354     
3355                         // 把已经处理完了的,或者,状态为非 progress(上传中)、
3356                         // interupt(暂停中) 的移除。
3357                         this.stack.splice( --i, 1 );
3358                     }
3359                 }
3360     
3361                 return null;
3362             },
3363     
3364             _nextBlock: function() {
3365                 var me = this,
3366                     opts = me.options,
3367                     act, next, done, preparing;
3368     
3369                 // 如果当前文件还有没有需要传输的,则直接返回剩下的。
3370                 if ( (act = this._getStack()) ) {
3371     
3372                     // 是否提前准备下一个文件
3373                     if ( opts.prepareNextFile && !me.pending.length ) {
3374                         me._prepareNextFile();
3375                     }
3376     
3377                     return act.shift();
3378     
3379                 // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。
3380                 } else if ( me.runing ) {
3381     
3382                     // 如果缓存中有,则直接在缓存中取,没有则去queue中取。
3383                     if ( !me.pending.length && me._getStats().numOfQueue ) {
3384                         me._prepareNextFile();
3385                     }
3386     
3387                     next = me.pending.shift();
3388                     done = function( file ) {
3389                         if ( !file ) {
3390                             return null;
3391                         }
3392     
3393                         act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );
3394                         me.stack.push(act);
3395                         return act.shift();
3396                     };
3397     
3398                     // 文件可能还在prepare中,也有可能已经完全准备好了。
3399                     if ( isPromise( next) ) {
3400                         preparing = next.file;
3401                         next = next[ next.pipe ? 'pipe' : 'then' ]( done );
3402                         next.file = preparing;
3403                         return next;
3404                     }
3405     
3406                     return done( next );
3407                 }
3408             },
3409     
3410     
3411             /**
3412              * @event uploadStart
3413              * @param {File} file File对象
3414              * @description 某个文件开始上传前触发,一个文件只会触发一次。
3415              * @for  Uploader
3416              */
3417             _prepareNextFile: function() {
3418                 var me = this,
3419                     file = me.request('fetch-file'),
3420                     pending = me.pending,
3421                     promise;
3422     
3423                 if ( file ) {
3424                     promise = me.request( 'before-send-file', file, function() {
3425     
3426                         // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued.
3427                         if ( file.getStatus() === Status.PROGRESS ||
3428                             file.getStatus() === Status.INTERRUPT ) {
3429                             return file;
3430                         }
3431     
3432                         return me._finishFile( file );
3433                     });
3434     
3435                     me.owner.trigger( 'uploadStart', file );
3436                     file.setStatus( Status.PROGRESS );
3437     
3438                     promise.file = file;
3439     
3440                     // 如果还在pending中,则替换成文件本身。
3441                     promise.done(function() {
3442                         var idx = $.inArray( promise, pending );
3443     
3444                         ~idx && pending.splice( idx, 1, file );
3445                     });
3446     
3447                     // befeore-send-file的钩子就有错误发生。
3448                     promise.fail(function( reason ) {
3449                         file.setStatus( Status.ERROR, reason );
3450                         me.owner.trigger( 'uploadError', file, reason );
3451                         me.owner.trigger( 'uploadComplete', file );
3452                     });
3453     
3454                     pending.push( promise );
3455                 }
3456             },
3457     
3458             // 让出位置了,可以让其他分片开始上传
3459             _popBlock: function( block ) {
3460                 var idx = $.inArray( block, this.pool );
3461     
3462                 this.pool.splice( idx, 1 );
3463                 block.file.remaning--;
3464                 this.remaning--;
3465             },
3466     
3467             // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。
3468             _startSend: function( block ) {
3469                 var me = this,
3470                     file = block.file,
3471                     promise;
3472     
3473                 // 有可能在 before-send-file 的 promise 期间改变了文件状态。
3474                 // 如:暂停,取消
3475                 // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。
3476                 if ( file.getStatus() !== Status.PROGRESS ) {
3477     
3478                     // 如果是中断,则还需要放回去。
3479                     if (file.getStatus() === Status.INTERRUPT) {
3480                         me._putback(block);
3481                     }
3482     
3483                     return;
3484                 }
3485     
3486                 me.pool.push( block );
3487                 me.remaning++;
3488     
3489                 // 如果没有分片,则直接使用原始的。
3490                 // 不会丢失content-type信息。
3491                 block.blob = block.chunks === 1 ? file.source :
3492                         file.source.slice( block.start, block.end );
3493     
3494                 // hook, 每个分片发送之前可能要做些异步的事情。
3495                 promise = me.request( 'before-send', block, function() {
3496     
3497                     // 有可能文件已经上传出错了,所以不需要再传输了。
3498                     if ( file.getStatus() === Status.PROGRESS ) {
3499                         me._doSend( block );
3500                     } else {
3501                         me._popBlock( block );
3502                         Base.nextTick( me.__tick );
3503                     }
3504                 });
3505     
3506                 // 如果为fail了,则跳过此分片。
3507                 promise.fail(function() {
3508                     if ( file.remaning === 1 ) {
3509                         me._finishFile( file ).always(function() {
3510                             block.percentage = 1;
3511                             me._popBlock( block );
3512                             me.owner.trigger( 'uploadComplete', file );
3513                             Base.nextTick( me.__tick );
3514                         });
3515                     } else {
3516                         block.percentage = 1;
3517                         me.updateFileProgress( file );
3518                         me._popBlock( block );
3519                         Base.nextTick( me.__tick );
3520                     }
3521                 });
3522             },
3523     
3524     
3525             /**
3526              * @event uploadBeforeSend
3527              * @param {Object} object
3528              * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。
3529              * @param {Object} headers 可以扩展此对象来控制上传头部。
3530              * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。
3531              * @for  Uploader
3532              */
3533     
3534             /**
3535              * @event uploadAccept
3536              * @param {Object} object
3537              * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。
3538              * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。
3539              * @for  Uploader
3540              */
3541     
3542             /**
3543              * @event uploadProgress
3544              * @param {File} file File对象
3545              * @param {Number} percentage 上传进度
3546              * @description 上传过程中触发,携带上传进度。
3547              * @for  Uploader
3548              */
3549     
3550     
3551             /**
3552              * @event uploadError
3553              * @param {File} file File对象
3554              * @param {String} reason 出错的code
3555              * @description 当文件上传出错时触发。
3556              * @for  Uploader
3557              */
3558     
3559             /**
3560              * @event uploadSuccess
3561              * @param {File} file File对象
3562              * @param {Object} response 服务端返回的数据
3563              * @description 当文件上传成功时触发。
3564              * @for  Uploader
3565              */
3566     
3567             /**
3568              * @event uploadComplete
3569              * @param {File} [file] File对象
3570              * @description 不管成功或者失败,文件上传完成时触发。
3571              * @for  Uploader
3572              */
3573     
3574             // 做上传操作。
3575             _doSend: function( block ) {
3576                 var me = this,
3577                     owner = me.owner,
3578                     opts = me.options,
3579                     file = block.file,
3580                     tr = new Transport( opts ),
3581                     data = $.extend({}, opts.formData ),
3582                     headers = $.extend({}, opts.headers ),
3583                     requestAccept, ret;
3584     
3585                 block.transport = tr;
3586     
3587                 tr.on( 'destroy', function() {
3588                     delete block.transport;
3589                     me._popBlock( block );
3590                     Base.nextTick( me.__tick );
3591                 });
3592     
3593                 // 广播上传进度。以文件为单位。
3594                 tr.on( 'progress', function( percentage ) {
3595                     block.percentage = percentage;
3596                     me.updateFileProgress( file );
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             updateFileProgress: function(file) {
3708                 var totalPercent = 0,
3709                     uploaded = 0;
3710     
3711                 if (!file.blocks) {
3712                     return;
3713                 }
3714     
3715                 $.each( file.blocks, function( _, v ) {
3716                     uploaded += (v.percentage || 0) * (v.end - v.start);
3717                 });
3718     
3719                 totalPercent = uploaded / file.size;
3720                 this.owner.trigger( 'uploadProgress', file, totalPercent || 0 );
3721             }
3722     
3723         });
3724     });
3725     /**
3726      * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。
3727      */
3728     
3729     define('widgets/validator',[
3730         'base',
3731         'uploader',
3732         'file',
3733         'widgets/widget'
3734     ], function( Base, Uploader, WUFile ) {
3735     
3736         var $ = Base.$,
3737             validators = {},
3738             api;
3739     
3740         /**
3741          * @event error
3742          * @param {String} type 错误类型。
3743          * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。
3744          *
3745          * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。
3746          * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。
3747          * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。
3748          * @for  Uploader
3749          */
3750     
3751         // 暴露给外面的api
3752         api = {
3753     
3754             // 添加验证器
3755             addValidator: function( type, cb ) {
3756                 validators[ type ] = cb;
3757             },
3758     
3759             // 移除验证器
3760             removeValidator: function( type ) {
3761                 delete validators[ type ];
3762             }
3763         };
3764     
3765         // 在Uploader初始化的时候启动Validators的初始化
3766         Uploader.register({
3767             name: 'validator',
3768     
3769             init: function() {
3770                 var me = this;
3771                 Base.nextTick(function() {
3772                     $.each( validators, function() {
3773                         this.call( me.owner );
3774                     });
3775                 });
3776             }
3777         });
3778     
3779         /**
3780          * @property {int} [fileNumLimit=undefined]
3781          * @namespace options
3782          * @for Uploader
3783          * @description 验证文件总数量, 超出则不允许加入队列。
3784          */
3785         api.addValidator( 'fileNumLimit', function() {
3786             var uploader = this,
3787                 opts = uploader.options,
3788                 count = 0,
3789                 max = parseInt( opts.fileNumLimit, 10 ),
3790                 flag = true;
3791     
3792             if ( !max ) {
3793                 return;
3794             }
3795     
3796             uploader.on( 'beforeFileQueued', function( file ) {
3797     
3798                 if ( count >= max && flag ) {
3799                     flag = false;
3800                     this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );
3801                     setTimeout(function() {
3802                         flag = true;
3803                     }, 1 );
3804                 }
3805     
3806                 return count >= max ? false : true;
3807             });
3808     
3809             uploader.on( 'fileQueued', function() {
3810                 count++;
3811             });
3812     
3813             uploader.on( 'fileDequeued', function() {
3814                 count--;
3815             });
3816     
3817             uploader.on( 'reset', function() {
3818                 count = 0;
3819             });
3820         });
3821     
3822     
3823         /**
3824          * @property {int} [fileSizeLimit=undefined]
3825          * @namespace options
3826          * @for Uploader
3827          * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。
3828          */
3829         api.addValidator( 'fileSizeLimit', function() {
3830             var uploader = this,
3831                 opts = uploader.options,
3832                 count = 0,
3833                 max = parseInt( opts.fileSizeLimit, 10 ),
3834                 flag = true;
3835     
3836             if ( !max ) {
3837                 return;
3838             }
3839     
3840             uploader.on( 'beforeFileQueued', function( file ) {
3841                 var invalid = count + file.size > max;
3842     
3843                 if ( invalid && flag ) {
3844                     flag = false;
3845                     this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );
3846                     setTimeout(function() {
3847                         flag = true;
3848                     }, 1 );
3849                 }
3850     
3851                 return invalid ? false : true;
3852             });
3853     
3854             uploader.on( 'fileQueued', function( file ) {
3855                 count += file.size;
3856             });
3857     
3858             uploader.on( 'fileDequeued', function( file ) {
3859                 count -= file.size;
3860             });
3861     
3862             uploader.on( 'reset', function() {
3863                 count = 0;
3864             });
3865         });
3866     
3867         /**
3868          * @property {int} [fileSingleSizeLimit=undefined]
3869          * @namespace options
3870          * @for Uploader
3871          * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。
3872          */
3873         api.addValidator( 'fileSingleSizeLimit', function() {
3874             var uploader = this,
3875                 opts = uploader.options,
3876                 max = opts.fileSingleSizeLimit;
3877     
3878             if ( !max ) {
3879                 return;
3880             }
3881     
3882             uploader.on( 'beforeFileQueued', function( file ) {
3883     
3884                 if ( file.size > max ) {
3885                     file.setStatus( WUFile.Status.INVALID, 'exceed_size' );
3886                     this.trigger( 'error', 'F_EXCEED_SIZE', max, file );
3887                     return false;
3888                 }
3889     
3890             });
3891     
3892         });
3893     
3894         /**
3895          * @property {Boolean} [duplicate=undefined]
3896          * @namespace options
3897          * @for Uploader
3898          * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key.
3899          */
3900         api.addValidator( 'duplicate', function() {
3901             var uploader = this,
3902                 opts = uploader.options,
3903                 mapping = {};
3904     
3905             if ( opts.duplicate ) {
3906                 return;
3907             }
3908     
3909             function hashString( str ) {
3910                 var hash = 0,
3911                     i = 0,
3912                     len = str.length,
3913                     _char;
3914     
3915                 for ( ; i < len; i++ ) {
3916                     _char = str.charCodeAt( i );
3917                     hash = _char + (hash << 6) + (hash << 16) - hash;
3918                 }
3919     
3920                 return hash;
3921             }
3922     
3923             uploader.on( 'beforeFileQueued', function( file ) {
3924                 var hash = file.__hash || (file.__hash = hashString( file.name +
3925                         file.size + file.lastModifiedDate ));
3926     
3927                 // 已经重复了
3928                 if ( mapping[ hash ] ) {
3929                     this.trigger( 'error', 'F_DUPLICATE', file );
3930                     return false;
3931                 }
3932             });
3933     
3934             uploader.on( 'fileQueued', function( file ) {
3935                 var hash = file.__hash;
3936     
3937                 hash && (mapping[ hash ] = true);
3938             });
3939     
3940             uploader.on( 'fileDequeued', function( file ) {
3941                 var hash = file.__hash;
3942     
3943                 hash && (delete mapping[ hash ]);
3944             });
3945     
3946             uploader.on( 'reset', function() {
3947                 mapping = {};
3948             });
3949         });
3950     
3951         return api;
3952     });
3953     
3954     /**
3955      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
3956      */
3957     define('runtime/compbase',[],function() {
3958     
3959         function CompBase( owner, runtime ) {
3960     
3961             this.owner = owner;
3962             this.options = owner.options;
3963     
3964             this.getRuntime = function() {
3965                 return runtime;
3966             };
3967     
3968             this.getRuid = function() {
3969                 return runtime.uid;
3970             };
3971     
3972             this.trigger = function() {
3973                 return owner.trigger.apply( owner, arguments );
3974             };
3975         }
3976     
3977         return CompBase;
3978     });
3979     /**
3980      * @fileOverview Html5Runtime
3981      */
3982     define('runtime/html5/runtime',[
3983         'base',
3984         'runtime/runtime',
3985         'runtime/compbase'
3986     ], function( Base, Runtime, CompBase ) {
3987     
3988         var type = 'html5',
3989             components = {};
3990     
3991         function Html5Runtime() {
3992             var pool = {},
3993                 me = this,
3994                 destroy = this.destroy;
3995     
3996             Runtime.apply( me, arguments );
3997             me.type = type;
3998     
3999     
4000             // 这个方法的调用者,实际上是RuntimeClient
4001             me.exec = function( comp, fn/*, args...*/) {
4002                 var client = this,
4003                     uid = client.uid,
4004                     args = Base.slice( arguments, 2 ),
4005                     instance;
4006     
4007                 if ( components[ comp ] ) {
4008                     instance = pool[ uid ] = pool[ uid ] ||
4009                             new components[ comp ]( client, me );
4010     
4011                     if ( instance[ fn ] ) {
4012                         return instance[ fn ].apply( instance, args );
4013                     }
4014                 }
4015             };
4016     
4017             me.destroy = function() {
4018                 // @todo 删除池子中的所有实例
4019                 return destroy && destroy.apply( this, arguments );
4020             };
4021         }
4022     
4023         Base.inherits( Runtime, {
4024             constructor: Html5Runtime,
4025     
4026             // 不需要连接其他程序,直接执行callback
4027             init: function() {
4028                 var me = this;
4029                 setTimeout(function() {
4030                     me.trigger('ready');
4031                 }, 1 );
4032             }
4033     
4034         });
4035     
4036         // 注册Components
4037         Html5Runtime.register = function( name, component ) {
4038             var klass = components[ name ] = Base.inherits( CompBase, component );
4039             return klass;
4040         };
4041     
4042         // 注册html5运行时。
4043         // 只有在支持的前提下注册。
4044         if ( window.Blob && window.FileReader && window.DataView ) {
4045             Runtime.addRuntime( type, Html5Runtime );
4046         }
4047     
4048         return Html5Runtime;
4049     });
4050     /**
4051      * @fileOverview Blob Html实现
4052      */
4053     define('runtime/html5/blob',[
4054         'runtime/html5/runtime',
4055         'lib/blob'
4056     ], function( Html5Runtime, Blob ) {
4057     
4058         return Html5Runtime.register( 'Blob', {
4059             slice: function( start, end ) {
4060                 var blob = this.owner.source,
4061                     slice = blob.slice || blob.webkitSlice || blob.mozSlice;
4062     
4063                 blob = slice.call( blob, start, end );
4064     
4065                 return new Blob( this.getRuid(), blob );
4066             }
4067         });
4068     });
4069     /**
4070      * @fileOverview FilePaste
4071      */
4072     define('runtime/html5/dnd',[
4073         'base',
4074         'runtime/html5/runtime',
4075         'lib/file'
4076     ], function( Base, Html5Runtime, File ) {
4077     
4078         var $ = Base.$,
4079             prefix = 'webuploader-dnd-';
4080     
4081         return Html5Runtime.register( 'DragAndDrop', {
4082             init: function() {
4083                 var elem = this.elem = this.options.container;
4084     
4085                 this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );
4086                 this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );
4087                 this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );
4088                 this.dropHandler = Base.bindFn( this._dropHandler, this );
4089                 this.dndOver = false;
4090     
4091                 elem.on( 'dragenter', this.dragEnterHandler );
4092                 elem.on( 'dragover', this.dragOverHandler );
4093                 elem.on( 'dragleave', this.dragLeaveHandler );
4094                 elem.on( 'drop', this.dropHandler );
4095     
4096                 if ( this.options.disableGlobalDnd ) {
4097                     $( document ).on( 'dragover', this.dragOverHandler );
4098                     $( document ).on( 'drop', this.dropHandler );
4099                 }
4100             },
4101     
4102             _dragEnterHandler: function( e ) {
4103                 var me = this,
4104                     denied = me._denied || false,
4105                     items;
4106     
4107                 e = e.originalEvent || e;
4108     
4109                 if ( !me.dndOver ) {
4110                     me.dndOver = true;
4111     
4112                     // 注意只有 chrome 支持。
4113                     items = e.dataTransfer.items;
4114     
4115                     if ( items && items.length ) {
4116                         me._denied = denied = !me.trigger( 'accept', items );
4117                     }
4118     
4119                     me.elem.addClass( prefix + 'over' );
4120                     me.elem[ denied ? 'addClass' :
4121                             'removeClass' ]( prefix + 'denied' );
4122                 }
4123     
4124                 e.dataTransfer.dropEffect = denied ? 'none' : 'copy';
4125     
4126                 return false;
4127             },
4128     
4129             _dragOverHandler: function( e ) {
4130                 // 只处理框内的。
4131                 var parentElem = this.elem.parent().get( 0 );
4132                 if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
4133                     return false;
4134                 }
4135     
4136                 clearTimeout( this._leaveTimer );
4137                 this._dragEnterHandler.call( this, e );
4138     
4139                 return false;
4140             },
4141     
4142             _dragLeaveHandler: function() {
4143                 var me = this,
4144                     handler;
4145     
4146                 handler = function() {
4147                     me.dndOver = false;
4148                     me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );
4149                 };
4150     
4151                 clearTimeout( me._leaveTimer );
4152                 me._leaveTimer = setTimeout( handler, 100 );
4153                 return false;
4154             },
4155     
4156             _dropHandler: function( e ) {
4157                 var me = this,
4158                     ruid = me.getRuid(),
4159                     parentElem = me.elem.parent().get( 0 ),
4160                     dataTransfer, data;
4161     
4162                 // 只处理框内的。
4163                 if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
4164                     return false;
4165                 }
4166     
4167                 e = e.originalEvent || e;
4168                 dataTransfer = e.dataTransfer;
4169     
4170                 // 如果是页面内拖拽,还不能处理,不阻止事件。
4171                 // 此处 ie11 下会报参数错误,
4172                 try {
4173                     data = dataTransfer.getData('text/html');
4174                 } catch( err ) {
4175                 }
4176     
4177                 if ( data ) {
4178                     return;
4179                 }
4180     
4181                 me._getTansferFiles( dataTransfer, function( results ) {
4182                     me.trigger( 'drop', $.map( results, function( file ) {
4183                         return new File( ruid, file );
4184                     }) );
4185                 });
4186     
4187                 me.dndOver = false;
4188                 me.elem.removeClass( prefix + 'over' );
4189                 return false;
4190             },
4191     
4192             // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。
4193             _getTansferFiles: function( dataTransfer, callback ) {
4194                 var results  = [],
4195                     promises = [],
4196                     items, files, file, item, i, len, canAccessFolder;
4197     
4198                 items = dataTransfer.items;
4199                 files = dataTransfer.files;
4200     
4201                 canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);
4202     
4203                 for ( i = 0, len = files.length; i < len; i++ ) {
4204                     file = files[ i ];
4205                     item = items && items[ i ];
4206     
4207                     if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {
4208     
4209                         promises.push( this._traverseDirectoryTree(
4210                                 item.webkitGetAsEntry(), results ) );
4211                     } else {
4212                         results.push( file );
4213                     }
4214                 }
4215     
4216                 Base.when.apply( Base, promises ).done(function() {
4217     
4218                     if ( !results.length ) {
4219                         return;
4220                     }
4221     
4222                     callback( results );
4223                 });
4224             },
4225     
4226             _traverseDirectoryTree: function( entry, results ) {
4227                 var deferred = Base.Deferred(),
4228                     me = this;
4229     
4230                 if ( entry.isFile ) {
4231                     entry.file(function( file ) {
4232                         results.push( file );
4233                         deferred.resolve();
4234                     });
4235                 } else if ( entry.isDirectory ) {
4236                     entry.createReader().readEntries(function( entries ) {
4237                         var len = entries.length,
4238                             promises = [],
4239                             arr = [],    // 为了保证顺序。
4240                             i;
4241     
4242                         for ( i = 0; i < len; i++ ) {
4243                             promises.push( me._traverseDirectoryTree(
4244                                     entries[ i ], arr ) );
4245                         }
4246     
4247                         Base.when.apply( Base, promises ).then(function() {
4248                             results.push.apply( results, arr );
4249                             deferred.resolve();
4250                         }, deferred.reject );
4251                     });
4252                 }
4253     
4254                 return deferred.promise();
4255             },
4256     
4257             destroy: function() {
4258                 var elem = this.elem;
4259     
4260                 // 还没 init 就调用 destroy
4261                 if (!elem) {
4262                     return;
4263                 }
4264                 
4265                 elem.off( 'dragenter', this.dragEnterHandler );
4266                 elem.off( 'dragover', this.dragOverHandler );
4267                 elem.off( 'dragleave', this.dragLeaveHandler );
4268                 elem.off( 'drop', this.dropHandler );
4269     
4270                 if ( this.options.disableGlobalDnd ) {
4271                     $( document ).off( 'dragover', this.dragOverHandler );
4272                     $( document ).off( 'drop', this.dropHandler );
4273                 }
4274             }
4275         });
4276     });
4277     
4278     /**
4279      * @fileOverview FilePaste
4280      */
4281     define('runtime/html5/filepaste',[
4282         'base',
4283         'runtime/html5/runtime',
4284         'lib/file'
4285     ], function( Base, Html5Runtime, File ) {
4286     
4287         return Html5Runtime.register( 'FilePaste', {
4288             init: function() {
4289                 var opts = this.options,
4290                     elem = this.elem = opts.container,
4291                     accept = '.*',
4292                     arr, i, len, item;
4293     
4294                 // accetp的mimeTypes中生成匹配正则。
4295                 if ( opts.accept ) {
4296                     arr = [];
4297     
4298                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
4299                         item = opts.accept[ i ].mimeTypes;
4300                         item && arr.push( item );
4301                     }
4302     
4303                     if ( arr.length ) {
4304                         accept = arr.join(',');
4305                         accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' );
4306                     }
4307                 }
4308                 this.accept = accept = new RegExp( accept, 'i' );
4309                 this.hander = Base.bindFn( this._pasteHander, this );
4310                 elem.on( 'paste', this.hander );
4311             },
4312     
4313             _pasteHander: function( e ) {
4314                 var allowed = [],
4315                     ruid = this.getRuid(),
4316                     items, item, blob, i, len;
4317     
4318                 e = e.originalEvent || e;
4319                 items = e.clipboardData.items;
4320     
4321                 for ( i = 0, len = items.length; i < len; i++ ) {
4322                     item = items[ i ];
4323     
4324                     if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {
4325                         continue;
4326                     }
4327     
4328                     allowed.push( new File( ruid, blob ) );
4329                 }
4330     
4331                 if ( allowed.length ) {
4332                     // 不阻止非文件粘贴(文字粘贴)的事件冒泡
4333                     e.preventDefault();
4334                     e.stopPropagation();
4335                     this.trigger( 'paste', allowed );
4336                 }
4337             },
4338     
4339             destroy: function() {
4340                 this.elem.off( 'paste', this.hander );
4341             }
4342         });
4343     });
4344     
4345     /**
4346      * @fileOverview FilePicker
4347      */
4348     define('runtime/html5/filepicker',[
4349         'base',
4350         'runtime/html5/runtime'
4351     ], function( Base, Html5Runtime ) {
4352     
4353         var $ = Base.$;
4354     
4355         return Html5Runtime.register( 'FilePicker', {
4356             init: function() {
4357                 var container = this.getRuntime().getContainer(),
4358                     me = this,
4359                     owner = me.owner,
4360                     opts = me.options,
4361                     label = this.label = $( document.createElement('label') ),
4362                     input =  this.input = $( document.createElement('input') ),
4363                     arr, i, len, mouseHandler;
4364     
4365                 input.attr( 'type', 'file' );
4366                 input.attr( 'name', opts.name );
4367                 input.addClass('webuploader-element-invisible');
4368     
4369                 label.on( 'click', function() {
4370                     input.trigger('click');
4371                 });
4372     
4373                 label.css({
4374                     opacity: 0,
4375                     width: '100%',
4376                     height: '100%',
4377                     display: 'block',
4378                     cursor: 'pointer',
4379                     background: '#ffffff'
4380                 });
4381     
4382                 if ( opts.multiple ) {
4383                     input.attr( 'multiple', 'multiple' );
4384                 }
4385     
4386                 // @todo Firefox不支持单独指定后缀
4387                 if ( opts.accept && opts.accept.length > 0 ) {
4388                     arr = [];
4389     
4390                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
4391                         arr.push( opts.accept[ i ].mimeTypes );
4392                     }
4393     
4394                     input.attr( 'accept', arr.join(',') );
4395                 }
4396     
4397                 container.append( input );
4398                 container.append( label );
4399     
4400                 mouseHandler = function( e ) {
4401                     owner.trigger( e.type );
4402                 };
4403     
4404                 input.on( 'change', function( e ) {
4405                     var fn = arguments.callee,
4406                         clone;
4407     
4408                     me.files = e.target.files;
4409     
4410                     // reset input
4411                     clone = this.cloneNode( true );
4412                     clone.value = null;
4413                     this.parentNode.replaceChild( clone, this );
4414     
4415                     input.off();
4416                     input = $( clone ).on( 'change', fn )
4417                             .on( 'mouseenter mouseleave', mouseHandler );
4418     
4419                     owner.trigger('change');
4420                 });
4421     
4422                 label.on( 'mouseenter mouseleave', mouseHandler );
4423     
4424             },
4425     
4426     
4427             getFiles: function() {
4428                 return this.files;
4429             },
4430     
4431             destroy: function() {
4432                 this.input.off();
4433                 this.label.off();
4434             }
4435         });
4436     });
4437     /**
4438      * @fileOverview Transport
4439      * @todo 支持chunked传输,优势:
4440      * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分,
4441      * 而不需要重头再传一次。另外断点续传也需要用chunked方式。
4442      */
4443     define('runtime/html5/transport',[
4444         'base',
4445         'runtime/html5/runtime'
4446     ], function( Base, Html5Runtime ) {
4447     
4448         var noop = Base.noop,
4449             $ = Base.$;
4450     
4451         return Html5Runtime.register( 'Transport', {
4452             init: function() {
4453                 this._status = 0;
4454                 this._response = null;
4455             },
4456     
4457             send: function() {
4458                 var owner = this.owner,
4459                     opts = this.options,
4460                     xhr = this._initAjax(),
4461                     blob = owner._blob,
4462                     server = opts.server,
4463                     formData, binary, fr;
4464     
4465                 if ( opts.sendAsBinary ) {
4466                     server += (/\?/.test( server ) ? '&' : '?') +
4467                             $.param( owner._formData );
4468     
4469                     binary = blob.getSource();
4470                 } else {
4471                     formData = new FormData();
4472                     $.each( owner._formData, function( k, v ) {
4473                         formData.append( k, v );
4474                     });
4475     
4476                     formData.append( opts.fileVal, blob.getSource(),
4477                             opts.filename || owner._formData.name || '' );
4478                 }
4479     
4480                 if ( opts.withCredentials && 'withCredentials' in xhr ) {
4481                     xhr.open( opts.method, server, true );
4482                     xhr.withCredentials = true;
4483                 } else {
4484                     xhr.open( opts.method, server );
4485                 }
4486     
4487                 this._setRequestHeader( xhr, opts.headers );
4488     
4489                 if ( binary ) {
4490                     // 强制设置成 content-type 为文件流。
4491                     xhr.overrideMimeType &&
4492                             xhr.overrideMimeType('application/octet-stream');
4493     
4494                     // android直接发送blob会导致服务端接收到的是空文件。
4495                     // bug详情。
4496                     // https://code.google.com/p/android/issues/detail?id=39882
4497                     // 所以先用fileReader读取出来再通过arraybuffer的方式发送。
4498                     if ( Base.os.android ) {
4499                         fr = new FileReader();
4500     
4501                         fr.onload = function() {
4502                             xhr.send( this.result );
4503                             fr = fr.onload = null;
4504                         };
4505     
4506                         fr.readAsArrayBuffer( binary );
4507                     } else {
4508                         xhr.send( binary );
4509                     }
4510                 } else {
4511                     xhr.send( formData );
4512                 }
4513             },
4514     
4515             getResponse: function() {
4516                 return this._response;
4517             },
4518     
4519             getResponseAsJson: function() {
4520                 return this._parseJson( this._response );
4521             },
4522     
4523             getStatus: function() {
4524                 return this._status;
4525             },
4526     
4527             abort: function() {
4528                 var xhr = this._xhr;
4529     
4530                 if ( xhr ) {
4531                     xhr.upload.onprogress = noop;
4532                     xhr.onreadystatechange = noop;
4533                     xhr.abort();
4534     
4535                     this._xhr = xhr = null;
4536                 }
4537             },
4538     
4539             destroy: function() {
4540                 this.abort();
4541             },
4542     
4543             _initAjax: function() {
4544                 var me = this,
4545                     xhr = new XMLHttpRequest(),
4546                     opts = this.options;
4547     
4548                 if ( opts.withCredentials && !('withCredentials' in xhr) &&
4549                         typeof XDomainRequest !== 'undefined' ) {
4550                     xhr = new XDomainRequest();
4551                 }
4552     
4553                 xhr.upload.onprogress = function( e ) {
4554                     var percentage = 0;
4555     
4556                     if ( e.lengthComputable ) {
4557                         percentage = e.loaded / e.total;
4558                     }
4559     
4560                     return me.trigger( 'progress', percentage );
4561                 };
4562     
4563                 xhr.onreadystatechange = function() {
4564     
4565                     if ( xhr.readyState !== 4 ) {
4566                         return;
4567                     }
4568     
4569                     xhr.upload.onprogress = noop;
4570                     xhr.onreadystatechange = noop;
4571                     me._xhr = null;
4572                     me._status = xhr.status;
4573     
4574                     if ( xhr.status >= 200 && xhr.status < 300 ) {
4575                         me._response = xhr.responseText;
4576                         return me.trigger('load');
4577                     } else if ( xhr.status >= 500 && xhr.status < 600 ) {
4578                         me._response = xhr.responseText;
4579                         return me.trigger( 'error', 'server' );
4580                     }
4581     
4582     
4583                     return me.trigger( 'error', me._status ? 'http' : 'abort' );
4584                 };
4585     
4586                 me._xhr = xhr;
4587                 return xhr;
4588             },
4589     
4590             _setRequestHeader: function( xhr, headers ) {
4591                 $.each( headers, function( key, val ) {
4592                     xhr.setRequestHeader( key, val );
4593                 });
4594             },
4595     
4596             _parseJson: function( str ) {
4597                 var json;
4598     
4599                 try {
4600                     json = JSON.parse( str );
4601                 } catch ( ex ) {
4602                     json = {};
4603                 }
4604     
4605                 return json;
4606             }
4607         });
4608     });
4609     /**
4610      * @fileOverview FlashRuntime
4611      */
4612     define('runtime/flash/runtime',[
4613         'base',
4614         'runtime/runtime',
4615         'runtime/compbase'
4616     ], function( Base, Runtime, CompBase ) {
4617     
4618         var $ = Base.$,
4619             type = 'flash',
4620             components = {};
4621     
4622     
4623         function getFlashVersion() {
4624             var version;
4625     
4626             try {
4627                 version = navigator.plugins[ 'Shockwave Flash' ];
4628                 version = version.description;
4629             } catch ( ex ) {
4630                 try {
4631                     version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')
4632                             .GetVariable('$version');
4633                 } catch ( ex2 ) {
4634                     version = '0.0';
4635                 }
4636             }
4637             version = version.match( /\d+/g );
4638             return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );
4639         }
4640     
4641         function FlashRuntime() {
4642             var pool = {},
4643                 clients = {},
4644                 destroy = this.destroy,
4645                 me = this,
4646                 jsreciver = Base.guid('webuploader_');
4647     
4648             Runtime.apply( me, arguments );
4649             me.type = type;
4650     
4651     
4652             // 这个方法的调用者,实际上是RuntimeClient
4653             me.exec = function( comp, fn/*, args...*/ ) {
4654                 var client = this,
4655                     uid = client.uid,
4656                     args = Base.slice( arguments, 2 ),
4657                     instance;
4658     
4659                 clients[ uid ] = client;
4660     
4661                 if ( components[ comp ] ) {
4662                     if ( !pool[ uid ] ) {
4663                         pool[ uid ] = new components[ comp ]( client, me );
4664                     }
4665     
4666                     instance = pool[ uid ];
4667     
4668                     if ( instance[ fn ] ) {
4669                         return instance[ fn ].apply( instance, args );
4670                     }
4671                 }
4672     
4673                 return me.flashExec.apply( client, arguments );
4674             };
4675     
4676             function handler( evt, obj ) {
4677                 var type = evt.type || evt,
4678                     parts, uid;
4679     
4680                 parts = type.split('::');
4681                 uid = parts[ 0 ];
4682                 type = parts[ 1 ];
4683     
4684                 // console.log.apply( console, arguments );
4685     
4686                 if ( type === 'Ready' && uid === me.uid ) {
4687                     me.trigger('ready');
4688                 } else if ( clients[ uid ] ) {
4689                     clients[ uid ].trigger( type.toLowerCase(), evt, obj );
4690                 }
4691     
4692                 // Base.log( evt, obj );
4693             }
4694     
4695             // flash的接受器。
4696             window[ jsreciver ] = function() {
4697                 var args = arguments;
4698     
4699                 // 为了能捕获得到。
4700                 setTimeout(function() {
4701                     handler.apply( null, args );
4702                 }, 1 );
4703             };
4704     
4705             this.jsreciver = jsreciver;
4706     
4707             this.destroy = function() {
4708                 // @todo 删除池子中的所有实例
4709                 return destroy && destroy.apply( this, arguments );
4710             };
4711     
4712             this.flashExec = function( comp, fn ) {
4713                 var flash = me.getFlash(),
4714                     args = Base.slice( arguments, 2 );
4715     
4716                 return flash.exec( this.uid, comp, fn, args );
4717             };
4718     
4719             // @todo
4720         }
4721     
4722         Base.inherits( Runtime, {
4723             constructor: FlashRuntime,
4724     
4725             init: function() {
4726                 var container = this.getContainer(),
4727                     opts = this.options,
4728                     html;
4729     
4730                 // if not the minimal height, shims are not initialized
4731                 // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)
4732                 container.css({
4733                     position: 'absolute',
4734                     top: '-8px',
4735                     left: '-8px',
4736                     width: '9px',
4737                     height: '9px',
4738                     overflow: 'hidden'
4739                 });
4740     
4741                 // insert flash object
4742                 html = '<object id="' + this.uid + '" type="application/' +
4743                         'x-shockwave-flash" data="' +  opts.swf + '" ';
4744     
4745                 if ( Base.browser.ie ) {
4746                     html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
4747                 }
4748     
4749                 html += 'width="100%" height="100%" style="outline:0">'  +
4750                     '<param name="movie" value="' + opts.swf + '" />' +
4751                     '<param name="flashvars" value="uid=' + this.uid +
4752                     '&jsreciver=' + this.jsreciver + '" />' +
4753                     '<param name="wmode" value="transparent" />' +
4754                     '<param name="allowscriptaccess" value="always" />' +
4755                 '</object>';
4756     
4757                 container.html( html );
4758             },
4759     
4760             getFlash: function() {
4761                 if ( this._flash ) {
4762                     return this._flash;
4763                 }
4764     
4765                 this._flash = $( '#' + this.uid ).get( 0 );
4766                 return this._flash;
4767             }
4768     
4769         });
4770     
4771         FlashRuntime.register = function( name, component ) {
4772             component = components[ name ] = Base.inherits( CompBase, $.extend({
4773     
4774                 // @todo fix this later
4775                 flashExec: function() {
4776                     var owner = this.owner,
4777                         runtime = this.getRuntime();
4778     
4779                     return runtime.flashExec.apply( owner, arguments );
4780                 }
4781             }, component ) );
4782     
4783             return component;
4784         };
4785     
4786         if ( getFlashVersion() >= 11.4 ) {
4787             Runtime.addRuntime( type, FlashRuntime );
4788         }
4789     
4790         return FlashRuntime;
4791     });
4792     /**
4793      * @fileOverview FilePicker
4794      */
4795     define('runtime/flash/filepicker',[
4796         'base',
4797         'runtime/flash/runtime'
4798     ], function( Base, FlashRuntime ) {
4799         var $ = Base.$;
4800     
4801         return FlashRuntime.register( 'FilePicker', {
4802             init: function( opts ) {
4803                 var copy = $.extend({}, opts ),
4804                     len, i;
4805     
4806                 // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.
4807                 len = copy.accept && copy.accept.length;
4808                 for (  i = 0; i < len; i++ ) {
4809                     if ( !copy.accept[ i ].title ) {
4810                         copy.accept[ i ].title = 'Files';
4811                     }
4812                 }
4813     
4814                 delete copy.button;
4815                 delete copy.id;
4816                 delete copy.container;
4817     
4818                 this.flashExec( 'FilePicker', 'init', copy );
4819             },
4820     
4821             destroy: function() {
4822                 this.flashExec( 'FilePicker', 'destroy' );
4823             }
4824         });
4825     });
4826     /**
4827      * @fileOverview  Transport flash实现
4828      */
4829     define('runtime/flash/transport',[
4830         'base',
4831         'runtime/flash/runtime',
4832         'runtime/client'
4833     ], function( Base, FlashRuntime, RuntimeClient ) {
4834         var $ = Base.$;
4835     
4836         return FlashRuntime.register( 'Transport', {
4837             init: function() {
4838                 this._status = 0;
4839                 this._response = null;
4840                 this._responseJson = null;
4841             },
4842     
4843             send: function() {
4844                 var owner = this.owner,
4845                     opts = this.options,
4846                     xhr = this._initAjax(),
4847                     blob = owner._blob,
4848                     server = opts.server,
4849                     binary;
4850     
4851                 xhr.connectRuntime( blob.ruid );
4852     
4853                 if ( opts.sendAsBinary ) {
4854                     server += (/\?/.test( server ) ? '&' : '?') +
4855                             $.param( owner._formData );
4856     
4857                     binary = blob.uid;
4858                 } else {
4859                     $.each( owner._formData, function( k, v ) {
4860                         xhr.exec( 'append', k, v );
4861                     });
4862     
4863                     xhr.exec( 'appendBlob', opts.fileVal, blob.uid,
4864                             opts.filename || owner._formData.name || '' );
4865                 }
4866     
4867                 this._setRequestHeader( xhr, opts.headers );
4868                 xhr.exec( 'send', {
4869                     method: opts.method,
4870                     url: server,
4871                     forceURLStream: opts.forceURLStream,
4872                     mimeType: 'application/octet-stream'
4873                 }, binary );
4874             },
4875     
4876             getStatus: function() {
4877                 return this._status;
4878             },
4879     
4880             getResponse: function() {
4881                 return this._response || '';
4882             },
4883     
4884             getResponseAsJson: function() {
4885                 return this._responseJson;
4886             },
4887     
4888             abort: function() {
4889                 var xhr = this._xhr;
4890     
4891                 if ( xhr ) {
4892                     xhr.exec('abort');
4893                     xhr.destroy();
4894                     this._xhr = xhr = null;
4895                 }
4896             },
4897     
4898             destroy: function() {
4899                 this.abort();
4900             },
4901     
4902             _initAjax: function() {
4903                 var me = this,
4904                     xhr = new RuntimeClient('XMLHttpRequest');
4905     
4906                 xhr.on( 'uploadprogress progress', function( e ) {
4907                     var percent = e.loaded / e.total;
4908                     percent = Math.min( 1, Math.max( 0, percent ) );
4909                     return me.trigger( 'progress', percent );
4910                 });
4911     
4912                 xhr.on( 'load', function() {
4913                     var status = xhr.exec('getStatus'),
4914                         readBody = false,
4915                         err = '',
4916                         p;
4917     
4918                     xhr.off();
4919                     me._xhr = null;
4920     
4921                     if ( status >= 200 && status < 300 ) {
4922                         readBody = true;
4923                     } else if ( status >= 500 && status < 600 ) {
4924                         readBody = true;
4925                         err = 'server';
4926                     } else {
4927                         err = 'http';
4928                     }
4929     
4930                     if ( readBody ) {
4931                         me._response = xhr.exec('getResponse');
4932                         me._response = decodeURIComponent( me._response );
4933     
4934                         // flash 处理可能存在 bug, 没辙只能靠 js 了
4935                         // try {
4936                         //     me._responseJson = xhr.exec('getResponseAsJson');
4937                         // } catch ( error ) {
4938                             
4939                         p = window.JSON && window.JSON.parse || function( s ) {
4940                             try {
4941                                 return new Function('return ' + s).call();
4942                             } catch ( err ) {
4943                                 return {};
4944                             }
4945                         };
4946                         me._responseJson  = me._response ? p(me._response) : {};
4947                             
4948                         // }
4949                     }
4950                     
4951                     xhr.destroy();
4952                     xhr = null;
4953     
4954                     return err ? me.trigger( 'error', err ) : me.trigger('load');
4955                 });
4956     
4957                 xhr.on( 'error', function() {
4958                     xhr.off();
4959                     me._xhr = null;
4960                     me.trigger( 'error', 'http' );
4961                 });
4962     
4963                 me._xhr = xhr;
4964                 return xhr;
4965             },
4966     
4967             _setRequestHeader: function( xhr, headers ) {
4968                 $.each( headers, function( key, val ) {
4969                     xhr.exec( 'setRequestHeader', key, val );
4970                 });
4971             }
4972         });
4973     });
4974     /**
4975      * @fileOverview Blob Html实现
4976      */
4977     define('runtime/flash/blob',[
4978         'runtime/flash/runtime',
4979         'lib/blob'
4980     ], function( FlashRuntime, Blob ) {
4981     
4982         return FlashRuntime.register( 'Blob', {
4983             slice: function( start, end ) {
4984                 var blob = this.flashExec( 'Blob', 'slice', start, end );
4985     
4986                 return new Blob( blob.uid, blob );
4987             }
4988         });
4989     });
4990     /**
4991      * @fileOverview 没有图像处理的版本。
4992      */
4993     define('preset/withoutimage',[
4994         'base',
4995     
4996         // widgets
4997         'widgets/filednd',
4998         'widgets/filepaste',
4999         'widgets/filepicker',
5000         'widgets/queue',
5001         'widgets/runtime',
5002         'widgets/upload',
5003         'widgets/validator',
5004     
5005         // runtimes
5006         // html5
5007         'runtime/html5/blob',
5008         'runtime/html5/dnd',
5009         'runtime/html5/filepaste',
5010         'runtime/html5/filepicker',
5011         'runtime/html5/transport',
5012     
5013         // flash
5014         'runtime/flash/filepicker',
5015         'runtime/flash/transport',
5016         'runtime/flash/blob'
5017     ], function( Base ) {
5018         return Base;
5019     });
5020     define('webuploader',[
5021         'preset/withoutimage'
5022     ], function( preset ) {
5023         return preset;
5024     });
5025     return require('webuploader');
5026 });