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