webuploader.html5only.js 195 KB

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