| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761 |
- /**
- * http://github.com/Valums-File-Uploader/file-uploader
- *
- * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
- *
- * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
- * Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
- *
- * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
- */
- /*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
- var qq = function(element) {
- "use strict";
- return {
- hide: function() {
- element.style.display = 'none';
- return this;
- },
- /** Returns the function which detaches attached event */
- attach: function(type, fn) {
- if (element.addEventListener){
- element.addEventListener(type, fn, false);
- } else if (element.attachEvent){
- element.attachEvent('on' + type, fn);
- }
- return function() {
- qq(element).detach(type, fn);
- };
- },
- detach: function(type, fn) {
- if (element.removeEventListener){
- element.removeEventListener(type, fn, false);
- } else if (element.attachEvent){
- element.detachEvent('on' + type, fn);
- }
- return this;
- },
- contains: function(descendant) {
- // compareposition returns false in this case
- if (element === descendant) {
- return true;
- }
- if (element.contains){
- return element.contains(descendant);
- } else {
- /*jslint bitwise: true*/
- return !!(descendant.compareDocumentPosition(element) & 8);
- }
- },
- /**
- * Insert this element before elementB.
- */
- insertBefore: function(elementB) {
- elementB.parentNode.insertBefore(element, elementB);
- return this;
- },
- remove: function() {
- element.parentNode.removeChild(element);
- return this;
- },
- /**
- * Sets styles for an element.
- * Fixes opacity in IE6-8.
- */
- css: function(styles) {
- if (styles.opacity !== null){
- if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
- styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
- }
- }
- qq.extend(element.style, styles);
- return this;
- },
- hasClass: function(name) {
- var re = new RegExp('(^| )' + name + '( |$)');
- return re.test(element.className);
- },
- addClass: function(name) {
- if (!qq(element).hasClass(name)){
- element.className += ' ' + name;
- }
- return this;
- },
- removeClass: function(name) {
- var re = new RegExp('(^| )' + name + '( |$)');
- element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
- return this;
- },
- getByClass: function(className) {
- var candidates,
- result = [];
- if (element.querySelectorAll){
- return element.querySelectorAll('.' + className);
- }
- candidates = element.getElementsByTagName("*");
- qq.each(candidates, function(idx, val) {
- if (qq(val).hasClass(className)){
- result.push(val);
- }
- });
- return result;
- },
- children: function() {
- var children = [],
- child = element.firstChild;
- while (child){
- if (child.nodeType === 1){
- children.push(child);
- }
- child = child.nextSibling;
- }
- return children;
- },
- setText: function(text) {
- element.innerText = text;
- element.textContent = text;
- return this;
- },
- clearText: function() {
- return qq(element).setText("");
- }
- };
- };
- qq.log = function(message, level) {
- "use strict";
- if (window.console) {
- if (!level || level === 'info') {
- window.console.log(message);
- }
- else
- {
- if (window.console[level]) {
- window.console[level](message);
- }
- else {
- window.console.log('<' + level + '> ' + message);
- }
- }
- }
- };
- qq.isObject = function(variable) {
- "use strict";
- return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
- };
- qq.isFunction = function(variable) {
- "use strict";
- return typeof(variable) === "function";
- };
- qq.trimStr = function(string) {
- if (String.prototype.trim) {
- return string.trim();
- }
- return string.replace(/^\s+|\s+$/g,'');
- };
- qq.isFileOrInput = function(maybeFileOrInput) {
- "use strict";
- if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
- return true;
- }
- else if (window.HTMLInputElement) {
- if (maybeFileOrInput instanceof HTMLInputElement) {
- if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
- return true;
- }
- }
- }
- else if (maybeFileOrInput.tagName) {
- if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
- if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
- return true;
- }
- }
- }
- return false;
- };
- qq.isBlob = function(maybeBlob) {
- "use strict";
- return window.Blob && maybeBlob instanceof Blob;
- };
- qq.isXhrUploadSupported = function() {
- "use strict";
- var input = document.createElement('input');
- input.type = 'file';
- return (
- input.multiple !== undefined &&
- typeof File !== "undefined" &&
- typeof FormData !== "undefined" &&
- typeof (new XMLHttpRequest()).upload !== "undefined" );
- };
- qq.isFolderDropSupported = function(dataTransfer) {
- "use strict";
- return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
- };
- qq.isFileChunkingSupported = function() {
- "use strict";
- return !qq.android() && //android's impl of Blob.slice is broken
- qq.isXhrUploadSupported() &&
- (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
- };
- qq.extend = function (first, second, extendNested) {
- "use strict";
- qq.each(second, function(prop, val) {
- if (extendNested && qq.isObject(val)) {
- if (first[prop] === undefined) {
- first[prop] = {};
- }
- qq.extend(first[prop], val, true);
- }
- else {
- first[prop] = val;
- }
- });
- };
- /**
- * Searches for a given element in the array, returns -1 if it is not present.
- * @param {Number} [from] The index at which to begin the search
- */
- qq.indexOf = function(arr, elt, from){
- "use strict";
- if (arr.indexOf) {
- return arr.indexOf(elt, from);
- }
- from = from || 0;
- var len = arr.length;
- if (from < 0) {
- from += len;
- }
- for (; from < len; from+=1){
- if (arr.hasOwnProperty(from) && arr[from] === elt){
- return from;
- }
- }
- return -1;
- };
- //this is a version 4 UUID
- qq.getUniqueId = function(){
- "use strict";
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
- /*jslint eqeq: true, bitwise: true*/
- var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
- return v.toString(16);
- });
- };
- //
- // Browsers and platforms detection
- qq.ie = function(){
- "use strict";
- return navigator.userAgent.indexOf('MSIE') !== -1;
- };
- qq.ie10 = function(){
- "use strict";
- return navigator.userAgent.indexOf('MSIE 10') !== -1;
- };
- qq.safari = function(){
- "use strict";
- return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
- };
- qq.chrome = function(){
- "use strict";
- return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
- };
- qq.firefox = function(){
- "use strict";
- return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
- };
- qq.windows = function(){
- "use strict";
- return navigator.platform === "Win32";
- };
- qq.android = function(){
- "use strict";
- return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
- };
- //
- // Events
- qq.preventDefault = function(e){
- "use strict";
- if (e.preventDefault){
- e.preventDefault();
- } else{
- e.returnValue = false;
- }
- };
- /**
- * Creates and returns element from html string
- * Uses innerHTML to create an element
- */
- qq.toElement = (function(){
- "use strict";
- var div = document.createElement('div');
- return function(html){
- div.innerHTML = html;
- var element = div.firstChild;
- div.removeChild(element);
- return element;
- };
- }());
- //key and value are passed to callback for each item in the object or array
- qq.each = function(obj, callback) {
- "use strict";
- var key, retVal;
- if (obj) {
- for (key in obj) {
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
- retVal = callback(key, obj[key]);
- if (retVal === false) {
- break;
- }
- }
- }
- }
- };
- /**
- * obj2url() takes a json-object as argument and generates
- * a querystring. pretty much like jQuery.param()
- *
- * how to use:
- *
- * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
- *
- * will result in:
- *
- * `http://any.url/upload?otherParam=value&a=b&c=d`
- *
- * @param Object JSON-Object
- * @param String current querystring-part
- * @return String encoded querystring
- */
- qq.obj2url = function(obj, temp, prefixDone){
- "use strict";
- /*jshint laxbreak: true*/
- var i, len,
- uristrings = [],
- prefix = '&',
- add = function(nextObj, i){
- var nextTemp = temp
- ? (/\[\]$/.test(temp)) // prevent double-encoding
- ? temp
- : temp+'['+i+']'
- : i;
- if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
- uristrings.push(
- (typeof nextObj === 'object')
- ? qq.obj2url(nextObj, nextTemp, true)
- : (Object.prototype.toString.call(nextObj) === '[object Function]')
- ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
- : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
- );
- }
- };
- if (!prefixDone && temp) {
- prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
- uristrings.push(temp);
- uristrings.push(qq.obj2url(obj));
- } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
- // we wont use a for-in-loop on an array (performance)
- for (i = -1, len = obj.length; i < len; i+=1){
- add(obj[i], i);
- }
- } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
- // for anything else but a scalar, we will use for-in-loop
- for (i in obj){
- if (obj.hasOwnProperty(i)) {
- add(obj[i], i);
- }
- }
- } else {
- uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
- }
- if (temp) {
- return uristrings.join(prefix);
- } else {
- return uristrings.join(prefix)
- .replace(/^&/, '')
- .replace(/%20/g, '+');
- }
- };
- qq.obj2FormData = function(obj, formData, arrayKeyName) {
- "use strict";
- if (!formData) {
- formData = new FormData();
- }
- qq.each(obj, function(key, val) {
- key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
- if (qq.isObject(val)) {
- qq.obj2FormData(val, formData, key);
- }
- else if (qq.isFunction(val)) {
- formData.append(key, val());
- }
- else {
- formData.append(key, val);
- }
- });
- return formData;
- };
- qq.obj2Inputs = function(obj, form) {
- "use strict";
- var input;
- if (!form) {
- form = document.createElement('form');
- }
- qq.obj2FormData(obj, {
- append: function(key, val) {
- input = document.createElement('input');
- input.setAttribute('name', key);
- input.setAttribute('value', val);
- form.appendChild(input);
- }
- });
- return form;
- };
- qq.setCookie = function(name, value, days) {
- var date = new Date(),
- expires = "";
- if (days) {
- date.setTime(date.getTime()+(days*24*60*60*1000));
- expires = "; expires="+date.toGMTString();
- }
- document.cookie = name+"="+value+expires+"; path=/";
- };
- qq.getCookie = function(name) {
- var nameEQ = name + "=",
- ca = document.cookie.split(';'),
- c;
- for(var i=0;i < ca.length;i++) {
- c = ca[i];
- while (c.charAt(0)==' ') {
- c = c.substring(1,c.length);
- }
- if (c.indexOf(nameEQ) === 0) {
- return c.substring(nameEQ.length,c.length);
- }
- }
- };
- qq.getCookieNames = function(regexp) {
- var cookies = document.cookie.split(';'),
- cookieNames = [];
- qq.each(cookies, function(idx, cookie) {
- cookie = qq.trimStr(cookie);
- var equalsIdx = cookie.indexOf("=");
- if (cookie.match(regexp)) {
- cookieNames.push(cookie.substr(0, equalsIdx));
- }
- });
- return cookieNames;
- };
- qq.deleteCookie = function(name) {
- qq.setCookie(name, "", -1);
- };
- qq.areCookiesEnabled = function() {
- var randNum = Math.random() * 100000,
- name = "qqCookieTest:" + randNum;
- qq.setCookie(name, 1);
- if (qq.getCookie(name)) {
- qq.deleteCookie(name);
- return true;
- }
- return false;
- };
- /**
- * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
- * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
- */
- qq.parseJson = function(json) {
- /*jshint evil: true*/
- if (window.JSON && qq.isFunction(JSON.parse)) {
- return JSON.parse(json);
- } else {
- return eval("(" + json + ")");
- }
- };
- /**
- * A generic module which supports object disposing in dispose() method.
- * */
- qq.DisposeSupport = function() {
- "use strict";
- var disposers = [];
- return {
- /** Run all registered disposers */
- dispose: function() {
- var disposer;
- do {
- disposer = disposers.shift();
- if (disposer) {
- disposer();
- }
- }
- while (disposer);
- },
- /** Attach event handler and register de-attacher as a disposer */
- attach: function() {
- var args = arguments;
- /*jslint undef:true*/
- this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
- },
- /** Add disposer to the collection */
- addDisposer: function(disposeFunction) {
- disposers.push(disposeFunction);
- }
- };
- };
- qq.UploadButton = function(o){
- this._options = {
- element: null,
- // if set to true adds multiple attribute to file input
- multiple: false,
- acceptFiles: null,
- // name attribute of file input
- name: 'file',
- onChange: function(input){},
- hoverClass: 'qq-upload-button-hover',
- focusClass: 'qq-upload-button-focus'
- };
- qq.extend(this._options, o);
- this._disposeSupport = new qq.DisposeSupport();
- this._element = this._options.element;
- // make button suitable container for input
- qq(this._element).css({
- position: 'relative',
- overflow: 'hidden',
- // Make sure browse button is in the right side
- // in Internet Explorer
- direction: 'ltr'
- });
- this._input = this._createInput();
- };
- qq.UploadButton.prototype = {
- /* returns file input element */
- getInput: function(){
- return this._input;
- },
- /* cleans/recreates the file input */
- reset: function(){
- if (this._input.parentNode){
- qq(this._input).remove();
- }
- qq(this._element).removeClass(this._options.focusClass);
- this._input = this._createInput();
- },
- _createInput: function(){
- var input = document.createElement("input");
- if (this._options.multiple){
- input.setAttribute("multiple", "multiple");
- }
- if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles);
- input.setAttribute("type", "file");
- input.setAttribute("name", this._options.name);
- qq(input).css({
- position: 'absolute',
- // in Opera only 'browse' button
- // is clickable and it is located at
- // the right side of the input
- right: 0,
- top: 0,
- fontFamily: 'Arial',
- // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
- fontSize: '118px',
- margin: 0,
- padding: 0,
- cursor: 'pointer',
- opacity: 0
- });
- this._element.appendChild(input);
- var self = this;
- this._disposeSupport.attach(input, 'change', function(){
- self._options.onChange(input);
- });
- this._disposeSupport.attach(input, 'mouseover', function(){
- qq(self._element).addClass(self._options.hoverClass);
- });
- this._disposeSupport.attach(input, 'mouseout', function(){
- qq(self._element).removeClass(self._options.hoverClass);
- });
- this._disposeSupport.attach(input, 'focus', function(){
- qq(self._element).addClass(self._options.focusClass);
- });
- this._disposeSupport.attach(input, 'blur', function(){
- qq(self._element).removeClass(self._options.focusClass);
- });
- // IE and Opera, unfortunately have 2 tab stops on file input
- // which is unacceptable in our case, disable keyboard access
- if (window.attachEvent){
- // it is IE or Opera
- input.setAttribute('tabIndex', "-1");
- }
- return input;
- }
- };
- qq.FineUploaderBasic = function(o){
- var that = this;
- this._options = {
- debug: false,
- button: null,
- multiple: true,
- maxConnections: 3,
- disableCancelForFormUploads: false,
- autoUpload: true,
- request: {
- endpoint: '/server/upload',
- params: {},
- paramsInBody: true,
- customHeaders: {},
- forceMultipart: true,
- inputName: 'qqfile',
- uuidName: 'qquuid',
- totalFileSizeName: 'qqtotalfilesize'
- },
- validation: {
- allowedExtensions: [],
- sizeLimit: 0,
- minSizeLimit: 0,
- stopOnFirstInvalidFile: true
- },
- callbacks: {
- onSubmit: function(id, name){},
- onComplete: function(id, name, responseJSON){},
- onCancel: function(id, name){},
- onUpload: function(id, name){},
- onUploadChunk: function(id, name, chunkData){},
- onResume: function(id, fileName, chunkData){},
- onProgress: function(id, name, loaded, total){},
- onError: function(id, name, reason) {},
- onAutoRetry: function(id, name, attemptNumber) {},
- onManualRetry: function(id, name) {},
- onValidateBatch: function(fileOrBlobData) {},
- onValidate: function(fileOrBlobData) {},
- onSubmitDelete: function(id) {},
- onDelete: function(id){},
- onDeleteComplete: function(id, xhr, isError){}
- },
- messages: {
- typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
- sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
- minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
- emptyError: "{file} is empty, please select files again without it.",
- noFilesError: "No files to upload.",
- onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
- },
- retry: {
- enableAuto: false,
- maxAutoAttempts: 3,
- autoAttemptDelay: 5,
- preventRetryResponseProperty: 'preventRetry'
- },
- classes: {
- buttonHover: 'qq-upload-button-hover',
- buttonFocus: 'qq-upload-button-focus'
- },
- chunking: {
- enabled: false,
- partSize: 2000000,
- paramNames: {
- partIndex: 'qqpartindex',
- partByteOffset: 'qqpartbyteoffset',
- chunkSize: 'qqchunksize',
- totalFileSize: 'qqtotalfilesize',
- totalParts: 'qqtotalparts',
- filename: 'qqfilename'
- }
- },
- resume: {
- enabled: false,
- id: null,
- cookiesExpireIn: 7, //days
- paramNames: {
- resuming: "qqresume"
- }
- },
- formatFileName: function(fileOrBlobName) {
- if (fileOrBlobName.length > 33) {
- fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
- }
- return fileOrBlobName;
- },
- text: {
- sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
- },
- deleteFile : {
- enabled: false,
- endpoint: '/server/upload',
- customHeaders: {},
- params: {}
- },
- cors: {
- expected: false,
- sendCredentials: false
- },
- blobs: {
- defaultName: 'Misc data',
- paramNames: {
- name: 'qqblobname'
- }
- }
- };
- qq.extend(this._options, o, true);
- this._wrapCallbacks();
- this._disposeSupport = new qq.DisposeSupport();
- // number of files being uploaded
- this._filesInProgress = [];
- this._storedIds = [];
- this._autoRetries = [];
- this._retryTimeouts = [];
- this._preventRetries = [];
- this._paramsStore = this._createParamsStore("request");
- this._deleteFileParamsStore = this._createParamsStore("deleteFile");
- this._endpointStore = this._createEndpointStore("request");
- this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
- this._handler = this._createUploadHandler();
- this._deleteHandler = this._createDeleteHandler();
- if (this._options.button){
- this._button = this._createUploadButton(this._options.button);
- }
- this._preventLeaveInProgress();
- };
- qq.FineUploaderBasic.prototype = {
- log: function(str, level) {
- if (this._options.debug && (!level || level === 'info')) {
- qq.log('[FineUploader] ' + str);
- }
- else if (level && level !== 'info') {
- qq.log('[FineUploader] ' + str, level);
- }
- },
- setParams: function(params, id) {
- /*jshint eqeqeq: true, eqnull: true*/
- if (id == null) {
- this._options.request.params = params;
- }
- else {
- this._paramsStore.setParams(params, id);
- }
- },
- setDeleteFileParams: function(params, id) {
- /*jshint eqeqeq: true, eqnull: true*/
- if (id == null) {
- this._options.deleteFile.params = params;
- }
- else {
- this._deleteFileParamsStore.setParams(params, id);
- }
- },
- setEndpoint: function(endpoint, id) {
- /*jshint eqeqeq: true, eqnull: true*/
- if (id == null) {
- this._options.request.endpoint = endpoint;
- }
- else {
- this._endpointStore.setEndpoint(endpoint, id);
- }
- },
- getInProgress: function(){
- return this._filesInProgress.length;
- },
- uploadStoredFiles: function(){
- "use strict";
- var idToUpload;
- while(this._storedIds.length) {
- idToUpload = this._storedIds.shift();
- this._filesInProgress.push(idToUpload);
- this._handler.upload(idToUpload);
- }
- },
- clearStoredFiles: function(){
- this._storedIds = [];
- },
- retry: function(id) {
- if (this._onBeforeManualRetry(id)) {
- this._handler.retry(id);
- return true;
- }
- else {
- return false;
- }
- },
- cancel: function(id) {
- this._handler.cancel(id);
- },
- cancelAll: function() {
- var storedIdsCopy = [],
- self = this;
- qq.extend(storedIdsCopy, this._storedIds);
- qq.each(storedIdsCopy, function(idx, storedFileId) {
- self.cancel(storedFileId);
- });
- this._handler.cancelAll();
- },
- reset: function() {
- this.log("Resetting uploader...");
- this._handler.reset();
- this._filesInProgress = [];
- this._storedIds = [];
- this._autoRetries = [];
- this._retryTimeouts = [];
- this._preventRetries = [];
- this._button.reset();
- this._paramsStore.reset();
- this._endpointStore.reset();
- },
- addFiles: function(filesBlobDataOrInputs) {
- var self = this,
- verifiedFilesOrInputs = [],
- index, fileOrInput;
- if (filesBlobDataOrInputs) {
- if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
- filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
- }
- for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
- fileOrInput = filesBlobDataOrInputs[index];
- if (qq.isFileOrInput(fileOrInput)) {
- verifiedFilesOrInputs.push(fileOrInput);
- }
- else {
- self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
- }
- }
- this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
- this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
- }
- },
- addBlobs: function(blobDataOrArray) {
- if (blobDataOrArray) {
- var blobDataArray = [].concat(blobDataOrArray),
- verifiedBlobDataList = [],
- self = this;
- qq.each(blobDataArray, function(idx, blobData) {
- if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
- verifiedBlobDataList.push({
- blob: blobData,
- name: self._options.blobs.defaultName
- });
- }
- else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
- verifiedBlobDataList.push(blobData);
- }
- else {
- self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
- }
- });
- this._uploadFileOrBlobDataList(verifiedBlobDataList);
- }
- else {
- this.log("undefined or non-array parameter passed into addBlobs", "error");
- }
- },
- getUuid: function(id) {
- return this._handler.getUuid(id);
- },
- getResumableFilesData: function() {
- return this._handler.getResumableFilesData();
- },
- getSize: function(id) {
- return this._handler.getSize(id);
- },
- getFile: function(fileOrBlobId) {
- return this._handler.getFile(fileOrBlobId);
- },
- deleteFile: function(id) {
- this._onSubmitDelete(id);
- },
- setDeleteFileEndpoint: function(endpoint, id) {
- /*jshint eqeqeq: true, eqnull: true*/
- if (id == null) {
- this._options.deleteFile.endpoint = endpoint;
- }
- else {
- this._deleteFileEndpointStore.setEndpoint(endpoint, id);
- }
- },
- _createUploadButton: function(element){
- var self = this;
- var button = new qq.UploadButton({
- element: element,
- multiple: this._options.multiple && qq.isXhrUploadSupported(),
- acceptFiles: this._options.validation.acceptFiles,
- onChange: function(input){
- self._onInputChange(input);
- },
- hoverClass: this._options.classes.buttonHover,
- focusClass: this._options.classes.buttonFocus
- });
- this._disposeSupport.addDisposer(function() { button.dispose(); });
- return button;
- },
- _createUploadHandler: function(){
- var self = this;
- return new qq.UploadHandler({
- debug: this._options.debug,
- forceMultipart: this._options.request.forceMultipart,
- maxConnections: this._options.maxConnections,
- customHeaders: this._options.request.customHeaders,
- inputName: this._options.request.inputName,
- uuidParamName: this._options.request.uuidName,
- totalFileSizeParamName: this._options.request.totalFileSizeName,
- cors: this._options.cors,
- demoMode: this._options.demoMode,
- paramsInBody: this._options.request.paramsInBody,
- paramsStore: this._paramsStore,
- endpointStore: this._endpointStore,
- chunking: this._options.chunking,
- resume: this._options.resume,
- blobs: this._options.blobs,
- log: function(str, level) {
- self.log(str, level);
- },
- onProgress: function(id, name, loaded, total){
- self._onProgress(id, name, loaded, total);
- self._options.callbacks.onProgress(id, name, loaded, total);
- },
- onComplete: function(id, name, result, xhr){
- self._onComplete(id, name, result, xhr);
- self._options.callbacks.onComplete(id, name, result);
- },
- onCancel: function(id, name){
- self._onCancel(id, name);
- self._options.callbacks.onCancel(id, name);
- },
- onUpload: function(id, name){
- self._onUpload(id, name);
- self._options.callbacks.onUpload(id, name);
- },
- onUploadChunk: function(id, name, chunkData){
- self._options.callbacks.onUploadChunk(id, name, chunkData);
- },
- onResume: function(id, name, chunkData) {
- return self._options.callbacks.onResume(id, name, chunkData);
- },
- onAutoRetry: function(id, name, responseJSON, xhr) {
- self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
- if (self._shouldAutoRetry(id, name, responseJSON)) {
- self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
- self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
- self._onBeforeAutoRetry(id, name);
- self._retryTimeouts[id] = setTimeout(function() {
- self._onAutoRetry(id, name, responseJSON)
- }, self._options.retry.autoAttemptDelay * 1000);
- return true;
- }
- else {
- return false;
- }
- }
- });
- },
- _createDeleteHandler: function() {
- var self = this;
- return new qq.DeleteFileAjaxRequestor({
- maxConnections: this._options.maxConnections,
- customHeaders: this._options.deleteFile.customHeaders,
- paramsStore: this._deleteFileParamsStore,
- endpointStore: this._deleteFileEndpointStore,
- demoMode: this._options.demoMode,
- cors: this._options.cors,
- log: function(str, level) {
- self.log(str, level);
- },
- onDelete: function(id) {
- self._onDelete(id);
- self._options.callbacks.onDelete(id);
- },
- onDeleteComplete: function(id, xhr, isError) {
- self._onDeleteComplete(id, xhr, isError);
- self._options.callbacks.onDeleteComplete(id, xhr, isError);
- }
- });
- },
- _preventLeaveInProgress: function(){
- var self = this;
- this._disposeSupport.attach(window, 'beforeunload', function(e){
- if (!self._filesInProgress.length){return;}
- var e = e || window.event;
- // for ie, ff
- e.returnValue = self._options.messages.onLeave;
- // for webkit
- return self._options.messages.onLeave;
- });
- },
- _onSubmit: function(id, name){
- if (this._options.autoUpload) {
- this._filesInProgress.push(id);
- }
- },
- _onProgress: function(id, name, loaded, total){
- },
- _onComplete: function(id, name, result, xhr){
- this._removeFromFilesInProgress(id);
- this._maybeParseAndSendUploadError(id, name, result, xhr);
- },
- _onCancel: function(id, name){
- this._removeFromFilesInProgress(id);
- clearTimeout(this._retryTimeouts[id]);
- var storedItemIndex = qq.indexOf(this._storedIds, id);
- if (!this._options.autoUpload && storedItemIndex >= 0) {
- this._storedIds.splice(storedItemIndex, 1);
- }
- },
- _isDeletePossible: function() {
- return (this._options.deleteFile.enabled &&
- (!this._options.cors.expected ||
- (this._options.cors.expected && (qq.ie10() || !qq.ie()))
- )
- );
- },
- _onSubmitDelete: function(id) {
- if (this._isDeletePossible()) {
- if (this._options.callbacks.onSubmitDelete(id)) {
- this._deleteHandler.sendDelete(id, this.getUuid(id));
- }
- }
- else {
- this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
- "due to CORS on a user agent that does not support pre-flighting.", "warn");
- return false;
- }
- },
- _onDelete: function(fileId) {},
- _onDeleteComplete: function(id, xhr, isError) {
- var name = this._handler.getName(id);
- if (isError) {
- this.log("Delete request for '" + name + "' has failed.", "error");
- this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status);
- }
- else {
- this.log("Delete request for '" + name + "' has succeeded.");
- }
- },
- _removeFromFilesInProgress: function(id) {
- var index = qq.indexOf(this._filesInProgress, id);
- if (index >= 0) {
- this._filesInProgress.splice(index, 1);
- }
- },
- _onUpload: function(id, name){},
- _onInputChange: function(input){
- if (qq.isXhrUploadSupported()){
- this.addFiles(input.files);
- } else {
- this.addFiles(input);
- }
- this._button.reset();
- },
- _onBeforeAutoRetry: function(id, name) {
- this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
- },
- _onAutoRetry: function(id, name, responseJSON) {
- this.log("Retrying " + name + "...");
- this._autoRetries[id]++;
- this._handler.retry(id);
- },
- _shouldAutoRetry: function(id, name, responseJSON) {
- if (!this._preventRetries[id] && this._options.retry.enableAuto) {
- if (this._autoRetries[id] === undefined) {
- this._autoRetries[id] = 0;
- }
- return this._autoRetries[id] < this._options.retry.maxAutoAttempts
- }
- return false;
- },
- //return false if we should not attempt the requested retry
- _onBeforeManualRetry: function(id) {
- if (this._preventRetries[id]) {
- this.log("Retries are forbidden for id " + id, 'warn');
- return false;
- }
- else if (this._handler.isValid(id)) {
- var fileName = this._handler.getName(id);
- if (this._options.callbacks.onManualRetry(id, fileName) === false) {
- return false;
- }
- this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
- this._filesInProgress.push(id);
- return true;
- }
- else {
- this.log("'" + id + "' is not a valid file ID", 'error');
- return false;
- }
- },
- _maybeParseAndSendUploadError: function(id, name, response, xhr) {
- //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
- if (!response.success){
- if (xhr && xhr.status !== 200 && !response.error) {
- this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status);
- }
- else {
- var errorReason = response.error ? response.error : "Upload failure reason unknown";
- this._options.callbacks.onError(id, name, errorReason);
- }
- }
- },
- _uploadFileOrBlobDataList: function(fileOrBlobDataList){
- var validationDescriptors, index, batchInvalid;
- validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList);
- batchInvalid = this._options.callbacks.onValidateBatch(validationDescriptors) === false;
- if (!batchInvalid) {
- if (fileOrBlobDataList.length > 0) {
- for (index = 0; index < fileOrBlobDataList.length; index++){
- if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
- this._upload(fileOrBlobDataList[index]);
- } else {
- if (this._options.validation.stopOnFirstInvalidFile){
- return;
- }
- }
- }
- }
- else {
- this._error('noFilesError', "");
- }
- }
- },
- _upload: function(blobOrFileContainer){
- var id = this._handler.add(blobOrFileContainer);
- var name = this._handler.getName(id);
- if (this._options.callbacks.onSubmit(id, name) !== false){
- this._onSubmit(id, name);
- if (this._options.autoUpload) {
- this._handler.upload(id);
- }
- else {
- this._storeForLater(id);
- }
- }
- },
- _storeForLater: function(id) {
- this._storedIds.push(id);
- },
- _validateFileOrBlobData: function(fileOrBlobData){
- var validationDescriptor, name, size;
- validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
- name = validationDescriptor.name;
- size = validationDescriptor.size;
- if (this._options.callbacks.onValidate(validationDescriptor) === false) {
- return false;
- }
- if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
- this._error('typeError', name);
- return false;
- }
- else if (size === 0){
- this._error('emptyError', name);
- return false;
- }
- else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
- this._error('sizeError', name);
- return false;
- }
- else if (size && size < this._options.validation.minSizeLimit){
- this._error('minSizeError', name);
- return false;
- }
- return true;
- },
- _error: function(code, name){
- var message = this._options.messages[code];
- function r(name, replacement){ message = message.replace(name, replacement); }
- var extensions = this._options.validation.allowedExtensions.join(', ').toLowerCase();
- r('{file}', this._options.formatFileName(name));
- r('{extensions}', extensions);
- r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
- r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
- this._options.callbacks.onError(null, name, message);
- return message;
- },
- _isAllowedExtension: function(fileName){
- var allowed = this._options.validation.allowedExtensions,
- valid = false;
- if (!allowed.length) {
- return true;
- }
- qq.each(allowed, function(idx, allowedExt) {
- /*jshint eqeqeq: true, eqnull: true*/
- var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
- if (fileName.match(extRegex) != null) {
- valid = true;
- return false;
- }
- });
- return valid;
- },
- _formatSize: function(bytes){
- var i = -1;
- do {
- bytes = bytes / 1024;
- i++;
- } while (bytes > 99);
- return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
- },
- _wrapCallbacks: function() {
- var self, safeCallback;
- self = this;
- safeCallback = function(name, callback, args) {
- try {
- return callback.apply(self, args);
- }
- catch (exception) {
- self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
- }
- }
- for (var prop in this._options.callbacks) {
- (function() {
- var callbackName, callbackFunc;
- callbackName = prop;
- callbackFunc = self._options.callbacks[callbackName];
- self._options.callbacks[callbackName] = function() {
- return safeCallback(callbackName, callbackFunc, arguments);
- }
- }());
- }
- },
- _parseFileOrBlobDataName: function(fileOrBlobData) {
- var name;
- if (qq.isFileOrInput(fileOrBlobData)) {
- if (fileOrBlobData.value) {
- // it is a file input
- // get input value and remove path to normalize
- name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
- } else {
- // fix missing properties in Safari 4 and firefox 11.0a2
- name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
- }
- }
- else {
- name = fileOrBlobData.name;
- }
- return name;
- },
- _parseFileOrBlobDataSize: function(fileOrBlobData) {
- var size;
- if (qq.isFileOrInput(fileOrBlobData)) {
- if (!fileOrBlobData.value){
- // fix missing properties in Safari 4 and firefox 11.0a2
- size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
- }
- }
- else {
- size = fileOrBlobData.blob.size;
- }
- return size;
- },
- _getValidationDescriptor: function(fileOrBlobData) {
- var name, size, fileDescriptor;
- fileDescriptor = {};
- name = this._parseFileOrBlobDataName(fileOrBlobData);
- size = this._parseFileOrBlobDataSize(fileOrBlobData);
- fileDescriptor.name = name;
- if (size) {
- fileDescriptor.size = size;
- }
- return fileDescriptor;
- },
- _getValidationDescriptors: function(files) {
- var self = this,
- fileDescriptors = [];
- qq.each(files, function(idx, file) {
- fileDescriptors.push(self._getValidationDescriptor(file));
- });
- return fileDescriptors;
- },
- _createParamsStore: function(type) {
- var paramsStore = {},
- self = this;
- return {
- setParams: function(params, id) {
- var paramsCopy = {};
- qq.extend(paramsCopy, params);
- paramsStore[id] = paramsCopy;
- },
- getParams: function(id) {
- /*jshint eqeqeq: true, eqnull: true*/
- var paramsCopy = {};
- if (id != null && paramsStore[id]) {
- qq.extend(paramsCopy, paramsStore[id]);
- }
- else {
- qq.extend(paramsCopy, self._options[type].params);
- }
- return paramsCopy;
- },
- remove: function(fileId) {
- return delete paramsStore[fileId];
- },
- reset: function() {
- paramsStore = {};
- }
- };
- },
- _createEndpointStore: function(type) {
- var endpointStore = {},
- self = this;
- return {
- setEndpoint: function(endpoint, id) {
- endpointStore[id] = endpoint;
- },
- getEndpoint: function(id) {
- /*jshint eqeqeq: true, eqnull: true*/
- if (id != null && endpointStore[id]) {
- return endpointStore[id];
- }
- return self._options[type].endpoint;
- },
- remove: function(fileId) {
- return delete endpointStore[fileId];
- },
- reset: function() {
- endpointStore = {};
- }
- };
- }
- };
- /*globals qq, document*/
- qq.DragAndDrop = function(o) {
- "use strict";
- var options, dz, dirPending,
- droppedFiles = [],
- droppedEntriesCount = 0,
- droppedEntriesParsedCount = 0,
- disposeSupport = new qq.DisposeSupport();
- options = {
- dropArea: null,
- extraDropzones: [],
- hideDropzones: true,
- multiple: true,
- classes: {
- dropActive: null
- },
- callbacks: {
- dropProcessing: function(isProcessing, files) {},
- error: function(code, filename) {},
- log: function(message, level) {}
- }
- };
- qq.extend(options, o);
- function maybeUploadDroppedFiles() {
- if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
- options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
- dz.dropDisabled(false);
- options.callbacks.dropProcessing(false, droppedFiles);
- }
- }
- function addDroppedFile(file) {
- droppedFiles.push(file);
- droppedEntriesParsedCount+=1;
- maybeUploadDroppedFiles();
- }
- function traverseFileTree(entry) {
- var dirReader, i;
- droppedEntriesCount+=1;
- if (entry.isFile) {
- entry.file(function(file) {
- addDroppedFile(file);
- });
- }
- else if (entry.isDirectory) {
- dirPending = true;
- dirReader = entry.createReader();
- dirReader.readEntries(function(entries) {
- droppedEntriesParsedCount+=1;
- for (i = 0; i < entries.length; i+=1) {
- traverseFileTree(entries[i]);
- }
- dirPending = false;
- if (!entries.length) {
- maybeUploadDroppedFiles();
- }
- });
- }
- }
- function handleDataTransfer(dataTransfer) {
- var i, items, entry;
- options.callbacks.dropProcessing(true);
- dz.dropDisabled(true);
- if (dataTransfer.files.length > 1 && !options.multiple) {
- options.callbacks.dropProcessing(false);
- options.callbacks.error('tooManyFilesError', "");
- dz.dropDisabled(false);
- }
- else {
- droppedFiles = [];
- droppedEntriesCount = 0;
- droppedEntriesParsedCount = 0;
- if (qq.isFolderDropSupported(dataTransfer)) {
- items = dataTransfer.items;
- for (i = 0; i < items.length; i+=1) {
- entry = items[i].webkitGetAsEntry();
- if (entry) {
- //due to a bug in Chrome's File System API impl - #149735
- if (entry.isFile) {
- droppedFiles.push(items[i].getAsFile());
- if (i === items.length-1) {
- maybeUploadDroppedFiles();
- }
- }
- else {
- traverseFileTree(entry);
- }
- }
- }
- }
- else {
- options.callbacks.dropProcessing(false, dataTransfer.files);
- dz.dropDisabled(false);
- }
- }
- }
- function setupDropzone(dropArea){
- dz = new qq.UploadDropZone({
- element: dropArea,
- onEnter: function(e){
- qq(dropArea).addClass(options.classes.dropActive);
- e.stopPropagation();
- },
- onLeaveNotDescendants: function(e){
- qq(dropArea).removeClass(options.classes.dropActive);
- },
- onDrop: function(e){
- if (options.hideDropzones) {
- qq(dropArea).hide();
- }
- qq(dropArea).removeClass(options.classes.dropActive);
- handleDataTransfer(e.dataTransfer);
- }
- });
- disposeSupport.addDisposer(function() {
- dz.dispose();
- });
- if (options.hideDropzones) {
- qq(dropArea).hide();
- }
- }
- function isFileDrag(dragEvent) {
- var fileDrag;
- qq.each(dragEvent.dataTransfer.types, function(key, val) {
- if (val === 'Files') {
- fileDrag = true;
- return false;
- }
- });
- return fileDrag;
- }
- function setupDragDrop(){
- if (options.dropArea) {
- options.extraDropzones.push(options.dropArea);
- }
- var i, dropzones = options.extraDropzones;
- for (i=0; i < dropzones.length; i+=1){
- setupDropzone(dropzones[i]);
- }
- // IE <= 9 does not support the File API used for drag+drop uploads
- if (options.dropArea && (!qq.ie() || qq.ie10())) {
- disposeSupport.attach(document, 'dragenter', function(e) {
- if (!dz.dropDisabled() && isFileDrag(e)) {
- if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
- return;
- }
- options.dropArea.style.display = 'block';
- for (i=0; i < dropzones.length; i+=1) {
- dropzones[i].style.display = 'block';
- }
- }
- });
- }
- disposeSupport.attach(document, 'dragleave', function(e){
- if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
- for (i=0; i < dropzones.length; i+=1) {
- qq(dropzones[i]).hide();
- }
- }
- });
- disposeSupport.attach(document, 'drop', function(e){
- if (options.hideDropzones) {
- for (i=0; i < dropzones.length; i+=1) {
- qq(dropzones[i]).hide();
- }
- }
- e.preventDefault();
- });
- }
- return {
- setup: function() {
- setupDragDrop();
- },
- setupExtraDropzone: function(element) {
- options.extraDropzones.push(element);
- setupDropzone(element);
- },
- removeExtraDropzone: function(element) {
- var i, dzs = options.extraDropzones;
- for(i in dzs) {
- if (dzs[i] === element) {
- return dzs.splice(i, 1);
- }
- }
- },
- dispose: function() {
- disposeSupport.dispose();
- dz.dispose();
- }
- };
- };
- qq.UploadDropZone = function(o){
- "use strict";
- var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
- options = {
- element: null,
- onEnter: function(e){},
- onLeave: function(e){},
- // is not fired when leaving element by hovering descendants
- onLeaveNotDescendants: function(e){},
- onDrop: function(e){}
- };
- qq.extend(options, o);
- element = options.element;
- function dragover_should_be_canceled(){
- return qq.safari() || (qq.firefox() && qq.windows());
- }
- function disableDropOutside(e){
- // run only once for all instances
- if (!dropOutsideDisabled ){
- // for these cases we need to catch onDrop to reset dropArea
- if (dragover_should_be_canceled){
- disposeSupport.attach(document, 'dragover', function(e){
- e.preventDefault();
- });
- } else {
- disposeSupport.attach(document, 'dragover', function(e){
- if (e.dataTransfer){
- e.dataTransfer.dropEffect = 'none';
- e.preventDefault();
- }
- });
- }
- dropOutsideDisabled = true;
- }
- }
- function isValidFileDrag(e){
- // e.dataTransfer currently causing IE errors
- // IE9 does NOT support file API, so drag-and-drop is not possible
- if (qq.ie() && !qq.ie10()) {
- return false;
- }
- var effectTest, dt = e.dataTransfer,
- // do not check dt.types.contains in webkit, because it crashes safari 4
- isSafari = qq.safari();
- // dt.effectAllowed is none in Safari 5
- // dt.types.contains check is for firefox
- effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
- return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
- }
- function isOrSetDropDisabled(isDisabled) {
- if (isDisabled !== undefined) {
- preventDrop = isDisabled;
- }
- return preventDrop;
- }
- function attachEvents(){
- disposeSupport.attach(element, 'dragover', function(e){
- if (!isValidFileDrag(e)) {
- return;
- }
- var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
- if (effect === 'move' || effect === 'linkMove'){
- e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
- } else {
- e.dataTransfer.dropEffect = 'copy'; // for Chrome
- }
- e.stopPropagation();
- e.preventDefault();
- });
- disposeSupport.attach(element, 'dragenter', function(e){
- if (!isOrSetDropDisabled()) {
- if (!isValidFileDrag(e)) {
- return;
- }
- options.onEnter(e);
- }
- });
- disposeSupport.attach(element, 'dragleave', function(e){
- if (!isValidFileDrag(e)) {
- return;
- }
- options.onLeave(e);
- var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
- // do not fire when moving a mouse over a descendant
- if (qq(this).contains(relatedTarget)) {
- return;
- }
- options.onLeaveNotDescendants(e);
- });
- disposeSupport.attach(element, 'drop', function(e){
- if (!isOrSetDropDisabled()) {
- if (!isValidFileDrag(e)) {
- return;
- }
- e.preventDefault();
- options.onDrop(e);
- }
- });
- }
- disableDropOutside();
- attachEvents();
- return {
- dropDisabled: function(isDisabled) {
- return isOrSetDropDisabled(isDisabled);
- },
- dispose: function() {
- disposeSupport.dispose();
- }
- };
- };
- /**
- * Class that creates upload widget with drag-and-drop and file list
- * @inherits qq.FineUploaderBasic
- */
- qq.FineUploader = function(o){
- // call parent constructor
- qq.FineUploaderBasic.apply(this, arguments);
- // additional options
- qq.extend(this._options, {
- element: null,
- listElement: null,
- dragAndDrop: {
- extraDropzones: [],
- hideDropzones: true,
- disableDefaultDropzone: false
- },
- text: {
- uploadButton: 'Upload a file',
- cancelButton: 'Cancel',
- retryButton: 'Retry',
- deleteButton: 'Delete',
- failUpload: 'Upload failed',
- dragZone: 'Drop files here to upload',
- dropProcessing: 'Processing dropped files...',
- formatProgress: "{percent}% of {total_size}",
- waitingForResponse: "Processing..."
- },
- template: '<div class="qq-uploader">' +
- ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '<div class="qq-upload-drop-area"><span>{dragZoneText}</span></div>' : '') +
- (!this._options.button ? '<div class="qq-upload-button"><div>{uploadButtonText}</div></div>' : '') +
- '<span class="qq-drop-processing"><span>{dropProcessingText}</span><span class="qq-drop-processing-spinner"></span></span>' +
- (!this._options.listElement ? '<ul class="qq-upload-list"></ul>' : '') +
- '</div>',
- // template for one item in file list
- fileTemplate: '<li>' +
- '<div class="qq-progress-bar"></div>' +
- '<span class="qq-upload-spinner"></span>' +
- '<span class="qq-upload-finished"></span>' +
- '<span class="qq-upload-file"></span>' +
- '<span class="qq-upload-size"></span>' +
- '<a class="qq-upload-cancel" href="#">{cancelButtonText}</a>' +
- '<a class="qq-upload-retry" href="#">{retryButtonText}</a>' +
- '<a class="qq-upload-delete" href="#">{deleteButtonText}</a>' +
- '<span class="qq-upload-status-text">{statusText}</span>' +
- '</li>',
- classes: {
- button: 'qq-upload-button',
- drop: 'qq-upload-drop-area',
- dropActive: 'qq-upload-drop-area-active',
- dropDisabled: 'qq-upload-drop-area-disabled',
- list: 'qq-upload-list',
- progressBar: 'qq-progress-bar',
- file: 'qq-upload-file',
- spinner: 'qq-upload-spinner',
- finished: 'qq-upload-finished',
- retrying: 'qq-upload-retrying',
- retryable: 'qq-upload-retryable',
- size: 'qq-upload-size',
- cancel: 'qq-upload-cancel',
- deleteButton: 'qq-upload-delete',
- retry: 'qq-upload-retry',
- statusText: 'qq-upload-status-text',
- success: 'qq-upload-success',
- fail: 'qq-upload-fail',
- successIcon: null,
- failIcon: null,
- dropProcessing: 'qq-drop-processing',
- dropProcessingSpinner: 'qq-drop-processing-spinner'
- },
- failedUploadTextDisplay: {
- mode: 'default', //default, custom, or none
- maxChars: 50,
- responseProperty: 'error',
- enableTooltip: true
- },
- messages: {
- tooManyFilesError: "You may only drop one file"
- },
- retry: {
- showAutoRetryNote: true,
- autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
- showButton: false
- },
- deleteFile: {
- forceConfirm: false,
- confirmMessage: "Are you sure you want to delete {filename}?",
- deletingStatusText: "Deleting...",
- deletingFailedText: "Delete failed"
- },
- display: {
- fileSizeOnSubmit: false
- },
- showMessage: function(message){
- setTimeout(function() {
- alert(message);
- }, 0);
- },
- showConfirm: function(message, okCallback, cancelCallback) {
- setTimeout(function() {
- var result = confirm(message);
- if (result) {
- okCallback();
- }
- else if (cancelCallback) {
- cancelCallback();
- }
- }, 0);
- }
- }, true);
- // overwrite options with user supplied
- qq.extend(this._options, o, true);
- this._wrapCallbacks();
- // overwrite the upload button text if any
- // same for the Cancel button and Fail message text
- this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
- this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
- this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
- this._element = this._options.element;
- this._element.innerHTML = this._options.template;
- this._listElement = this._options.listElement || this._find(this._element, 'list');
- this._classes = this._options.classes;
- if (!this._button) {
- this._button = this._createUploadButton(this._find(this._element, 'button'));
- }
- this._bindCancelAndRetryEvents();
- this._dnd = this._setupDragAndDrop();
- };
- // inherit from Basic Uploader
- qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
- qq.extend(qq.FineUploader.prototype, {
- clearStoredFiles: function() {
- qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
- this._listElement.innerHTML = "";
- },
- addExtraDropzone: function(element){
- this._dnd.setupExtraDropzone(element);
- },
- removeExtraDropzone: function(element){
- return this._dnd.removeExtraDropzone(element);
- },
- getItemByFileId: function(id){
- var item = this._listElement.firstChild;
- // there can't be txt nodes in dynamically created list
- // and we can use nextSibling
- while (item){
- if (item.qqFileId == id) return item;
- item = item.nextSibling;
- }
- },
- reset: function() {
- qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
- this._element.innerHTML = this._options.template;
- this._listElement = this._options.listElement || this._find(this._element, 'list');
- if (!this._options.button) {
- this._button = this._createUploadButton(this._find(this._element, 'button'));
- }
- this._bindCancelAndRetryEvents();
- this._dnd.dispose();
- this._dnd = this._setupDragAndDrop();
- },
- _removeFileItem: function(fileId) {
- var item = this.getItemByFileId(fileId);
- qq(item).remove();
- },
- _setupDragAndDrop: function() {
- var self = this,
- dropProcessingEl = this._find(this._element, 'dropProcessing'),
- dnd, preventSelectFiles, defaultDropAreaEl;
- preventSelectFiles = function(event) {
- event.preventDefault();
- };
- if (!this._options.dragAndDrop.disableDefaultDropzone) {
- defaultDropAreaEl = this._find(this._options.element, 'drop');
- }
- dnd = new qq.DragAndDrop({
- dropArea: defaultDropAreaEl,
- extraDropzones: this._options.dragAndDrop.extraDropzones,
- hideDropzones: this._options.dragAndDrop.hideDropzones,
- multiple: this._options.multiple,
- classes: {
- dropActive: this._options.classes.dropActive
- },
- callbacks: {
- dropProcessing: function(isProcessing, files) {
- var input = self._button.getInput();
- if (isProcessing) {
- qq(dropProcessingEl).css({display: 'block'});
- qq(input).attach('click', preventSelectFiles);
- }
- else {
- qq(dropProcessingEl).hide();
- qq(input).detach('click', preventSelectFiles);
- }
- if (files) {
- self.addFiles(files);
- }
- },
- error: function(code, filename) {
- self._error(code, filename);
- },
- log: function(message, level) {
- self.log(message, level);
- }
- }
- });
- dnd.setup();
- return dnd;
- },
- _leaving_document_out: function(e){
- return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
- || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
- },
- _storeForLater: function(id) {
- qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
- var item = this.getItemByFileId(id);
- qq(this._find(item, 'spinner')).hide();
- },
- /**
- * Gets one of the elements listed in this._options.classes
- **/
- _find: function(parent, type){
- var element = qq(parent).getByClass(this._options.classes[type])[0];
- if (!element){
- throw new Error('element not found ' + type);
- }
- return element;
- },
- _onSubmit: function(id, name){
- qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
- this._addToList(id, name);
- },
- // Update the progress bar & percentage as the file is uploaded
- _onProgress: function(id, name, loaded, total){
- qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
- var item, progressBar, percent, cancelLink;
- item = this.getItemByFileId(id);
- progressBar = this._find(item, 'progressBar');
- percent = Math.round(loaded / total * 100);
- if (loaded === total) {
- cancelLink = this._find(item, 'cancel');
- qq(cancelLink).hide();
- qq(progressBar).hide();
- qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
- // If last byte was sent, display total file size
- this._displayFileSize(id);
- }
- else {
- // If still uploading, display percentage - total size is actually the total request(s) size
- this._displayFileSize(id, loaded, total);
- qq(progressBar).css({display: 'block'});
- }
- // Update progress bar element
- qq(progressBar).css({width: percent + '%'});
- },
- _onComplete: function(id, name, result, xhr){
- qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
- var item = this.getItemByFileId(id);
- qq(this._find(item, 'statusText')).clearText();
- qq(item).removeClass(this._classes.retrying);
- qq(this._find(item, 'progressBar')).hide();
- if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
- qq(this._find(item, 'cancel')).hide();
- }
- qq(this._find(item, 'spinner')).hide();
- if (result.success) {
- if (this._isDeletePossible()) {
- this._showDeleteLink(id);
- }
- qq(item).addClass(this._classes.success);
- if (this._classes.successIcon) {
- this._find(item, 'finished').style.display = "inline-block";
- qq(item).addClass(this._classes.successIcon);
- }
- } else {
- qq(item).addClass(this._classes.fail);
- if (this._classes.failIcon) {
- this._find(item, 'finished').style.display = "inline-block";
- qq(item).addClass(this._classes.failIcon);
- }
- if (this._options.retry.showButton && !this._preventRetries[id]) {
- qq(item).addClass(this._classes.retryable);
- }
- this._controlFailureTextDisplay(item, result);
- }
- },
- _onUpload: function(id, name){
- qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
- this._showSpinner(id);
- },
- _onCancel: function(id, name) {
- qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
- this._removeFileItem(id);
- },
- _onBeforeAutoRetry: function(id) {
- var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
- qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
- item = this.getItemByFileId(id);
- progressBar = this._find(item, 'progressBar');
- this._showCancelLink(item);
- progressBar.style.width = 0;
- qq(progressBar).hide();
- if (this._options.retry.showAutoRetryNote) {
- failTextEl = this._find(item, 'statusText');
- retryNumForDisplay = this._autoRetries[id] + 1;
- maxAuto = this._options.retry.maxAutoAttempts;
- retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
- retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
- qq(failTextEl).setText(retryNote);
- if (retryNumForDisplay === 1) {
- qq(item).addClass(this._classes.retrying);
- }
- }
- },
- //return false if we should not attempt the requested retry
- _onBeforeManualRetry: function(id) {
- if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
- var item = this.getItemByFileId(id);
- this._find(item, 'progressBar').style.width = 0;
- qq(item).removeClass(this._classes.fail);
- qq(this._find(item, 'statusText')).clearText();
- this._showSpinner(id);
- this._showCancelLink(item);
- return true;
- }
- return false;
- },
- _onSubmitDelete: function(id) {
- if (this._isDeletePossible()) {
- if (this._options.callbacks.onSubmitDelete(id) !== false) {
- if (this._options.deleteFile.forceConfirm) {
- this._showDeleteConfirm(id);
- }
- else {
- this._sendDeleteRequest(id);
- }
- }
- }
- else {
- this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
- return false;
- }
- },
- _onDeleteComplete: function(id, xhr, isError) {
- qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
- var item = this.getItemByFileId(id),
- spinnerEl = this._find(item, 'spinner'),
- statusTextEl = this._find(item, 'statusText');
- qq(spinnerEl).hide();
- if (isError) {
- qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
- this._showDeleteLink(id);
- }
- else {
- this._removeFileItem(id);
- }
- },
- _sendDeleteRequest: function(id) {
- var item = this.getItemByFileId(id),
- deleteLink = this._find(item, 'deleteButton'),
- statusTextEl = this._find(item, 'statusText');
- qq(deleteLink).hide();
- this._showSpinner(id);
- qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
- this._deleteHandler.sendDelete(id, this.getUuid(id));
- },
- _showDeleteConfirm: function(id) {
- var fileName = this._handler.getName(id),
- confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
- uuid = this.getUuid(id),
- self = this;
- this._options.showConfirm(confirmMessage, function() {
- self._sendDeleteRequest(id);
- });
- },
- _addToList: function(id, name){
- var item = qq.toElement(this._options.fileTemplate);
- if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
- var cancelLink = this._find(item, 'cancel');
- qq(cancelLink).remove();
- }
- item.qqFileId = id;
- var fileElement = this._find(item, 'file');
- qq(fileElement).setText(this._options.formatFileName(name));
- qq(this._find(item, 'size')).hide();
- if (!this._options.multiple) {
- this._handler.cancelAll();
- this._clearList();
- }
- this._listElement.appendChild(item);
- if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
- this._displayFileSize(id);
- }
- },
- _clearList: function(){
- this._listElement.innerHTML = '';
- this.clearStoredFiles();
- },
- _displayFileSize: function(id, loadedSize, totalSize) {
- var item = this.getItemByFileId(id),
- size = this.getSize(id),
- sizeForDisplay = this._formatSize(size),
- sizeEl = this._find(item, 'size');
- if (loadedSize !== undefined && totalSize !== undefined) {
- sizeForDisplay = this._formatProgress(loadedSize, totalSize);
- }
- qq(sizeEl).css({display: 'inline'});
- qq(sizeEl).setText(sizeForDisplay);
- },
- /**
- * delegate click event for cancel & retry links
- **/
- _bindCancelAndRetryEvents: function(){
- var self = this,
- list = this._listElement;
- this._disposeSupport.attach(list, 'click', function(e){
- e = e || window.event;
- var target = e.target || e.srcElement;
- if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
- qq.preventDefault(e);
- var item = target.parentNode;
- while(item.qqFileId === undefined) {
- item = target = target.parentNode;
- }
- if (qq(target).hasClass(self._classes.deleteButton)) {
- self.deleteFile(item.qqFileId);
- }
- else if (qq(target).hasClass(self._classes.cancel)) {
- self.cancel(item.qqFileId);
- }
- else {
- qq(item).removeClass(self._classes.retryable);
- self.retry(item.qqFileId);
- }
- }
- });
- },
- _formatProgress: function (uploadedSize, totalSize) {
- var message = this._options.text.formatProgress;
- function r(name, replacement) { message = message.replace(name, replacement); }
- r('{percent}', Math.round(uploadedSize / totalSize * 100));
- r('{total_size}', this._formatSize(totalSize));
- return message;
- },
- _controlFailureTextDisplay: function(item, response) {
- var mode, maxChars, responseProperty, failureReason, shortFailureReason;
- mode = this._options.failedUploadTextDisplay.mode;
- maxChars = this._options.failedUploadTextDisplay.maxChars;
- responseProperty = this._options.failedUploadTextDisplay.responseProperty;
- if (mode === 'custom') {
- failureReason = response[responseProperty];
- if (failureReason) {
- if (failureReason.length > maxChars) {
- shortFailureReason = failureReason.substring(0, maxChars) + '...';
- }
- }
- else {
- failureReason = this._options.text.failUpload;
- this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
- }
- qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
- if (this._options.failedUploadTextDisplay.enableTooltip) {
- this._showTooltip(item, failureReason);
- }
- }
- else if (mode === 'default') {
- qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
- }
- else if (mode !== 'none') {
- this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
- }
- },
- _showTooltip: function(item, text) {
- item.title = text;
- },
- _showSpinner: function(id) {
- var item = this.getItemByFileId(id),
- spinnerEl = this._find(item, 'spinner');
- spinnerEl.style.display = "inline-block";
- },
- _showCancelLink: function(item) {
- if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
- var cancelLink = this._find(item, 'cancel');
- qq(cancelLink).css({display: 'inline'});
- }
- },
- _showDeleteLink: function(id) {
- var item = this.getItemByFileId(id),
- deleteLink = this._find(item, 'deleteButton');
- qq(deleteLink).css({display: 'inline'});
- },
- _error: function(code, name){
- var message = qq.FineUploaderBasic.prototype._error.apply(this, arguments);
- this._options.showMessage(message);
- }
- });
- /** Generic class for sending non-upload ajax requests and handling the associated responses **/
- //TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
- /*globals qq, XMLHttpRequest*/
- qq.AjaxRequestor = function(o) {
- "use strict";
- var log, shouldParamsBeInQueryString,
- queue = [],
- requestState = [],
- options = {
- method: 'POST',
- maxConnections: 3,
- customHeaders: {},
- endpointStore: {},
- paramsStore: {},
- successfulResponseCodes: [200],
- demoMode: false,
- cors: {
- expected: false,
- sendCredentials: false
- },
- log: function(str, level) {},
- onSend: function(id) {},
- onComplete: function(id, xhr, isError) {},
- onCancel: function(id) {}
- };
- qq.extend(options, o);
- log = options.log;
- shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
- /**
- * Removes element from queue, sends next request
- */
- function dequeue(id) {
- var i = qq.indexOf(queue, id),
- max = options.maxConnections,
- nextId;
- delete requestState[id];
- queue.splice(i, 1);
- if (queue.length >= max && i < max){
- nextId = queue[max-1];
- sendRequest(nextId);
- }
- }
- function onComplete(id) {
- var xhr = requestState[id].xhr,
- method = getMethod(),
- isError = false;
- dequeue(id);
- if (!isResponseSuccessful(xhr.status)) {
- isError = true;
- log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
- }
- options.onComplete(id, xhr, isError);
- }
- function sendRequest(id) {
- var xhr = new XMLHttpRequest(),
- method = getMethod(),
- params = {},
- url;
- options.onSend(id);
- if (options.paramsStore.getParams) {
- params = options.paramsStore.getParams(id);
- }
- url = createUrl(id, params);
- requestState[id].xhr = xhr;
- xhr.onreadystatechange = getReadyStateChangeHandler(id);
- xhr.open(method, url, true);
- if (options.cors.expected && options.cors.sendCredentials) {
- xhr.withCredentials = true;
- }
- setHeaders(id);
- log('Sending ' + method + " request for " + id);
- if (!shouldParamsBeInQueryString && params) {
- xhr.send(qq.obj2url(params, ""));
- }
- else {
- xhr.send();
- }
- }
- function createUrl(id, params) {
- var endpoint = options.endpointStore.getEndpoint(id),
- addToPath = requestState[id].addToPath;
- if (addToPath !== undefined) {
- endpoint += "/" + addToPath;
- }
- if (shouldParamsBeInQueryString && params) {
- return qq.obj2url(params, endpoint);
- }
- else {
- return endpoint;
- }
- }
- function getReadyStateChangeHandler(id) {
- var xhr = requestState[id].xhr;
- return function() {
- if (xhr.readyState === 4) {
- onComplete(id, xhr);
- }
- };
- }
- function setHeaders(id) {
- var xhr = requestState[id].xhr,
- customHeaders = options.customHeaders;
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
- xhr.setRequestHeader("Cache-Control", "no-cache");
- qq.each(customHeaders, function(name, val) {
- xhr.setRequestHeader(name, val);
- });
- }
- function cancelRequest(id) {
- var xhr = requestState[id].xhr,
- method = getMethod();
- if (xhr) {
- xhr.onreadystatechange = null;
- xhr.abort();
- dequeue(id);
- log('Cancelled ' + method + " for " + id);
- options.onCancel(id);
- return true;
- }
- return false;
- }
- function isResponseSuccessful(responseCode) {
- return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
- }
- function getMethod() {
- if (options.demoMode) {
- return "GET";
- }
- return options.method;
- }
- return {
- send: function(id, addToPath) {
- requestState[id] = {
- addToPath: addToPath
- };
- var len = queue.push(id);
- // if too many active connections, wait...
- if (len <= options.maxConnections){
- sendRequest(id);
- }
- },
- cancel: function(id) {
- return cancelRequest(id);
- }
- };
- };
- /** Generic class for sending non-upload ajax requests and handling the associated responses **/
- /*globals qq, XMLHttpRequest*/
- qq.DeleteFileAjaxRequestor = function(o) {
- "use strict";
- var requestor,
- options = {
- endpointStore: {},
- maxConnections: 3,
- customHeaders: {},
- paramsStore: {},
- demoMode: false,
- cors: {
- expected: false,
- sendCredentials: false
- },
- log: function(str, level) {},
- onDelete: function(id) {},
- onDeleteComplete: function(id, xhr, isError) {}
- };
- qq.extend(options, o);
- requestor = new qq.AjaxRequestor({
- method: 'DELETE',
- endpointStore: options.endpointStore,
- paramsStore: options.paramsStore,
- maxConnections: options.maxConnections,
- customHeaders: options.customHeaders,
- successfulResponseCodes: [200, 202, 204],
- demoMode: options.demoMode,
- log: options.log,
- onSend: options.onDelete,
- onComplete: options.onDeleteComplete
- });
- return {
- sendDelete: function(id, uuid) {
- requestor.send(id, uuid);
- options.log("Submitted delete file request for " + id);
- }
- };
- };
- qq.WindowReceiveMessage = function(o) {
- var options = {
- log: function(message, level) {}
- },
- callbackWrapperDetachers = {};
- qq.extend(options, o);
- return {
- receiveMessage : function(id, callback) {
- var onMessageCallbackWrapper = function(event) {
- callback(event.data);
- };
- if (window.postMessage) {
- callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
- }
- else {
- log("iframe message passing not supported in this browser!", "error");
- }
- },
- stopReceivingMessages : function(id) {
- if (window.postMessage) {
- var detacher = callbackWrapperDetachers[id];
- if (detacher) {
- detacher();
- }
- }
- }
- };
- };
- /**
- * Class for uploading files, uploading itself is handled by child classes
- */
- /*globals qq*/
- qq.UploadHandler = function(o) {
- "use strict";
- var queue = [],
- options, log, dequeue, handlerImpl;
- // Default options, can be overridden by the user
- options = {
- debug: false,
- forceMultipart: true,
- paramsInBody: false,
- paramsStore: {},
- endpointStore: {},
- cors: {
- expected: false,
- sendCredentials: false
- },
- maxConnections: 3, // maximum number of concurrent uploads
- uuidParamName: 'qquuid',
- totalFileSizeParamName: 'qqtotalfilesize',
- chunking: {
- enabled: false,
- partSize: 2000000, //bytes
- paramNames: {
- partIndex: 'qqpartindex',
- partByteOffset: 'qqpartbyteoffset',
- chunkSize: 'qqchunksize',
- totalParts: 'qqtotalparts',
- filename: 'qqfilename'
- }
- },
- resume: {
- enabled: false,
- id: null,
- cookiesExpireIn: 7, //days
- paramNames: {
- resuming: "qqresume"
- }
- },
- blobs: {
- paramNames: {
- name: 'qqblobname'
- }
- },
- log: function(str, level) {},
- onProgress: function(id, fileName, loaded, total){},
- onComplete: function(id, fileName, response, xhr){},
- onCancel: function(id, fileName){},
- onUpload: function(id, fileName){},
- onUploadChunk: function(id, fileName, chunkData){},
- onAutoRetry: function(id, fileName, response, xhr){},
- onResume: function(id, fileName, chunkData){}
- };
- qq.extend(options, o);
- log = options.log;
- /**
- * Removes element from queue, starts upload of next
- */
- dequeue = function(id) {
- var i = qq.indexOf(queue, id),
- max = options.maxConnections,
- nextId;
- if (i >= 0) {
- queue.splice(i, 1);
- if (queue.length >= max && i < max){
- nextId = queue[max-1];
- handlerImpl.upload(nextId);
- }
- }
- };
- if (qq.isXhrUploadSupported()) {
- handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
- }
- else {
- handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
- }
- return {
- /**
- * Adds file or file input to the queue
- * @returns id
- **/
- add: function(file){
- return handlerImpl.add(file);
- },
- /**
- * Sends the file identified by id
- */
- upload: function(id){
- var len = queue.push(id);
- // if too many active uploads, wait...
- if (len <= options.maxConnections){
- return handlerImpl.upload(id);
- }
- },
- retry: function(id) {
- var i = qq.indexOf(queue, id);
- if (i >= 0) {
- return handlerImpl.upload(id, true);
- }
- else {
- return this.upload(id);
- }
- },
- /**
- * Cancels file upload by id
- */
- cancel: function(id) {
- log('Cancelling ' + id);
- options.paramsStore.remove(id);
- handlerImpl.cancel(id);
- dequeue(id);
- },
- /**
- * Cancels all queued or in-progress uploads
- */
- cancelAll: function() {
- var self = this,
- queueCopy = [];
- qq.extend(queueCopy, queue);
- qq.each(queueCopy, function(idx, fileId) {
- self.cancel(fileId);
- });
- queue = [];
- },
- /**
- * Returns name of the file identified by id
- */
- getName: function(id){
- return handlerImpl.getName(id);
- },
- /**
- * Returns size of the file identified by id
- */
- getSize: function(id){
- if (handlerImpl.getSize) {
- return handlerImpl.getSize(id);
- }
- },
- getFile: function(id) {
- if (handlerImpl.getFile) {
- return handlerImpl.getFile(id);
- }
- },
- /**
- * Returns id of files being uploaded or
- * waiting for their turn
- */
- getQueue: function(){
- return queue;
- },
- reset: function() {
- log('Resetting upload handler');
- queue = [];
- handlerImpl.reset();
- },
- getUuid: function(id) {
- return handlerImpl.getUuid(id);
- },
- /**
- * Determine if the file exists.
- */
- isValid: function(id) {
- return handlerImpl.isValid(id);
- },
- getResumableFilesData: function() {
- if (handlerImpl.getResumableFilesData) {
- return handlerImpl.getResumableFilesData();
- }
- return [];
- }
- };
- };
- /*globals qq, document, setTimeout*/
- /*globals clearTimeout*/
- qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
- "use strict";
- var options = o,
- inputs = [],
- uuids = [],
- detachLoadEvents = {},
- postMessageCallbackTimers = {},
- uploadComplete = uploadCompleteCallback,
- log = logCallback,
- corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
- onloadCallbacks = {},
- api;
- function detachLoadEvent(id) {
- if (detachLoadEvents[id] !== undefined) {
- detachLoadEvents[id]();
- delete detachLoadEvents[id];
- }
- }
- function registerPostMessageCallback(iframe, callback) {
- var id = iframe.id;
- onloadCallbacks[uuids[id]] = callback;
- detachLoadEvents[id] = qq(iframe).attach('load', function() {
- if (inputs[id]) {
- log("Received iframe load event for CORS upload request (file id " + id + ")");
- postMessageCallbackTimers[id] = setTimeout(function() {
- var errorMessage = "No valid message received from loaded iframe for file id " + id;
- log(errorMessage, "error");
- callback({
- error: errorMessage
- });
- }, 1000);
- }
- });
- corsMessageReceiver.receiveMessage(id, function(message) {
- log("Received the following window message: '" + message + "'");
- var response = qq.parseJson(message),
- uuid = response.uuid,
- onloadCallback;
- if (uuid && onloadCallbacks[uuid]) {
- clearTimeout(postMessageCallbackTimers[id]);
- delete postMessageCallbackTimers[id];
- detachLoadEvent(id);
- onloadCallback = onloadCallbacks[uuid];
- delete onloadCallbacks[uuid];
- corsMessageReceiver.stopReceivingMessages(id);
- onloadCallback(response);
- }
- else if (!uuid) {
- log("'" + message + "' does not contain a UUID - ignoring.");
- }
- });
- }
- function attachLoadEvent(iframe, callback) {
- /*jslint eqeq: true*/
- if (options.cors.expected) {
- registerPostMessageCallback(iframe, callback);
- }
- else {
- detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
- log('Received response for ' + iframe.id);
- // when we remove iframe from dom
- // the request stops, but in IE load
- // event fires
- if (!iframe.parentNode){
- return;
- }
- try {
- // fixing Opera 10.53
- if (iframe.contentDocument &&
- iframe.contentDocument.body &&
- iframe.contentDocument.body.innerHTML == "false"){
- // In Opera event is fired second time
- // when body.innerHTML changed from false
- // to server response approx. after 1 sec
- // when we upload file with iframe
- return;
- }
- }
- catch (error) {
- //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
- log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
- }
- callback();
- });
- }
- }
- /**
- * Returns json object received by iframe from server.
- */
- function getIframeContentJson(iframe) {
- /*jshint evil: true*/
- var response;
- //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
- try {
- // iframe.contentWindow.document - for IE<7
- var doc = iframe.contentDocument || iframe.contentWindow.document,
- innerHTML = doc.body.innerHTML;
- log("converting iframe's innerHTML to JSON");
- log("innerHTML = " + innerHTML);
- //plain text response may be wrapped in <pre> tag
- if (innerHTML && innerHTML.match(/^<pre/i)) {
- innerHTML = doc.body.firstChild.firstChild.nodeValue;
- }
- response = qq.parseJson(innerHTML);
- } catch(error){
- log('Error when attempting to parse form upload response (' + error + ")", 'error');
- response = {success: false};
- }
- return response;
- }
- /**
- * Creates iframe with unique name
- */
- function createIframe(id){
- // We can't use following code as the name attribute
- // won't be properly registered in IE6, and new window
- // on form submit will open
- // var iframe = document.createElement('iframe');
- // iframe.setAttribute('name', id);
- var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
- iframe.setAttribute('id', id);
- iframe.style.display = 'none';
- document.body.appendChild(iframe);
- return iframe;
- }
- /**
- * Creates form, that will be submitted to iframe
- */
- function createForm(id, iframe){
- var params = options.paramsStore.getParams(id),
- protocol = options.demoMode ? "GET" : "POST",
- form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>'),
- endpoint = options.endpointStore.getEndpoint(id),
- url = endpoint;
- params[options.uuidParamName] = uuids[id];
- if (!options.paramsInBody) {
- url = qq.obj2url(params, endpoint);
- }
- else {
- qq.obj2Inputs(params, form);
- }
- form.setAttribute('action', url);
- form.setAttribute('target', iframe.name);
- form.style.display = 'none';
- document.body.appendChild(form);
- return form;
- }
- api = {
- add: function(fileInput) {
- fileInput.setAttribute('name', options.inputName);
- var id = inputs.push(fileInput) - 1;
- uuids[id] = qq.getUniqueId();
- // remove file input from DOM
- if (fileInput.parentNode){
- qq(fileInput).remove();
- }
- return id;
- },
- getName: function(id) {
- /*jslint regexp: true*/
- // get input value and remove path to normalize
- return inputs[id].value.replace(/.*(\/|\\)/, "");
- },
- isValid: function(id) {
- return inputs[id] !== undefined;
- },
- reset: function() {
- qq.UploadHandler.prototype.reset.apply(this, arguments);
- inputs = [];
- uuids = [];
- detachLoadEvents = {};
- },
- getUuid: function(id) {
- return uuids[id];
- },
- cancel: function(id) {
- options.onCancel(id, this.getName(id));
- delete inputs[id];
- delete uuids[id];
- delete detachLoadEvents[id];
- if (options.cors.expected) {
- clearTimeout(postMessageCallbackTimers[id]);
- delete postMessageCallbackTimers[id];
- corsMessageReceiver.stopReceivingMessages(id);
- }
- var iframe = document.getElementById(id);
- if (iframe) {
- // to cancel request set src to something else
- // we use src="javascript:false;" because it doesn't
- // trigger ie6 prompt on https
- iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
- qq(iframe).remove();
- }
- },
- upload: function(id){
- var input = inputs[id],
- fileName = api.getName(id),
- iframe = createIframe(id),
- form;
- if (!input){
- throw new Error('file with passed id was not added, or already uploaded or cancelled');
- }
- options.onUpload(id, this.getName(id));
- form = createForm(id, iframe);
- form.appendChild(input);
- attachLoadEvent(iframe, function(responseFromMessage){
- log('iframe loaded');
- var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
- detachLoadEvent(id);
- //we can't remove an iframe if the iframe doesn't belong to the same domain
- if (!options.cors.expected) {
- qq(iframe).remove();
- }
- if (!response.success) {
- if (options.onAutoRetry(id, fileName, response)) {
- return;
- }
- }
- options.onComplete(id, fileName, response);
- uploadComplete(id);
- });
- log('Sending upload request for ' + id);
- form.submit();
- qq(form).remove();
- return id;
- }
- };
- return api;
- };
- /*globals qq, File, XMLHttpRequest, FormData, Blob*/
- qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
- "use strict";
-
- var options = o,
- uploadComplete = uploadCompleteCallback,
- log = logCallback,
- fileState = [],
- cookieItemDelimiter = "|",
- chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
- resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
- resumeId = getResumeId(),
- multipart = options.forceMultipart || options.paramsInBody,
- api;
- function addChunkingSpecificParams(id, params, chunkData) {
- var size = api.getSize(id),
- name = api.getName(id);
- params[options.chunking.paramNames.partIndex] = chunkData.part;
- params[options.chunking.paramNames.partByteOffset] = chunkData.start;
- params[options.chunking.paramNames.chunkSize] = chunkData.size;
- params[options.chunking.paramNames.totalParts] = chunkData.count;
- params[options.totalFileSizeParamName] = size;
- /**
- * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
- * or an empty string. So, we will need to include the actual file name as a param in this case.
- */
- if (multipart) {
- params[options.chunking.paramNames.filename] = name;
- }
- }
- function addResumeSpecificParams(params) {
- params[options.resume.paramNames.resuming] = true;
- }
- function getChunk(fileOrBlob, startByte, endByte) {
- if (fileOrBlob.slice) {
- return fileOrBlob.slice(startByte, endByte);
- }
- else if (fileOrBlob.mozSlice) {
- return fileOrBlob.mozSlice(startByte, endByte);
- }
- else if (fileOrBlob.webkitSlice) {
- return fileOrBlob.webkitSlice(startByte, endByte);
- }
- }
- function getChunkData(id, chunkIndex) {
- var chunkSize = options.chunking.partSize,
- fileSize = api.getSize(id),
- fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
- startBytes = chunkSize * chunkIndex,
- endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
- totalChunks = getTotalChunks(id);
- return {
- part: chunkIndex,
- start: startBytes,
- end: endBytes,
- count: totalChunks,
- blob: getChunk(fileOrBlob, startBytes, endBytes),
- size: endBytes - startBytes
- };
- }
- function getTotalChunks(id) {
- var fileSize = api.getSize(id),
- chunkSize = options.chunking.partSize;
- return Math.ceil(fileSize / chunkSize);
- }
- function createXhr(id) {
- var xhr = new XMLHttpRequest();
- fileState[id].xhr = xhr;
- return xhr;
- }
- function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
- var formData = new FormData(),
- method = options.demoMode ? "GET" : "POST",
- endpoint = options.endpointStore.getEndpoint(id),
- url = endpoint,
- name = api.getName(id),
- size = api.getSize(id),
- blobData = fileState[id].blobData;
- params[options.uuidParamName] = fileState[id].uuid;
- if (multipart) {
- params[options.totalFileSizeParamName] = size;
- if (blobData) {
- /**
- * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
- * or an empty string. So, we will need to include the actual file name as a param in this case.
- */
- params[options.blobs.paramNames.name] = blobData.name;
- }
- }
- //build query string
- if (!options.paramsInBody) {
- if (!multipart) {
- params[options.inputName] = name;
- }
- url = qq.obj2url(params, endpoint);
- }
- xhr.open(method, url, true);
- if (options.cors.expected && options.cors.sendCredentials) {
- xhr.withCredentials = true;
- }
- if (multipart) {
- if (options.paramsInBody) {
- qq.obj2FormData(params, formData);
- }
- formData.append(options.inputName, fileOrBlob);
- return formData;
- }
- return fileOrBlob;
- }
- function setHeaders(id, xhr) {
- var extraHeaders = options.customHeaders,
- fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
- xhr.setRequestHeader("Cache-Control", "no-cache");
- if (!multipart) {
- xhr.setRequestHeader("Content-Type", "application/octet-stream");
- //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
- xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
- }
- qq.each(extraHeaders, function(name, val) {
- xhr.setRequestHeader(name, val);
- });
- }
- function handleCompletedItem(id, response, xhr) {
- var name = api.getName(id),
- size = api.getSize(id);
- fileState[id].attemptingResume = false;
- options.onProgress(id, name, size, size);
- options.onComplete(id, name, response, xhr);
- delete fileState[id].xhr;
- uploadComplete(id);
- }
- function uploadNextChunk(id) {
- var chunkIdx = fileState[id].remainingChunkIdxs[0],
- chunkData = getChunkData(id, chunkIdx),
- xhr = createXhr(id),
- size = api.getSize(id),
- name = api.getName(id),
- toSend, params;
- if (fileState[id].loaded === undefined) {
- fileState[id].loaded = 0;
- }
- if (resumeEnabled && fileState[id].file) {
- persistChunkData(id, chunkData);
- }
- xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
- xhr.upload.onprogress = function(e) {
- if (e.lengthComputable) {
- var totalLoaded = e.loaded + fileState[id].loaded,
- estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
- options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
- }
- };
- options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
- params = options.paramsStore.getParams(id);
- addChunkingSpecificParams(id, params, chunkData);
- if (fileState[id].attemptingResume) {
- addResumeSpecificParams(params);
- }
- toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
- setHeaders(id, xhr);
- log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
- xhr.send(toSend);
- }
- function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
- var chunkData = getChunkData(id, chunkIdx),
- blobSize = chunkData.size,
- overhead = requestSize - blobSize,
- size = api.getSize(id),
- chunkCount = chunkData.count,
- initialRequestOverhead = fileState[id].initialRequestOverhead,
- overheadDiff = overhead - initialRequestOverhead;
- fileState[id].lastRequestOverhead = overhead;
- if (chunkIdx === 0) {
- fileState[id].lastChunkIdxProgress = 0;
- fileState[id].initialRequestOverhead = overhead;
- fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
- }
- else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
- fileState[id].lastChunkIdxProgress = chunkIdx;
- fileState[id].estTotalRequestsSize += overheadDiff;
- }
- return fileState[id].estTotalRequestsSize;
- }
- function getLastRequestOverhead(id) {
- if (multipart) {
- return fileState[id].lastRequestOverhead;
- }
- else {
- return 0;
- }
- }
- function handleSuccessfullyCompletedChunk(id, response, xhr) {
- var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
- chunkData = getChunkData(id, chunkIdx);
- fileState[id].attemptingResume = false;
- fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
- if (fileState[id].remainingChunkIdxs.length > 0) {
- uploadNextChunk(id);
- }
- else {
- if (resumeEnabled) {
- deletePersistedChunkData(id);
- }
- handleCompletedItem(id, response, xhr);
- }
- }
- function isErrorResponse(xhr, response) {
- return xhr.status !== 200 || !response.success || response.reset;
- }
- function parseResponse(xhr) {
- var response;
- try {
- response = qq.parseJson(xhr.responseText);
- }
- catch(error) {
- log('Error when attempting to parse xhr response text (' + error + ')', 'error');
- response = {};
- }
- return response;
- }
- function handleResetResponse(id) {
- log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
- if (resumeEnabled) {
- deletePersistedChunkData(id);
- fileState[id].attemptingResume = false;
- }
- fileState[id].remainingChunkIdxs = [];
- delete fileState[id].loaded;
- delete fileState[id].estTotalRequestsSize;
- delete fileState[id].initialRequestOverhead;
- }
- function handleResetResponseOnResumeAttempt(id) {
- fileState[id].attemptingResume = false;
- log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
- handleResetResponse(id);
- api.upload(id, true);
- }
- function handleNonResetErrorResponse(id, response, xhr) {
- var name = api.getName(id);
- if (options.onAutoRetry(id, name, response, xhr)) {
- return;
- }
- else {
- handleCompletedItem(id, response, xhr);
- }
- }
- function onComplete(id, xhr) {
- var response;
- // the request was aborted/cancelled
- if (!fileState[id]) {
- return;
- }
- log("xhr - server response received for " + id);
- log("responseText = " + xhr.responseText);
- response = parseResponse(xhr);
- if (isErrorResponse(xhr, response)) {
- if (response.reset) {
- handleResetResponse(id);
- }
- if (fileState[id].attemptingResume && response.reset) {
- handleResetResponseOnResumeAttempt(id);
- }
- else {
- handleNonResetErrorResponse(id, response, xhr);
- }
- }
- else if (chunkFiles) {
- handleSuccessfullyCompletedChunk(id, response, xhr);
- }
- else {
- handleCompletedItem(id, response, xhr);
- }
- }
- function getChunkDataForCallback(chunkData) {
- return {
- partIndex: chunkData.part,
- startByte: chunkData.start + 1,
- endByte: chunkData.end,
- totalParts: chunkData.count
- };
- }
- function getReadyStateChangeHandler(id, xhr) {
- return function() {
- if (xhr.readyState === 4) {
- onComplete(id, xhr);
- }
- };
- }
- function persistChunkData(id, chunkData) {
- var fileUuid = api.getUuid(id),
- lastByteSent = fileState[id].loaded,
- initialRequestOverhead = fileState[id].initialRequestOverhead,
- estTotalRequestsSize = fileState[id].estTotalRequestsSize,
- cookieName = getChunkDataCookieName(id),
- cookieValue = fileUuid +
- cookieItemDelimiter + chunkData.part +
- cookieItemDelimiter + lastByteSent +
- cookieItemDelimiter + initialRequestOverhead +
- cookieItemDelimiter + estTotalRequestsSize,
- cookieExpDays = options.resume.cookiesExpireIn;
- qq.setCookie(cookieName, cookieValue, cookieExpDays);
- }
- function deletePersistedChunkData(id) {
- if (fileState[id].file) {
- var cookieName = getChunkDataCookieName(id);
- qq.deleteCookie(cookieName);
- }
- }
- function getPersistedChunkData(id) {
- var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
- filename = api.getName(id),
- sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
- if (chunkCookieValue) {
- sections = chunkCookieValue.split(cookieItemDelimiter);
- if (sections.length === 5) {
- uuid = sections[0];
- partIndex = parseInt(sections[1], 10);
- lastByteSent = parseInt(sections[2], 10);
- initialRequestOverhead = parseInt(sections[3], 10);
- estTotalRequestsSize = parseInt(sections[4], 10);
- return {
- uuid: uuid,
- part: partIndex,
- lastByteSent: lastByteSent,
- initialRequestOverhead: initialRequestOverhead,
- estTotalRequestsSize: estTotalRequestsSize
- };
- }
- else {
- log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
- }
- }
- }
- function getChunkDataCookieName(id) {
- var filename = api.getName(id),
- fileSize = api.getSize(id),
- maxChunkSize = options.chunking.partSize,
- cookieName;
- cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
- if (resumeId !== undefined) {
- cookieName += cookieItemDelimiter + resumeId;
- }
- return cookieName;
- }
- function getResumeId() {
- if (options.resume.id !== null &&
- options.resume.id !== undefined &&
- !qq.isFunction(options.resume.id) &&
- !qq.isObject(options.resume.id)) {
- return options.resume.id;
- }
- }
- function handleFileChunkingUpload(id, retry) {
- var name = api.getName(id),
- firstChunkIndex = 0,
- persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
- if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
- fileState[id].remainingChunkIdxs = [];
- if (resumeEnabled && !retry && fileState[id].file) {
- persistedChunkInfoForResume = getPersistedChunkData(id);
- if (persistedChunkInfoForResume) {
- firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
- if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
- firstChunkIndex = persistedChunkInfoForResume.part;
- fileState[id].uuid = persistedChunkInfoForResume.uuid;
- fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
- fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
- fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
- fileState[id].attemptingResume = true;
- log('Resuming ' + name + " at partition index " + firstChunkIndex);
- }
- }
- }
- for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
- fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
- }
- }
- uploadNextChunk(id);
- }
- function handleStandardFileUpload(id) {
- var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
- name = api.getName(id),
- xhr, params, toSend;
- fileState[id].loaded = 0;
- xhr = createXhr(id);
- xhr.upload.onprogress = function(e){
- if (e.lengthComputable){
- fileState[id].loaded = e.loaded;
- options.onProgress(id, name, e.loaded, e.total);
- }
- };
- xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
- params = options.paramsStore.getParams(id);
- toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
- setHeaders(id, xhr);
- log('Sending upload request for ' + id);
- xhr.send(toSend);
- }
- api = {
- /**
- * Adds File or Blob to the queue
- * Returns id to use with upload, cancel
- **/
- add: function(fileOrBlobData){
- var id;
- if (fileOrBlobData instanceof File) {
- id = fileState.push({file: fileOrBlobData}) - 1;
- }
- else if (fileOrBlobData.blob instanceof Blob) {
- id = fileState.push({blobData: fileOrBlobData}) - 1;
- }
- else {
- throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
- }
- fileState[id].uuid = qq.getUniqueId();
- return id;
- },
- getName: function(id){
- var file = fileState[id].file,
- blobData = fileState[id].blobData;
- if (file) {
- // fix missing name in Safari 4
- //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
- return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
- }
- else {
- return blobData.name;
- }
- },
- getSize: function(id){
- /*jshint eqnull: true*/
- var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
- if (qq.isFileOrInput(fileOrBlob)) {
- return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
- }
- else {
- return fileOrBlob.size;
- }
- },
- getFile: function(id) {
- if (fileState[id]) {
- return fileState[id].file || fileState[id].blobData.blob;
- }
- },
- /**
- * Returns uploaded bytes for file identified by id
- */
- getLoaded: function(id){
- return fileState[id].loaded || 0;
- },
- isValid: function(id) {
- return fileState[id] !== undefined;
- },
- reset: function() {
- fileState = [];
- },
- getUuid: function(id) {
- return fileState[id].uuid;
- },
- /**
- * Sends the file identified by id to the server
- */
- upload: function(id, retry){
- var name = this.getName(id);
- options.onUpload(id, name);
- if (chunkFiles) {
- handleFileChunkingUpload(id, retry);
- }
- else {
- handleStandardFileUpload(id);
- }
- },
- cancel: function(id){
- var xhr = fileState[id].xhr;
- options.onCancel(id, this.getName(id));
- if (xhr) {
- xhr.onreadystatechange = null;
- xhr.abort();
- }
- if (resumeEnabled) {
- deletePersistedChunkData(id);
- }
- delete fileState[id];
- },
- getResumableFilesData: function() {
- var matchingCookieNames = [],
- resumableFilesData = [];
- if (chunkFiles && resumeEnabled) {
- if (resumeId === undefined) {
- matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
- cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
- }
- else {
- matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
- cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
- cookieItemDelimiter + resumeId + "="));
- }
- qq.each(matchingCookieNames, function(idx, cookieName) {
- var cookiesNameParts = cookieName.split(cookieItemDelimiter);
- var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
- resumableFilesData.push({
- name: decodeURIComponent(cookiesNameParts[1]),
- size: cookiesNameParts[2],
- uuid: cookieValueParts[0],
- partIdx: cookieValueParts[1]
- });
- });
- return resumableFilesData;
- }
- return [];
- }
- };
- return api;
- };
|