檔案上傳外掛
阿新 • • 發佈:2019-01-17
這兩天工作需要寫一個檔案上傳功能,這個外掛很好,只是對於js新手不太合適,能改的東西少,不過用起來功能很強大,推薦使用,只是我不會傳圖片.還有幾張圖片沒有傳上來.對於直接複製貼上程式碼的小夥伴可能有些問題
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <title>myUploader</title> <link rel="stylesheet" type="text/css" href="../css/webuploader.css" /> <link rel="stylesheet" type="text/css" href="../css/style.css" /> </head> <body onload=""> <div id="wrapper"> <div id="container"> <!--頭部,相簿選擇和格式選擇--> <div id="uploader"> <div class="queueList"> <div id="dndArea" class="placeholder"> <div id="filePicker"></div> </div> </div> <div class="statusBar" style="display:none;"> <div class="progress"> <span class="text">0%</span> <span class="percentage"></span> </div><div class="info"></div> <div class="btns"> <div id="filePicker2"></div><div class="uploadBtn">開始上傳</div> </div> </div> </div> </div> </div> <script src="../js/jquery.js"></script> <script src="../js/webuploader.js"></script> <script src="../js/upload.js"></script> </body> </html>
/* ----------------Reset Css--------------------- */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video, input { margin: 0; padding: 0; border: none; outline: 0; font-size: 100%; font: inherit; vertical-align: baseline; } html, body, form, fieldset, p, div, h1, h2, h3, h4, h5, h6 { -webkit-text-size-adjust: none; } article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } body { font-family: arial, sans-serif; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } ins { text-decoration: none; } del { text-decoration: line-through; } table { border-collapse: collapse; border-spacing: 0; } /* ------------ */ #wrapper { width: 980px; margin: 0 auto; margin: 1em; width: auto; } #container { border: 1px solid #dadada; color: #838383; font-size: 12px; margin-top: 10px; background-color: #FFF; } #uploader .queueList { margin: 20px; } .element-invisible { position: absolute !important; clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ clip: rect(1px,1px,1px,1px); } #uploader .placeholder { border: 3px dashed #e6e6e6; min-height: 238px; padding-top: 158px; text-align: center; background: url(../images/image.png) center 93px no-repeat; color: #cccccc; font-size: 18px; position: relative; } #uploader .placeholder .webuploader-pick { font-size: 18px; background: #00b7ee; border-radius: 3px; line-height: 44px; padding: 0 30px; color: #fff; display: inline-block; margin: 20px auto; cursor: pointer; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); } #uploader .placeholder .webuploader-pick-hover { background: #00a2d4; } #uploader .placeholder .flashTip { color: #666666; font-size: 12px; position: absolute; width: 100%; text-align: center; bottom: 20px; } #uploader .placeholder .flashTip a { color: #0785d1; text-decoration: none; } #uploader .placeholder .flashTip a:hover { text-decoration: underline; } #uploader .placeholder.webuploader-dnd-over { border-color: #999999; } #uploader .placeholder.webuploader-dnd-over.webuploader-dnd-denied { border-color: red; } #uploader .filelist { list-style: none; margin: 0; padding: 0; } #uploader .filelist:after { content: ''; display: block; width: 0; height: 0; overflow: hidden; clear: both; } #uploader .filelist li { width: 110px; height: 110px; background: url(../images/bg.png) no-repeat; text-align: center; margin: 0 8px 20px 0; position: relative; display: inline; float: left; overflow: hidden; font-size: 12px; } #uploader .filelist li p.log { position: relative; top: -45px; } #uploader .filelist li p.title { position: absolute; top: 0; left: 0; width: 100%; overflow: hidden; white-space: nowrap; text-overflow : ellipsis; top: 5px; text-indent: 5px; text-align: left; } #uploader .filelist li p.progress { position: absolute; width: 100%; bottom: 0; left: 0; height: 8px; overflow: hidden; z-index: 50; } #uploader .filelist li p.progress span { display: none; overflow: hidden; width: 0; height: 100%; background: #1483d8 url(../images/progress.png) repeat-x; -webit-transition: width 200ms linear; -moz-transition: width 200ms linear; -o-transition: width 200ms linear; -ms-transition: width 200ms linear; transition: width 200ms linear; -webkit-animation: progressmove 2s linear infinite; -moz-animation: progressmove 2s linear infinite; -o-animation: progressmove 2s linear infinite; -ms-animation: progressmove 2s linear infinite; animation: progressmove 2s linear infinite; -webkit-transform: translateZ(0); } @-webkit-keyframes progressmove { 0% { background-position: 0 0; } 100% { background-position: 17px 0; } } @-moz-keyframes progressmove { 0% { background-position: 0 0; } 100% { background-position: 17px 0; } } @keyframes progressmove { 0% { background-position: 0 0; } 100% { background-position: 17px 0; } } #uploader .filelist li p.imgWrap { position: relative; z-index: 2; line-height: 110px; vertical-align: middle; overflow: hidden; width: 110px; height: 110px; -webkit-transform-origin: 50% 50%; -moz-transform-origin: 50% 50%; -o-transform-origin: 50% 50%; -ms-transform-origin: 50% 50%; transform-origin: 50% 50%; -webit-transition: 200ms ease-out; -moz-transition: 200ms ease-out; -o-transition: 200ms ease-out; -ms-transition: 200ms ease-out; transition: 200ms ease-out; } #uploader .filelist li img { width: 100%; } #uploader .filelist li p.error { background: #f43838; color: #fff; position: absolute; bottom: 0; left: 0; height: 28px; line-height: 28px; width: 100%; z-index: 100; } #uploader .filelist li .success { display: block; position: absolute; left: 0; bottom: 0; height: 40px; width: 100%; z-index: 200; background: url(../images/success.png) no-repeat right bottom; } #uploader .filelist div.file-panel { position: absolute; height: 0; filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#80000000', endColorstr='#80000000')\0; background: rgba( 0, 0, 0, 0.5 ); width: 100%; top: 0; left: 0; overflow: hidden; z-index: 300; } #uploader .filelist div.file-panel span { width: 24px; height: 24px; display: inline; float: right; text-indent: -9999px; overflow: hidden; background: url(../images/icons.png) no-repeat; margin: 5px 1px 1px; cursor: pointer; } #uploader .filelist div.file-panel span.rotateLeft { background-position: 0 -24px; } #uploader .filelist div.file-panel span.rotateLeft:hover { background-position: 0 0; } #uploader .filelist div.file-panel span.rotateRight { background-position: -24px -24px; } #uploader .filelist div.file-panel span.rotateRight:hover { background-position: -24px 0; } #uploader .filelist div.file-panel span.cancel { background-position: -48px -24px; } #uploader .filelist div.file-panel span.cancel:hover { background-position: -48px 0; } #uploader .statusBar { height: 63px; border-top: 1px solid #dadada; padding: 0 20px; line-height: 63px; vertical-align: middle; position: relative; } #uploader .statusBar .progress { border: 1px solid #1483d8; width: 198px; background: #fff; height: 18px; position: relative; display: inline-block; text-align: center; line-height: 20px; color: #6dbfff; position: relative; margin-right: 10px; } #uploader .statusBar .progress span.percentage { width: 0; height: 100%; left: 0; top: 0; background: #1483d8; position: absolute; } #uploader .statusBar .progress span.text { position: relative; z-index: 10; } #uploader .statusBar .info { display: inline-block; font-size: 14px; color: #666666; } #uploader .statusBar .btns { position: absolute; top: 10px; right: 20px; line-height: 40px; } #filePicker2 { display: inline-block; float: left; } #uploader .statusBar .btns .webuploader-pick, #uploader .statusBar .btns .uploadBtn, #uploader .statusBar .btns .uploadBtn.state-uploading, #uploader .statusBar .btns .uploadBtn.state-paused { background: #ffffff; border: 1px solid #cfcfcf; color: #565656; padding: 0 18px; display: inline-block; border-radius: 3px; margin-left: 10px; cursor: pointer; font-size: 14px; float: left; } #uploader .statusBar .btns .webuploader-pick-hover, #uploader .statusBar .btns .uploadBtn:hover, #uploader .statusBar .btns .uploadBtn.state-uploading:hover, #uploader .statusBar .btns .uploadBtn.state-paused:hover { background: #f0f0f0; } #uploader .statusBar .btns .uploadBtn { background: #00b7ee; color: #fff; border-color: transparent; } #uploader .statusBar .btns .uploadBtn:hover { background: #00a2d4; } #uploader .statusBar .btns .uploadBtn.disabled { pointer-events: none; opacity: 0.6; } /**以下是webuploader.css**/ .webuploader-container { position: relative; } .webuploader-element-invisible { position: absolute !important; clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ clip: rect(1px,1px,1px,1px); } .webuploader-pick { position: relative; display: inline-block; cursor: pointer; background: #00b7ee; padding: 10px 15px; color: #fff; text-align: center; border-radius: 3px; overflow: hidden; } .webuploader-pick-hover { background: #00a2d4; } .webuploader-pick-disable { opacity: 0.6; pointer-events:none; }
//webuploader.js
/*! WebUploader 0.1.5 */
/**
* @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
*/
define('dollar-third',[],function() {
var $ = window.__dollar || window.jQuery || window.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.5',
/**
* @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', functio