hdjs
Version:
hdjs framework
1,606 lines (1,338 loc) • 182 kB
JavaScript
/* WebUploader 0.1.0 */
(function( window, undefined ) {
/**
* @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。
*
* AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。
*/
var internalAmd = (function( global, undefined ) {
var modules = {},
// 简单不完全实现https://github.com/amdjs/amdjs-api/wiki/require
require = function( deps, callback ) {
var args, len, i;
// 如果deps不是数组,则直接返回指定module
if ( typeof deps === 'string' ) {
return getModule( deps );
} else {
args = [];
for( len = deps.length, i = 0; i < len; i++ ) {
args.push( getModule( deps[ i ] ) );
}
return callback.apply( null, args );
}
},
// 内部的define,暂时不支持不指定id.
define = function( id, deps, factory ) {
if ( arguments.length === 2 ) {
factory = deps;
deps = null;
}
if ( typeof id !== 'string' || !factory ) {
throw new Error('Define Error');
}
require( deps || [], function() {
setModule( id, factory, arguments );
});
},
// 设置module, 兼容CommonJs写法。
setModule = function( id, factory, args ) {
var module = {
exports: factory
},
returned;
if ( typeof factory === 'function' ) {
args.length || (args = [ require, module.exports, module ]);
returned = factory.apply( null, args );
returned !== undefined && (module.exports = returned);
}
modules[ id ] = module.exports;
},
// 根据id获取module
getModule = function( id ) {
var module = modules[ id ] || global[ id ];
if ( !module ) {
throw new Error( '`' + id + '` is undefined' );
}
return module;
};
return {
define: define,
require: require,
// 暴露所有的模块。
modules: modules
};
})( window ),
/* jshint unused: false */
require = internalAmd.require,
define = internalAmd.define;
/**
* @fileOverview 基础类方法。
*/
/**
* Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。
*
* As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.
* 默认module id该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如:
*
* * module `base`:WebUploader.Base
* * module `file`: WebUploader.File
* * module `lib/dnd`: WebUploader.Lib.Dnd
* * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd
*
*
* 以下文档将可能省略`WebUploader`前缀。
* @module WebUploader
* @title WebUploader API文档
*/
define( 'base', [
'jQuery'
], function( $ ) {
var noop = function() {},
call = Function.call;
// http://jsperf.com/uncurrythis
// 反科里化
function uncurryThis( fn ) {
return function() {
return call.apply( fn, arguments );
};
}
function bindFn( fn, context ) {
return function() {
return fn.apply( context, arguments );
};
}
function createObject( proto ) {
var f;
if ( Object.create ) {
return Object.create( proto );
} else {
f = function() {};
f.prototype = proto;
return new f();
}
}
/**
* 基础类,提供一些简单常用的方法。
* @class Base
*/
return {
/**
* @property {String} version 当前版本号。
*/
version: '0.1.0',
/**
* @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。
*/
$: $,
/**
* 创建一个[Deferred](http://api.jquery.com/category/deferred-object/)对象。
* 详细的Deferred用法说明,请参照jQuery的API文档。
*
* Deferred对象在钩子回掉函数中经常要用到,用来处理需要等待的异步操作。
*
*
* @method Deferred
* @grammar Base.Deferred() => Deferred
* @example
* // 在文件开始发送前做些异步操作。
* // WebUploader会等待此异步操作完成后,开始发送文件。
* Uploader.register({
* 'before-send-file': 'doSomthingAsync'
* }, {
*
* doSomthingAsync: function() {
* var deferred = Base.Deferred();
*
* // 模拟一次异步操作。
* setTimeout(deferred.resolve, 2000);
*
* return deferred.promise();
* }
* });
*/
Deferred: $.Deferred,
/**
* 判断传入的参数是否为一个promise对象。
* @method isPromise
* @grammar Base.isPromise( anything ) => Boolean
* @param {*} anything 检测对象。
* @return {Boolean}
* @example
* console.log( Base.isPromise() ); // => false
* console.log( Base.isPromise({ key: '123' }) ); // => false
* console.log( Base.isPromise( Base.Deferred().promise() ) ); // => true
*
* // Deferred也是一个Promise
* console.log( Base.isPromise( Base.Deferred() ) ); // => true
*/
isPromise: function( anything ) {
return anything && typeof anything.then === 'function';
},
/**
* 返回一个promise,此promise在所有传入的promise都完成了后完成。
* 详细请查看[这里](http://api.jquery.com/jQuery.when/)。
*
* @method when
* @grammar Base.when( promise1[, promise2[, promise3...]] ) => Promise
*/
when: $.when,
/**
* @description 简单的浏览器检查结果。
*
* * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。
* * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。
* * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+**
* * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。
* * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。
* * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。
*
* @property {Object} [browser]
*/
browser: (function( ua ) {
var ret = {},
webkit = ua.match( /WebKit\/([\d.]+)/ ),
chrome = ua.match( /Chrome\/([\d.]+)/ ) ||
ua.match( /CriOS\/([\d.]+)/ ),
ie = ua.match( /MSIE\s([\d.]+)/ ),
firefox = ua.match( /Firefox\/([\d.]+)/ ),
safari = ua.match( /Safari\/([\d.]+)/ ),
opera = ua.match( /OPR\/([\d.]+)/ );
webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));
chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));
ie && (ret.ie = parseFloat( ie[ 1 ] ));
firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));
safari && (ret.safari = parseFloat( safari[ 1 ] ));
opera && (ret.opera = parseFloat( opera[ 1 ] ));
return ret;
})( navigator.userAgent ),
/**
* 实现类与类之间的继承。
* @method inherits
* @grammar Base.inherits( super ) => child
* @grammar Base.inherits( super, protos ) => child
* @grammar Base.inherits( super, protos, statics ) => child
* @param {Class} super 父类
* @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。
* @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。
* @param {Object} [statics] 静态属性或方法。
* @return {Class} 返回子类。
* @example
* function Person() {
* console.log( 'Super' );
* }
* Person.prototype.hello = function() {
* console.log( 'hello' );
* };
*
* var Manager = Base.inherits( Person, {
* world: function() {
* console.log( 'World' );
* }
* });
*
* // 因为没有指定构造器,父类的构造器将会执行。
* var instance = new Manager(); // => Super
*
* // 继承子父类的方法
* instance.hello(); // => hello
* instance.world(); // => World
*
* // 子类的__super__属性指向父类
* console.log( Manager.__super__ === Person ); // => true
*/
inherits: function( Super, protos, staticProtos ) {
var child;
if ( typeof protos === 'function' ) {
child = protos;
protos = null;
} else if ( protos && protos.hasOwnProperty('constructor') ) {
child = protos.constructor;
} else {
child = function() {
return Super.apply( this, arguments );
};
}
// 复制静态方法
$.extend( true, child, Super, staticProtos || {} );
/* jshint camelcase: false */
// 让子类的__super__属性指向父类。
child.__super__ = Super.prototype;
// 构建原型,添加原型方法或属性。
// 暂时用Object.create实现。
child.prototype = createObject( Super.prototype );
protos && $.extend( true, child.prototype, protos );
return child;
},
/**
* 一个不做任何事情的方法。可以用来赋值给默认的callback.
* @method noop
*/
noop: noop,
/**
* 返回一个新的方法,此方法将已指定的`context`来执行。
* @grammar Base.bindFn( fn, context ) => Function
* @method bindFn
* @example
* var doSomething = function() {
* console.log( this.name );
* },
* obj = {
* name: 'Object Name'
* },
* aliasFn = Base.bind( doSomething, obj );
*
* aliasFn(); // => Object Name
*
*/
bindFn: bindFn,
/**
* 引用Console.log如果存在的话,否则引用一个[空函数loop](#WebUploader:Base.log)。
* @grammar Base.log( args... ) => undefined
* @method log
*/
log: (function() {
if ( window.console ) {
return bindFn( console.log, console );
}
return noop;
})(),
nextTick: (function() {
return function( cb ) {
setTimeout( cb, 1 );
};
// @bug 当浏览器不在当前窗口时就停了。
// var next = window.requestAnimationFrame ||
// window.webkitRequestAnimationFrame ||
// window.mozRequestAnimationFrame ||
// function( cb ) {
// window.setTimeout( cb, 1000 / 60 );
// };
// // fix: Uncaught TypeError: Illegal invocation
// return bindFn( next, window );
})(),
/**
* 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。
* 将用来将非数组对象转化成数组对象。
* @grammar Base.slice( target, start[, end] ) => Array
* @method slice
* @example
* function doSomthing() {
* var args = Base.slice( arguments, 1 );
* console.log( args );
* }
*
* doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"]
*/
slice: uncurryThis( [].slice ),
/**
* 生成唯一的ID
* @method guid
* @grammar Base.guid() => String
* @grammar Base.guid( prefx ) => String
*/
guid: (function() {
var counter = 0;
return function( prefix ) {
var guid = (+new Date()).toString( 32 ),
i = 0;
for ( ; i < 5; i++ ) {
guid += Math.floor( Math.random() * 65535 ).toString( 32 );
}
return (prefix || 'wu_') + guid + (counter++).toString( 32 );
};
})(),
/**
* 格式化文件大小, 输出成带单位的字符串
* @method formatSize
* @grammar Base.formatSize( size ) => String
* @grammar Base.formatSize( size, pointLength ) => String
* @grammar Base.formatSize( size, pointLength, units ) => String
* @param {Number} size 文件大小
* @param {Number} [pointLength=2] 精确到的小数点数。
* @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K.
* @example
* console.log( Base.formatSize( 100 ) ); // => 100B
* console.log( Base.formatSize( 1024 ) ); // => 1.00K
* console.log( Base.formatSize( 1024, 0 ) ); // => 1K
* console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M
* console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G
* console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB
*/
formatSize: function( size, pointLength, units ) {
var unit;
units = units || [ 'B', 'K', 'M', 'G', 'TB' ];
while ( (unit = units.shift()) && size > 1024 ) {
size = size / 1024;
}
return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +
unit;
}
};
});
/**
* @fileOverview Mediator
*/
define( 'mediator', [
'base'
], function( Base ) {
var $ = Base.$,
slice = [].slice,
separator = /\s+/,
protos;
// 根据条件过滤出事件handlers.
function findHandlers( arr, name, callback, context ) {
return $.grep( arr, function( handler ) {
return handler &&
(!name || handler.e === name) &&
(!callback || handler.cb === callback ||
handler.cb._cb === callback) &&
(!context || handler.ctx === context);
});
}
function eachEvent( events, callback, iterator ) {
// 不支持对象,只支持多个event用空格隔开
$.each( (events || '').split( separator ), function( _, key ) {
iterator( key, callback );
});
}
function triggerHanders( events, args ) {
var stoped = false,
i = -1,
len = events.length,
handler;
while ( ++i < len ) {
handler = events[ i ];
if ( handler.cb.apply( handler.ctx2, args ) === false ) {
stoped = true;
break;
}
}
return !stoped;
}
protos = {
/**
* 绑定事件。
*
* `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如
* ```javascript
* var obj = {};
*
* // 使得obj有事件行为
* Mediator.installTo( obj );
*
* obj.on( 'testa', function( arg1, arg2 ) {
* console.log( arg1, arg2 ); // => 'arg1', 'arg2'
* });
*
* obj.trigger( 'testa', 'arg1', 'arg2' );
* ```
*
* 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。
* 切会影响到`trigger`方法的返回值,为`false`。
*
* `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处,
* 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。
* ```javascript
* obj.on( 'all', function( type, arg1, arg2 ) {
* console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'
* });
* ```
*
* @method on
* @grammar on( name, callback[, context] ) => self
* @param {String} name 事件名,支持多个事件用空格隔开
* @param {Function} callback 事件处理器
* @param {Object} [context] 事件处理器的上下文。
* @return {self} 返回自身,方便链式
* @chainable
* @class Mediator
*/
on: function( name, callback, context ) {
var me = this,
set;
if ( !callback ) {
return this;
}
set = this._events || (this._events = []);
eachEvent( name, callback, function( name, callback ) {
var handler = { e: name };
handler.cb = callback;
handler.ctx = context;
handler.ctx2 = context || me;
handler.id = set.length;
set.push( handler );
});
return this;
},
/**
* 绑定事件,且当handler执行完后,自动解除绑定。
* @method once
* @grammar once( name, callback[, context] ) => self
* @param {String} name 事件名
* @param {Function} callback 事件处理器
* @param {Object} [context] 事件处理器的上下文。
* @return {self} 返回自身,方便链式
* @chainable
*/
once: function( name, callback, context ) {
var me = this;
if ( !callback ) {
return me;
}
eachEvent( name, callback, function( name, callback ) {
var once = function() {
me.off( name, once );
return callback.apply( context || me, arguments );
};
once._cb = callback;
me.on( name, once, context );
});
return me;
},
/**
* 解除事件绑定
* @method off
* @grammar off( [name[, callback[, context] ] ] ) => self
* @param {String} [name] 事件名
* @param {Function} [callback] 事件处理器
* @param {Object} [context] 事件处理器的上下文。
* @return {self} 返回自身,方便链式
* @chainable
*/
off: function( name, cb, ctx ) {
var events = this._events;
if ( !events ) {
return this;
}
if ( !name && !cb && !ctx ) {
this._events = [];
return this;
}
eachEvent( name, cb, function( name, cb ) {
$.each( findHandlers( events, name, cb, ctx ), function() {
delete events[ this.id ];
});
});
return this;
},
/**
* 触发事件
* @method trigger
* @grammar trigger( name[, args...] ) => self
* @param {String} type 事件名
* @param {*} [...] 任意参数
* @return {Boolean} 如果handler中return false了,则返回false, 否则返回true
*/
trigger: function( type ) {
var args, events, allEvents;
if ( !this._events || !type ) {
return this;
}
args = slice.call( arguments, 1 );
events = findHandlers( this._events, type );
allEvents = findHandlers( this._events, 'all' );
return triggerHanders( events, args ) &&
triggerHanders( allEvents, arguments );
}
};
/**
* 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。
* 主要目的是负责模块与模块之间的合作,降低耦合度。
*
* @class Mediator
*/
return $.extend({
/**
* 可以通过这个接口,使任何对象具备事件功能。
* @method installTo
* @param {Object} obj 需要具备事件行为的对象。
* @return {Object} 返回obj.
*/
installTo: function( obj ) {
return $.extend( obj, protos );
}
}, protos );
});
/**
* @fileOverview Uploader上传类
*/
define( 'uploader', [
'base',
'mediator'
], function( Base, Mediator ) {
var $ = Base.$;
/**
* 上传入口类。
* @class Uploader
* @constructor
* @grammar new Uploader( opts ) => Uploader
* @example
* var uploader = WebUploader.Uploader({
* swf: 'path_of_swf/Uploader.swf',
*
* // 开起分片上传。
* chunked: true
* });
*/
function Uploader( opts ) {
this.options = $.extend( true, {}, Uploader.options, opts );
this._init( this.options );
}
// default Options
// widgets中有相应扩展
Uploader.options = {};
Mediator.installTo( Uploader.prototype );
// 批量添加纯命令式方法。
$.each({
upload: 'start-upload',
stop: 'stop-upload',
getFile: 'get-file',
getFiles: 'get-files',
// addFile: 'add-file',
// addFiles: 'add-file',
removeFile: 'remove-file',
skipFile: 'skip-file',
retry: 'retry',
isInProgress: 'is-in-progress',
makeThumb: 'make-thumb',
getDimension: 'get-dimension',
addButton: 'add-btn',
getRuntimeType: 'get-runtime-type',
refresh: 'refresh',
disable: 'disable',
enable: 'enable'
}, function( fn, command ) {
Uploader.prototype[ fn ] = function() {
return this.request( command, arguments );
};
});
$.extend( Uploader.prototype, {
state: 'pending',
_init: function( opts ) {
var me = this;
me.request( 'init', opts, function() {
me.state = 'ready';
me.trigger('ready');
});
},
/**
* 获取或者设置Uploader配置项。
* @method option
* @grammar option( key ) => *
* @grammar option( key, val ) => self
* @example
*
* // 初始状态图片上传前不会压缩
* var uploader = new WebUploader.Uploader({
* resize: null;
* });
*
* // 修改后图片上传前,尝试将图片压缩到1600 * 1600
* uploader.options( 'resize', {
* width: 1600,
* height: 1600
* });
*/
option: function( key, val ) {
var opts = this.options;
// setter
if ( arguments.length > 1 ) {
if ( $.isPlainObject( val ) &&
$.isPlainObject( opts[ key ] ) ) {
$.extend( opts[ key ], val );
} else {
opts[ key ] = val;
}
} else { // getter
return key ? opts[ key ] : opts;
}
},
/**
* 获取文件统计信息。返回一个包含一下信息的对象。
* * `successNum` 上传成功的文件数
* * `uploadFailNum` 上传失败的文件数
* * `cancelNum` 被删除的文件数
* * `invalidNum` 无效的文件数
* * `queueNum` 还在队列中的文件数
* @method getStats
* @grammar getStats() => Object
*/
getStats: function() {
// return this._mgr.getStats.apply( this._mgr, arguments );
var stats = this.request('get-stats');
return {
successNum: stats.numOfSuccess,
// who care?
// queueFailNum: 0,
cancelNum: stats.numOfCancel,
invalidNum: stats.numOfInvalid,
uploadFailNum: stats.numOfUploadFailed,
queueNum: stats.numOfQueue
};
},
// 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器
trigger: function( type/*, args...*/ ) {
var args = [].slice.call( arguments, 1 ),
opts = this.options,
name = 'on' + type.substring( 0, 1 ).toUpperCase() +
type.substring( 1 );
if ( Mediator.trigger.apply( this, arguments ) === false ) {
return false;
}
if ( $.isFunction( opts[ name ] ) &&
opts[ name ].apply( this, args ) === false ) {
return false;
}
if ( $.isFunction( this[ name ] ) &&
this[ name ].apply( this, args ) === false ) {
return false;
}
return true;
},
// widgets/widget.js将补充此方法的详细文档。
request: Base.noop,
reset: function() {
// @todo
}
});
/**
* 创建Uploader实例,等同于new Uploader( opts );
* @method create
* @class Base
* @static
* @grammar Base.create( opts ) => Uploader
*/
Base.create = function( opts ) {
return new Uploader( opts );
};
// 暴露Uploader,可以通过它来扩展业务逻辑。
Base.Uploader = Uploader;
return Uploader;
});
/**
* @fileOverview Runtime管理器,负责Runtime的选择, 连接
*/
define( 'runtime/runtime', [
'base',
'mediator'
], function( Base, Mediator ) {
var $ = Base.$,
factories = {},
// 获取对象的第一个key
getFirstKey = function( obj ) {
for ( var key in obj ) {
if ( obj.hasOwnProperty( key ) ) {
return key;
}
}
return null;
};
// 接口类。
function Runtime( options ) {
this.options = $.extend({
container: document.body
}, options );
this.uid = Base.guid('rt_');
}
$.extend( Runtime.prototype, {
getContainer: function() {
var opts = this.options,
parent, container;
if ( this._container ) {
return this._container;
}
parent = opts.container || $( document.body );
container = $( document.createElement('div') );
container.attr( 'id', 'rt_' + this.uid );
container.css({
position: 'absolute',
top: '0px',
left: '0px',
width: '1px',
height: '1px',
overflow: 'hidden'
});
parent.append( container );
parent.addClass('webuploader-container');
this._container = container;
return container;
},
init: Base.noop,
exec: Base.noop,
destroy: function() {
if ( this._container ) {
this._container.parentNode.removeChild( this.__container );
}
this.off();
}
});
Runtime.orders = 'html5,flash';
/**
* 添加Runtime实现。
* @param {String} type 类型
* @param {Runtime} factory 具体Runtime实现。
*/
Runtime.addRuntime = function( type, factory ) {
factories[ type ] = factory;
};
Runtime.hasRuntime = function( type ) {
return !!(type ? factories[ type ] : getFirstKey( factories ));
};
Runtime.create = function( opts, orders ) {
var type, runtime;
orders = orders || Runtime.orders;
$.each( orders.split( /\s*,\s*/g ), function() {
if ( factories[ this ] ) {
type = this;
return false;
}
});
type = type || getFirstKey( factories );
if ( !type ) {
throw new Error('Runtime Error');
}
runtime = new factories[ type ]( opts );
return runtime;
};
Mediator.installTo( Runtime.prototype );
return Runtime;
});
/**
* @fileOverview Runtime管理器,负责Runtime的选择, 连接
*/
define( 'runtime/client', [
'base',
'mediator',
'runtime/runtime'
], function( Base, Mediator, Runtime ) {
var cache = (function() {
var obj = {};
return {
add: function( runtime ) {
obj[ runtime.uid ] = runtime;
},
get: function( ruid ) {
var i;
if ( ruid ) {
return obj[ ruid ];
}
for ( i in obj ) {
return obj[ i ];
}
return null;
},
remove: function( runtime ) {
delete obj[ runtime.uid ];
},
has: function() {
return !!this.get.apply( this, arguments );
}
};
})();
function RuntimeClient( component, standalone ) {
var deferred = Base.Deferred(),
runtime;
this.uid = Base.guid('client_');
this.runtimeReady = function( cb ) {
return deferred.done( cb );
};
this.connectRuntime = function( opts, cb ) {
if ( runtime ) {
return;
}
deferred.done( cb );
if ( typeof opts === 'string' && cache.get( opts ) ) {
runtime = cache.get( opts );
// 像filePicker只能独立存在,不能公用。
} else if ( !standalone && cache.has() ) {
runtime = cache.get();
}
if ( !runtime ) {
runtime = Runtime.create( opts, opts.runtimeOrder );
cache.add( runtime );
runtime.promise = deferred.promise();
runtime.once( 'ready', deferred.resolve );
runtime.init();
runtime.client = 1;
return runtime;
}
runtime.promise.then( deferred.resolve );
runtime.client++;
return runtime;
};
this.getRuntime = function() {
return runtime;
};
this.disconnectRuntime = function() {
if ( !runtime ) {
return;
}
runtime.client--;
if ( runtime.client <= 0 ) {
cache.remove( runtime );
delete runtime.promise;
runtime.destroy();
}
runtime = null;
};
this.exec = function() {
if ( !runtime ) {
return;
}
var args = Base.slice( arguments );
component && args.unshift( component );
return runtime.exec.apply( this, args );
};
this.getRuid = function() {
return runtime && runtime.uid;
};
this.destroy = (function( destroy ) {
return function() {
destroy && destroy.apply( this, arguments );
this.trigger('destroy');
this.off();
this.exec('destroy');
this.disconnectRuntime();
};
})( this.destroy );
}
Mediator.installTo( RuntimeClient.prototype );
return RuntimeClient;
});
/**
* @fileOverview Blob
*/
define( 'lib/blob', [
'base',
'runtime/client'
], function( Base, RuntimeClient ) {
function Blob( ruid, source ) {
var me = this;
me.source = source;
me.ruid = ruid;
RuntimeClient.call( me, 'Blob' );
this.uid = source.uid || this.uid;
this.type = source.type || '';
this.size = source.size || 0;
if ( ruid ) {
me.connectRuntime( ruid );
}
}
Base.inherits( RuntimeClient, {
constructor: Blob,
slice: function( start, end ) {
return this.exec( 'slice', start, end );
},
getSource: function() {
return this.source;
}
});
return Blob;
});
/**
* @fileOverview File
*/
define( 'lib/file', [
'base',
'lib/blob'
], function( Base, Blob ) {
var uid = 0,
rExt = /\.([^.]+)$/;
function File( ruid, file ) {
var ext;
Blob.apply( this, arguments );
this.name = file.name || ('untitled' + uid++);
ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';
if ( !this.type && ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {
this.type = 'image/' + ext;
}
this.ext = ext;
this.lastModifiedDate = file.lastModifiedDate ||
(new Date()).toLocaleString();
}
return Base.inherits( Blob, File );
});
/**
* @fileOverview 错误信息
*/
define( 'lib/filepicker', [
'base',
'runtime/client',
'lib/file'
], function( Base, RuntimeClent, File ) {
var $ = Base.$;
function FilePicker( opts ) {
opts = this.options = $.extend({}, FilePicker.options, opts );
opts.container = $( opts.id );
if ( !opts.container.length ) {
throw new Error('按钮指定错误');
}
opts.label = opts.label || opts.container.text() || '选择文件';
opts.button = $( opts.button || document.createElement('div') );
opts.button.text( opts.label );
opts.container.html( opts.button );
RuntimeClent.call( this, 'FilePicker', true );
}
FilePicker.options = {
button: null,
container: null,
label: null,
multiple: true,
accept: null
};
Base.inherits( RuntimeClent, {
constructor: FilePicker,
init: function() {
var me = this,
opts = me.options,
button = opts.button;
button.addClass('webuploader-pick');
me.on( 'all', function( type ) {
var files;
switch ( type ) {
case 'mouseenter':
button.addClass('webuploader-pick-hover');
break;
case 'mouseleave':
button.removeClass('webuploader-pick-hover');
break;
case 'change':
files = me.exec('getFiles');
me.trigger( 'select', $.map( files, function( file ) {
return new File( me.getRuid(), file );
}) );
break;
}
});
me.connectRuntime( opts, function() {
me.refresh();
me.exec( 'init', opts );
});
$( window ).on( 'resize', function() {
me.refresh();
});
},
refresh: function() {
var shimContainer = this.getRuntime().getContainer(),
button = this.options.button,
width = button.outerWidth(),
height = button.outerHeight(),
pos = button.offset();
width && shimContainer.css({
width: width + 'px',
height: height + 'px'
}).offset( pos );
},
destroy: function() {
if ( this.runtime ) {
this.exec('destroy');
this.disconnectRuntime();
}
}
});
return FilePicker;
});
/**
* @fileOverview 组件基类。
*/
define( 'widgets/widget', [
'base',
'uploader'
], function( Base, Uploader ) {
var $ = Base.$,
_init = Uploader.prototype._init,
IGNORE = {},
widgetClass = [];
function isArrayLike( obj ) {
if ( !obj ) {
return false;
}
var length = obj.length,
type = $.type( obj );
if ( obj.nodeType === 1 && length ) {
return true;
}
return type === 'array' || type !== 'function' && type !== 'string' &&
(length === 0 || typeof length === 'number' && length > 0 &&
(length - 1) in obj);
}
function Widget( uploader ) {
this.owner = uploader;
this.options = uploader.options;
}
$.extend( Widget.prototype, {
init: Base.noop,
// 类Backbone的事件监听声明,监听uploader实例上的事件
// widget直接无法监听事件,事件只能通过uploader来传递
invoke: function( apiName, args ) {
/*
{
'make-thumb': 'makeThumb'
}
*/
var map = this.responseMap;
// 如果无API响应声明则忽略
if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||
!$.isFunction( this[ map[ apiName ] ] ) ) {
return IGNORE;
}
return this[ map[ apiName ] ].apply( this, args );
},
/**
* 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。
* @method request
* @grammar request( command, args ) => * | Promise
* @grammar request( command, args, callback ) => Promise
* @for Uploader
*/
request: function() {
return this.owner.request.apply( this.owner, arguments );
}
});
// 扩展Uploader.
$.extend( Uploader.prototype, {
// 覆写_init用来初始化widgets
_init: function() {
var me = this,
widgets = me._widgets = [];
$.each( widgetClass, function( _, klass ) {
widgets.push( new klass( me ) );
});
return _init.apply( me, arguments );
},
request: function( apiName, args, callback ) {
var i = 0,
widgets = this._widgets,
len = widgets.length,
rlts = [],
dfds = [],
widget, rlt;
args = isArrayLike( args ) ? args : [ args ];
for ( ; i < len; i++ ) {
widget = widgets[ i ];
rlt = widget.invoke( apiName, args );
if ( rlt !== IGNORE ) {
// Deferred对象
if ( Base.isPromise( rlt ) ) {
dfds.push( rlt );
} else {
rlts.push( rlt );
}
}
}
// 如果有callback,则用异步方式。
if ( callback || dfds.length ) {
return Base.when.apply( Base, dfds )
// 很重要不能删除。删除了会死循环。
// 保证执行顺序。让callback总是在下一个tick中执行。
.then(function() {
var deferred = Base.Deferred(),
args = arguments;
setTimeout(function() {
deferred.resolve.apply( deferred, args );
}, 1 );
return deferred.promise();
})
.then( callback || Base.noop );
} else {
return rlts[ 0 ];
}
}
});
/**
* 添加组件
* @param {object} widgetProto 组件原型,构造函数通过constructor属性定义
* @param {object} responseMap API名称与函数实现的映射
* @example
* Uploader.register( {
* init: function( options ) {},
* makeThumb: function() {}
* }, {
* 'make-thumb': 'makeThumb'
* } );
*/
Uploader.register = Widget.register = function( responseMap, widgetProto ) {
var map = { init: 'init' },
klass;
if ( arguments.length === 1 ) {
widgetProto = responseMap;
widgetProto.responseMap = map;
} else {
widgetProto.responseMap = $.extend( map, responseMap );
}
klass = Base.inherits( Widget, widgetProto );
widgetClass.push( klass );
return klass;
};
return Widget;
});
/**
* @fileOverview 文件选择相关
*/
define( 'widgets/filepicker', [
'base',
'uploader',
'lib/filepicker',
'widgets/widget'
], function( Base, Uploader, FilePicker ) {
Base.$.extend( Uploader.options, {
/**
* @property {Selector | Object} [pick=undefined]
* @namespace options
* @for Uploader
* @description 指定选择文件的按钮容器,不指定则不创建按钮。
*
* * `id` {Seletor} 指定选择文件的按钮容器,不指定则不创建按钮。
* * `label` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。
* * `multiple` {Boolean} 是否开起同时选择多个文件能力。
*/
pick: null,
/**
* @property {Arroy} [accept=null]
* @namespace options
* @for Uploader
* @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。
*
* * `title` {String} 文字描述
* * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。
* * `mimeTypes` {String} 多个用逗号分割。
*
* 如:
*
* ```
* {
* title: 'Images',
* extensions: 'gif,jpg,jpeg,bmp,png',
* mimeTypes: 'image/*'
* }
* ```
*/
accept: null/*{
title: 'Images',
extensions: 'gif,jpg,jpeg,bmp,png',
mimeTypes: 'image/*'
}*/
});
return Uploader.register({
'add-btn': 'addButton',
'refresh': 'refresh'
}, {
init: function( opts ) {
this.pickers = [];
return opts.pick && this.addButton( opts.pick );
},
refresh: function() {
$.each( this.pickers, function() {
this.refresh();
});
},
/**
* @method addButton
* @for Uploader
* @grammar addButton( pick ) => Promise
* @description
* 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。
* @example
* uploader.addButton({
* id: '#btnContainer',
* label: '选择文件'
* });
*/
addButton: function( pick ) {
var me = this,
opts = me.options,
accept = opts.accept,
options, picker, deferred;
if ( !pick ) {
return;
}
deferred = Base.Deferred();
if ( typeof pick === 'string' ) {
pick = {
id: pick
};
}
options = $.extend({}, pick, {
accept: $.isPlainObject( accept ) ? [ accept ] : accept,
swf: opts.swf,
runtimeOrder: opts.runtimeOrder
});
picker = new FilePicker( options );
picker.once( 'ready', deferred.resolve );
picker.on( 'select', function( files ) {
me.owner.request( 'add-file', [ files ]);
});
picker.init();
this.pickers.push( picker );
return deferred.promise();
}
});
});
/**
* @fileOverview 文件属性封装
*/
define( 'file', [
'base',
'mediator'
], function( Base, Mediator ) {
var $ = Base.$,
idPrefix = 'WU_FILE_',
idSuffix = 0,
rExt = /\.([^.]+)$/,
statusMap = {};
function gid() {
return idPrefix + idSuffix++;
}
/**
* 文件类
* @class File
* @constructor 构造函数
* @grammar new File( source ) => File
* @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。
*/
function WUFile( source ) {
/**
* 文件名,包括扩展名(后缀)
* @property name
* @type {string}
*/
this.name = source.name || 'Untitled';
/**
* 文件体积(字节)
* @property size
* @type {uint}
* @default 0
*/
this.size = source.size || 0;
/**
* 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)
* @property type
* @type {string}
* @default 'image/png'
*/
this.type = source.type || 'image/png';
/**
* 文件最后修改日期
* @property lastModifiedDate
* @type {int}
* @default 当前时间戳
*/
this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);
/**
* 文件ID,每个对象具有唯一ID,与文件名无关
* @property id
* @type {string}
*/
this.id = gid();
/**
* 文件扩展名,通过文件名获取,例如test.png的扩展名为png
* @property ext
* @type {string}
*/
this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';
/**
* 状态文字说明。在不同的status语境下有不同的用途。
* @property statusText
* @type {string}
*/
this.statusText = '';