hdjs
Version:
hdjs framework
1,614 lines (1,345 loc) • 169 kB
JavaScript
/*! WebUploader 0.1.6 */
/**
* @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。
*
* AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。
*/
(function( root, factory ) {
var modules = {},
// 内部require, 简单不完全实现。
// 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;
}
_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 ] || root[ id ];
if ( !module ) {
throw new Error( '`' + id + '` is undefined' );
}
return module;
},
// 将所有modules,将路径ids装换成对象。
exportsTo = function( obj ) {
var key, host, parts, part, last, ucFirst;
// make the first character upper case.
ucFirst = function( str ) {
return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));
};
for ( key in modules ) {
host = obj;
if ( !modules.hasOwnProperty( key ) ) {
continue;
}
parts = key.split('/');
last = ucFirst( parts.pop() );
while( (part = ucFirst( parts.shift() )) ) {
host[ part ] = host[ part ] || {};
host = host[ part ];
}
host[ last ] = modules[ key ];
}
return obj;
},
makeExport = function( dollar ) {
root.__dollar = dollar;
// exports every module.
return exportsTo( factory( root, _define, _require ) );
},
origin;
if ( typeof module === 'object' && typeof module.exports === 'object' ) {
// For CommonJS and CommonJS-like environments where a proper window is present,
module.exports = makeExport();
} else if ( typeof define === 'function' && define.amd ) {
// Allow using this built library as an AMD module
// in another project. That other project will only
// see this AMD call, not the internal modules in
// the closure below.
define([ 'jquery' ], makeExport );
} else {
// Browser globals case. Just assign the
// result to a property on the global.
origin = root.WebUploader;
root.WebUploader = makeExport();
root.WebUploader.noConflict = function() {
root.WebUploader = origin;
};
}
})( window, function( window, define, require ) {
/**
* @fileOverview jQuery or Zepto
* @require "jquery"
* @require "zepto"
*/
define('dollar-third',[],function() {
var req = window.require;
var $ = window.__dollar ||
window.jQuery ||
window.Zepto ||
req('jquery') ||
req('zepto');
if ( !$ ) {
throw new Error('jQuery or Zepto not found!');
}
return $;
});
/**
* @fileOverview Dom 操作相关
*/
define('dollar',[
'dollar-third'
], function( _ ) {
return _;
});
/**
* @fileOverview 使用jQuery的Promise
*/
define('promise-third',[
'dollar'
], function( $ ) {
return {
Deferred: $.Deferred,
when: $.when,
isPromise: function( anything ) {
return anything && typeof anything.then === 'function';
}
};
});
/**
* @fileOverview Promise/A+
*/
define('promise',[
'promise-third'
], function( _ ) {
return _;
});
/**
* @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',[
'dollar',
'promise'
], function( $, promise ) {
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.6',
/**
* @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。
*/
$: $,
Deferred: promise.Deferred,
isPromise: promise.isPromise,
when: promise.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\.]+)/ ) ||
ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ),
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 ),
/**
* @description 操作系统检查结果。
*
* * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。
* * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。
* @property {Object} [os]
*/
os: (function( ua ) {
var ret = {},
// osx = !!ua.match( /\(Macintosh\; Intel / ),
android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ),
ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ );
// osx && (ret.osx = true);
android && (ret.android = parseFloat( android[ 1 ] ));
ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));
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如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。
* @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',
sort: 'sort-files',
removeFile: 'remove-file',
cancelFile: 'cancel-file',
skipFile: 'skip-file',
retry: 'retry',
isInProgress: 'is-in-progress',
makeThumb: 'make-thumb',
md5File: 'md5-file',
getDimension: 'get-dimension',
addButton: 'add-btn',
predictRuntimeType: 'predict-runtime-type',
refresh: 'refresh',
disable: 'disable',
enable: 'enable',
reset: 'reset'
}, 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({
* compress: null;
* });
*
* // 修改后图片上传前,尝试将图片压缩到1600 * 1600
* uploader.option( 'compress', {
* 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` 上传成功的文件数
* * `progressNum` 上传中的文件数
* * `cancelNum` 被删除的文件数
* * `invalidNum` 无效的文件数
* * `uploadFailNum` 上传失败的文件数
* * `queueNum` 还在队列中的文件数
* * `interruptNum` 被暂停的文件数
* @method getStats
* @grammar getStats() => Object
*/
getStats: function() {
// return this._mgr.getStats.apply( this._mgr, arguments );
var stats = this.request('get-stats');
return stats ? {
successNum: stats.numOfSuccess,
progressNum: stats.numOfProgress,
// who care?
// queueFailNum: 0,
cancelNum: stats.numOfCancel,
invalidNum: stats.numOfInvalid,
uploadFailNum: stats.numOfUploadFailed,
queueNum: stats.numOfQueue,
interruptNum: stats.numofInterrupt
} : {};
},
// 需要重写此方法来来支持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 (
// 调用通过on方法注册的handler.
Mediator.trigger.apply( this, arguments ) === false ||
// 调用opts.onEvent
$.isFunction( opts[ name ] ) &&
opts[ name ].apply( this, args ) === false ||
// 调用this.onEvent
$.isFunction( this[ name ] ) &&
this[ name ].apply( this, args ) === false ||
// 广播所有uploader的事件。
Mediator.trigger.apply( Mediator,
[ this, type ].concat( args ) ) === false ) {
return false;
}
return true;
},
/**
* 销毁 webuploader 实例
* @method destroy
* @grammar destroy() => undefined
*/
destroy: function() {
this.request( 'destroy', arguments );
this.off();
},
// widgets/widget.js将补充此方法的详细文档。
request: Base.noop
});
/**
* 创建Uploader实例,等同于new Uploader( opts );
* @method create
* @class Base
* @static
* @grammar Base.create( opts ) => Uploader
*/
Base.create = Uploader.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;
this._parent = parent;
return container;
},
init: Base.noop,
exec: Base.noop,
destroy: function() {
this._container && this._container.remove();
this._parent && this._parent.removeClass('webuploader-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;
cache = (function() {
var obj = {};
return {
add: function( runtime ) {
obj[ runtime.uid ] = runtime;
},
get: function( ruid, standalone ) {
var i;
if ( ruid ) {
return obj[ ruid ];
}
for ( i in obj ) {
// 有些类型不能重用,比如filepicker.
if ( standalone && obj[ i ].__standalone ) {
continue;
}
return obj[ i ];
}
return null;
},
remove: function( runtime ) {
delete obj[ runtime.uid ];
}
};
})();
function RuntimeClient( component, standalone ) {
var deferred = Base.Deferred(),
runtime;
this.uid = Base.guid('client_');
// 允许runtime没有初始化之前,注册一些方法在初始化后执行。
this.runtimeReady = function( cb ) {
return deferred.done( cb );
};
this.connectRuntime = function( opts, cb ) {
// already connected.
if ( runtime ) {
throw new Error('already connected!');
}
deferred.done( cb );
if ( typeof opts === 'string' && cache.get( opts ) ) {
runtime = cache.get( opts );
}
// 像filePicker只能独立存在,不能公用。
runtime = runtime || cache.get( null, standalone );
// 需要创建
if ( !runtime ) {
runtime = Runtime.create( opts, opts.runtimeOrder );
runtime.__promise = deferred.promise();
runtime.once( 'ready', deferred.resolve );
runtime.init();
cache.add( runtime );
runtime.__client = 1;
} else {
// 来自cache
Base.$.extend( runtime.options, opts );
runtime.__promise.then( deferred.resolve );
runtime.__client++;
}
standalone && (runtime.__standalone = standalone);
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 错误信息
*/
define('lib/dnd',[
'base',
'mediator',
'runtime/client'
], function( Base, Mediator, RuntimeClent ) {
var $ = Base.$;
function DragAndDrop( opts ) {
opts = this.options = $.extend({}, DragAndDrop.options, opts );
opts.container = $( opts.container );
if ( !opts.container.length ) {
return;
}
RuntimeClent.call( this, 'DragAndDrop' );
}
DragAndDrop.options = {
accept: null,
disableGlobalDnd: false
};
Base.inherits( RuntimeClent, {
constructor: DragAndDrop,
init: function() {
var me = this;
me.connectRuntime( me.options, function() {
me.exec('init');
me.trigger('ready');
});
}
});
Mediator.installTo( DragAndDrop.prototype );
return DragAndDrop;
});
/**
* @fileOverview 组件基类。
*/
define('widgets/widget',[
'base',
'uploader'
], function( Base, Uploader ) {
var $ = Base.$,
_init = Uploader.prototype._init,
_destroy = Uploader.prototype.destroy,
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, {
/**
* @property {String | Array} [disableWidgets=undefined]
* @namespace options
* @for Uploader
* @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。
*/
// 覆写_init用来初始化widgets
_init: function() {
var me = this,
widgets = me._widgets = [],
deactives = me.options.disableWidgets || '';
$.each( widgetClass, function( _, klass ) {
(!deactives || !~deactives.indexOf( klass._name )) &&
widgets.push( new klass( me ) );
});
return _init.apply( me, arguments );
},
request: function( apiName, args, callback ) {
var i = 0,
widgets = this._widgets,
len = widgets && widgets.length,
rlts = [],
dfds = [],
widget, rlt, promise, key;
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 ) {
promise = Base.when.apply( Base, dfds );
key = promise.pipe ? 'pipe' : 'then';
// 很重要不能删除。删除了会死循环。
// 保证执行顺序。让callback总是在下一个 tick 中执行。
return promise[ key ](function() {
var deferred = Base.Deferred(),
args = arguments;
if ( args.length === 1 ) {
args = args[ 0 ];
}
setTimeout(function() {
deferred.resolve( args );
}, 1 );
return deferred.promise();
})[ callback ? key : 'done' ]( callback || Base.noop );
} else {
return rlts[ 0 ];
}
},
destroy: function() {
_destroy.apply( this, arguments );
this._widgets = null;
}
});
/**
* 添加组件
* @grammar Uploader.register(proto);
* @grammar Uploader.register(map, proto);
* @param {object} responseMap API 名称与函数实现的映射
* @param {object} proto 组件原型,构造函数通过 constructor 属性定义
* @method Uploader.register
* @for Uploader
* @example
* Uploader.register({
* 'make-thumb': 'makeThumb'
* }, {
* init: function( options ) {},
* makeThumb: function() {}
* });
*
* Uploader.register({
* 'make-thumb': function() {
*
* }
* });
*/
Uploader.register = Widget.register = function( responseMap, widgetProto ) {
var map = { init: 'init', destroy: 'destroy', name: 'anonymous' },
klass;
if ( arguments.length === 1 ) {
widgetProto = responseMap;
// 自动生成 map 表。
$.each(widgetProto, function(key) {
if ( key[0] === '_' || key === 'name' ) {
key === 'name' && (map.name = widgetProto.name);
return;
}
map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key;
});
} else {
map = $.extend( map, responseMap );
}
widgetProto.responseMap = map;
klass = Base.inherits( Widget, widgetProto );
klass._name = map.name;
widgetClass.push( klass );
return klass;
};
/**
* 删除插件,只有在注册时指定了名字的才能被删除。
* @grammar Uploader.unRegister(name);
* @param {string} name 组件名字
* @method Uploader.unRegister
* @for Uploader
* @example
*
* Uploader.register({
* name: 'custom',
*
* 'make-thumb': function() {
*
* }
* });
*
* Uploader.unRegister('custom');
*/
Uploader.unRegister = Widget.unRegister = function( name ) {
if ( !name || name === 'anonymous' ) {
return;
}
// 删除指定的插件。
for ( var i = widgetClass.length; i--; ) {
if ( widgetClass[i]._name === name ) {
widgetClass.splice(i, 1)
}
}
};
return Widget;
});
/**
* @fileOverview DragAndDrop Widget。
*/
define('widgets/filednd',[
'base',
'uploader',
'lib/dnd',
'widgets/widget'
], function( Base, Uploader, Dnd ) {
var $ = Base.$;
Uploader.options.dnd = '';
/**
* @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。
* @namespace options
* @for Uploader
*/
/**
* @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。
* @namespace options
* @for Uploader
*/
/**
* @event dndAccept
* @param {DataTransferItemList} items DataTransferItem
* @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。
* @for Uploader
*/
return Uploader.register({
name: 'dnd',
init: function( opts ) {
if ( !opts.dnd ||
this.request('predict-runtime-type') !== 'html5' ) {
return;
}
var me = this,
deferred = Base.Deferred(),
options = $.extend({}, {
disableGlobalDnd: opts.disableGlobalDnd,
container: opts.dnd,
accept: opts.accept
}),
dnd;
this.dnd = dnd = new Dnd( options );
dnd.once( 'ready', deferred.resolve );
dnd.on( 'drop', function( files ) {
me.request( 'add-file', [ files ]);
});
// 检测文件是否全部允许添加。
dnd.on( 'accept', function( items ) {
return me.owner.trigger( 'dndAccept', items );
});
dnd.init();
return deferred.promise();
},
destroy: function() {
this.dnd && this.dnd.destroy();
}
});
});
/**
* @fileOverview 错误信息
*/
define('lib/filepaste',[
'base',
'mediator',
'runtime/client'
], function( Base, Mediator, RuntimeClent ) {
var $ = Base.$;
function FilePaste( opts ) {
opts = this.options = $.extend({}, opts );
opts.container = $( opts.container || document.body );
RuntimeClent.call( this, 'FilePaste' );
}
Base.inherits( RuntimeClent, {
constructor: FilePaste,
init: function() {
var me = this;
me.connectRuntime( me.options, function() {
me.exec('init');
me.trigger('ready');
});
}
});
Mediator.installTo( FilePaste.prototype );
return FilePaste;
});
/**
* @fileOverview 组件基类。
*/
define('widgets/filepaste',[
'base',
'uploader',
'lib/filepaste',
'widgets/widget'
], function( Base, Uploader, FilePaste ) {
var $ = Base.$;
/**
* @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.
* @namespace options
* @for Uploader
*/
return Uploader.register({
name: 'paste',
init: function( opts ) {
if ( !opts.paste ||
this.request('predict-runtime-type') !== 'html5' ) {
return;
}
var me = this,
deferred = Base.Deferred(),
options = $.extend({}, {
container: opts.paste,
accept: opts.accept
}),
paste;
this.paste = paste = new FilePaste( options );
paste.once( 'ready', deferred.resolve );
paste.on( 'paste', function( files ) {
me.owner.request( 'add-file', [ files ]);
});
paste.init();
return deferred.promise();
},
destroy: function() {
this.paste && this.paste.destroy();
}
});
});
/**
* @fileOverview Blob
*/
define('lib/blob',[
'base',
'runtime/client'
], function( Base, RuntimeClient ) {
function Blob( rui