Administrator
2023-04-21 195945efc5db921a4c9eb8cf9421c172273293f5
提交 | 用户 | 时间
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 Blob
1159      */
1160     define('lib/blob',[
1161         'base',
1162         'runtime/client'
1163     ], function( Base, RuntimeClient ) {
1164     
1165         function Blob( ruid, source ) {
1166             var me = this;
1167     
1168             me.source = source;
1169             me.ruid = ruid;
1170             this.size = source.size || 0;
1171     
1172             // 如果没有指定 mimetype, 但是知道文件后缀。
1173             if ( !source.type && this.ext &&
1174                     ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) {
1175                 this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext);
1176             } else {
1177                 this.type = source.type || 'application/octet-stream';
1178             }
1179     
1180             RuntimeClient.call( me, 'Blob' );
1181             this.uid = source.uid || this.uid;
1182     
1183             if ( ruid ) {
1184                 me.connectRuntime( ruid );
1185             }
1186         }
1187     
1188         Base.inherits( RuntimeClient, {
1189             constructor: Blob,
1190     
1191             slice: function( start, end ) {
1192                 return this.exec( 'slice', start, end );
1193             },
1194     
1195             getSource: function() {
1196                 return this.source;
1197             }
1198         });
1199     
1200         return Blob;
1201     });
1202     /**
1203      * 为了统一化Flash的File和HTML5的File而存在。
1204      * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。
1205      * @fileOverview File
1206      */
1207     define('lib/file',[
1208         'base',
1209         'lib/blob'
1210     ], function( Base, Blob ) {
1211     
1212         var uid = 1,
1213             rExt = /\.([^.]+)$/;
1214     
1215         function File( ruid, file ) {
1216             var ext;
1217     
1218             this.name = file.name || ('untitled' + uid++);
1219             ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';
1220     
1221             // todo 支持其他类型文件的转换。
1222             // 如果有 mimetype, 但是文件名里面没有找出后缀规律
1223             if ( !ext && file.type ) {
1224                 ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ?
1225                         RegExp.$1.toLowerCase() : '';
1226                 this.name += '.' + ext;
1227             }
1228     
1229             this.ext = ext;
1230             this.lastModifiedDate = file.lastModifiedDate ||
1231                     (new Date()).toLocaleString();
1232     
1233             Blob.apply( this, arguments );
1234         }
1235     
1236         return Base.inherits( Blob, File );
1237     });
1238     
1239     /**
1240      * @fileOverview 错误信息
1241      */
1242     define('lib/filepicker',[
1243         'base',
1244         'runtime/client',
1245         'lib/file'
1246     ], function( Base, RuntimeClent, File ) {
1247     
1248         var $ = Base.$;
1249     
1250         function FilePicker( opts ) {
1251             opts = this.options = $.extend({}, FilePicker.options, opts );
1252     
1253             opts.container = $( opts.id );
1254     
1255             if ( !opts.container.length ) {
1256                 throw new Error('按钮指定错误');
1257             }
1258     
1259             opts.innerHTML = opts.innerHTML || opts.label ||
1260                     opts.container.html() || '';
1261     
1262             opts.button = $( opts.button || document.createElement('div') );
1263             opts.button.html( opts.innerHTML );
1264             opts.container.html( opts.button );
1265     
1266             RuntimeClent.call( this, 'FilePicker', true );
1267         }
1268     
1269         FilePicker.options = {
1270             button: null,
1271             container: null,
1272             label: null,
1273             innerHTML: null,
1274             multiple: true,
1275             accept: null,
1276             name: 'file'
1277         };
1278     
1279         Base.inherits( RuntimeClent, {
1280             constructor: FilePicker,
1281     
1282             init: function() {
1283                 var me = this,
1284                     opts = me.options,
1285                     button = opts.button;
1286     
1287                 button.addClass('webuploader-pick');
1288     
1289                 me.on( 'all', function( type ) {
1290                     var files;
1291     
1292                     switch ( type ) {
1293                         case 'mouseenter':
1294                             button.addClass('webuploader-pick-hover');
1295                             break;
1296     
1297                         case 'mouseleave':
1298                             button.removeClass('webuploader-pick-hover');
1299                             break;
1300     
1301                         case 'change':
1302                             files = me.exec('getFiles');
1303                             me.trigger( 'select', $.map( files, function( file ) {
1304                                 file = new File( me.getRuid(), file );
1305     
1306                                 // 记录来源。
1307                                 file._refer = opts.container;
1308                                 return file;
1309                             }), opts.container );
1310                             break;
1311                     }
1312                 });
1313     
1314                 me.connectRuntime( opts, function() {
1315                     me.refresh();
1316                     me.exec( 'init', opts );
1317                     me.trigger('ready');
1318                 });
1319     
1320                 this._resizeHandler = Base.bindFn( this.refresh, this );
1321                 $( window ).on( 'resize', this._resizeHandler );
1322             },
1323     
1324             refresh: function() {
1325                 var shimContainer = this.getRuntime().getContainer(),
1326                     button = this.options.button,
1327                     width = button.outerWidth ?
1328                             button.outerWidth() : button.width(),
1329     
1330                     height = button.outerHeight ?
1331                             button.outerHeight() : button.height(),
1332     
1333                     pos = button.offset();
1334     
1335                 width && height && shimContainer.css({
1336                     bottom: 'auto',
1337                     right: 'auto',
1338                     width: width + 'px',
1339                     height: height + 'px'
1340                 }).offset( pos );
1341             },
1342     
1343             enable: function() {
1344                 var btn = this.options.button;
1345     
1346                 btn.removeClass('webuploader-pick-disable');
1347                 this.refresh();
1348             },
1349     
1350             disable: function() {
1351                 var btn = this.options.button;
1352     
1353                 this.getRuntime().getContainer().css({
1354                     top: '-99999px'
1355                 });
1356     
1357                 btn.addClass('webuploader-pick-disable');
1358             },
1359     
1360             destroy: function() {
1361                 var btn = this.options.button;
1362                 $( window ).off( 'resize', this._resizeHandler );
1363                 btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' +
1364                     'webuploader-pick');
1365             }
1366         });
1367     
1368         return FilePicker;
1369     });
1370     
1371     /**
1372      * @fileOverview 组件基类。
1373      */
1374     define('widgets/widget',[
1375         'base',
1376         'uploader'
1377     ], function( Base, Uploader ) {
1378     
1379         var $ = Base.$,
1380             _init = Uploader.prototype._init,
1381             _destroy = Uploader.prototype.destroy,
1382             IGNORE = {},
1383             widgetClass = [];
1384     
1385         function isArrayLike( obj ) {
1386             if ( !obj ) {
1387                 return false;
1388             }
1389     
1390             var length = obj.length,
1391                 type = $.type( obj );
1392     
1393             if ( obj.nodeType === 1 && length ) {
1394                 return true;
1395             }
1396     
1397             return type === 'array' || type !== 'function' && type !== 'string' &&
1398                     (length === 0 || typeof length === 'number' && length > 0 &&
1399                     (length - 1) in obj);
1400         }
1401     
1402         function Widget( uploader ) {
1403             this.owner = uploader;
1404             this.options = uploader.options;
1405         }
1406     
1407         $.extend( Widget.prototype, {
1408     
1409             init: Base.noop,
1410     
1411             // 类Backbone的事件监听声明,监听uploader实例上的事件
1412             // widget直接无法监听事件,事件只能通过uploader来传递
1413             invoke: function( apiName, args ) {
1414     
1415                 /*
1416                     {
1417                         'make-thumb': 'makeThumb'
1418                     }
1419                  */
1420                 var map = this.responseMap;
1421     
1422                 // 如果无API响应声明则忽略
1423                 if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||
1424                         !$.isFunction( this[ map[ apiName ] ] ) ) {
1425     
1426                     return IGNORE;
1427                 }
1428     
1429                 return this[ map[ apiName ] ].apply( this, args );
1430     
1431             },
1432     
1433             /**
1434              * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。
1435              * @method request
1436              * @grammar request( command, args ) => * | Promise
1437              * @grammar request( command, args, callback ) => Promise
1438              * @for  Uploader
1439              */
1440             request: function() {
1441                 return this.owner.request.apply( this.owner, arguments );
1442             }
1443         });
1444     
1445         // 扩展Uploader.
1446         $.extend( Uploader.prototype, {
1447     
1448             /**
1449              * @property {String | Array} [disableWidgets=undefined]
1450              * @namespace options
1451              * @for Uploader
1452              * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。
1453              */
1454     
1455             // 覆写_init用来初始化widgets
1456             _init: function() {
1457                 var me = this,
1458                     widgets = me._widgets = [],
1459                     deactives = me.options.disableWidgets || '';
1460     
1461                 $.each( widgetClass, function( _, klass ) {
1462                     (!deactives || !~deactives.indexOf( klass._name )) &&
1463                         widgets.push( new klass( me ) );
1464                 });
1465     
1466                 return _init.apply( me, arguments );
1467             },
1468     
1469             request: function( apiName, args, callback ) {
1470                 var i = 0,
1471                     widgets = this._widgets,
1472                     len = widgets && widgets.length,
1473                     rlts = [],
1474                     dfds = [],
1475                     widget, rlt, promise, key;
1476     
1477                 args = isArrayLike( args ) ? args : [ args ];
1478     
1479                 for ( ; i < len; i++ ) {
1480                     widget = widgets[ i ];
1481                     rlt = widget.invoke( apiName, args );
1482     
1483                     if ( rlt !== IGNORE ) {
1484     
1485                         // Deferred对象
1486                         if ( Base.isPromise( rlt ) ) {
1487                             dfds.push( rlt );
1488                         } else {
1489                             rlts.push( rlt );
1490                         }
1491                     }
1492                 }
1493     
1494                 // 如果有callback,则用异步方式。
1495                 if ( callback || dfds.length ) {
1496                     promise = Base.when.apply( Base, dfds );
1497                     key = promise.pipe ? 'pipe' : 'then';
1498     
1499                     // 很重要不能删除。删除了会死循环。
1500                     // 保证执行顺序。让callback总是在下一个 tick 中执行。
1501                     return promise[ key ](function() {
1502                                 var deferred = Base.Deferred(),
1503                                     args = arguments;
1504     
1505                                 if ( args.length === 1 ) {
1506                                     args = args[ 0 ];
1507                                 }
1508     
1509                                 setTimeout(function() {
1510                                     deferred.resolve( args );
1511                                 }, 1 );
1512     
1513                                 return deferred.promise();
1514                             })[ callback ? key : 'done' ]( callback || Base.noop );
1515                 } else {
1516                     return rlts[ 0 ];
1517                 }
1518             },
1519     
1520             destroy: function() {
1521                 _destroy.apply( this, arguments );
1522                 this._widgets = null;
1523             }
1524         });
1525     
1526         /**
1527          * 添加组件
1528          * @grammar Uploader.register(proto);
1529          * @grammar Uploader.register(map, proto);
1530          * @param  {object} responseMap API 名称与函数实现的映射
1531          * @param  {object} proto 组件原型,构造函数通过 constructor 属性定义
1532          * @method Uploader.register
1533          * @for Uploader
1534          * @example
1535          * Uploader.register({
1536          *     'make-thumb': 'makeThumb'
1537          * }, {
1538          *     init: function( options ) {},
1539          *     makeThumb: function() {}
1540          * });
1541          *
1542          * Uploader.register({
1543          *     'make-thumb': function() {
1544          *         
1545          *     }
1546          * });
1547          */
1548         Uploader.register = Widget.register = function( responseMap, widgetProto ) {
1549             var map = { init: 'init', destroy: 'destroy', name: 'anonymous' },
1550                 klass;
1551     
1552             if ( arguments.length === 1 ) {
1553                 widgetProto = responseMap;
1554     
1555                 // 自动生成 map 表。
1556                 $.each(widgetProto, function(key) {
1557                     if ( key[0] === '_' || key === 'name' ) {
1558                         key === 'name' && (map.name = widgetProto.name);
1559                         return;
1560                     }
1561     
1562                     map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key;
1563                 });
1564     
1565             } else {
1566                 map = $.extend( map, responseMap );
1567             }
1568     
1569             widgetProto.responseMap = map;
1570             klass = Base.inherits( Widget, widgetProto );
1571             klass._name = map.name;
1572             widgetClass.push( klass );
1573     
1574             return klass;
1575         };
1576     
1577         /**
1578          * 删除插件,只有在注册时指定了名字的才能被删除。
1579          * @grammar Uploader.unRegister(name);
1580          * @param  {string} name 组件名字
1581          * @method Uploader.unRegister
1582          * @for Uploader
1583          * @example
1584          *
1585          * Uploader.register({
1586          *     name: 'custom',
1587          *     
1588          *     'make-thumb': function() {
1589          *         
1590          *     }
1591          * });
1592          *
1593          * Uploader.unRegister('custom');
1594          */
1595         Uploader.unRegister = Widget.unRegister = function( name ) {
1596             if ( !name || name === 'anonymous' ) {
1597                 return;
1598             }
1599             
1600             // 删除指定的插件。
1601             for ( var i = widgetClass.length; i--; ) {
1602                 if ( widgetClass[i]._name === name ) {
1603                     widgetClass.splice(i, 1)
1604                 }
1605             }
1606         };
1607     
1608         return Widget;
1609     });
1610     /**
1611      * @fileOverview 文件选择相关
1612      */
1613     define('widgets/filepicker',[
1614         'base',
1615         'uploader',
1616         'lib/filepicker',
1617         'widgets/widget'
1618     ], function( Base, Uploader, FilePicker ) {
1619         var $ = Base.$;
1620     
1621         $.extend( Uploader.options, {
1622     
1623             /**
1624              * @property {Selector | Object} [pick=undefined]
1625              * @namespace options
1626              * @for Uploader
1627              * @description 指定选择文件的按钮容器,不指定则不创建按钮。
1628              *
1629              * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。
1630              * * `label` {String} 请采用 `innerHTML` 代替
1631              * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。
1632              * * `multiple` {Boolean} 是否开起同时选择多个文件能力。
1633              */
1634             pick: null,
1635     
1636             /**
1637              * @property {Arroy} [accept=null]
1638              * @namespace options
1639              * @for Uploader
1640              * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。
1641              *
1642              * * `title` {String} 文字描述
1643              * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。
1644              * * `mimeTypes` {String} 多个用逗号分割。
1645              *
1646              * 如:
1647              *
1648              * ```
1649              * {
1650              *     title: 'Images',
1651              *     extensions: 'gif,jpg,jpeg,bmp,png',
1652              *     mimeTypes: 'image/*'
1653              * }
1654              * ```
1655              */
1656             accept: null/*{
1657                 title: 'Images',
1658                 extensions: 'gif,jpg,jpeg,bmp,png',
1659                 mimeTypes: 'image/*'
1660             }*/
1661         });
1662     
1663         return Uploader.register({
1664             name: 'picker',
1665     
1666             init: function( opts ) {
1667                 this.pickers = [];
1668                 return opts.pick && this.addBtn( opts.pick );
1669             },
1670     
1671             refresh: function() {
1672                 $.each( this.pickers, function() {
1673                     this.refresh();
1674                 });
1675             },
1676     
1677             /**
1678              * @method addButton
1679              * @for Uploader
1680              * @grammar addButton( pick ) => Promise
1681              * @description
1682              * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。
1683              * @example
1684              * uploader.addButton({
1685              *     id: '#btnContainer',
1686              *     innerHTML: '选择文件'
1687              * });
1688              */
1689             addBtn: function( pick ) {
1690                 var me = this,
1691                     opts = me.options,
1692                     accept = opts.accept,
1693                     promises = [];
1694     
1695                 if ( !pick ) {
1696                     return;
1697                 }
1698     
1699                 $.isPlainObject( pick ) || (pick = {
1700                     id: pick
1701                 });
1702     
1703                 $( pick.id ).each(function() {
1704                     var options, picker, deferred;
1705     
1706                     deferred = Base.Deferred();
1707     
1708                     options = $.extend({}, pick, {
1709                         accept: $.isPlainObject( accept ) ? [ accept ] : accept,
1710                         swf: opts.swf,
1711                         runtimeOrder: opts.runtimeOrder,
1712                         id: this
1713                     });
1714     
1715                     picker = new FilePicker( options );
1716     
1717                     picker.once( 'ready', deferred.resolve );
1718                     picker.on( 'select', function( files ) {
1719                         me.owner.request( 'add-file', [ files ]);
1720                     });
1721                     picker.init();
1722     
1723                     me.pickers.push( picker );
1724     
1725                     promises.push( deferred.promise() );
1726                 });
1727     
1728                 return Base.when.apply( Base, promises );
1729             },
1730     
1731             disable: function() {
1732                 $.each( this.pickers, function() {
1733                     this.disable();
1734                 });
1735             },
1736     
1737             enable: function() {
1738                 $.each( this.pickers, function() {
1739                     this.enable();
1740                 });
1741             },
1742     
1743             destroy: function() {
1744                 $.each( this.pickers, function() {
1745                     this.destroy();
1746                 });
1747                 this.pickers = null;
1748             }
1749         });
1750     });
1751     /**
1752      * @fileOverview Image
1753      */
1754     define('lib/image',[
1755         'base',
1756         'runtime/client',
1757         'lib/blob'
1758     ], function( Base, RuntimeClient, Blob ) {
1759         var $ = Base.$;
1760     
1761         // 构造器。
1762         function Image( opts ) {
1763             this.options = $.extend({}, Image.options, opts );
1764             RuntimeClient.call( this, 'Image' );
1765     
1766             this.on( 'load', function() {
1767                 this._info = this.exec('info');
1768                 this._meta = this.exec('meta');
1769             });
1770         }
1771     
1772         // 默认选项。
1773         Image.options = {
1774     
1775             // 默认的图片处理质量
1776             quality: 90,
1777     
1778             // 是否裁剪
1779             crop: false,
1780     
1781             // 是否保留头部信息
1782             preserveHeaders: false,
1783     
1784             // 是否允许放大。
1785             allowMagnify: false
1786         };
1787     
1788         // 继承RuntimeClient.
1789         Base.inherits( RuntimeClient, {
1790             constructor: Image,
1791     
1792             info: function( val ) {
1793     
1794                 // setter
1795                 if ( val ) {
1796                     this._info = val;
1797                     return this;
1798                 }
1799     
1800                 // getter
1801                 return this._info;
1802             },
1803     
1804             meta: function( val ) {
1805     
1806                 // setter
1807                 if ( val ) {
1808                     this._meta = val;
1809                     return this;
1810                 }
1811     
1812                 // getter
1813                 return this._meta;
1814             },
1815     
1816             loadFromBlob: function( blob ) {
1817                 var me = this,
1818                     ruid = blob.getRuid();
1819     
1820                 this.connectRuntime( ruid, function() {
1821                     me.exec( 'init', me.options );
1822                     me.exec( 'loadFromBlob', blob );
1823                 });
1824             },
1825     
1826             resize: function() {
1827                 var args = Base.slice( arguments );
1828                 return this.exec.apply( this, [ 'resize' ].concat( args ) );
1829             },
1830     
1831             crop: function() {
1832                 var args = Base.slice( arguments );
1833                 return this.exec.apply( this, [ 'crop' ].concat( args ) );
1834             },
1835     
1836             getAsDataUrl: function( type ) {
1837                 return this.exec( 'getAsDataUrl', type );
1838             },
1839     
1840             getAsBlob: function( type ) {
1841                 var blob = this.exec( 'getAsBlob', type );
1842     
1843                 return new Blob( this.getRuid(), blob );
1844             }
1845         });
1846     
1847         return Image;
1848     });
1849     /**
1850      * @fileOverview 图片操作, 负责预览图片和上传前压缩图片
1851      */
1852     define('widgets/image',[
1853         'base',
1854         'uploader',
1855         'lib/image',
1856         'widgets/widget'
1857     ], function( Base, Uploader, Image ) {
1858     
1859         var $ = Base.$,
1860             throttle;
1861     
1862         // 根据要处理的文件大小来节流,一次不能处理太多,会卡。
1863         throttle = (function( max ) {
1864             var occupied = 0,
1865                 waiting = [],
1866                 tick = function() {
1867                     var item;
1868     
1869                     while ( waiting.length && occupied < max ) {
1870                         item = waiting.shift();
1871                         occupied += item[ 0 ];
1872                         item[ 1 ]();
1873                     }
1874                 };
1875     
1876             return function( emiter, size, cb ) {
1877                 waiting.push([ size, cb ]);
1878                 emiter.once( 'destroy', function() {
1879                     occupied -= size;
1880                     setTimeout( tick, 1 );
1881                 });
1882                 setTimeout( tick, 1 );
1883             };
1884         })( 5 * 1024 * 1024 );
1885     
1886         $.extend( Uploader.options, {
1887     
1888             /**
1889              * @property {Object} [thumb]
1890              * @namespace options
1891              * @for Uploader
1892              * @description 配置生成缩略图的选项。
1893              *
1894              * 默认为:
1895              *
1896              * ```javascript
1897              * {
1898              *     width: 110,
1899              *     height: 110,
1900              *
1901              *     // 图片质量,只有type为`image/jpeg`的时候才有效。
1902              *     quality: 70,
1903              *
1904              *     // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false.
1905              *     allowMagnify: true,
1906              *
1907              *     // 是否允许裁剪。
1908              *     crop: true,
1909              *
1910              *     // 为空的话则保留原有图片格式。
1911              *     // 否则强制转换成指定的类型。
1912              *     type: 'image/jpeg'
1913              * }
1914              * ```
1915              */
1916             thumb: {
1917                 width: 110,
1918                 height: 110,
1919                 quality: 70,
1920                 allowMagnify: true,
1921                 crop: true,
1922                 preserveHeaders: false,
1923     
1924                 // 为空的话则保留原有图片格式。
1925                 // 否则强制转换成指定的类型。
1926                 // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可
1927                 // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg
1928                 type: 'image/jpeg'
1929             },
1930     
1931             /**
1932              * @property {Object} [compress]
1933              * @namespace options
1934              * @for Uploader
1935              * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。
1936              *
1937              * 默认为:
1938              *
1939              * ```javascript
1940              * {
1941              *     width: 1600,
1942              *     height: 1600,
1943              *
1944              *     // 图片质量,只有type为`image/jpeg`的时候才有效。
1945              *     quality: 90,
1946              *
1947              *     // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false.
1948              *     allowMagnify: false,
1949              *
1950              *     // 是否允许裁剪。
1951              *     crop: false,
1952              *
1953              *     // 是否保留头部meta信息。
1954              *     preserveHeaders: true,
1955              *
1956              *     // 如果发现压缩后文件大小比原来还大,则使用原来图片
1957              *     // 此属性可能会影响图片自动纠正功能
1958              *     noCompressIfLarger: false,
1959              *
1960              *     // 单位字节,如果图片大小小于此值,不会采用压缩。
1961              *     compressSize: 0
1962              * }
1963              * ```
1964              */
1965             compress: {
1966                 width: 1600,
1967                 height: 1600,
1968                 quality: 90,
1969                 allowMagnify: false,
1970                 crop: false,
1971                 preserveHeaders: true
1972             }
1973         });
1974     
1975         return Uploader.register({
1976     
1977             name: 'image',
1978     
1979     
1980             /**
1981              * 生成缩略图,此过程为异步,所以需要传入`callback`。
1982              * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。
1983              *
1984              * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。
1985              *
1986              * `callback`中可以接收到两个参数。
1987              * * 第一个为error,如果生成缩略图有错误,此error将为真。
1988              * * 第二个为ret, 缩略图的Data URL值。
1989              *
1990              * **注意**
1991              * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。
1992              * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。
1993              *
1994              * @method makeThumb
1995              * @grammar makeThumb( file, callback ) => undefined
1996              * @grammar makeThumb( file, callback, width, height ) => undefined
1997              * @for Uploader
1998              * @example
1999              *
2000              * uploader.on( 'fileQueued', function( file ) {
2001              *     var $li = ...;
2002              *
2003              *     uploader.makeThumb( file, function( error, ret ) {
2004              *         if ( error ) {
2005              *             $li.text('预览错误');
2006              *         } else {
2007              *             $li.append('<img alt="" src="' + ret + '" />');
2008              *         }
2009              *     });
2010              *
2011              * });
2012              */
2013             makeThumb: function( file, cb, width, height ) {
2014                 var opts, image;
2015     
2016                 file = this.request( 'get-file', file );
2017     
2018                 // 只预览图片格式。
2019                 if ( !file.type.match( /^image/ ) ) {
2020                     cb( true );
2021                     return;
2022                 }
2023     
2024                 opts = $.extend({}, this.options.thumb );
2025     
2026                 // 如果传入的是object.
2027                 if ( $.isPlainObject( width ) ) {
2028                     opts = $.extend( opts, width );
2029                     width = null;
2030                 }
2031     
2032                 width = width || opts.width;
2033                 height = height || opts.height;
2034     
2035                 image = new Image( opts );
2036     
2037                 image.once( 'load', function() {
2038                     file._info = file._info || image.info();
2039                     file._meta = file._meta || image.meta();
2040     
2041                     // 如果 width 的值介于 0 - 1
2042                     // 说明设置的是百分比。
2043                     if ( width <= 1 && width > 0 ) {
2044                         width = file._info.width * width;
2045                     }
2046     
2047                     // 同样的规则应用于 height
2048                     if ( height <= 1 && height > 0 ) {
2049                         height = file._info.height * height;
2050                     }
2051     
2052                     image.resize( width, height );
2053                 });
2054     
2055                 // 当 resize 完后
2056                 image.once( 'complete', function() {
2057                     cb( false, image.getAsDataUrl( opts.type ) );
2058                     image.destroy();
2059                 });
2060     
2061                 image.once( 'error', function( reason ) {
2062                     cb( reason || true );
2063                     image.destroy();
2064                 });
2065     
2066                 throttle( image, file.source.size, function() {
2067                     file._info && image.info( file._info );
2068                     file._meta && image.meta( file._meta );
2069                     image.loadFromBlob( file.source );
2070                 });
2071             },
2072     
2073             beforeSendFile: function( file ) {
2074                 var opts = this.options.compress || this.options.resize,
2075                     compressSize = opts && opts.compressSize || 0,
2076                     noCompressIfLarger = opts && opts.noCompressIfLarger || false,
2077                     image, deferred;
2078     
2079                 file = this.request( 'get-file', file );
2080     
2081                 // 只压缩 jpeg 图片格式。
2082                 // gif 可能会丢失针
2083                 // bmp png 基本上尺寸都不大,且压缩比比较小。
2084                 if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||
2085                         file.size < compressSize ||
2086                         file._compressed ) {
2087                     return;
2088                 }
2089     
2090                 opts = $.extend({}, opts );
2091                 deferred = Base.Deferred();
2092     
2093                 image = new Image( opts );
2094     
2095                 deferred.always(function() {
2096                     image.destroy();
2097                     image = null;
2098                 });
2099                 image.once( 'error', deferred.reject );
2100                 image.once( 'load', function() {
2101                     var width = opts.width,
2102                         height = opts.height;
2103     
2104                     file._info = file._info || image.info();
2105                     file._meta = file._meta || image.meta();
2106     
2107                     // 如果 width 的值介于 0 - 1
2108                     // 说明设置的是百分比。
2109                     if ( width <= 1 && width > 0 ) {
2110                         width = file._info.width * width;
2111                     }
2112     
2113                     // 同样的规则应用于 height
2114                     if ( height <= 1 && height > 0 ) {
2115                         height = file._info.height * height;
2116                     }
2117     
2118                     image.resize( width, height );
2119                 });
2120     
2121                 image.once( 'complete', function() {
2122                     var blob, size;
2123     
2124                     // 移动端 UC / qq 浏览器的无图模式下
2125                     // ctx.getImageData 处理大图的时候会报 Exception
2126                     // INDEX_SIZE_ERR: DOM Exception 1
2127                     try {
2128                         blob = image.getAsBlob( opts.type );
2129     
2130                         size = file.size;
2131     
2132                         // 如果压缩后,比原来还大则不用压缩后的。
2133                         if ( !noCompressIfLarger || blob.size < size ) {
2134                             // file.source.destroy && file.source.destroy();
2135                             file.source = blob;
2136                             file.size = blob.size;
2137     
2138                             file.trigger( 'resize', blob.size, size );
2139                         }
2140     
2141                         // 标记,避免重复压缩。
2142                         file._compressed = true;
2143                         deferred.resolve();
2144                     } catch ( e ) {
2145                         // 出错了直接继续,让其上传原始图片
2146                         deferred.resolve();
2147                     }
2148                 });
2149     
2150                 file._info && image.info( file._info );
2151                 file._meta && image.meta( file._meta );
2152     
2153                 image.loadFromBlob( file.source );
2154                 return deferred.promise();
2155             }
2156         });
2157     });
2158     /**
2159      * @fileOverview 文件属性封装
2160      */
2161     define('file',[
2162         'base',
2163         'mediator'
2164     ], function( Base, Mediator ) {
2165     
2166         var $ = Base.$,
2167             idPrefix = 'WU_FILE_',
2168             idSuffix = 0,
2169             rExt = /\.([^.]+)$/,
2170             statusMap = {};
2171     
2172         function gid() {
2173             return idPrefix + idSuffix++;
2174         }
2175     
2176         /**
2177          * 文件类
2178          * @class File
2179          * @constructor 构造函数
2180          * @grammar new File( source ) => File
2181          * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。
2182          */
2183         function WUFile( source ) {
2184     
2185             /**
2186              * 文件名,包括扩展名(后缀)
2187              * @property name
2188              * @type {string}
2189              */
2190             this.name = source.name || 'Untitled';
2191     
2192             /**
2193              * 文件体积(字节)
2194              * @property size
2195              * @type {uint}
2196              * @default 0
2197              */
2198             this.size = source.size || 0;
2199     
2200             /**
2201              * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)
2202              * @property type
2203              * @type {string}
2204              * @default 'application/octet-stream'
2205              */
2206             this.type = source.type || 'application/octet-stream';
2207     
2208             /**
2209              * 文件最后修改日期
2210              * @property lastModifiedDate
2211              * @type {int}
2212              * @default 当前时间戳
2213              */
2214             this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);
2215     
2216             /**
2217              * 文件ID,每个对象具有唯一ID,与文件名无关
2218              * @property id
2219              * @type {string}
2220              */
2221             this.id = gid();
2222     
2223             /**
2224              * 文件扩展名,通过文件名获取,例如test.png的扩展名为png
2225              * @property ext
2226              * @type {string}
2227              */
2228             this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';
2229     
2230     
2231             /**
2232              * 状态文字说明。在不同的status语境下有不同的用途。
2233              * @property statusText
2234              * @type {string}
2235              */
2236             this.statusText = '';
2237     
2238             // 存储文件状态,防止通过属性直接修改
2239             statusMap[ this.id ] = WUFile.Status.INITED;
2240     
2241             this.source = source;
2242             this.loaded = 0;
2243     
2244             this.on( 'error', function( msg ) {
2245                 this.setStatus( WUFile.Status.ERROR, msg );
2246             });
2247         }
2248     
2249         $.extend( WUFile.prototype, {
2250     
2251             /**
2252              * 设置状态,状态变化时会触发`change`事件。
2253              * @method setStatus
2254              * @grammar setStatus( status[, statusText] );
2255              * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)
2256              * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。
2257              */
2258             setStatus: function( status, text ) {
2259     
2260                 var prevStatus = statusMap[ this.id ];
2261     
2262                 typeof text !== 'undefined' && (this.statusText = text);
2263     
2264                 if ( status !== prevStatus ) {
2265                     statusMap[ this.id ] = status;
2266                     /**
2267                      * 文件状态变化
2268                      * @event statuschange
2269                      */
2270                     this.trigger( 'statuschange', status, prevStatus );
2271                 }
2272     
2273             },
2274     
2275             /**
2276              * 获取文件状态
2277              * @return {File.Status}
2278              * @example
2279                      文件状态具体包括以下几种类型:
2280                      {
2281                          // 初始化
2282                         INITED:     0,
2283                         // 已入队列
2284                         QUEUED:     1,
2285                         // 正在上传
2286                         PROGRESS:     2,
2287                         // 上传出错
2288                         ERROR:         3,
2289                         // 上传成功
2290                         COMPLETE:     4,
2291                         // 上传取消
2292                         CANCELLED:     5
2293                     }
2294              */
2295             getStatus: function() {
2296                 return statusMap[ this.id ];
2297             },
2298     
2299             /**
2300              * 获取文件原始信息。
2301              * @return {*}
2302              */
2303             getSource: function() {
2304                 return this.source;
2305             },
2306     
2307             destroy: function() {
2308                 this.off();
2309                 delete statusMap[ this.id ];
2310             }
2311         });
2312     
2313         Mediator.installTo( WUFile.prototype );
2314     
2315         /**
2316          * 文件状态值,具体包括以下几种类型:
2317          * * `inited` 初始状态
2318          * * `queued` 已经进入队列, 等待上传
2319          * * `progress` 上传中
2320          * * `complete` 上传完成。
2321          * * `error` 上传出错,可重试
2322          * * `interrupt` 上传中断,可续传。
2323          * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。
2324          * * `cancelled` 文件被移除。
2325          * @property {Object} Status
2326          * @namespace File
2327          * @class File
2328          * @static
2329          */
2330         WUFile.Status = {
2331             INITED:     'inited',    // 初始状态
2332             QUEUED:     'queued',    // 已经进入队列, 等待上传
2333             PROGRESS:   'progress',    // 上传中
2334             ERROR:      'error',    // 上传出错,可重试
2335             COMPLETE:   'complete',    // 上传完成。
2336             CANCELLED:  'cancelled',    // 上传取消。
2337             INTERRUPT:  'interrupt',    // 上传中断,可续传。
2338             INVALID:    'invalid'    // 文件不合格,不能重试上传。
2339         };
2340     
2341         return WUFile;
2342     });
2343     
2344     /**
2345      * @fileOverview 文件队列
2346      */
2347     define('queue',[
2348         'base',
2349         'mediator',
2350         'file'
2351     ], function( Base, Mediator, WUFile ) {
2352     
2353         var $ = Base.$,
2354             STATUS = WUFile.Status;
2355     
2356         /**
2357          * 文件队列, 用来存储各个状态中的文件。
2358          * @class Queue
2359          * @extends Mediator
2360          */
2361         function Queue() {
2362     
2363             /**
2364              * 统计文件数。
2365              * * `numOfQueue` 队列中的文件数。
2366              * * `numOfSuccess` 上传成功的文件数
2367              * * `numOfCancel` 被取消的文件数
2368              * * `numOfProgress` 正在上传中的文件数
2369              * * `numOfUploadFailed` 上传错误的文件数。
2370              * * `numOfInvalid` 无效的文件数。
2371              * * `numofDeleted` 被移除的文件数。
2372              * @property {Object} stats
2373              */
2374             this.stats = {
2375                 numOfQueue: 0,
2376                 numOfSuccess: 0,
2377                 numOfCancel: 0,
2378                 numOfProgress: 0,
2379                 numOfUploadFailed: 0,
2380                 numOfInvalid: 0,
2381                 numofDeleted: 0,
2382                 numofInterrupt: 0
2383             };
2384     
2385             // 上传队列,仅包括等待上传的文件
2386             this._queue = [];
2387     
2388             // 存储所有文件
2389             this._map = {};
2390         }
2391     
2392         $.extend( Queue.prototype, {
2393     
2394             /**
2395              * 将新文件加入对队列尾部
2396              *
2397              * @method append
2398              * @param  {File} file   文件对象
2399              */
2400             append: function( file ) {
2401                 this._queue.push( file );
2402                 this._fileAdded( file );
2403                 return this;
2404             },
2405     
2406             /**
2407              * 将新文件加入对队列头部
2408              *
2409              * @method prepend
2410              * @param  {File} file   文件对象
2411              */
2412             prepend: function( file ) {
2413                 this._queue.unshift( file );
2414                 this._fileAdded( file );
2415                 return this;
2416             },
2417     
2418             /**
2419              * 获取文件对象
2420              *
2421              * @method getFile
2422              * @param  {String} fileId   文件ID
2423              * @return {File}
2424              */
2425             getFile: function( fileId ) {
2426                 if ( typeof fileId !== 'string' ) {
2427                     return fileId;
2428                 }
2429                 return this._map[ fileId ];
2430             },
2431     
2432             /**
2433              * 从队列中取出一个指定状态的文件。
2434              * @grammar fetch( status ) => File
2435              * @method fetch
2436              * @param {String} status [文件状态值](#WebUploader:File:File.Status)
2437              * @return {File} [File](#WebUploader:File)
2438              */
2439             fetch: function( status ) {
2440                 var len = this._queue.length,
2441                     i, file;
2442     
2443                 status = status || STATUS.QUEUED;
2444     
2445                 for ( i = 0; i < len; i++ ) {
2446                     file = this._queue[ i ];
2447     
2448                     if ( status === file.getStatus() ) {
2449                         return file;
2450                     }
2451                 }
2452     
2453                 return null;
2454             },
2455     
2456             /**
2457              * 对队列进行排序,能够控制文件上传顺序。
2458              * @grammar sort( fn ) => undefined
2459              * @method sort
2460              * @param {Function} fn 排序方法
2461              */
2462             sort: function( fn ) {
2463                 if ( typeof fn === 'function' ) {
2464                     this._queue.sort( fn );
2465                 }
2466             },
2467     
2468             /**
2469              * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。
2470              * @grammar getFiles( [status1[, status2 ...]] ) => Array
2471              * @method getFiles
2472              * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)
2473              */
2474             getFiles: function() {
2475                 var sts = [].slice.call( arguments, 0 ),
2476                     ret = [],
2477                     i = 0,
2478                     len = this._queue.length,
2479                     file;
2480     
2481                 for ( ; i < len; i++ ) {
2482                     file = this._queue[ i ];
2483     
2484                     if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {
2485                         continue;
2486                     }
2487     
2488                     ret.push( file );
2489                 }
2490     
2491                 return ret;
2492             },
2493     
2494             /**
2495              * 在队列中删除文件。
2496              * @grammar removeFile( file ) => Array
2497              * @method removeFile
2498              * @param {File} 文件对象。
2499              */
2500             removeFile: function( file ) {
2501                 var me = this,
2502                     existing = this._map[ file.id ];
2503     
2504                 if ( existing ) {
2505                     delete this._map[ file.id ];
2506                     file.destroy();
2507                     this.stats.numofDeleted++;
2508                 }
2509             },
2510     
2511             _fileAdded: function( file ) {
2512                 var me = this,
2513                     existing = this._map[ file.id ];
2514     
2515                 if ( !existing ) {
2516                     this._map[ file.id ] = file;
2517     
2518                     file.on( 'statuschange', function( cur, pre ) {
2519                         me._onFileStatusChange( cur, pre );
2520                     });
2521                 }
2522             },
2523     
2524             _onFileStatusChange: function( curStatus, preStatus ) {
2525                 var stats = this.stats;
2526     
2527                 switch ( preStatus ) {
2528                     case STATUS.PROGRESS:
2529                         stats.numOfProgress--;
2530                         break;
2531     
2532                     case STATUS.QUEUED:
2533                         stats.numOfQueue --;
2534                         break;
2535     
2536                     case STATUS.ERROR:
2537                         stats.numOfUploadFailed--;
2538                         break;
2539     
2540                     case STATUS.INVALID:
2541                         stats.numOfInvalid--;
2542                         break;
2543     
2544                     case STATUS.INTERRUPT:
2545                         stats.numofInterrupt--;
2546                         break;
2547                 }
2548     
2549                 switch ( curStatus ) {
2550                     case STATUS.QUEUED:
2551                         stats.numOfQueue++;
2552                         break;
2553     
2554                     case STATUS.PROGRESS:
2555                         stats.numOfProgress++;
2556                         break;
2557     
2558                     case STATUS.ERROR:
2559                         stats.numOfUploadFailed++;
2560                         break;
2561     
2562                     case STATUS.COMPLETE:
2563                         stats.numOfSuccess++;
2564                         break;
2565     
2566                     case STATUS.CANCELLED:
2567                         stats.numOfCancel++;
2568                         break;
2569     
2570     
2571                     case STATUS.INVALID:
2572                         stats.numOfInvalid++;
2573                         break;
2574     
2575                     case STATUS.INTERRUPT:
2576                         stats.numofInterrupt++;
2577                         break;
2578                 }
2579             }
2580     
2581         });
2582     
2583         Mediator.installTo( Queue.prototype );
2584     
2585         return Queue;
2586     });
2587     /**
2588      * @fileOverview 队列
2589      */
2590     define('widgets/queue',[
2591         'base',
2592         'uploader',
2593         'queue',
2594         'file',
2595         'lib/file',
2596         'runtime/client',
2597         'widgets/widget'
2598     ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {
2599     
2600         var $ = Base.$,
2601             rExt = /\.\w+$/,
2602             Status = WUFile.Status;
2603     
2604         return Uploader.register({
2605             name: 'queue',
2606     
2607             init: function( opts ) {
2608                 var me = this,
2609                     deferred, len, i, item, arr, accept, runtime;
2610     
2611                 if ( $.isPlainObject( opts.accept ) ) {
2612                     opts.accept = [ opts.accept ];
2613                 }
2614     
2615                 // accept中的中生成匹配正则。
2616                 if ( opts.accept ) {
2617                     arr = [];
2618     
2619                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
2620                         item = opts.accept[ i ].extensions;
2621                         item && arr.push( item );
2622                     }
2623     
2624                     if ( arr.length ) {
2625                         accept = '\\.' + arr.join(',')
2626                                 .replace( /,/g, '$|\\.' )
2627                                 .replace( /\*/g, '.*' ) + '$';
2628                     }
2629     
2630                     me.accept = new RegExp( accept, 'i' );
2631                 }
2632     
2633                 me.queue = new Queue();
2634                 me.stats = me.queue.stats;
2635     
2636                 // 如果当前不是html5运行时,那就算了。
2637                 // 不执行后续操作
2638                 if ( this.request('predict-runtime-type') !== 'html5' ) {
2639                     return;
2640                 }
2641     
2642                 // 创建一个 html5 运行时的 placeholder
2643                 // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。
2644                 deferred = Base.Deferred();
2645                 this.placeholder = runtime = new RuntimeClient('Placeholder');
2646                 runtime.connectRuntime({
2647                     runtimeOrder: 'html5'
2648                 }, function() {
2649                     me._ruid = runtime.getRuid();
2650                     deferred.resolve();
2651                 });
2652                 return deferred.promise();
2653             },
2654     
2655     
2656             // 为了支持外部直接添加一个原生File对象。
2657             _wrapFile: function( file ) {
2658                 if ( !(file instanceof WUFile) ) {
2659     
2660                     if ( !(file instanceof File) ) {
2661                         if ( !this._ruid ) {
2662                             throw new Error('Can\'t add external files.');
2663                         }
2664                         file = new File( this._ruid, file );
2665                     }
2666     
2667                     file = new WUFile( file );
2668                 }
2669     
2670                 return file;
2671             },
2672     
2673             // 判断文件是否可以被加入队列
2674             acceptFile: function( file ) {
2675                 var invalid = !file || !file.size || this.accept &&
2676     
2677                         // 如果名字中有后缀,才做后缀白名单处理。
2678                         rExt.exec( file.name ) && !this.accept.test( file.name );
2679     
2680                 return !invalid;
2681             },
2682     
2683     
2684             /**
2685              * @event beforeFileQueued
2686              * @param {File} file File对象
2687              * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。
2688              * @for  Uploader
2689              */
2690     
2691             /**
2692              * @event fileQueued
2693              * @param {File} file File对象
2694              * @description 当文件被加入队列以后触发。
2695              * @for  Uploader
2696              */
2697     
2698             _addFile: function( file ) {
2699                 var me = this;
2700     
2701                 file = me._wrapFile( file );
2702     
2703                 // 不过类型判断允许不允许,先派送 `beforeFileQueued`
2704                 if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {
2705                     return;
2706                 }
2707     
2708                 // 类型不匹配,则派送错误事件,并返回。
2709                 if ( !me.acceptFile( file ) ) {
2710                     me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );
2711                     return;
2712                 }
2713     
2714                 me.queue.append( file );
2715                 me.owner.trigger( 'fileQueued', file );
2716                 return file;
2717             },
2718     
2719             getFile: function( fileId ) {
2720                 return this.queue.getFile( fileId );
2721             },
2722     
2723             /**
2724              * @event filesQueued
2725              * @param {File} files 数组,内容为原始File(lib/File)对象。
2726              * @description 当一批文件添加进队列以后触发。
2727              * @for  Uploader
2728              */
2729             
2730             /**
2731              * @property {Boolean} [auto=false]
2732              * @namespace options
2733              * @for Uploader
2734              * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。
2735              * 
2736              */
2737     
2738             /**
2739              * @method addFiles
2740              * @grammar addFiles( file ) => undefined
2741              * @grammar addFiles( [file1, file2 ...] ) => undefined
2742              * @param {Array of File or File} [files] Files 对象 数组
2743              * @description 添加文件到队列
2744              * @for  Uploader
2745              */
2746             addFile: function( files ) {
2747                 var me = this;
2748     
2749                 if ( !files.length ) {
2750                     files = [ files ];
2751                 }
2752     
2753                 files = $.map( files, function( file ) {
2754                     return me._addFile( file );
2755                 });
2756     
2757                 me.owner.trigger( 'filesQueued', files );
2758     
2759                 if ( me.options.auto ) {
2760                     setTimeout(function() {
2761                         me.request('start-upload');
2762                     }, 20 );
2763                 }
2764             },
2765     
2766             getStats: function() {
2767                 return this.stats;
2768             },
2769     
2770             /**
2771              * @event fileDequeued
2772              * @param {File} file File对象
2773              * @description 当文件被移除队列后触发。
2774              * @for  Uploader
2775              */
2776     
2777              /**
2778              * @method removeFile
2779              * @grammar removeFile( file ) => undefined
2780              * @grammar removeFile( id ) => undefined
2781              * @grammar removeFile( file, true ) => undefined
2782              * @grammar removeFile( id, true ) => undefined
2783              * @param {File|id} file File对象或这File对象的id
2784              * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。
2785              * @for  Uploader
2786              * @example
2787              *
2788              * $li.on('click', '.remove-this', function() {
2789              *     uploader.removeFile( file );
2790              * })
2791              */
2792             removeFile: function( file, remove ) {
2793                 var me = this;
2794     
2795                 file = file.id ? file : me.queue.getFile( file );
2796     
2797                 this.request( 'cancel-file', file );
2798     
2799                 if ( remove ) {
2800                     this.queue.removeFile( file );
2801                 }
2802             },
2803     
2804             /**
2805              * @method getFiles
2806              * @grammar getFiles() => Array
2807              * @grammar getFiles( status1, status2, status... ) => Array
2808              * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。
2809              * @for  Uploader
2810              * @example
2811              * console.log( uploader.getFiles() );    // => all files
2812              * console.log( uploader.getFiles('error') )    // => all error files.
2813              */
2814             getFiles: function() {
2815                 return this.queue.getFiles.apply( this.queue, arguments );
2816             },
2817     
2818             fetchFile: function() {
2819                 return this.queue.fetch.apply( this.queue, arguments );
2820             },
2821     
2822             /**
2823              * @method retry
2824              * @grammar retry() => undefined
2825              * @grammar retry( file ) => undefined
2826              * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。
2827              * @for  Uploader
2828              * @example
2829              * function retry() {
2830              *     uploader.retry();
2831              * }
2832              */
2833             retry: function( file, noForceStart ) {
2834                 var me = this,
2835                     files, i, len;
2836     
2837                 if ( file ) {
2838                     file = file.id ? file : me.queue.getFile( file );
2839                     file.setStatus( Status.QUEUED );
2840                     noForceStart || me.request('start-upload');
2841                     return;
2842                 }
2843     
2844                 files = me.queue.getFiles( Status.ERROR );
2845                 i = 0;
2846                 len = files.length;
2847     
2848                 for ( ; i < len; i++ ) {
2849                     file = files[ i ];
2850                     file.setStatus( Status.QUEUED );
2851                 }
2852     
2853                 me.request('start-upload');
2854             },
2855     
2856             /**
2857              * @method sort
2858              * @grammar sort( fn ) => undefined
2859              * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。
2860              * @for  Uploader
2861              */
2862             sortFiles: function() {
2863                 return this.queue.sort.apply( this.queue, arguments );
2864             },
2865     
2866             /**
2867              * @event reset
2868              * @description 当 uploader 被重置的时候触发。
2869              * @for  Uploader
2870              */
2871     
2872             /**
2873              * @method reset
2874              * @grammar reset() => undefined
2875              * @description 重置uploader。目前只重置了队列。
2876              * @for  Uploader
2877              * @example
2878              * uploader.reset();
2879              */
2880             reset: function() {
2881                 this.owner.trigger('reset');
2882                 this.queue = new Queue();
2883                 this.stats = this.queue.stats;
2884             },
2885     
2886             destroy: function() {
2887                 this.reset();
2888                 this.placeholder && this.placeholder.destroy();
2889             }
2890         });
2891     
2892     });
2893     /**
2894      * @fileOverview 添加获取Runtime相关信息的方法。
2895      */
2896     define('widgets/runtime',[
2897         'uploader',
2898         'runtime/runtime',
2899         'widgets/widget'
2900     ], function( Uploader, Runtime ) {
2901     
2902         Uploader.support = function() {
2903             return Runtime.hasRuntime.apply( Runtime, arguments );
2904         };
2905     
2906         /**
2907          * @property {Object} [runtimeOrder=html5,flash]
2908          * @namespace options
2909          * @for Uploader
2910          * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash.
2911          *
2912          * 可以将此值设置成 `flash`,来强制使用 flash 运行时。
2913          */
2914     
2915         return Uploader.register({
2916             name: 'runtime',
2917     
2918             init: function() {
2919                 if ( !this.predictRuntimeType() ) {
2920                     throw Error('Runtime Error');
2921                 }
2922             },
2923     
2924             /**
2925              * 预测Uploader将采用哪个`Runtime`
2926              * @grammar predictRuntimeType() => String
2927              * @method predictRuntimeType
2928              * @for  Uploader
2929              */
2930             predictRuntimeType: function() {
2931                 var orders = this.options.runtimeOrder || Runtime.orders,
2932                     type = this.type,
2933                     i, len;
2934     
2935                 if ( !type ) {
2936                     orders = orders.split( /\s*,\s*/g );
2937     
2938                     for ( i = 0, len = orders.length; i < len; i++ ) {
2939                         if ( Runtime.hasRuntime( orders[ i ] ) ) {
2940                             this.type = type = orders[ i ];
2941                             break;
2942                         }
2943                     }
2944                 }
2945     
2946                 return type;
2947             }
2948         });
2949     });
2950     /**
2951      * @fileOverview Transport
2952      */
2953     define('lib/transport',[
2954         'base',
2955         'runtime/client',
2956         'mediator'
2957     ], function( Base, RuntimeClient, Mediator ) {
2958     
2959         var $ = Base.$;
2960     
2961         function Transport( opts ) {
2962             var me = this;
2963     
2964             opts = me.options = $.extend( true, {}, Transport.options, opts || {} );
2965             RuntimeClient.call( this, 'Transport' );
2966     
2967             this._blob = null;
2968             this._formData = opts.formData || {};
2969             this._headers = opts.headers || {};
2970     
2971             this.on( 'progress', this._timeout );
2972             this.on( 'load error', function() {
2973                 me.trigger( 'progress', 1 );
2974                 clearTimeout( me._timer );
2975             });
2976         }
2977     
2978         Transport.options = {
2979             server: '',
2980             method: 'POST',
2981     
2982             // 跨域时,是否允许携带cookie, 只有html5 runtime才有效
2983             withCredentials: false,
2984             fileVal: 'file',
2985             timeout: 2 * 60 * 1000,    // 2分钟
2986             formData: {},
2987             headers: {},
2988             sendAsBinary: false
2989         };
2990     
2991         $.extend( Transport.prototype, {
2992     
2993             // 添加Blob, 只能添加一次,最后一次有效。
2994             appendBlob: function( key, blob, filename ) {
2995                 var me = this,
2996                     opts = me.options;
2997     
2998                 if ( me.getRuid() ) {
2999                     me.disconnectRuntime();
3000                 }
3001     
3002                 // 连接到blob归属的同一个runtime.
3003                 me.connectRuntime( blob.ruid, function() {
3004                     me.exec('init');
3005                 });
3006     
3007                 me._blob = blob;
3008                 opts.fileVal = key || opts.fileVal;
3009                 opts.filename = filename || opts.filename;
3010             },
3011     
3012             // 添加其他字段
3013             append: function( key, value ) {
3014                 if ( typeof key === 'object' ) {
3015                     $.extend( this._formData, key );
3016                 } else {
3017                     this._formData[ key ] = value;
3018                 }
3019             },
3020     
3021             setRequestHeader: function( key, value ) {
3022                 if ( typeof key === 'object' ) {
3023                     $.extend( this._headers, key );
3024                 } else {
3025                     this._headers[ key ] = value;
3026                 }
3027             },
3028     
3029             send: function( method ) {
3030                 this.exec( 'send', method );
3031                 this._timeout();
3032             },
3033     
3034             abort: function() {
3035                 clearTimeout( this._timer );
3036                 return this.exec('abort');
3037             },
3038     
3039             destroy: function() {
3040                 this.trigger('destroy');
3041                 this.off();
3042                 this.exec('destroy');
3043                 this.disconnectRuntime();
3044             },
3045     
3046             getResponse: function() {
3047                 return this.exec('getResponse');
3048             },
3049     
3050             getResponseAsJson: function() {
3051                 return this.exec('getResponseAsJson');
3052             },
3053     
3054             getStatus: function() {
3055                 return this.exec('getStatus');
3056             },
3057     
3058             _timeout: function() {
3059                 var me = this,
3060                     duration = me.options.timeout;
3061     
3062                 if ( !duration ) {
3063                     return;
3064                 }
3065     
3066                 clearTimeout( me._timer );
3067                 me._timer = setTimeout(function() {
3068                     me.abort();
3069                     me.trigger( 'error', 'timeout' );
3070                 }, duration );
3071             }
3072     
3073         });
3074     
3075         // 让Transport具备事件功能。
3076         Mediator.installTo( Transport.prototype );
3077     
3078         return Transport;
3079     });
3080     /**
3081      * @fileOverview 负责文件上传相关。
3082      */
3083     define('widgets/upload',[
3084         'base',
3085         'uploader',
3086         'file',
3087         'lib/transport',
3088         'widgets/widget'
3089     ], function( Base, Uploader, WUFile, Transport ) {
3090     
3091         var $ = Base.$,
3092             isPromise = Base.isPromise,
3093             Status = WUFile.Status;
3094     
3095         // 添加默认配置项
3096         $.extend( Uploader.options, {
3097     
3098     
3099             /**
3100              * @property {Boolean} [prepareNextFile=false]
3101              * @namespace options
3102              * @for Uploader
3103              * @description 是否允许在文件传输时提前把下一个文件准备好。
3104              * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。
3105              * 如果能提前在当前文件传输期处理,可以节省总体耗时。
3106              */
3107             prepareNextFile: false,
3108     
3109             /**
3110              * @property {Boolean} [chunked=false]
3111              * @namespace options
3112              * @for Uploader
3113              * @description 是否要分片处理大文件上传。
3114              */
3115             chunked: false,
3116     
3117             /**
3118              * @property {Boolean} [chunkSize=5242880]
3119              * @namespace options
3120              * @for Uploader
3121              * @description 如果要分片,分多大一片? 默认大小为5M.
3122              */
3123             chunkSize: 5 * 1024 * 1024,
3124     
3125             /**
3126              * @property {Boolean} [chunkRetry=2]
3127              * @namespace options
3128              * @for Uploader
3129              * @description 如果某个分片由于网络问题出错,允许自动重传多少次?
3130              */
3131             chunkRetry: 2,
3132     
3133             /**
3134              * @property {Boolean} [threads=3]
3135              * @namespace options
3136              * @for Uploader
3137              * @description 上传并发数。允许同时最大上传进程数。
3138              */
3139             threads: 3,
3140     
3141     
3142             /**
3143              * @property {Object} [formData={}]
3144              * @namespace options
3145              * @for Uploader
3146              * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。
3147              */
3148             formData: {}
3149     
3150             /**
3151              * @property {Object} [fileVal='file']
3152              * @namespace options
3153              * @for Uploader
3154              * @description 设置文件上传域的name。
3155              */
3156     
3157             /**
3158              * @property {Object} [method='POST']
3159              * @namespace options
3160              * @for Uploader
3161              * @description 文件上传方式,`POST`或者`GET`。
3162              */
3163     
3164             /**
3165              * @property {Object} [sendAsBinary=false]
3166              * @namespace options
3167              * @for Uploader
3168              * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容,
3169              * 其他参数在$_GET数组中。
3170              */
3171         });
3172     
3173         // 负责将文件切片。
3174         function CuteFile( file, chunkSize ) {
3175             var pending = [],
3176                 blob = file.source,
3177                 total = blob.size,
3178                 chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,
3179                 start = 0,
3180                 index = 0,
3181                 len, api;
3182     
3183             api = {
3184                 file: file,
3185     
3186                 has: function() {
3187                     return !!pending.length;
3188                 },
3189     
3190                 shift: function() {
3191                     return pending.shift();
3192                 },
3193     
3194                 unshift: function( block ) {
3195                     pending.unshift( block );
3196                 }
3197             };
3198     
3199             while ( index < chunks ) {
3200                 len = Math.min( chunkSize, total - start );
3201     
3202                 pending.push({
3203                     file: file,
3204                     start: start,
3205                     end: chunkSize ? (start + len) : total,
3206                     total: total,
3207                     chunks: chunks,
3208                     chunk: index++,
3209                     cuted: api
3210                 });
3211                 start += len;
3212             }
3213     
3214             file.blocks = pending.concat();
3215             file.remaning = pending.length;
3216     
3217             return api;
3218         }
3219     
3220         Uploader.register({
3221             name: 'upload',
3222     
3223             init: function() {
3224                 var owner = this.owner,
3225                     me = this;
3226     
3227                 this.runing = false;
3228                 this.progress = false;
3229     
3230                 owner
3231                     .on( 'startUpload', function() {
3232                         me.progress = true;
3233                     })
3234                     .on( 'uploadFinished', function() {
3235                         me.progress = false;
3236                     });
3237     
3238                 // 记录当前正在传的数据,跟threads相关
3239                 this.pool = [];
3240     
3241                 // 缓存分好片的文件。
3242                 this.stack = [];
3243     
3244                 // 缓存即将上传的文件。
3245                 this.pending = [];
3246     
3247                 // 跟踪还有多少分片在上传中但是没有完成上传。
3248                 this.remaning = 0;
3249                 this.__tick = Base.bindFn( this._tick, this );
3250     
3251                 owner.on( 'uploadComplete', function( file ) {
3252     
3253                     // 把其他块取消了。
3254                     file.blocks && $.each( file.blocks, function( _, v ) {
3255                         v.transport && (v.transport.abort(), v.transport.destroy());
3256                         delete v.transport;
3257                     });
3258     
3259                     delete file.blocks;
3260                     delete file.remaning;
3261                 });
3262             },
3263     
3264             reset: function() {
3265                 this.request( 'stop-upload', true );
3266                 this.runing = false;
3267                 this.pool = [];
3268                 this.stack = [];
3269                 this.pending = [];
3270                 this.remaning = 0;
3271                 this._trigged = false;
3272                 this._promise = null;
3273             },
3274     
3275             /**
3276              * @event startUpload
3277              * @description 当开始上传流程时触发。
3278              * @for  Uploader
3279              */
3280     
3281             /**
3282              * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。
3283              *
3284              * 可以指定开始某一个文件。
3285              * @grammar upload() => undefined
3286              * @grammar upload( file | fileId) => undefined
3287              * @method upload
3288              * @for  Uploader
3289              */
3290             startUpload: function(file) {
3291                 var me = this;
3292     
3293                 // 移出invalid的文件
3294                 $.each( me.request( 'get-files', Status.INVALID ), function() {
3295                     me.request( 'remove-file', this );
3296                 });
3297     
3298                 // 如果指定了开始某个文件,则只开始指定文件。
3299                 if ( file ) {
3300                     file = file.id ? file : me.request( 'get-file', file );
3301     
3302                     if (file.getStatus() === Status.INTERRUPT) {
3303                         $.each( me.pool, function( _, v ) {
3304     
3305                             // 之前暂停过。
3306                             if (v.file !== file) {
3307                                 return;
3308                             }
3309     
3310                             v.transport && v.transport.send();
3311                         });
3312     
3313                         file.setStatus( Status.QUEUED );
3314                     } else if (file.getStatus() === Status.PROGRESS) {
3315                         return;
3316                     } else {
3317                         file.setStatus( Status.QUEUED );
3318                     }
3319                 } else {
3320                     $.each( me.request( 'get-files', [ Status.INITED ] ), function() {
3321                         this.setStatus( Status.QUEUED );
3322                     });
3323                 }
3324     
3325                 if ( me.runing ) {
3326                     return;
3327                 }
3328     
3329                 me.runing = true;
3330     
3331                 var files = [];
3332     
3333                 // 如果有暂停的,则续传
3334                 $.each( me.pool, function( _, v ) {
3335                     var file = v.file;
3336     
3337                     if ( file.getStatus() === Status.INTERRUPT ) {
3338                         files.push(file);
3339                         me._trigged = false;
3340                         v.transport && v.transport.send();
3341                     }
3342                 });
3343     
3344                 var file;
3345                 while ( (file = files.shift()) ) {
3346                     file.setStatus( Status.PROGRESS );
3347                 }
3348     
3349                 file || $.each( me.request( 'get-files',
3350                         Status.INTERRUPT ), function() {
3351                     this.setStatus( Status.PROGRESS );
3352                 });
3353     
3354                 me._trigged = false;
3355                 Base.nextTick( me.__tick );
3356                 me.owner.trigger('startUpload');
3357             },
3358     
3359             /**
3360              * @event stopUpload
3361              * @description 当开始上传流程暂停时触发。
3362              * @for  Uploader
3363              */
3364     
3365             /**
3366              * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。
3367              *
3368              * 如果第一个参数是文件,则只暂停指定文件。
3369              * @grammar stop() => undefined
3370              * @grammar stop( true ) => undefined
3371              * @grammar stop( file ) => undefined
3372              * @method stop
3373              * @for  Uploader
3374              */
3375             stopUpload: function( file, interrupt ) {
3376                 var me = this;
3377     
3378                 if (file === true) {
3379                     interrupt = file;
3380                     file = null;
3381                 }
3382     
3383                 if ( me.runing === false ) {
3384                     return;
3385                 }
3386     
3387                 // 如果只是暂停某个文件。
3388                 if ( file ) {
3389                     file = file.id ? file : me.request( 'get-file', file );
3390     
3391                     if ( file.getStatus() !== Status.PROGRESS &&
3392                             file.getStatus() !== Status.QUEUED ) {
3393                         return;
3394                     }
3395     
3396                     file.setStatus( Status.INTERRUPT );
3397                     $.each( me.pool, function( _, v ) {
3398     
3399                         // 只 abort 指定的文件。
3400                         if (v.file !== file) {
3401                             return;
3402                         }
3403     
3404                         v.transport && v.transport.abort();
3405                         me._putback(v);
3406                         me._popBlock(v);
3407                     });
3408     
3409                     return Base.nextTick( me.__tick );
3410                 }
3411     
3412                 me.runing = false;
3413     
3414                 if (this._promise && this._promise.file) {
3415                     this._promise.file.setStatus( Status.INTERRUPT );
3416                 }
3417     
3418                 interrupt && $.each( me.pool, function( _, v ) {
3419                     v.transport && v.transport.abort();
3420                     v.file.setStatus( Status.INTERRUPT );
3421                 });
3422     
3423                 me.owner.trigger('stopUpload');
3424             },
3425     
3426             /**
3427              * @method cancelFile
3428              * @grammar cancelFile( file ) => undefined
3429              * @grammar cancelFile( id ) => undefined
3430              * @param {File|id} file File对象或这File对象的id
3431              * @description 标记文件状态为已取消, 同时将中断文件传输。
3432              * @for  Uploader
3433              * @example
3434              *
3435              * $li.on('click', '.remove-this', function() {
3436              *     uploader.cancelFile( file );
3437              * })
3438              */
3439             cancelFile: function( file ) {
3440                 file = file.id ? file : this.request( 'get-file', file );
3441     
3442                 // 如果正在上传。
3443                 file.blocks && $.each( file.blocks, function( _, v ) {
3444                     var _tr = v.transport;
3445     
3446                     if ( _tr ) {
3447                         _tr.abort();
3448                         _tr.destroy();
3449                         delete v.transport;
3450                     }
3451                 });
3452     
3453                 file.setStatus( Status.CANCELLED );
3454                 this.owner.trigger( 'fileDequeued', file );
3455             },
3456     
3457             /**
3458              * 判断`Uplaode`r是否正在上传中。
3459              * @grammar isInProgress() => Boolean
3460              * @method isInProgress
3461              * @for  Uploader
3462              */
3463             isInProgress: function() {
3464                 return !!this.progress;
3465             },
3466     
3467             _getStats: function() {
3468                 return this.request('get-stats');
3469             },
3470     
3471             /**
3472              * 掉过一个文件上传,直接标记指定文件为已上传状态。
3473              * @grammar skipFile( file ) => undefined
3474              * @method skipFile
3475              * @for  Uploader
3476              */
3477             skipFile: function( file, status ) {
3478                 file = file.id ? file : this.request( 'get-file', file );
3479     
3480                 file.setStatus( status || Status.COMPLETE );
3481                 file.skipped = true;
3482     
3483                 // 如果正在上传。
3484                 file.blocks && $.each( file.blocks, function( _, v ) {
3485                     var _tr = v.transport;
3486     
3487                     if ( _tr ) {
3488                         _tr.abort();
3489                         _tr.destroy();
3490                         delete v.transport;
3491                     }
3492                 });
3493     
3494                 this.owner.trigger( 'uploadSkip', file );
3495             },
3496     
3497             /**
3498              * @event uploadFinished
3499              * @description 当所有文件上传结束时触发。
3500              * @for  Uploader
3501              */
3502             _tick: function() {
3503                 var me = this,
3504                     opts = me.options,
3505                     fn, val;
3506     
3507                 // 上一个promise还没有结束,则等待完成后再执行。
3508                 if ( me._promise ) {
3509                     return me._promise.always( me.__tick );
3510                 }
3511     
3512                 // 还有位置,且还有文件要处理的话。
3513                 if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {
3514                     me._trigged = false;
3515     
3516                     fn = function( val ) {
3517                         me._promise = null;
3518     
3519                         // 有可能是reject过来的,所以要检测val的类型。
3520                         val && val.file && me._startSend( val );
3521                         Base.nextTick( me.__tick );
3522                     };
3523     
3524                     me._promise = isPromise( val ) ? val.always( fn ) : fn( val );
3525     
3526                 // 没有要上传的了,且没有正在传输的了。
3527                 } else if ( !me.remaning && !me._getStats().numOfQueue &&
3528                     !me._getStats().numofInterrupt ) {
3529                     me.runing = false;
3530     
3531                     me._trigged || Base.nextTick(function() {
3532                         me.owner.trigger('uploadFinished');
3533                     });
3534                     me._trigged = true;
3535                 }
3536             },
3537     
3538             _putback: function(block) {
3539                 var idx;
3540     
3541                 block.cuted.unshift(block);
3542                 idx = this.stack.indexOf(block.cuted);
3543     
3544                 if (!~idx) {
3545                     this.stack.unshift(block.cuted);
3546                 }
3547             },
3548     
3549             _getStack: function() {
3550                 var i = 0,
3551                     act;
3552     
3553                 while ( (act = this.stack[ i++ ]) ) {
3554                     if ( act.has() && act.file.getStatus() === Status.PROGRESS ) {
3555                         return act;
3556                     } else if (!act.has() ||
3557                             act.file.getStatus() !== Status.PROGRESS &&
3558                             act.file.getStatus() !== Status.INTERRUPT ) {
3559     
3560                         // 把已经处理完了的,或者,状态为非 progress(上传中)、
3561                         // interupt(暂停中) 的移除。
3562                         this.stack.splice( --i, 1 );
3563                     }
3564                 }
3565     
3566                 return null;
3567             },
3568     
3569             _nextBlock: function() {
3570                 var me = this,
3571                     opts = me.options,
3572                     act, next, done, preparing;
3573     
3574                 // 如果当前文件还有没有需要传输的,则直接返回剩下的。
3575                 if ( (act = this._getStack()) ) {
3576     
3577                     // 是否提前准备下一个文件
3578                     if ( opts.prepareNextFile && !me.pending.length ) {
3579                         me._prepareNextFile();
3580                     }
3581     
3582                     return act.shift();
3583     
3584                 // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。
3585                 } else if ( me.runing ) {
3586     
3587                     // 如果缓存中有,则直接在缓存中取,没有则去queue中取。
3588                     if ( !me.pending.length && me._getStats().numOfQueue ) {
3589                         me._prepareNextFile();
3590                     }
3591     
3592                     next = me.pending.shift();
3593                     done = function( file ) {
3594                         if ( !file ) {
3595                             return null;
3596                         }
3597     
3598                         act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );
3599                         me.stack.push(act);
3600                         return act.shift();
3601                     };
3602     
3603                     // 文件可能还在prepare中,也有可能已经完全准备好了。
3604                     if ( isPromise( next) ) {
3605                         preparing = next.file;
3606                         next = next[ next.pipe ? 'pipe' : 'then' ]( done );
3607                         next.file = preparing;
3608                         return next;
3609                     }
3610     
3611                     return done( next );
3612                 }
3613             },
3614     
3615     
3616             /**
3617              * @event uploadStart
3618              * @param {File} file File对象
3619              * @description 某个文件开始上传前触发,一个文件只会触发一次。
3620              * @for  Uploader
3621              */
3622             _prepareNextFile: function() {
3623                 var me = this,
3624                     file = me.request('fetch-file'),
3625                     pending = me.pending,
3626                     promise;
3627     
3628                 if ( file ) {
3629                     promise = me.request( 'before-send-file', file, function() {
3630     
3631                         // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued.
3632                         if ( file.getStatus() === Status.PROGRESS ||
3633                             file.getStatus() === Status.INTERRUPT ) {
3634                             return file;
3635                         }
3636     
3637                         return me._finishFile( file );
3638                     });
3639     
3640                     me.owner.trigger( 'uploadStart', file );
3641                     file.setStatus( Status.PROGRESS );
3642     
3643                     promise.file = file;
3644     
3645                     // 如果还在pending中,则替换成文件本身。
3646                     promise.done(function() {
3647                         var idx = $.inArray( promise, pending );
3648     
3649                         ~idx && pending.splice( idx, 1, file );
3650                     });
3651     
3652                     // befeore-send-file的钩子就有错误发生。
3653                     promise.fail(function( reason ) {
3654                         file.setStatus( Status.ERROR, reason );
3655                         me.owner.trigger( 'uploadError', file, reason );
3656                         me.owner.trigger( 'uploadComplete', file );
3657                     });
3658     
3659                     pending.push( promise );
3660                 }
3661             },
3662     
3663             // 让出位置了,可以让其他分片开始上传
3664             _popBlock: function( block ) {
3665                 var idx = $.inArray( block, this.pool );
3666     
3667                 this.pool.splice( idx, 1 );
3668                 block.file.remaning--;
3669                 this.remaning--;
3670             },
3671     
3672             // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。
3673             _startSend: function( block ) {
3674                 var me = this,
3675                     file = block.file,
3676                     promise;
3677     
3678                 // 有可能在 before-send-file 的 promise 期间改变了文件状态。
3679                 // 如:暂停,取消
3680                 // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。
3681                 if ( file.getStatus() !== Status.PROGRESS ) {
3682     
3683                     // 如果是中断,则还需要放回去。
3684                     if (file.getStatus() === Status.INTERRUPT) {
3685                         me._putback(block);
3686                     }
3687     
3688                     return;
3689                 }
3690     
3691                 me.pool.push( block );
3692                 me.remaning++;
3693     
3694                 // 如果没有分片,则直接使用原始的。
3695                 // 不会丢失content-type信息。
3696                 block.blob = block.chunks === 1 ? file.source :
3697                         file.source.slice( block.start, block.end );
3698     
3699                 // hook, 每个分片发送之前可能要做些异步的事情。
3700                 promise = me.request( 'before-send', block, function() {
3701     
3702                     // 有可能文件已经上传出错了,所以不需要再传输了。
3703                     if ( file.getStatus() === Status.PROGRESS ) {
3704                         me._doSend( block );
3705                     } else {
3706                         me._popBlock( block );
3707                         Base.nextTick( me.__tick );
3708                     }
3709                 });
3710     
3711                 // 如果为fail了,则跳过此分片。
3712                 promise.fail(function() {
3713                     if ( file.remaning === 1 ) {
3714                         me._finishFile( file ).always(function() {
3715                             block.percentage = 1;
3716                             me._popBlock( block );
3717                             me.owner.trigger( 'uploadComplete', file );
3718                             Base.nextTick( me.__tick );
3719                         });
3720                     } else {
3721                         block.percentage = 1;
3722                         me.updateFileProgress( file );
3723                         me._popBlock( block );
3724                         Base.nextTick( me.__tick );
3725                     }
3726                 });
3727             },
3728     
3729     
3730             /**
3731              * @event uploadBeforeSend
3732              * @param {Object} object
3733              * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。
3734              * @param {Object} headers 可以扩展此对象来控制上传头部。
3735              * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。
3736              * @for  Uploader
3737              */
3738     
3739             /**
3740              * @event uploadAccept
3741              * @param {Object} object
3742              * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。
3743              * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。
3744              * @for  Uploader
3745              */
3746     
3747             /**
3748              * @event uploadProgress
3749              * @param {File} file File对象
3750              * @param {Number} percentage 上传进度
3751              * @description 上传过程中触发,携带上传进度。
3752              * @for  Uploader
3753              */
3754     
3755     
3756             /**
3757              * @event uploadError
3758              * @param {File} file File对象
3759              * @param {String} reason 出错的code
3760              * @description 当文件上传出错时触发。
3761              * @for  Uploader
3762              */
3763     
3764             /**
3765              * @event uploadSuccess
3766              * @param {File} file File对象
3767              * @param {Object} response 服务端返回的数据
3768              * @description 当文件上传成功时触发。
3769              * @for  Uploader
3770              */
3771     
3772             /**
3773              * @event uploadComplete
3774              * @param {File} [file] File对象
3775              * @description 不管成功或者失败,文件上传完成时触发。
3776              * @for  Uploader
3777              */
3778     
3779             // 做上传操作。
3780             _doSend: function( block ) {
3781                 var me = this,
3782                     owner = me.owner,
3783                     opts = me.options,
3784                     file = block.file,
3785                     tr = new Transport( opts ),
3786                     data = $.extend({}, opts.formData ),
3787                     headers = $.extend({}, opts.headers ),
3788                     requestAccept, ret;
3789     
3790                 block.transport = tr;
3791     
3792                 tr.on( 'destroy', function() {
3793                     delete block.transport;
3794                     me._popBlock( block );
3795                     Base.nextTick( me.__tick );
3796                 });
3797     
3798                 // 广播上传进度。以文件为单位。
3799                 tr.on( 'progress', function( percentage ) {
3800                     block.percentage = percentage;
3801                     me.updateFileProgress( file );
3802                 });
3803     
3804                 // 用来询问,是否返回的结果是有错误的。
3805                 requestAccept = function( reject ) {
3806                     var fn;
3807     
3808                     ret = tr.getResponseAsJson() || {};
3809                     ret._raw = tr.getResponse();
3810                     fn = function( value ) {
3811                         reject = value;
3812                     };
3813     
3814                     // 服务端响应了,不代表成功了,询问是否响应正确。
3815                     if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {
3816                         reject = reject || 'server';
3817                     }
3818     
3819                     return reject;
3820                 };
3821     
3822                 // 尝试重试,然后广播文件上传出错。
3823                 tr.on( 'error', function( type, flag ) {
3824                     block.retried = block.retried || 0;
3825     
3826                     // 自动重试
3827                     if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&
3828                             block.retried < opts.chunkRetry ) {
3829     
3830                         block.retried++;
3831                         tr.send();
3832     
3833                     } else {
3834     
3835                         // http status 500 ~ 600
3836                         if ( !flag && type === 'server' ) {
3837                             type = requestAccept( type );
3838                         }
3839     
3840                         file.setStatus( Status.ERROR, type );
3841                         owner.trigger( 'uploadError', file, type );
3842                         owner.trigger( 'uploadComplete', file );
3843                     }
3844                 });
3845     
3846                 // 上传成功
3847                 tr.on( 'load', function() {
3848                     var reason;
3849     
3850                     // 如果非预期,转向上传出错。
3851                     if ( (reason = requestAccept()) ) {
3852                         tr.trigger( 'error', reason, true );
3853                         return;
3854                     }
3855     
3856                     // 全部上传完成。
3857                     if ( file.remaning === 1 ) {
3858                         me._finishFile( file, ret );
3859                     } else {
3860                         tr.destroy();
3861                     }
3862                 });
3863     
3864                 // 配置默认的上传字段。
3865                 data = $.extend( data, {
3866                     id: file.id,
3867                     name: file.name,
3868                     type: file.type,
3869                     lastModifiedDate: file.lastModifiedDate,
3870                     size: file.size
3871                 });
3872     
3873                 block.chunks > 1 && $.extend( data, {
3874                     chunks: block.chunks,
3875                     chunk: block.chunk
3876                 });
3877     
3878                 // 在发送之间可以添加字段什么的。。。
3879                 // 如果默认的字段不够使用,可以通过监听此事件来扩展
3880                 owner.trigger( 'uploadBeforeSend', block, data, headers );
3881     
3882                 // 开始发送。
3883                 tr.appendBlob( opts.fileVal, block.blob, file.name );
3884                 tr.append( data );
3885                 tr.setRequestHeader( headers );
3886                 tr.send();
3887             },
3888     
3889             // 完成上传。
3890             _finishFile: function( file, ret, hds ) {
3891                 var owner = this.owner;
3892     
3893                 return owner
3894                         .request( 'after-send-file', arguments, function() {
3895                             file.setStatus( Status.COMPLETE );
3896                             owner.trigger( 'uploadSuccess', file, ret, hds );
3897                         })
3898                         .fail(function( reason ) {
3899     
3900                             // 如果外部已经标记为invalid什么的,不再改状态。
3901                             if ( file.getStatus() === Status.PROGRESS ) {
3902                                 file.setStatus( Status.ERROR, reason );
3903                             }
3904     
3905                             owner.trigger( 'uploadError', file, reason );
3906                         })
3907                         .always(function() {
3908                             owner.trigger( 'uploadComplete', file );
3909                         });
3910             },
3911     
3912             updateFileProgress: function(file) {
3913                 var totalPercent = 0,
3914                     uploaded = 0;
3915     
3916                 if (!file.blocks) {
3917                     return;
3918                 }
3919     
3920                 $.each( file.blocks, function( _, v ) {
3921                     uploaded += (v.percentage || 0) * (v.end - v.start);
3922                 });
3923     
3924                 totalPercent = uploaded / file.size;
3925                 this.owner.trigger( 'uploadProgress', file, totalPercent || 0 );
3926             }
3927     
3928         });
3929     });
3930     /**
3931      * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。
3932      */
3933     
3934     define('widgets/validator',[
3935         'base',
3936         'uploader',
3937         'file',
3938         'widgets/widget'
3939     ], function( Base, Uploader, WUFile ) {
3940     
3941         var $ = Base.$,
3942             validators = {},
3943             api;
3944     
3945         /**
3946          * @event error
3947          * @param {String} type 错误类型。
3948          * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。
3949          *
3950          * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。
3951          * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。
3952          * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。
3953          * @for  Uploader
3954          */
3955     
3956         // 暴露给外面的api
3957         api = {
3958     
3959             // 添加验证器
3960             addValidator: function( type, cb ) {
3961                 validators[ type ] = cb;
3962             },
3963     
3964             // 移除验证器
3965             removeValidator: function( type ) {
3966                 delete validators[ type ];
3967             }
3968         };
3969     
3970         // 在Uploader初始化的时候启动Validators的初始化
3971         Uploader.register({
3972             name: 'validator',
3973     
3974             init: function() {
3975                 var me = this;
3976                 Base.nextTick(function() {
3977                     $.each( validators, function() {
3978                         this.call( me.owner );
3979                     });
3980                 });
3981             }
3982         });
3983     
3984         /**
3985          * @property {int} [fileNumLimit=undefined]
3986          * @namespace options
3987          * @for Uploader
3988          * @description 验证文件总数量, 超出则不允许加入队列。
3989          */
3990         api.addValidator( 'fileNumLimit', function() {
3991             var uploader = this,
3992                 opts = uploader.options,
3993                 count = 0,
3994                 max = parseInt( opts.fileNumLimit, 10 ),
3995                 flag = true;
3996     
3997             if ( !max ) {
3998                 return;
3999             }
4000     
4001             uploader.on( 'beforeFileQueued', function( file ) {
4002     
4003                 if ( count >= max && flag ) {
4004                     flag = false;
4005                     this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );
4006                     setTimeout(function() {
4007                         flag = true;
4008                     }, 1 );
4009                 }
4010     
4011                 return count >= max ? false : true;
4012             });
4013     
4014             uploader.on( 'fileQueued', function() {
4015                 count++;
4016             });
4017     
4018             uploader.on( 'fileDequeued', function() {
4019                 count--;
4020             });
4021     
4022             uploader.on( 'reset', function() {
4023                 count = 0;
4024             });
4025         });
4026     
4027     
4028         /**
4029          * @property {int} [fileSizeLimit=undefined]
4030          * @namespace options
4031          * @for Uploader
4032          * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。
4033          */
4034         api.addValidator( 'fileSizeLimit', function() {
4035             var uploader = this,
4036                 opts = uploader.options,
4037                 count = 0,
4038                 max = parseInt( opts.fileSizeLimit, 10 ),
4039                 flag = true;
4040     
4041             if ( !max ) {
4042                 return;
4043             }
4044     
4045             uploader.on( 'beforeFileQueued', function( file ) {
4046                 var invalid = count + file.size > max;
4047     
4048                 if ( invalid && flag ) {
4049                     flag = false;
4050                     this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );
4051                     setTimeout(function() {
4052                         flag = true;
4053                     }, 1 );
4054                 }
4055     
4056                 return invalid ? false : true;
4057             });
4058     
4059             uploader.on( 'fileQueued', function( file ) {
4060                 count += file.size;
4061             });
4062     
4063             uploader.on( 'fileDequeued', function( file ) {
4064                 count -= file.size;
4065             });
4066     
4067             uploader.on( 'reset', function() {
4068                 count = 0;
4069             });
4070         });
4071     
4072         /**
4073          * @property {int} [fileSingleSizeLimit=undefined]
4074          * @namespace options
4075          * @for Uploader
4076          * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。
4077          */
4078         api.addValidator( 'fileSingleSizeLimit', function() {
4079             var uploader = this,
4080                 opts = uploader.options,
4081                 max = opts.fileSingleSizeLimit;
4082     
4083             if ( !max ) {
4084                 return;
4085             }
4086     
4087             uploader.on( 'beforeFileQueued', function( file ) {
4088     
4089                 if ( file.size > max ) {
4090                     file.setStatus( WUFile.Status.INVALID, 'exceed_size' );
4091                     this.trigger( 'error', 'F_EXCEED_SIZE', max, file );
4092                     return false;
4093                 }
4094     
4095             });
4096     
4097         });
4098     
4099         /**
4100          * @property {Boolean} [duplicate=undefined]
4101          * @namespace options
4102          * @for Uploader
4103          * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key.
4104          */
4105         api.addValidator( 'duplicate', function() {
4106             var uploader = this,
4107                 opts = uploader.options,
4108                 mapping = {};
4109     
4110             if ( opts.duplicate ) {
4111                 return;
4112             }
4113     
4114             function hashString( str ) {
4115                 var hash = 0,
4116                     i = 0,
4117                     len = str.length,
4118                     _char;
4119     
4120                 for ( ; i < len; i++ ) {
4121                     _char = str.charCodeAt( i );
4122                     hash = _char + (hash << 6) + (hash << 16) - hash;
4123                 }
4124     
4125                 return hash;
4126             }
4127     
4128             uploader.on( 'beforeFileQueued', function( file ) {
4129                 var hash = file.__hash || (file.__hash = hashString( file.name +
4130                         file.size + file.lastModifiedDate ));
4131     
4132                 // 已经重复了
4133                 if ( mapping[ hash ] ) {
4134                     this.trigger( 'error', 'F_DUPLICATE', file );
4135                     return false;
4136                 }
4137             });
4138     
4139             uploader.on( 'fileQueued', function( file ) {
4140                 var hash = file.__hash;
4141     
4142                 hash && (mapping[ hash ] = true);
4143             });
4144     
4145             uploader.on( 'fileDequeued', function( file ) {
4146                 var hash = file.__hash;
4147     
4148                 hash && (delete mapping[ hash ]);
4149             });
4150     
4151             uploader.on( 'reset', function() {
4152                 mapping = {};
4153             });
4154         });
4155     
4156         return api;
4157     });
4158     
4159     /**
4160      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
4161      */
4162     define('runtime/compbase',[],function() {
4163     
4164         function CompBase( owner, runtime ) {
4165     
4166             this.owner = owner;
4167             this.options = owner.options;
4168     
4169             this.getRuntime = function() {
4170                 return runtime;
4171             };
4172     
4173             this.getRuid = function() {
4174                 return runtime.uid;
4175             };
4176     
4177             this.trigger = function() {
4178                 return owner.trigger.apply( owner, arguments );
4179             };
4180         }
4181     
4182         return CompBase;
4183     });
4184     /**
4185      * @fileOverview FlashRuntime
4186      */
4187     define('runtime/flash/runtime',[
4188         'base',
4189         'runtime/runtime',
4190         'runtime/compbase'
4191     ], function( Base, Runtime, CompBase ) {
4192     
4193         var $ = Base.$,
4194             type = 'flash',
4195             components = {};
4196     
4197     
4198         function getFlashVersion() {
4199             var version;
4200     
4201             try {
4202                 version = navigator.plugins[ 'Shockwave Flash' ];
4203                 version = version.description;
4204             } catch ( ex ) {
4205                 try {
4206                     version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')
4207                             .GetVariable('$version');
4208                 } catch ( ex2 ) {
4209                     version = '0.0';
4210                 }
4211             }
4212             version = version.match( /\d+/g );
4213             return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );
4214         }
4215     
4216         function FlashRuntime() {
4217             var pool = {},
4218                 clients = {},
4219                 destroy = this.destroy,
4220                 me = this,
4221                 jsreciver = Base.guid('webuploader_');
4222     
4223             Runtime.apply( me, arguments );
4224             me.type = type;
4225     
4226     
4227             // 这个方法的调用者,实际上是RuntimeClient
4228             me.exec = function( comp, fn/*, args...*/ ) {
4229                 var client = this,
4230                     uid = client.uid,
4231                     args = Base.slice( arguments, 2 ),
4232                     instance;
4233     
4234                 clients[ uid ] = client;
4235     
4236                 if ( components[ comp ] ) {
4237                     if ( !pool[ uid ] ) {
4238                         pool[ uid ] = new components[ comp ]( client, me );
4239                     }
4240     
4241                     instance = pool[ uid ];
4242     
4243                     if ( instance[ fn ] ) {
4244                         return instance[ fn ].apply( instance, args );
4245                     }
4246                 }
4247     
4248                 return me.flashExec.apply( client, arguments );
4249             };
4250     
4251             function handler( evt, obj ) {
4252                 var type = evt.type || evt,
4253                     parts, uid;
4254     
4255                 parts = type.split('::');
4256                 uid = parts[ 0 ];
4257                 type = parts[ 1 ];
4258     
4259                 // console.log.apply( console, arguments );
4260     
4261                 if ( type === 'Ready' && uid === me.uid ) {
4262                     me.trigger('ready');
4263                 } else if ( clients[ uid ] ) {
4264                     clients[ uid ].trigger( type.toLowerCase(), evt, obj );
4265                 }
4266     
4267                 // Base.log( evt, obj );
4268             }
4269     
4270             // flash的接受器。
4271             window[ jsreciver ] = function() {
4272                 var args = arguments;
4273     
4274                 // 为了能捕获得到。
4275                 setTimeout(function() {
4276                     handler.apply( null, args );
4277                 }, 1 );
4278             };
4279     
4280             this.jsreciver = jsreciver;
4281     
4282             this.destroy = function() {
4283                 // @todo 删除池子中的所有实例
4284                 return destroy && destroy.apply( this, arguments );
4285             };
4286     
4287             this.flashExec = function( comp, fn ) {
4288                 var flash = me.getFlash(),
4289                     args = Base.slice( arguments, 2 );
4290     
4291                 return flash.exec( this.uid, comp, fn, args );
4292             };
4293     
4294             // @todo
4295         }
4296     
4297         Base.inherits( Runtime, {
4298             constructor: FlashRuntime,
4299     
4300             init: function() {
4301                 var container = this.getContainer(),
4302                     opts = this.options,
4303                     html;
4304     
4305                 // if not the minimal height, shims are not initialized
4306                 // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)
4307                 container.css({
4308                     position: 'absolute',
4309                     top: '-8px',
4310                     left: '-8px',
4311                     width: '9px',
4312                     height: '9px',
4313                     overflow: 'hidden'
4314                 });
4315     
4316                 // insert flash object
4317                 html = '<object id="' + this.uid + '" type="application/' +
4318                         'x-shockwave-flash" data="' +  opts.swf + '" ';
4319     
4320                 if ( Base.browser.ie ) {
4321                     html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
4322                 }
4323     
4324                 html += 'width="100%" height="100%" style="outline:0">'  +
4325                     '<param name="movie" value="' + opts.swf + '" />' +
4326                     '<param name="flashvars" value="uid=' + this.uid +
4327                     '&jsreciver=' + this.jsreciver + '" />' +
4328                     '<param name="wmode" value="transparent" />' +
4329                     '<param name="allowscriptaccess" value="always" />' +
4330                 '</object>';
4331     
4332                 container.html( html );
4333             },
4334     
4335             getFlash: function() {
4336                 if ( this._flash ) {
4337                     return this._flash;
4338                 }
4339     
4340                 this._flash = $( '#' + this.uid ).get( 0 );
4341                 return this._flash;
4342             }
4343     
4344         });
4345     
4346         FlashRuntime.register = function( name, component ) {
4347             component = components[ name ] = Base.inherits( CompBase, $.extend({
4348     
4349                 // @todo fix this later
4350                 flashExec: function() {
4351                     var owner = this.owner,
4352                         runtime = this.getRuntime();
4353     
4354                     return runtime.flashExec.apply( owner, arguments );
4355                 }
4356             }, component ) );
4357     
4358             return component;
4359         };
4360     
4361         if ( getFlashVersion() >= 11.4 ) {
4362             Runtime.addRuntime( type, FlashRuntime );
4363         }
4364     
4365         return FlashRuntime;
4366     });
4367     /**
4368      * @fileOverview FilePicker
4369      */
4370     define('runtime/flash/filepicker',[
4371         'base',
4372         'runtime/flash/runtime'
4373     ], function( Base, FlashRuntime ) {
4374         var $ = Base.$;
4375     
4376         return FlashRuntime.register( 'FilePicker', {
4377             init: function( opts ) {
4378                 var copy = $.extend({}, opts ),
4379                     len, i;
4380     
4381                 // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.
4382                 len = copy.accept && copy.accept.length;
4383                 for (  i = 0; i < len; i++ ) {
4384                     if ( !copy.accept[ i ].title ) {
4385                         copy.accept[ i ].title = 'Files';
4386                     }
4387                 }
4388     
4389                 delete copy.button;
4390                 delete copy.id;
4391                 delete copy.container;
4392     
4393                 this.flashExec( 'FilePicker', 'init', copy );
4394             },
4395     
4396             destroy: function() {
4397                 this.flashExec( 'FilePicker', 'destroy' );
4398             }
4399         });
4400     });
4401     /**
4402      * @fileOverview 图片压缩
4403      */
4404     define('runtime/flash/image',[
4405         'runtime/flash/runtime'
4406     ], function( FlashRuntime ) {
4407     
4408         return FlashRuntime.register( 'Image', {
4409             // init: function( options ) {
4410             //     var owner = this.owner;
4411     
4412             //     this.flashExec( 'Image', 'init', options );
4413             //     owner.on( 'load', function() {
4414             //         debugger;
4415             //     });
4416             // },
4417     
4418             loadFromBlob: function( blob ) {
4419                 var owner = this.owner;
4420     
4421                 owner.info() && this.flashExec( 'Image', 'info', owner.info() );
4422                 owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() );
4423     
4424                 this.flashExec( 'Image', 'loadFromBlob', blob.uid );
4425             }
4426         });
4427     });
4428     /**
4429      * @fileOverview Blob Html实现
4430      */
4431     define('runtime/flash/blob',[
4432         'runtime/flash/runtime',
4433         'lib/blob'
4434     ], function( FlashRuntime, Blob ) {
4435     
4436         return FlashRuntime.register( 'Blob', {
4437             slice: function( start, end ) {
4438                 var blob = this.flashExec( 'Blob', 'slice', start, end );
4439     
4440                 return new Blob( blob.uid, blob );
4441             }
4442         });
4443     });
4444     /**
4445      * @fileOverview  Transport flash实现
4446      */
4447     define('runtime/flash/transport',[
4448         'base',
4449         'runtime/flash/runtime',
4450         'runtime/client'
4451     ], function( Base, FlashRuntime, RuntimeClient ) {
4452         var $ = Base.$;
4453     
4454         return FlashRuntime.register( 'Transport', {
4455             init: function() {
4456                 this._status = 0;
4457                 this._response = null;
4458                 this._responseJson = null;
4459             },
4460     
4461             send: function() {
4462                 var owner = this.owner,
4463                     opts = this.options,
4464                     xhr = this._initAjax(),
4465                     blob = owner._blob,
4466                     server = opts.server,
4467                     binary;
4468     
4469                 xhr.connectRuntime( blob.ruid );
4470     
4471                 if ( opts.sendAsBinary ) {
4472                     server += (/\?/.test( server ) ? '&' : '?') +
4473                             $.param( owner._formData );
4474     
4475                     binary = blob.uid;
4476                 } else {
4477                     $.each( owner._formData, function( k, v ) {
4478                         xhr.exec( 'append', k, v );
4479                     });
4480     
4481                     xhr.exec( 'appendBlob', opts.fileVal, blob.uid,
4482                             opts.filename || owner._formData.name || '' );
4483                 }
4484     
4485                 this._setRequestHeader( xhr, opts.headers );
4486                 xhr.exec( 'send', {
4487                     method: opts.method,
4488                     url: server,
4489                     forceURLStream: opts.forceURLStream,
4490                     mimeType: 'application/octet-stream'
4491                 }, binary );
4492             },
4493     
4494             getStatus: function() {
4495                 return this._status;
4496             },
4497     
4498             getResponse: function() {
4499                 return this._response || '';
4500             },
4501     
4502             getResponseAsJson: function() {
4503                 return this._responseJson;
4504             },
4505     
4506             abort: function() {
4507                 var xhr = this._xhr;
4508     
4509                 if ( xhr ) {
4510                     xhr.exec('abort');
4511                     xhr.destroy();
4512                     this._xhr = xhr = null;
4513                 }
4514             },
4515     
4516             destroy: function() {
4517                 this.abort();
4518             },
4519     
4520             _initAjax: function() {
4521                 var me = this,
4522                     xhr = new RuntimeClient('XMLHttpRequest');
4523     
4524                 xhr.on( 'uploadprogress progress', function( e ) {
4525                     var percent = e.loaded / e.total;
4526                     percent = Math.min( 1, Math.max( 0, percent ) );
4527                     return me.trigger( 'progress', percent );
4528                 });
4529     
4530                 xhr.on( 'load', function() {
4531                     var status = xhr.exec('getStatus'),
4532                         readBody = false,
4533                         err = '',
4534                         p;
4535     
4536                     xhr.off();
4537                     me._xhr = null;
4538     
4539                     if ( status >= 200 && status < 300 ) {
4540                         readBody = true;
4541                     } else if ( status >= 500 && status < 600 ) {
4542                         readBody = true;
4543                         err = 'server';
4544                     } else {
4545                         err = 'http';
4546                     }
4547     
4548                     if ( readBody ) {
4549                         me._response = xhr.exec('getResponse');
4550                         me._response = decodeURIComponent( me._response );
4551     
4552                         // flash 处理可能存在 bug, 没辙只能靠 js 了
4553                         // try {
4554                         //     me._responseJson = xhr.exec('getResponseAsJson');
4555                         // } catch ( error ) {
4556                             
4557                         p = window.JSON && window.JSON.parse || function( s ) {
4558                             try {
4559                                 return new Function('return ' + s).call();
4560                             } catch ( err ) {
4561                                 return {};
4562                             }
4563                         };
4564                         me._responseJson  = me._response ? p(me._response) : {};
4565                             
4566                         // }
4567                     }
4568                     
4569                     xhr.destroy();
4570                     xhr = null;
4571     
4572                     return err ? me.trigger( 'error', err ) : me.trigger('load');
4573                 });
4574     
4575                 xhr.on( 'error', function() {
4576                     xhr.off();
4577                     me._xhr = null;
4578                     me.trigger( 'error', 'http' );
4579                 });
4580     
4581                 me._xhr = xhr;
4582                 return xhr;
4583             },
4584     
4585             _setRequestHeader: function( xhr, headers ) {
4586                 $.each( headers, function( key, val ) {
4587                     xhr.exec( 'setRequestHeader', key, val );
4588                 });
4589             }
4590         });
4591     });
4592     /**
4593      * @fileOverview 只有flash实现的文件版本。
4594      */
4595     define('preset/flashonly',[
4596         'base',
4597     
4598         // widgets
4599         'widgets/filepicker',
4600         'widgets/image',
4601         'widgets/queue',
4602         'widgets/runtime',
4603         'widgets/upload',
4604         'widgets/validator',
4605     
4606         // runtimes
4607     
4608         // flash
4609         'runtime/flash/filepicker',
4610         'runtime/flash/image',
4611         'runtime/flash/blob',
4612         'runtime/flash/transport'
4613     ], function( Base ) {
4614         return Base;
4615     });
4616     define('webuploader',[
4617         'preset/flashonly'
4618     ], function( preset ) {
4619         return preset;
4620     });
4621     return require('webuploader');
4622 });