网站
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 

5437 líneas
176 KiB

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