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