vendor: 368,238 bytes, lines 1-7938
1// Fine Uploader 5.16.2 - MIT licensed. http://fineuploader.com 2(function(global) { 3 (function($) { 4 "use strict"; 5 var $el, pluginOptions = [ "uploaderType", "endpointType" ]; 6 function init(options) { 7 var xformedOpts = transformVariables(options || {}), newUploaderInstance = getNewUploaderInstance(xformedOpts); 8 uploader(newUploaderInstance); 9 addCallbacks(xformedOpts, newUploaderInstance); 10 return $el; 11 } 12 function getNewUploaderInstance(params) { 13 var uploaderType = pluginOption("uploaderType"), namespace = pluginOption("endpointType"); 14 if (uploaderType) { 15 uploaderType = uploaderType.charAt(0).toUpperCase() + uploaderType.slice(1).toLowerCase(); 16 if (namespace) { 17 return new qq[namespace]["FineUploader" + uploaderType](params); 18 } 19 return new qq["FineUploader" + uploaderType](params); 20 } else { 21 if (namespace) { 22 return new qq[namespace].FineUploader(params); 23 } 24 return new qq.FineUploader(params); 25 } 26 } 27 function dataStore(key, val) { 28 var data = $el.data("fineuploader"); 29 if (val) { 30 if (data === undefined) { 31 data = {}; 32 } 33 data[key] = val; 34 $el.data("fineuploader", data); 35 } else { 36 if (data === undefined) { 37 return null; 38 } 39 return data[key]; 40 } 41 } 42 function uploader(instanceToStore) { 43 return dataStore("uploader", instanceToStore); 44 } 45 function pluginOption(option, optionVal) { 46 return dataStore(option, optionVal); 47 } 48 function addCallbacks(transformedOpts, newUploaderInstance) { 49 var callbacks = transformedOpts.callbacks = {}; 50 $.each(newUploaderInstance._options.callbacks, function(prop, nonJqueryCallback) { 51 var name, callbackEventTarget; 52 name = /^on(\w+)/.exec(prop)[1]; 53 name = name.substring(0, 1).toLowerCase() + name.substring(1); 54 callbackEventTarget = $el; 55 callbacks[prop] = function() { 56 var originalArgs = Array.prototype.slice.call(arguments), transformedArgs = [], nonJqueryCallbackRetVal, jqueryEventCallbackRetVal; 57 $.each(originalArgs, function(idx, arg) { 58 transformedArgs.push(maybeWrapInJquery(arg)); 59 }); 60 nonJqueryCallbackRetVal = nonJqueryCallback.apply(this, originalArgs); 61 try { 62 jqueryEventCallbackRetVal = callbackEventTarget.triggerHandler(name, transformedArgs); 63 } catch (error) { 64 qq.log("Caught error in Fine Uploader jQuery event handler: " + error.message, "error"); 65 } 66 if (nonJqueryCallbackRetVal != null) { 67 return nonJqueryCallbackRetVal; 68 } 69 return jqueryEventCallbackRetVal; 70 }; 71 }); 72 newUploaderInstance._options.callbacks = callbacks; 73 } 74 function transformVariables(source, dest) { 75 var xformed, arrayVals; 76 if (dest === undefined) { 77 if (source.uploaderType !== "basic") { 78 xformed = { 79 element: $el[0] 80 }; 81 } else { 82 xformed = {}; 83 } 84 } else { 85 xformed = dest; 86 } 87 $.each(source, function(prop, val) { 88 if ($.inArray(prop, pluginOptions) >= 0) { 89 pluginOption(prop, val); 90 } else if (val instanceof $) { 91 xformed[prop] = val[0]; 92 } else if ($.isPlainObject(val)) { 93 xformed[prop] = {}; 94 transformVariables(val, xformed[prop]); 95 } else if ($.isArray(val)) { 96 arrayVals = []; 97 $.each(val, function(idx, arrayVal) { 98 var arrayObjDest = {}; 99 if (arrayVal instanceof $) { 100 $.merge(arrayVals, arrayVal); 101 } else if ($.isPlainObject(arrayVal)) { 102 transformVariables(arrayVal, arrayObjDest); 103 arrayVals.push(arrayObjDest); 104 } else { 105 arrayVals.push(arrayVal); 106 } 107 }); 108 xformed[prop] = arrayVals; 109 } else { 110 xformed[prop] = val; 111 } 112 }); 113 if (dest === undefined) { 114 return xformed; 115 } 116 } 117 function isValidCommand(command) { 118 return $.type(command) === "string" && !command.match(/^_/) && uploader()[command] !== undefined; 119 } 120 function delegateCommand(command) { 121 var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1), retVal; 122 transformVariables(origArgs, xformedArgs); 123 retVal = uploader()[command].apply(uploader(), xformedArgs); 124 return maybeWrapInJquery(retVal); 125 } 126 function maybeWrapInJquery(val) { 127 var transformedVal = val; 128 if (val != null && typeof val === "object" && (val.nodeType === 1 || val.nodeType === 9) && val.cloneNode) { 129 transformedVal = $(val); 130 } 131 return transformedVal; 132 } 133 $.fn.fineUploader = function(optionsOrCommand) { 134 var self = this, selfArgs = arguments, retVals = []; 135 this.each(function(index, el) { 136 $el = $(el); 137 if (uploader() && isValidCommand(optionsOrCommand)) { 138 retVals.push(delegateCommand.apply(self, selfArgs)); 139 if (self.length === 1) { 140 return false; 141 } 142 } else if (typeof optionsOrCommand === "object" || !optionsOrCommand) { 143 init.apply(self, selfArgs); 144 } else { 145 $.error("Method " + optionsOrCommand + " does not exist on jQuery.fineUploader"); 146 } 147 }); 148 if (retVals.length === 1) { 149 return retVals[0]; 150 } else if (retVals.length > 1) { 151 return retVals; 152 } 153 return this; 154 }; 155 })(jQuery); 156 (function($) { 157 "use strict"; 158 var rootDataKey = "fineUploaderDnd", $el; 159 function init(options) { 160 if (!options) { 161 options = {}; 162 } 163 options.dropZoneElements = [ $el ]; 164 var xformedOpts = transformVariables(options); 165 addCallbacks(xformedOpts); 166 dnd(new qq.DragAndDrop(xformedOpts)); 167 return $el; 168 } 169 function dataStore(key, val) { 170 var data = $el.data(rootDataKey); 171 if (val) { 172 if (data === undefined) { 173 data = {}; 174 } 175 data[key] = val; 176 $el.data(rootDataKey, data); 177 } else { 178 if (data === undefined) { 179 return null; 180 } 181 return data[key]; 182 } 183 } 184 function dnd(instanceToStore) { 185 return dataStore("dndInstance", instanceToStore); 186 } 187 function addCallbacks(transformedOpts) { 188 var callbacks = transformedOpts.callbacks = {}; 189 $.each(new qq.DragAndDrop.callbacks(), function(prop, func) { 190 var name = prop, $callbackEl; 191 $callbackEl = $el; 192 callbacks[prop] = function() { 193 var args = Array.prototype.slice.call(arguments), jqueryHandlerResult = $callbackEl.triggerHandler(name, args); 194 return jqueryHandlerResult; 195 }; 196 }); 197 } 198 function transformVariables(source, dest) { 199 var xformed, arrayVals; 200 if (dest === undefined) { 201 xformed = {}; 202 } else { 203 xformed = dest; 204 } 205 $.each(source, function(prop, val) { 206 if (val instanceof $) { 207 xformed[prop] = val[0]; 208 } else if ($.isPlainObject(val)) { 209 xformed[prop] = {}; 210 transformVariables(val, xformed[prop]); 211 } else if ($.isArray(val)) { 212 arrayVals = []; 213 $.each(val, function(idx, arrayVal) { 214 if (arrayVal instanceof $) { 215 $.merge(arrayVals, arrayVal); 216 } else { 217 arrayVals.push(arrayVal); 218 } 219 }); 220 xformed[prop] = arrayVals; 221 } else { 222 xformed[prop] = val; 223 } 224 }); 225 if (dest === undefined) { 226 return xformed; 227 } 228 } 229 function isValidCommand(command) { 230 return $.type(command) === "string" && command === "dispose" && dnd()[command] !== undefined; 231 } 232 function delegateCommand(command) { 233 var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1); 234 transformVariables(origArgs, xformedArgs); 235 return dnd()[command].apply(dnd(), xformedArgs); 236 } 237 $.fn.fineUploaderDnd = function(optionsOrCommand) { 238 var self = this, selfArgs = arguments, retVals = []; 239 this.each(function(index, el) { 240 $el = $(el); 241 if (dnd() && isValidCommand(optionsOrCommand)) { 242 retVals.push(delegateCommand.apply(self, selfArgs)); 243 if (self.length === 1) { 244 return false; 245 } 246 } else if (typeof optionsOrCommand === "object" || !optionsOrCommand) { 247 init.apply(self, selfArgs); 248 } else { 249 $.error("Method " + optionsOrCommand + " does not exist in Fine Uploader's DnD module."); 250 } 251 }); 252 if (retVals.length === 1) { 253 return retVals[0]; 254 } else if (retVals.length > 1) { 255 return retVals; 256 } 257 return this; 258 }; 259 })(jQuery); 260 var qq = function(element) { 261 "use strict"; 262 return { 263 hide: function() { 264 element.style.display = "none"; 265 return this; 266 }, 267 attach: function(type, fn) { 268 if (element.addEventListener) { 269 element.addEventListener(type, fn, false); 270 } else if (element.attachEvent) { 271 element.attachEvent("on" + type, fn); 272 } 273 return function() { 274 qq(element).detach(type, fn); 275 }; 276 }, 277 detach: function(type, fn) { 278 if (element.removeEventListener) { 279 element.removeEventListener(type, fn, false); 280 } else if (element.attachEvent) { 281 element.detachEvent("on" + type, fn); 282 } 283 return this; 284 }, 285 contains: function(descendant) { 286 if (!descendant) { 287 return false; 288 } 289 if (element === descendant) { 290 return true; 291 } 292 if (element.contains) { 293 return element.contains(descendant); 294 } else { 295 return !!(descendant.compareDocumentPosition(element) & 8); 296 } 297 }, 298 insertBefore: function(elementB) { 299 elementB.parentNode.insertBefore(element, elementB); 300 return this; 301 }, 302 remove: function() { 303 element.parentNode.removeChild(element); 304 return this; 305 }, 306 css: function(styles) { 307 if (element.style == null) { 308 throw new qq.Error("Can't apply style to node as it is not on the HTMLElement prototype chain!"); 309 } 310 if (styles.opacity != null) { 311 if (typeof element.style.opacity !== "string" && typeof element.filters !== "undefined") { 312 styles.filter = "alpha(opacity=" + Math.round(100 * styles.opacity) + ")"; 313 } 314 } 315 qq.extend(element.style, styles); 316 return this; 317 }, 318 hasClass: function(name, considerParent) { 319 var re = new RegExp("(^| )" + name + "( |$)"); 320 return re.test(element.className) || !!(considerParent && re.test(element.parentNode.className)); 321 }, 322 addClass: function(name) { 323 if (!qq(element).hasClass(name)) { 324 element.className += " " + name; 325 } 326 return this; 327 }, 328 removeClass: function(name) { 329 var re = new RegExp("(^| )" + name + "( |$)"); 330 element.className = element.className.replace(re, " ").replace(/^\s+|\s+$/g, ""); 331 return this; 332 }, 333 getByClass: function(className, first) { 334 var candidates, result = []; 335 if (first && element.querySelector) { 336 return element.querySelector("." + className); 337 } else if (element.querySelectorAll) { 338 return element.querySelectorAll("." + className); 339 } 340 candidates = element.getElementsByTagName("*"); 341 qq.each(candidates, function(idx, val) { 342 if (qq(val).hasClass(className)) { 343 result.push(val); 344 } 345 }); 346 return first ? result[0] : result; 347 }, 348 getFirstByClass: function(className) { 349 return qq(element).getByClass(className, true); 350 }, 351 children: function() { 352 var children = [], child = element.firstChild; 353 while (child) { 354 if (child.nodeType === 1) { 355 children.push(child); 356 } 357 child = child.nextSibling; 358 } 359 return children; 360 }, 361 setText: function(text) { 362 element.innerText = text; 363 element.textContent = text; 364 return this; 365 }, 366 clearText: function() { 367 return qq(element).setText(""); 368 }, 369 hasAttribute: function(attrName) { 370 var attrVal; 371 if (element.hasAttribute) { 372 if (!element.hasAttribute(attrName)) { 373 return false; 374 } 375 return /^false$/i.exec(element.getAttribute(attrName)) == null; 376 } else { 377 attrVal = element[attrName]; 378 if (attrVal === undefined) { 379 return false; 380 } 381 return /^false$/i.exec(attrVal) == null; 382 } 383 } 384 }; 385 }; 386 (function() { 387 "use strict"; 388 qq.canvasToBlob = function(canvas, mime, quality) { 389 return qq.dataUriToBlob(canvas.toDataURL(mime, quality)); 390 }; 391 qq.dataUriToBlob = function(dataUri) { 392 var arrayBuffer, byteString, createBlob = function(data, mime) { 393 var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder, blobBuilder = BlobBuilder && new BlobBuilder(); 394 if (blobBuilder) { 395 blobBuilder.append(data); 396 return blobBuilder.getBlob(mime); 397 } else { 398 return new Blob([ data ], { 399 type: mime 400 }); 401 } 402 }, intArray, mimeString; 403 if (dataUri.split(",")[0].indexOf("base64") >= 0) { 404 byteString = atob(dataUri.split(",")[1]); 405 } else { 406 byteString = decodeURI(dataUri.split(",")[1]); 407 } 408 mimeString = dataUri.split(",")[0].split(":")[1].split(";")[0]; 409 arrayBuffer = new ArrayBuffer(byteString.length); 410 intArray = new Uint8Array(arrayBuffer); 411 qq.each(byteString, function(idx, character) { 412 intArray[idx] = character.charCodeAt(0); 413 }); 414 return createBlob(arrayBuffer, mimeString); 415 }; 416 qq.log = function(message, level) { 417 if (window.console) { 418 if (!level || level === "info") { 419 window.console.log(message); 420 } else { 421 if (window.console[level]) { 422 window.console[level](message); 423 } else { 424 window.console.log("<" + level + "> " + message); 425 } 426 } 427 } 428 }; 429 qq.isObject = function(variable) { 430 return variable && !variable.nodeType && Object.prototype.toString.call(variable) === "[object Object]"; 431 }; 432 qq.isFunction = function(variable) { 433 return typeof variable === "function"; 434 }; 435 qq.isArray = function(value) { 436 return Object.prototype.toString.call(value) === "[object Array]" || value && window.ArrayBuffer && value.buffer && value.buffer.constructor === ArrayBuffer; 437 }; 438 qq.isItemList = function(maybeItemList) { 439 return Object.prototype.toString.call(maybeItemList) === "[object DataTransferItemList]"; 440 }; 441 qq.isNodeList = function(maybeNodeList) { 442 return Object.prototype.toString.call(maybeNodeList) === "[object NodeList]" || maybeNodeList.item && maybeNodeList.namedItem; 443 }; 444 qq.isString = function(maybeString) { 445 return Object.prototype.toString.call(maybeString) === "[object String]"; 446 }; 447 qq.trimStr = function(string) { 448 if (String.prototype.trim) { 449 return string.trim(); 450 } 451 return string.replace(/^\s+|\s+$/g, ""); 452 }; 453 qq.format = function(str) { 454 var args = Array.prototype.slice.call(arguments, 1), newStr = str, nextIdxToReplace = newStr.indexOf("{}"); 455 qq.each(args, function(idx, val) { 456 var strBefore = newStr.substring(0, nextIdxToReplace), strAfter = newStr.substring(nextIdxToReplace + 2); 457 newStr = strBefore + val + strAfter; 458 nextIdxToReplace = newStr.indexOf("{}", nextIdxToReplace + val.length); 459 if (nextIdxToReplace < 0) { 460 return false; 461 } 462 }); 463 return newStr; 464 }; 465 qq.isFile = function(maybeFile) { 466 return window.File && Object.prototype.toString.call(maybeFile) === "[object File]"; 467 }; 468 qq.isFileList = function(maybeFileList) { 469 return window.FileList && Object.prototype.toString.call(maybeFileList) === "[object FileList]"; 470 }; 471 qq.isFileOrInput = function(maybeFileOrInput) { 472 return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput); 473 }; 474 qq.isInput = function(maybeInput, notFile) { 475 var evaluateType = function(type) { 476 var normalizedType = type.toLowerCase(); 477 if (notFile) { 478 return normalizedType !== "file"; 479 } 480 return normalizedType === "file"; 481 }; 482 if (window.HTMLInputElement) { 483 if (Object.prototype.toString.call(maybeInput) === "[object HTMLInputElement]") { 484 if (maybeInput.type && evaluateType(maybeInput.type)) { 485 return true; 486 } 487 } 488 } 489 if (maybeInput.tagName) { 490 if (maybeInput.tagName.toLowerCase() === "input") { 491 if (maybeInput.type && evaluateType(maybeInput.type)) { 492 return true; 493 } 494 } 495 } 496 return false; 497 }; 498 qq.isBlob = function(maybeBlob) { 499 if (window.Blob && Object.prototype.toString.call(maybeBlob) === "[object Blob]") { 500 return true; 501 } 502 }; 503 qq.isXhrUploadSupported = function() { 504 var input = document.createElement("input"); 505 input.type = "file"; 506 return input.multiple !== undefined && typeof File !== "undefined" && typeof FormData !== "undefined" && typeof qq.createXhrInstance().upload !== "undefined"; 507 }; 508 qq.createXhrInstance = function() { 509 if (window.XMLHttpRequest) { 510 return new XMLHttpRequest(); 511 } 512 try { 513 return new ActiveXObject("MSXML2.XMLHTTP.3.0"); 514 } catch (error) { 515 qq.log("Neither XHR or ActiveX are supported!", "error"); 516 return null; 517 } 518 }; 519 qq.isFolderDropSupported = function(dataTransfer) { 520 return dataTransfer.items && dataTransfer.items.length > 0 && dataTransfer.items[0].webkitGetAsEntry; 521 }; 522 qq.isFileChunkingSupported = function() { 523 return !qq.androidStock() && qq.isXhrUploadSupported() && (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined); 524 }; 525 qq.sliceBlob = function(fileOrBlob, start, end) { 526 var slicer = fileOrBlob.slice || fileOrBlob.mozSlice || fileOrBlob.webkitSlice; 527 return slicer.call(fileOrBlob, start, end); 528 }; 529 qq.arrayBufferToHex = function(buffer) { 530 var bytesAsHex = "", bytes = new Uint8Array(buffer); 531 qq.each(bytes, function(idx, byt) { 532 var byteAsHexStr = byt.toString(16); 533 if (byteAsHexStr.length < 2) { 534 byteAsHexStr = "0" + byteAsHexStr; 535 } 536 bytesAsHex += byteAsHexStr; 537 }); 538 return bytesAsHex; 539 }; 540 qq.readBlobToHex = function(blob, startOffset, length) { 541 var initialBlob = qq.sliceBlob(blob, startOffset, startOffset + length), fileReader = new FileReader(), promise = new qq.Promise(); 542 fileReader.onload = function() { 543 promise.success(qq.arrayBufferToHex(fileReader.result)); 544 }; 545 fileReader.onerror = promise.failure; 546 fileReader.readAsArrayBuffer(initialBlob); 547 return promise; 548 }; 549 qq.extend = function(first, second, extendNested) { 550 qq.each(second, function(prop, val) { 551 if (extendNested && qq.isObject(val)) { 552 if (first[prop] === undefined) { 553 first[prop] = {}; 554 } 555 qq.extend(first[prop], val, true); 556 } else { 557 first[prop] = val; 558 } 559 }); 560 return first; 561 }; 562 qq.override = function(target, sourceFn) { 563 var super_ = {}, source = sourceFn(super_); 564 qq.each(source, function(srcPropName, srcPropVal) { 565 if (target[srcPropName] !== undefined) { 566 super_[srcPropName] = target[srcPropName]; 567 } 568 target[srcPropName] = srcPropVal; 569 }); 570 return target; 571 }; 572 qq.indexOf = function(arr, elt, from) { 573 if (arr.indexOf) { 574 return arr.indexOf(elt, from); 575 } 576 from = from || 0; 577 var len = arr.length; 578 if (from < 0) { 579 from += len; 580 } 581 for (;from < len; from += 1) { 582 if (arr.hasOwnProperty(from) && arr[from] === elt) { 583 return from; 584 } 585 } 586 return -1; 587 }; 588 qq.getUniqueId = function() { 589 return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { 590 var r = Math.random() * 16 | 0, v = c == "x" ? r : r & 3 | 8; 591 return v.toString(16); 592 }); 593 }; 594 qq.ie = function() { 595 return navigator.userAgent.indexOf("MSIE") !== -1 || navigator.userAgent.indexOf("Trident") !== -1; 596 }; 597 qq.ie7 = function() { 598 return navigator.userAgent.indexOf("MSIE 7") !== -1; 599 }; 600 qq.ie8 = function() { 601 return navigator.userAgent.indexOf("MSIE 8") !== -1; 602 }; 603 qq.ie10 = function() { 604 return navigator.userAgent.indexOf("MSIE 10") !== -1; 605 }; 606 qq.ie11 = function() { 607 return qq.ie() && navigator.userAgent.indexOf("rv:11") !== -1; 608 }; 609 qq.edge = function() { 610 return navigator.userAgent.indexOf("Edge") >= 0; 611 }; 612 qq.safari = function() { 613 return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1; 614 }; 615 qq.chrome = function() { 616 return navigator.vendor !== undefined && navigator.vendor.indexOf("Google") !== -1; 617 }; 618 qq.opera = function() { 619 return navigator.vendor !== undefined && navigator.vendor.indexOf("Opera") !== -1; 620 }; 621 qq.firefox = function() { 622 return !qq.edge() && !qq.ie11() && navigator.userAgent.indexOf("Mozilla") !== -1 && navigator.vendor !== undefined && navigator.vendor === ""; 623 }; 624 qq.windows = function() { 625 return navigator.platform === "Win32"; 626 }; 627 qq.android = function() { 628 return navigator.userAgent.toLowerCase().indexOf("android") !== -1; 629 }; 630 qq.androidStock = function() { 631 return qq.android() && navigator.userAgent.toLowerCase().indexOf("chrome") < 0; 632 }; 633 qq.ios6 = function() { 634 return qq.ios() && navigator.userAgent.indexOf(" OS 6_") !== -1; 635 }; 636 qq.ios7 = function() { 637 return qq.ios() && navigator.userAgent.indexOf(" OS 7_") !== -1; 638 }; 639 qq.ios8 = function() { 640 return qq.ios() && navigator.userAgent.indexOf(" OS 8_") !== -1; 641 }; 642 qq.ios800 = function() { 643 return qq.ios() && navigator.userAgent.indexOf(" OS 8_0 ") !== -1; 644 }; 645 qq.ios = function() { 646 return navigator.userAgent.indexOf("iPad") !== -1 || navigator.userAgent.indexOf("iPod") !== -1 || navigator.userAgent.indexOf("iPhone") !== -1; 647 }; 648 qq.iosChrome = function() { 649 return qq.ios() && navigator.userAgent.indexOf("CriOS") !== -1; 650 }; 651 qq.iosSafari = function() { 652 return qq.ios() && !qq.iosChrome() && navigator.userAgent.indexOf("Safari") !== -1; 653 }; 654 qq.iosSafariWebView = function() { 655 return qq.ios() && !qq.iosChrome() && !qq.iosSafari(); 656 }; 657 qq.preventDefault = function(e) { 658 if (e.preventDefault) { 659 e.preventDefault(); 660 } else { 661 e.returnValue = false; 662 } 663 }; 664 qq.toElement = function() { 665 var div = document.createElement("div"); 666 return function(html) { 667 div.innerHTML = html; 668 var element = div.firstChild; 669 div.removeChild(element); 670 return element; 671 }; 672 }(); 673 qq.each = function(iterableItem, callback) { 674 var keyOrIndex, retVal; 675 if (iterableItem) { 676 if (window.Storage && iterableItem.constructor === window.Storage) { 677 for (keyOrIndex = 0; keyOrIndex < iterableItem.length; keyOrIndex++) { 678 retVal = callback(iterableItem.key(keyOrIndex), iterableItem.getItem(iterableItem.key(keyOrIndex))); 679 if (retVal === false) { 680 break; 681 } 682 } 683 } else if (qq.isArray(iterableItem) || qq.isItemList(iterableItem) || qq.isNodeList(iterableItem)) { 684 for (keyOrIndex = 0; keyOrIndex < iterableItem.length; keyOrIndex++) { 685 retVal = callback(keyOrIndex, iterableItem[keyOrIndex]); 686 if (retVal === false) { 687 break; 688 } 689 } 690 } else if (qq.isString(iterableItem)) { 691 for (keyOrIndex = 0; keyOrIndex < iterableItem.length; keyOrIndex++) { 692 retVal = callback(keyOrIndex, iterableItem.charAt(keyOrIndex)); 693 if (retVal === false) { 694 break; 695 } 696 } 697 } else { 698 for (keyOrIndex in iterableItem) { 699 if (Object.prototype.hasOwnProperty.call(iterableItem, keyOrIndex)) { 700 retVal = callback(keyOrIndex, iterableItem[keyOrIndex]); 701 if (retVal === false) { 702 break; 703 } 704 } 705 } 706 } 707 } 708 }; 709 qq.bind = function(oldFunc, context) { 710 if (qq.isFunction(oldFunc)) { 711 var args = Array.prototype.slice.call(arguments, 2); 712 return function() { 713 var newArgs = qq.extend([], args); 714 if (arguments.length) { 715 newArgs = newArgs.concat(Array.prototype.slice.call(arguments)); 716 } 717 return oldFunc.apply(context, newArgs); 718 }; 719 } 720 throw new Error("first parameter must be a function!"); 721 }; 722 qq.obj2url = function(obj, temp, prefixDone) { 723 var uristrings = [], prefix = "&", add = function(nextObj, i) { 724 var nextTemp = temp ? /\[\]$/.test(temp) ? temp : temp + "[" + i + "]" : i; 725 if (nextTemp !== "undefined" && i !== "undefined") { 726 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)); 727 } 728 }; 729 if (!prefixDone && temp) { 730 prefix = /\?/.test(temp) ? /\?$/.test(temp) ? "" : "&" : "?"; 731 uristrings.push(temp); 732 uristrings.push(qq.obj2url(obj)); 733 } else if (Object.prototype.toString.call(obj) === "[object Array]" && typeof obj !== "undefined") { 734 qq.each(obj, function(idx, val) { 735 add(val, idx); 736 }); 737 } else if (typeof obj !== "undefined" && obj !== null && typeof obj === "object") { 738 qq.each(obj, function(prop, val) { 739 add(val, prop); 740 }); 741 } else { 742 uristrings.push(encodeURIComponent(temp) + "=" + encodeURIComponent(obj)); 743 } 744 if (temp) { 745 return uristrings.join(prefix); 746 } else { 747 return uristrings.join(prefix).replace(/^&/, "").replace(/%20/g, "+"); 748 } 749 }; 750 qq.obj2FormData = function(obj, formData, arrayKeyName) { 751 if (!formData) { 752 formData = new FormData(); 753 } 754 qq.each(obj, function(key, val) { 755 key = arrayKeyName ? arrayKeyName + "[" + key + "]" : key; 756 if (qq.isObject(val)) { 757 qq.obj2FormData(val, formData, key); 758 } else if (qq.isFunction(val)) { 759 formData.append(key, val()); 760 } else { 761 formData.append(key, val); 762 } 763 }); 764 return formData; 765 }; 766 qq.obj2Inputs = function(obj, form) { 767 var input; 768 if (!form) { 769 form = document.createElement("form"); 770 } 771 qq.obj2FormData(obj, { 772 append: function(key, val) { 773 input = document.createElement("input"); 774 input.setAttribute("name", key); 775 input.setAttribute("value", val); 776 form.appendChild(input); 777 } 778 }); 779 return form; 780 }; 781 qq.parseJson = function(json) { 782 if (window.JSON && qq.isFunction(JSON.parse)) { 783 return JSON.parse(json); 784 } else { 785 return eval("(" + json + ")"); 786 } 787 }; 788 qq.getExtension = function(filename) { 789 var extIdx = filename.lastIndexOf(".") + 1; 790 if (extIdx > 0) { 791 return filename.substr(extIdx, filename.length - extIdx); 792 } 793 }; 794 qq.getFilename = function(blobOrFileInput) { 795 if (qq.isInput(blobOrFileInput)) { 796 return blobOrFileInput.value.replace(/.*(\/|\\)/, ""); 797 } else if (qq.isFile(blobOrFileInput)) { 798 if (blobOrFileInput.fileName !== null && blobOrFileInput.fileName !== undefined) { 799 return blobOrFileInput.fileName; 800 } 801 } 802 return blobOrFileInput.name; 803 }; 804 qq.DisposeSupport = function() { 805 var disposers = []; 806 return { 807 dispose: function() { 808 var disposer; 809 do { 810 disposer = disposers.shift(); 811 if (disposer) { 812 disposer(); 813 } 814 } while (disposer); 815 }, 816 attach: function() { 817 var args = arguments; 818 this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1))); 819 }, 820 addDisposer: function(disposeFunction) { 821 disposers.push(disposeFunction); 822 } 823 }; 824 }; 825 })(); 826 (function() { 827 "use strict"; 828 if (typeof define === "function" && define.amd) { 829 define(function() { 830 return qq; 831 }); 832 } else if (typeof module !== "undefined" && module.exports) { 833 module.exports = qq; 834 } else { 835 global.qq = qq; 836 } 837 })(); 838 (function() { 839 "use strict"; 840 qq.Error = function(message) { 841 this.message = "[Fine Uploader " + qq.version + "] " + message; 842 }; 843 qq.Error.prototype = new Error(); 844 })(); 845 qq.version = "5.16.2"; 846 qq.supportedFeatures = function() { 847 "use strict"; 848 var supportsUploading, supportsUploadingBlobs, supportsFileDrop, supportsAjaxFileUploading, supportsFolderDrop, supportsChunking, supportsResume, supportsUploadViaPaste, supportsUploadCors, supportsDeleteFileXdr, supportsDeleteFileCorsXhr, supportsDeleteFileCors, supportsFolderSelection, supportsImagePreviews, supportsUploadProgress; 849 function testSupportsFileInputElement() { 850 var supported = true, tempInput; 851 try { 852 tempInput = document.createElement("input"); 853 tempInput.type = "file"; 854 qq(tempInput).hide(); 855 if (tempInput.disabled) { 856 supported = false; 857 } 858 } catch (ex) { 859 supported = false; 860 } 861 return supported; 862 } 863 function isChrome14OrHigher() { 864 return (qq.chrome() || qq.opera()) && navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined; 865 } 866 function isCrossOriginXhrSupported() { 867 if (window.XMLHttpRequest) { 868 var xhr = qq.createXhrInstance(); 869 return xhr.withCredentials !== undefined; 870 } 871 return false; 872 } 873 function isXdrSupported() { 874 return window.XDomainRequest !== undefined; 875 } 876 function isCrossOriginAjaxSupported() { 877 if (isCrossOriginXhrSupported()) { 878 return true; 879 } 880 return isXdrSupported(); 881 } 882 function isFolderSelectionSupported() { 883 return document.createElement("input").webkitdirectory !== undefined; 884 } 885 function isLocalStorageSupported() { 886 try { 887 return !!window.localStorage && qq.isFunction(window.localStorage.setItem); 888 } catch (error) { 889 return false; 890 } 891 } 892 function isDragAndDropSupported() { 893 var span = document.createElement("span"); 894 return ("draggable" in span || "ondragstart" in span && "ondrop" in span) && !qq.android() && !qq.ios(); 895 } 896 supportsUploading = testSupportsFileInputElement(); 897 supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported(); 898 supportsUploadingBlobs = supportsAjaxFileUploading && !qq.androidStock(); 899 supportsFileDrop = supportsAjaxFileUploading && isDragAndDropSupported(); 900 supportsFolderDrop = supportsFileDrop && function() { 901 var input = document.createElement("input"); 902 input.type = "file"; 903 return !!("webkitdirectory" in (input || document.querySelectorAll("input[type=file]")[0])); 904 }(); 905 supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported(); 906 supportsResume = supportsAjaxFileUploading && supportsChunking && isLocalStorageSupported(); 907 supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher(); 908 supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading); 909 supportsDeleteFileCorsXhr = isCrossOriginXhrSupported(); 910 supportsDeleteFileXdr = isXdrSupported(); 911 supportsDeleteFileCors = isCrossOriginAjaxSupported(); 912 supportsFolderSelection = isFolderSelectionSupported(); 913 supportsImagePreviews = supportsAjaxFileUploading && window.FileReader !== undefined; 914 supportsUploadProgress = function() { 915 if (supportsAjaxFileUploading) { 916 return !qq.androidStock() && !qq.iosChrome(); 917 } 918 return false; 919 }(); 920 return { 921 ajaxUploading: supportsAjaxFileUploading, 922 blobUploading: supportsUploadingBlobs, 923 canDetermineSize: supportsAjaxFileUploading, 924 chunking: supportsChunking, 925 deleteFileCors: supportsDeleteFileCors, 926 deleteFileCorsXdr: supportsDeleteFileXdr, 927 deleteFileCorsXhr: supportsDeleteFileCorsXhr, 928 dialogElement: !!window.HTMLDialogElement, 929 fileDrop: supportsFileDrop, 930 folderDrop: supportsFolderDrop, 931 folderSelection: supportsFolderSelection, 932 imagePreviews: supportsImagePreviews, 933 imageValidation: supportsImagePreviews, 934 itemSizeValidation: supportsAjaxFileUploading, 935 pause: supportsChunking, 936 progressBar: supportsUploadProgress, 937 resume: supportsResume, 938 scaling: supportsImagePreviews && supportsUploadingBlobs, 939 tiffPreviews: qq.safari(), 940 unlimitedScaledImageSize: !qq.ios(), 941 uploading: supportsUploading, 942 uploadCors: supportsUploadCors, 943 uploadCustomHeaders: supportsAjaxFileUploading, 944 uploadNonMultipart: supportsAjaxFileUploading, 945 uploadViaPaste: supportsUploadViaPaste 946 }; 947 }(); 948 qq.isGenericPromise = function(maybePromise) { 949 "use strict"; 950 return !!(maybePromise && maybePromise.then && qq.isFunction(maybePromise.then)); 951 }; 952 qq.Promise = function() { 953 "use strict"; 954 var successArgs, failureArgs, successCallbacks = [], failureCallbacks = [], doneCallbacks = [], state = 0; 955 qq.extend(this, { 956 then: function(onSuccess, onFailure) { 957 if (state === 0) { 958 if (onSuccess) { 959 successCallbacks.push(onSuccess); 960 } 961 if (onFailure) { 962 failureCallbacks.push(onFailure); 963 } 964 } else if (state === -1) { 965 onFailure && onFailure.apply(null, failureArgs); 966 } else if (onSuccess) { 967 onSuccess.apply(null, successArgs); 968 } 969 return this; 970 }, 971 done: function(callback) { 972 if (state === 0) { 973 doneCallbacks.push(callback); 974 } else { 975 callback.apply(null, failureArgs === undefined ? successArgs : failureArgs); 976 } 977 return this; 978 }, 979 success: function() { 980 state = 1; 981 successArgs = arguments; 982 if (successCallbacks.length) { 983 qq.each(successCallbacks, function(idx, callback) { 984 callback.apply(null, successArgs); 985 }); 986 } 987 if (doneCallbacks.length) { 988 qq.each(doneCallbacks, function(idx, callback) { 989 callback.apply(null, successArgs); 990 }); 991 } 992 return this; 993 }, 994 failure: function() { 995 state = -1; 996 failureArgs = arguments; 997 if (failureCallbacks.length) { 998 qq.each(failureCallbacks, function(idx, callback) { 999 callback.apply(null, failureArgs); 1000 }); 1001 } 1002 if (doneCallbacks.length) { 1003 qq.each(doneCallbacks, function(idx, callback) { 1004 callback.apply(null, failureArgs); 1005 }); 1006 } 1007 return this; 1008 } 1009 }); 1010 }; 1011 qq.BlobProxy = function(referenceBlob, onCreate) { 1012 "use strict"; 1013 qq.extend(this, { 1014 referenceBlob: referenceBlob, 1015 create: function() { 1016 return onCreate(referenceBlob); 1017 } 1018 }); 1019 }; 1020 qq.UploadButton = function(o) { 1021 "use strict"; 1022 var self = this, disposeSupport = new qq.DisposeSupport(), options = { 1023 acceptFiles: null, 1024 element: null, 1025 focusClass: "qq-upload-button-focus", 1026 folders: false, 1027 hoverClass: "qq-upload-button-hover", 1028 ios8BrowserCrashWorkaround: false, 1029 multiple: false, 1030 name: "qqfile", 1031 onChange: function(input) {}, 1032 title: null 1033 }, input, buttonId; 1034 qq.extend(options, o); 1035 buttonId = qq.getUniqueId(); 1036 function createInput() { 1037 var input = document.createElement("input"); 1038 input.setAttribute(qq.UploadButton.BUTTON_ID_ATTR_NAME, buttonId); 1039 input.setAttribute("title", options.title); 1040 self.setMultiple(options.multiple, input); 1041 if (options.folders && qq.supportedFeatures.folderSelection) { 1042 input.setAttribute("webkitdirectory", ""); 1043 } 1044 if (options.acceptFiles) { 1045 input.setAttribute("accept", options.acceptFiles); 1046 } 1047 input.setAttribute("type", "file"); 1048 input.setAttribute("name", options.name); 1049 qq(input).css({ 1050 position: "absolute", 1051 right: 0, 1052 top: 0, 1053 fontFamily: "Arial", 1054 fontSize: qq.ie() && !qq.ie8() ? "3500px" : "118px", 1055 margin: 0, 1056 padding: 0, 1057 cursor: "pointer", 1058 opacity: 0 1059 }); 1060 !qq.ie7() && qq(input).css({ 1061 height: "100%" 1062 }); 1063 options.element.appendChild(input); 1064 disposeSupport.attach(input, "change", function() { 1065 options.onChange(input); 1066 }); 1067 disposeSupport.attach(input, "mouseover", function() { 1068 qq(options.element).addClass(options.hoverClass); 1069 }); 1070 disposeSupport.attach(input, "mouseout", function() { 1071 qq(options.element).removeClass(options.hoverClass); 1072 }); 1073 disposeSupport.attach(input, "focus", function() { 1074 qq(options.element).addClass(options.focusClass); 1075 }); 1076 disposeSupport.attach(input, "blur", function() { 1077 qq(options.element).removeClass(options.focusClass); 1078 }); 1079 return input; 1080 } 1081 qq(options.element).css({ 1082 position: "relative", 1083 overflow: "hidden", 1084 direction: "ltr" 1085 }); 1086 qq.extend(this, { 1087 getInput: function() { 1088 return input; 1089 }, 1090 getButtonId: function() { 1091 return buttonId; 1092 }, 1093 setMultiple: function(isMultiple, optInput) { 1094 var input = optInput || this.getInput(); 1095 if (options.ios8BrowserCrashWorkaround && qq.ios8() && (qq.iosChrome() || qq.iosSafariWebView())) { 1096 input.setAttribute("multiple", ""); 1097 } else { 1098 if (isMultiple) { 1099 input.setAttribute("multiple", ""); 1100 } else { 1101 input.removeAttribute("multiple"); 1102 } 1103 } 1104 }, 1105 setAcceptFiles: function(acceptFiles) { 1106 if (acceptFiles !== options.acceptFiles) { 1107 input.setAttribute("accept", acceptFiles); 1108 } 1109 }, 1110 reset: function() { 1111 if (input.parentNode) { 1112 qq(input).remove(); 1113 } 1114 qq(options.element).removeClass(options.focusClass); 1115 input = null; 1116 input = createInput(); 1117 } 1118 }); 1119 input = createInput(); 1120 }; 1121 qq.UploadButton.BUTTON_ID_ATTR_NAME = "qq-button-id"; 1122 qq.UploadData = function(uploaderProxy) { 1123 "use strict"; 1124 var data = [], byUuid = {}, byStatus = {}, byProxyGroupId = {}, byBatchId = {}; 1125 function getDataByIds(idOrIds) { 1126 if (qq.isArray(idOrIds)) { 1127 var entries = []; 1128 qq.each(idOrIds, function(idx, id) { 1129 entries.push(data[id]); 1130 }); 1131 return entries; 1132 } 1133 return data[idOrIds]; 1134 } 1135 function getDataByUuids(uuids) { 1136 if (qq.isArray(uuids)) { 1137 var entries = []; 1138 qq.each(uuids, function(idx, uuid) { 1139 entries.push(data[byUuid[uuid]]); 1140 }); 1141 return entries; 1142 } 1143 return data[byUuid[uuids]]; 1144 } 1145 function getDataByStatus(status) { 1146 var statusResults = [], statuses = [].concat(status); 1147 qq.each(statuses, function(index, statusEnum) { 1148 var statusResultIndexes = byStatus[statusEnum]; 1149 if (statusResultIndexes !== undefined) { 1150 qq.each(statusResultIndexes, function(i, dataIndex) { 1151 statusResults.push(data[dataIndex]); 1152 }); 1153 } 1154 }); 1155 return statusResults; 1156 } 1157 qq.extend(this, { 1158 addFile: function(spec) { 1159 var status = spec.status || qq.status.SUBMITTING, id = data.push({ 1160 name: spec.name, 1161 originalName: spec.name, 1162 uuid: spec.uuid, 1163 size: spec.size == null ? -1 : spec.size, 1164 status: status, 1165 file: spec.file 1166 }) - 1; 1167 if (spec.batchId) { 1168 data[id].batchId = spec.batchId; 1169 if (byBatchId[spec.batchId] === undefined) { 1170 byBatchId[spec.batchId] = []; 1171 } 1172 byBatchId[spec.batchId].push(id); 1173 } 1174 if (spec.proxyGroupId) { 1175 data[id].proxyGroupId = spec.proxyGroupId; 1176 if (byProxyGroupId[spec.proxyGroupId] === undefined) { 1177 byProxyGroupId[spec.proxyGroupId] = []; 1178 } 1179 byProxyGroupId[spec.proxyGroupId].push(id); 1180 } 1181 data[id].id = id; 1182 byUuid[spec.uuid] = id; 1183 if (byStatus[status] === undefined) { 1184 byStatus[status] = []; 1185 } 1186 byStatus[status].push(id); 1187 spec.onBeforeStatusChange && spec.onBeforeStatusChange(id); 1188 uploaderProxy.onStatusChange(id, null, status); 1189 return id; 1190 }, 1191 retrieve: function(optionalFilter) { 1192 if (qq.isObject(optionalFilter) && data.length) { 1193 if (optionalFilter.id !== undefined) { 1194 return getDataByIds(optionalFilter.id); 1195 } else if (optionalFilter.uuid !== undefined) { 1196 return getDataByUuids(optionalFilter.uuid); 1197 } else if (optionalFilter.status) { 1198 return getDataByStatus(optionalFilter.status); 1199 } 1200 } else { 1201 return qq.extend([], data, true); 1202 } 1203 }, 1204 removeFileRef: function(id) { 1205 var record = getDataByIds(id); 1206 if (record) { 1207 delete record.file; 1208 } 1209 }, 1210 reset: function() { 1211 data = []; 1212 byUuid = {}; 1213 byStatus = {}; 1214 byBatchId = {}; 1215 }, 1216 setStatus: function(id, newStatus) { 1217 var oldStatus = data[id].status, byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], id); 1218 byStatus[oldStatus].splice(byStatusOldStatusIndex, 1); 1219 data[id].status = newStatus; 1220 if (byStatus[newStatus] === undefined) { 1221 byStatus[newStatus] = []; 1222 } 1223 byStatus[newStatus].push(id); 1224 uploaderProxy.onStatusChange(id, oldStatus, newStatus); 1225 }, 1226 uuidChanged: function(id, newUuid) { 1227 var oldUuid = data[id].uuid; 1228 data[id].uuid = newUuid; 1229 byUuid[newUuid] = id; 1230 delete byUuid[oldUuid]; 1231 }, 1232 updateName: function(id, newName) { 1233 data[id].name = newName; 1234 }, 1235 updateSize: function(id, newSize) { 1236 data[id].size = newSize; 1237 }, 1238 setParentId: function(targetId, parentId) { 1239 data[targetId].parentId = parentId; 1240 }, 1241 getIdsInProxyGroup: function(id) { 1242 var proxyGroupId = data[id].proxyGroupId; 1243 if (proxyGroupId) { 1244 return byProxyGroupId[proxyGroupId]; 1245 } 1246 return []; 1247 }, 1248 getIdsInBatch: function(id) { 1249 var batchId = data[id].batchId; 1250 return byBatchId[batchId]; 1251 } 1252 }); 1253 }; 1254 qq.status = { 1255 SUBMITTING: "submitting", 1256 SUBMITTED: "submitted", 1257 REJECTED: "rejected", 1258 QUEUED: "queued", 1259 CANCELED: "canceled", 1260 PAUSED: "paused", 1261 UPLOADING: "uploading", 1262 UPLOAD_FINALIZING: "upload finalizing", 1263 UPLOAD_RETRYING: "retrying upload", 1264 UPLOAD_SUCCESSFUL: "upload successful", 1265 UPLOAD_FAILED: "upload failed", 1266 DELETE_FAILED: "delete failed", 1267 DELETING: "deleting", 1268 DELETED: "deleted" 1269 }; 1270 (function() { 1271 "use strict"; 1272 qq.basePublicApi = { 1273 addBlobs: function(blobDataOrArray, params, endpoint) { 1274 this.addFiles(blobDataOrArray, params, endpoint); 1275 }, 1276 addInitialFiles: function(cannedFileList) { 1277 var self = this; 1278 qq.each(cannedFileList, function(index, cannedFile) { 1279 self._addCannedFile(cannedFile); 1280 }); 1281 }, 1282 addFiles: function(data, params, endpoint) { 1283 this._maybeHandleIos8SafariWorkaround(); 1284 var batchId = this._storedIds.length === 0 ? qq.getUniqueId() : this._currentBatchId, processBlob = qq.bind(function(blob) { 1285 this._handleNewFile({ 1286 blob: blob, 1287 name: this._options.blobs.defaultName 1288 }, batchId, verifiedFiles); 1289 }, this), processBlobData = qq.bind(function(blobData) { 1290 this._handleNewFile(blobData, batchId, verifiedFiles); 1291 }, this), processCanvas = qq.bind(function(canvas) { 1292 var blob = qq.canvasToBlob(canvas); 1293 this._handleNewFile({ 1294 blob: blob, 1295 name: this._options.blobs.defaultName + ".png" 1296 }, batchId, verifiedFiles); 1297 }, this), processCanvasData = qq.bind(function(canvasData) { 1298 var normalizedQuality = canvasData.quality && canvasData.quality / 100, blob = qq.canvasToBlob(canvasData.canvas, canvasData.type, normalizedQuality); 1299 this._handleNewFile({ 1300 blob: blob, 1301 name: canvasData.name 1302 }, batchId, verifiedFiles); 1303 }, this), processFileOrInput = qq.bind(function(fileOrInput) { 1304 if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) { 1305 var files = Array.prototype.slice.call(fileOrInput.files), self = this; 1306 qq.each(files, function(idx, file) { 1307 self._handleNewFile(file, batchId, verifiedFiles); 1308 }); 1309 } else { 1310 this._handleNewFile(fileOrInput, batchId, verifiedFiles); 1311 } 1312 }, this), normalizeData = function() { 1313 if (qq.isFileList(data)) { 1314 data = Array.prototype.slice.call(data); 1315 } 1316 data = [].concat(data); 1317 }, self = this, verifiedFiles = []; 1318 this._currentBatchId = batchId; 1319 if (data) { 1320 normalizeData(); 1321 qq.each(data, function(idx, fileContainer) { 1322 if (qq.isFileOrInput(fileContainer)) { 1323 processFileOrInput(fileContainer); 1324 } else if (qq.isBlob(fileContainer)) { 1325 processBlob(fileContainer); 1326 } else if (qq.isObject(fileContainer)) { 1327 if (fileContainer.blob && fileContainer.name) { 1328 processBlobData(fileContainer); 1329 } else if (fileContainer.canvas && fileContainer.name) { 1330 processCanvasData(fileContainer); 1331 } 1332 } else if (fileContainer.tagName && fileContainer.tagName.toLowerCase() === "canvas") { 1333 processCanvas(fileContainer); 1334 } else { 1335 self.log(fileContainer + " is not a valid file container! Ignoring!", "warn"); 1336 } 1337 }); 1338 this.log("Received " + verifiedFiles.length + " files."); 1339 this._prepareItemsForUpload(verifiedFiles, params, endpoint); 1340 } 1341 }, 1342 cancel: function(id) { 1343 var uploadData = this._uploadData.retrieve({ 1344 id: id 1345 }); 1346 if (uploadData && uploadData.status === qq.status.UPLOAD_FINALIZING) { 1347 this.log(qq.format("Ignoring cancel for file ID {} ({}). Finalizing upload.", id, this.getName(id)), "error"); 1348 } else { 1349 this._handler.cancel(id); 1350 } 1351 }, 1352 cancelAll: function() { 1353 var storedIdsCopy = [], self = this; 1354 qq.extend(storedIdsCopy, this._storedIds); 1355 qq.each(storedIdsCopy, function(idx, storedFileId) { 1356 self.cancel(storedFileId); 1357 }); 1358 this._handler.cancelAll(); 1359 }, 1360 clearStoredFiles: function() { 1361 this._storedIds = []; 1362 }, 1363 continueUpload: function(id) { 1364 var uploadData = this._uploadData.retrieve({ 1365 id: id 1366 }); 1367 if (!qq.supportedFeatures.pause || !this._options.chunking.enabled) { 1368 return false; 1369 } 1370 if (uploadData.status === qq.status.PAUSED) { 1371 this.log(qq.format("Paused file ID {} ({}) will be continued. Not paused.", id, this.getName(id))); 1372 this._uploadFile(id); 1373 return true; 1374 } else { 1375 this.log(qq.format("Ignoring continue for file ID {} ({}). Not paused.", id, this.getName(id)), "error"); 1376 } 1377 return false; 1378 }, 1379 deleteFile: function(id) { 1380 return this._onSubmitDelete(id); 1381 }, 1382 doesExist: function(fileOrBlobId) { 1383 return this._handler.isValid(fileOrBlobId); 1384 }, 1385 drawThumbnail: function(fileId, imgOrCanvas, maxSize, fromServer, customResizeFunction) { 1386 var promiseToReturn = new qq.Promise(), fileOrUrl, options; 1387 if (this._imageGenerator) { 1388 fileOrUrl = this._thumbnailUrls[fileId]; 1389 options = { 1390 customResizeFunction: customResizeFunction, 1391 maxSize: maxSize > 0 ? maxSize : null, 1392 scale: maxSize > 0 1393 }; 1394 if (!fromServer && qq.supportedFeatures.imagePreviews) { 1395 fileOrUrl = this.getFile(fileId); 1396 } 1397 if (fileOrUrl == null) { 1398 promiseToReturn.failure({ 1399 container: imgOrCanvas, 1400 error: "File or URL not found." 1401 }); 1402 } else { 1403 this._imageGenerator.generate(fileOrUrl, imgOrCanvas, options).then(function success(modifiedContainer) { 1404 promiseToReturn.success(modifiedContainer); 1405 }, function failure(container, reason) { 1406 promiseToReturn.failure({ 1407 container: container, 1408 error: reason || "Problem generating thumbnail" 1409 }); 1410 }); 1411 } 1412 } else { 1413 promiseToReturn.failure({ 1414 container: imgOrCanvas, 1415 error: "Missing image generator module" 1416 }); 1417 } 1418 return promiseToReturn; 1419 }, 1420 getButton: function(fileId) { 1421 return this._getButton(this._buttonIdsForFileIds[fileId]); 1422 }, 1423 getEndpoint: function(fileId) { 1424 return this._endpointStore.get(fileId); 1425 }, 1426 getFile: function(fileOrBlobId) { 1427 var file = this._handler.getFile(fileOrBlobId); 1428 var uploadDataRecord; 1429 if (!file) { 1430 uploadDataRecord = this._uploadData.retrieve({ 1431 id: fileOrBlobId 1432 }); 1433 if (uploadDataRecord) { 1434 file = uploadDataRecord.file; 1435 } 1436 } 1437 return file || null; 1438 }, 1439 getInProgress: function() { 1440 return this._uploadData.retrieve({ 1441 status: [ qq.status.UPLOADING, qq.status.UPLOAD_RETRYING, qq.status.QUEUED ] 1442 }).length; 1443 }, 1444 getName: function(id) { 1445 return this._uploadData.retrieve({ 1446 id: id 1447 }).name; 1448 }, 1449 getParentId: function(id) { 1450 var uploadDataEntry = this.getUploads({ 1451 id: id 1452 }), parentId = null; 1453 if (uploadDataEntry) { 1454 if (uploadDataEntry.parentId !== undefined) { 1455 parentId = uploadDataEntry.parentId; 1456 } 1457 } 1458 return parentId; 1459 }, 1460 getResumableFilesData: function() { 1461 return this._handler.getResumableFilesData(); 1462 }, 1463 getSize: function(id) { 1464 return this._uploadData.retrieve({ 1465 id: id 1466 }).size; 1467 }, 1468 getNetUploads: function() { 1469 return this._netUploaded; 1470 }, 1471 getRemainingAllowedItems: function() { 1472 var allowedItems = this._currentItemLimit; 1473 if (allowedItems > 0) { 1474 return allowedItems - this._netUploadedOrQueued; 1475 } 1476 return null; 1477 }, 1478 getUploads: function(optionalFilter) { 1479 return this._uploadData.retrieve(optionalFilter); 1480 }, 1481 getUuid: function(id) { 1482 return this._uploadData.retrieve({ 1483 id: id 1484 }).uuid; 1485 }, 1486 isResumable: function(id) { 1487 return this._handler.hasResumeRecord(id); 1488 }, 1489 log: function(str, level) { 1490 if (this._options.debug && (!level || level === "info")) { 1491 qq.log("[Fine Uploader " + qq.version + "] " + str); 1492 } else if (level && level !== "info") { 1493 qq.log("[Fine Uploader " + qq.version + "] " + str, level); 1494 } 1495 }, 1496 pauseUpload: function(id) { 1497 var uploadData = this._uploadData.retrieve({ 1498 id: id 1499 }); 1500 if (!qq.supportedFeatures.pause || !this._options.chunking.enabled) { 1501 return false; 1502 } 1503 if (qq.indexOf([ qq.status.UPLOADING, qq.status.UPLOAD_RETRYING ], uploadData.status) >= 0) { 1504 if (this._handler.pause(id)) { 1505 this._uploadData.setStatus(id, qq.status.PAUSED); 1506 return true; 1507 } else { 1508 this.log(qq.format("Unable to pause file ID {} ({}).", id, this.getName(id)), "error"); 1509 } 1510 } else { 1511 this.log(qq.format("Ignoring pause for file ID {} ({}). Not in progress.", id, this.getName(id)), "error"); 1512 } 1513 return false; 1514 }, 1515 removeFileRef: function(id) { 1516 this._handler.expunge(id); 1517 this._uploadData.removeFileRef(id); 1518 }, 1519 reset: function() { 1520 this.log("Resetting uploader..."); 1521 this._handler.reset(); 1522 this._storedIds = []; 1523 this._autoRetries = []; 1524 this._retryTimeouts = []; 1525 this._preventRetries = []; 1526 this._thumbnailUrls = []; 1527 qq.each(this._buttons, function(idx, button) { 1528 button.reset(); 1529 }); 1530 this._paramsStore.reset(); 1531 this._endpointStore.reset(); 1532 this._netUploadedOrQueued = 0; 1533 this._netUploaded = 0; 1534 this._uploadData.reset(); 1535 this._buttonIdsForFileIds = []; 1536 this._pasteHandler && this._pasteHandler.reset(); 1537 this._options.session.refreshOnReset && this._refreshSessionData(); 1538 this._succeededSinceLastAllComplete = []; 1539 this._failedSinceLastAllComplete = []; 1540 this._totalProgress && this._totalProgress.reset(); 1541 this._customResumeDataStore.reset(); 1542 }, 1543 retry: function(id) { 1544 return this._manualRetry(id); 1545 }, 1546 scaleImage: function(id, specs) { 1547 var self = this; 1548 return qq.Scaler.prototype.scaleImage(id, specs, { 1549 log: qq.bind(self.log, self), 1550 getFile: qq.bind(self.getFile, self), 1551 uploadData: self._uploadData 1552 }); 1553 }, 1554 setCustomHeaders: function(headers, id) { 1555 this._customHeadersStore.set(headers, id); 1556 }, 1557 setCustomResumeData: function(id, data) { 1558 this._customResumeDataStore.set(data, id); 1559 }, 1560 setDeleteFileCustomHeaders: function(headers, id) { 1561 this._deleteFileCustomHeadersStore.set(headers, id); 1562 }, 1563 setDeleteFileEndpoint: function(endpoint, id) { 1564 this._deleteFileEndpointStore.set(endpoint, id); 1565 }, 1566 setDeleteFileParams: function(params, id) { 1567 this._deleteFileParamsStore.set(params, id); 1568 }, 1569 setEndpoint: function(endpoint, id) { 1570 this._endpointStore.set(endpoint, id); 1571 }, 1572 setForm: function(elementOrId) { 1573 this._updateFormSupportAndParams(elementOrId); 1574 }, 1575 setItemLimit: function(newItemLimit) { 1576 this._currentItemLimit = newItemLimit; 1577 }, 1578 setName: function(id, newName) { 1579 this._uploadData.updateName(id, newName); 1580 }, 1581 setParams: function(params, id) { 1582 this._paramsStore.set(params, id); 1583 }, 1584 setUuid: function(id, newUuid) { 1585 return this._uploadData.uuidChanged(id, newUuid); 1586 }, 1587 setStatus: function(id, newStatus) { 1588 var fileRecord = this.getUploads({ 1589 id: id 1590 }); 1591 if (!fileRecord) { 1592 throw new qq.Error(id + " is not a valid file ID."); 1593 } 1594 switch (newStatus) { 1595 case qq.status.DELETED: 1596 this._onDeleteComplete(id, null, false); 1597 break; 1598 1599 case qq.status.DELETE_FAILED: 1600 this._onDeleteComplete(id, null, true); 1601 break; 1602 1603 default: 1604 var errorMessage = "Method setStatus called on '" + name + "' not implemented yet for " + newStatus; 1605 this.log(errorMessage); 1606 throw new qq.Error(errorMessage); 1607 } 1608 }, 1609 uploadStoredFiles: function() { 1610 if (this._storedIds.length === 0) { 1611 this._itemError("noFilesError"); 1612 } else { 1613 this._uploadStoredFiles(); 1614 } 1615 } 1616 }; 1617 qq.basePrivateApi = { 1618 _addCannedFile: function(sessionData) { 1619 var self = this; 1620 return this._uploadData.addFile({ 1621 uuid: sessionData.uuid, 1622 name: sessionData.name, 1623 size: sessionData.size, 1624 status: qq.status.UPLOAD_SUCCESSFUL, 1625 onBeforeStatusChange: function(id) { 1626 sessionData.deleteFileEndpoint && self.setDeleteFileEndpoint(sessionData.deleteFileEndpoint, id); 1627 sessionData.deleteFileParams && self.setDeleteFileParams(sessionData.deleteFileParams, id); 1628 if (sessionData.thumbnailUrl) { 1629 self._thumbnailUrls[id] = sessionData.thumbnailUrl; 1630 } 1631 self._netUploaded++; 1632 self._netUploadedOrQueued++; 1633 } 1634 }); 1635 }, 1636 _annotateWithButtonId: function(file, associatedInput) { 1637 if (qq.isFile(file)) { 1638 file.qqButtonId = this._getButtonId(associatedInput); 1639 } 1640 }, 1641 _batchError: function(message) { 1642 this._options.callbacks.onError(null, null, message, undefined); 1643 }, 1644 _createDeleteHandler: function() { 1645 var self = this; 1646 return new qq.DeleteFileAjaxRequester({ 1647 method: this._options.deleteFile.method.toUpperCase(), 1648 maxConnections: this._options.maxConnections, 1649 uuidParamName: this._options.request.uuidName, 1650 customHeaders: this._deleteFileCustomHeadersStore, 1651 paramsStore: this._deleteFileParamsStore, 1652 endpointStore: this._deleteFileEndpointStore, 1653 cors: this._options.cors, 1654 log: qq.bind(self.log, self), 1655 onDelete: function(id) { 1656 self._onDelete(id); 1657 self._options.callbacks.onDelete(id); 1658 }, 1659 onDeleteComplete: function(id, xhrOrXdr, isError) { 1660 self._onDeleteComplete(id, xhrOrXdr, isError); 1661 self._options.callbacks.onDeleteComplete(id, xhrOrXdr, isError); 1662 } 1663 }); 1664 }, 1665 _createPasteHandler: function() { 1666 var self = this; 1667 return new qq.PasteSupport({ 1668 targetElement: this._options.paste.targetElement, 1669 callbacks: { 1670 log: qq.bind(self.log, self), 1671 pasteReceived: function(blob) { 1672 self._handleCheckedCallback({ 1673 name: "onPasteReceived", 1674 callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob), 1675 onSuccess: qq.bind(self._handlePasteSuccess, self, blob), 1676 identifier: "pasted image" 1677 }); 1678 } 1679 } 1680 }); 1681 }, 1682 _createStore: function(initialValue, _readOnlyValues_) { 1683 var store = {}, catchall = initialValue, perIdReadOnlyValues = {}, readOnlyValues = _readOnlyValues_, copy = function(orig) { 1684 if (qq.isObject(orig)) { 1685 return qq.extend({}, orig); 1686 } 1687 return orig; 1688 }, getReadOnlyValues = function() { 1689 if (qq.isFunction(readOnlyValues)) { 1690 return readOnlyValues(); 1691 } 1692 return readOnlyValues; 1693 }, includeReadOnlyValues = function(id, existing) { 1694 if (readOnlyValues && qq.isObject(existing)) { 1695 qq.extend(existing, getReadOnlyValues()); 1696 } 1697 if (perIdReadOnlyValues[id]) { 1698 qq.extend(existing, perIdReadOnlyValues[id]); 1699 } 1700 }; 1701 return { 1702 set: function(val, id) { 1703 if (id == null) { 1704 store = {}; 1705 catchall = copy(val); 1706 } else { 1707 store[id] = copy(val); 1708 } 1709 }, 1710 get: function(id) { 1711 var values; 1712 if (id != null && store[id]) { 1713 values = store[id]; 1714 } else { 1715 values = copy(catchall); 1716 } 1717 includeReadOnlyValues(id, values); 1718 return copy(values); 1719 }, 1720 addReadOnly: function(id, values) { 1721 if (qq.isObject(store)) { 1722 if (id === null) { 1723 if (qq.isFunction(values)) { 1724 readOnlyValues = values; 1725 } else { 1726 readOnlyValues = readOnlyValues || {}; 1727 qq.extend(readOnlyValues, values); 1728 } 1729 } else { 1730 perIdReadOnlyValues[id] = perIdReadOnlyValues[id] || {}; 1731 qq.extend(perIdReadOnlyValues[id], values); 1732 } 1733 } 1734 }, 1735 remove: function(fileId) { 1736 return delete store[fileId]; 1737 }, 1738 reset: function() { 1739 store = {}; 1740 perIdReadOnlyValues = {}; 1741 catchall = initialValue; 1742 } 1743 }; 1744 }, 1745 _createUploadDataTracker: function() { 1746 var self = this; 1747 return new qq.UploadData({ 1748 getName: function(id) { 1749 return self.getName(id); 1750 }, 1751 getUuid: function(id) { 1752 return self.getUuid(id); 1753 }, 1754 getSize: function(id) { 1755 return self.getSize(id); 1756 }, 1757 onStatusChange: function(id, oldStatus, newStatus) { 1758 self._onUploadStatusChange(id, oldStatus, newStatus); 1759 self._options.callbacks.onStatusChange(id, oldStatus, newStatus); 1760 self._maybeAllComplete(id, newStatus); 1761 if (self._totalProgress) { 1762 setTimeout(function() { 1763 self._totalProgress.onStatusChange(id, oldStatus, newStatus); 1764 }, 0); 1765 } 1766 } 1767 }); 1768 }, 1769 _createUploadButton: function(spec) { 1770 var self = this, acceptFiles = spec.accept || this._options.validation.acceptFiles, allowedExtensions = spec.allowedExtensions || this._options.validation.allowedExtensions, button; 1771 function allowMultiple() { 1772 if (qq.supportedFeatures.ajaxUploading) { 1773 if (self._options.workarounds.iosEmptyVideos && qq.ios() && !qq.ios6() && self._isAllowedExtension(allowedExtensions, ".mov")) { 1774 return false; 1775 } 1776 if (spec.multiple === undefined) { 1777 return self._options.multiple; 1778 } 1779 return spec.multiple; 1780 } 1781 return false; 1782 } 1783 button = new qq.UploadButton({ 1784 acceptFiles: acceptFiles, 1785 element: spec.element, 1786 focusClass: this._options.classes.buttonFocus, 1787 folders: spec.folders, 1788 hoverClass: this._options.classes.buttonHover, 1789 ios8BrowserCrashWorkaround: this._options.workarounds.ios8BrowserCrash, 1790 multiple: allowMultiple(), 1791 name: this._options.request.inputName, 1792 onChange: function(input) { 1793 self._onInputChange(input); 1794 }, 1795 title: spec.title == null ? this._options.text.fileInputTitle : spec.title 1796 }); 1797 this._disposeSupport.addDisposer(function() { 1798 button.dispose(); 1799 }); 1800 self._buttons.push(button); 1801 return button; 1802 }, 1803 _createUploadHandler: function(additionalOptions, namespace) { 1804 var self = this, lastOnProgress = {}, options = { 1805 debug: this._options.debug, 1806 maxConnections: this._options.maxConnections, 1807 cors: this._options.cors, 1808 paramsStore: this._paramsStore, 1809 endpointStore: this._endpointStore, 1810 chunking: this._options.chunking, 1811 resume: this._options.resume, 1812 blobs: this._options.blobs, 1813 log: qq.bind(self.log, self), 1814 preventRetryParam: this._options.retry.preventRetryResponseProperty, 1815 onProgress: function(id, name, loaded, total) { 1816 if (loaded < 0 || total < 0) { 1817 return; 1818 } 1819 if (lastOnProgress[id]) { 1820 if (lastOnProgress[id].loaded !== loaded || lastOnProgress[id].total !== total) { 1821 self._onProgress(id, name, loaded, total); 1822 self._options.callbacks.onProgress(id, name, loaded, total); 1823 } 1824 } else { 1825 self._onProgress(id, name, loaded, total); 1826 self._options.callbacks.onProgress(id, name, loaded, total); 1827 } 1828 lastOnProgress[id] = { 1829 loaded: loaded, 1830 total: total 1831 }; 1832 }, 1833 onComplete: function(id, name, result, xhr) { 1834 delete lastOnProgress[id]; 1835 var status = self.getUploads({ 1836 id: id 1837 }).status, retVal; 1838 if (status === qq.status.UPLOAD_SUCCESSFUL || status === qq.status.UPLOAD_FAILED) { 1839 return; 1840 } 1841 retVal = self._onComplete(id, name, result, xhr); 1842 if (retVal instanceof qq.Promise) { 1843 retVal.done(function() { 1844 self._options.callbacks.onComplete(id, name, result, xhr); 1845 }); 1846 } else { 1847 self._options.callbacks.onComplete(id, name, result, xhr); 1848 } 1849 }, 1850 onCancel: function(id, name, cancelFinalizationEffort) { 1851 var promise = new qq.Promise(); 1852 self._handleCheckedCallback({ 1853 name: "onCancel", 1854 callback: qq.bind(self._options.callbacks.onCancel, self, id, name), 1855 onFailure: promise.failure, 1856 onSuccess: function() { 1857 cancelFinalizationEffort.then(function() { 1858 self._onCancel(id, name); 1859 }); 1860 promise.success(); 1861 }, 1862 identifier: id 1863 }); 1864 return promise; 1865 }, 1866 onUploadPrep: qq.bind(this._onUploadPrep, this), 1867 onUpload: function(id, name) { 1868 self._onUpload(id, name); 1869 var onUploadResult = self._options.callbacks.onUpload(id, name); 1870 if (qq.isGenericPromise(onUploadResult)) { 1871 self.log(qq.format("onUpload for {} returned a Promise - waiting for resolution.", id)); 1872 return onUploadResult; 1873 } 1874 return new qq.Promise().success(); 1875 }, 1876 onUploadChunk: function(id, name, chunkData) { 1877 self._onUploadChunk(id, chunkData); 1878 var onUploadChunkResult = self._options.callbacks.onUploadChunk(id, name, chunkData); 1879 if (qq.isGenericPromise(onUploadChunkResult)) { 1880 self.log(qq.format("onUploadChunk for {}.{} returned a Promise - waiting for resolution.", id, chunkData.partIndex)); 1881 return onUploadChunkResult; 1882 } 1883 return new qq.Promise().success(); 1884 }, 1885 onUploadChunkSuccess: function(id, chunkData, result, xhr) { 1886 self._onUploadChunkSuccess(id, chunkData); 1887 self._options.callbacks.onUploadChunkSuccess.apply(self, arguments); 1888 }, 1889 onResume: function(id, name, chunkData, customResumeData) { 1890 return self._options.callbacks.onResume(id, name, chunkData, customResumeData); 1891 }, 1892 onAutoRetry: function(id, name, responseJSON, xhr) { 1893 return self._onAutoRetry.apply(self, arguments); 1894 }, 1895 onUuidChanged: function(id, newUuid) { 1896 self.log("Server requested UUID change from '" + self.getUuid(id) + "' to '" + newUuid + "'"); 1897 self.setUuid(id, newUuid); 1898 }, 1899 getName: qq.bind(self.getName, self), 1900 getUuid: qq.bind(self.getUuid, self), 1901 getSize: qq.bind(self.getSize, self), 1902 setSize: qq.bind(self._setSize, self), 1903 getDataByUuid: function(uuid) { 1904 return self.getUploads({ 1905 uuid: uuid 1906 }); 1907 }, 1908 isQueued: function(id) { 1909 var status = self.getUploads({ 1910 id: id 1911 }).status; 1912 return status === qq.status.QUEUED || status === qq.status.SUBMITTED || status === qq.status.UPLOAD_RETRYING || status === qq.status.PAUSED; 1913 }, 1914 getIdsInProxyGroup: self._uploadData.getIdsInProxyGroup, 1915 getIdsInBatch: self._uploadData.getIdsInBatch, 1916 isInProgress: function(id) { 1917 return self.getUploads({ 1918 id: id 1919 }).status === qq.status.UPLOADING; 1920 }, 1921 getCustomResumeData: qq.bind(self._getCustomResumeData, self), 1922 setStatus: function(id, status) { 1923 self._uploadData.setStatus(id, status); 1924 } 1925 }; 1926 qq.each(this._options.request, function(prop, val) { 1927 options[prop] = val; 1928 }); 1929 options.customHeaders = this._customHeadersStore; 1930 if (additionalOptions) { 1931 qq.each(additionalOptions, function(key, val) { 1932 options[key] = val; 1933 }); 1934 } 1935 return new qq.UploadHandlerController(options, namespace); 1936 }, 1937 _fileOrBlobRejected: function(id) { 1938 this._netUploadedOrQueued--; 1939 this._uploadData.setStatus(id, qq.status.REJECTED); 1940 }, 1941 _formatSize: function(bytes) { 1942 if (bytes === 0) { 1943 return bytes + this._options.text.sizeSymbols[0]; 1944 } 1945 var i = -1; 1946 do { 1947 bytes = bytes / 1e3; 1948 i++; 1949 } while (bytes > 999); 1950 return Math.max(bytes, .1).toFixed(1) + this._options.text.sizeSymbols[i]; 1951 }, 1952 _generateExtraButtonSpecs: function() { 1953 var self = this; 1954 this._extraButtonSpecs = {}; 1955 qq.each(this._options.extraButtons, function(idx, extraButtonOptionEntry) { 1956 var multiple = extraButtonOptionEntry.multiple, validation = qq.extend({}, self._options.validation, true), extraButtonSpec = qq.extend({}, extraButtonOptionEntry); 1957 if (multiple === undefined) { 1958 multiple = self._options.multiple; 1959 } 1960 if (extraButtonSpec.validation) { 1961 qq.extend(validation, extraButtonOptionEntry.validation, true); 1962 } 1963 qq.extend(extraButtonSpec, { 1964 multiple: multiple, 1965 validation: validation 1966 }, true); 1967 self._initExtraButton(extraButtonSpec); 1968 }); 1969 }, 1970 _getButton: function(buttonId) { 1971 var extraButtonsSpec = this._extraButtonSpecs[buttonId]; 1972 if (extraButtonsSpec) { 1973 return extraButtonsSpec.element; 1974 } else if (buttonId === this._defaultButtonId) { 1975 return this._options.button; 1976 } 1977 }, 1978 _getButtonId: function(buttonOrFileInputOrFile) { 1979 var inputs, fileInput, fileBlobOrInput = buttonOrFileInputOrFile; 1980 if (fileBlobOrInput instanceof qq.BlobProxy) { 1981 fileBlobOrInput = fileBlobOrInput.referenceBlob; 1982 } 1983 if (fileBlobOrInput && !qq.isBlob(fileBlobOrInput)) { 1984 if (qq.isFile(fileBlobOrInput)) { 1985 return fileBlobOrInput.qqButtonId; 1986 } else if (fileBlobOrInput.tagName.toLowerCase() === "input" && fileBlobOrInput.type.toLowerCase() === "file") { 1987 return fileBlobOrInput.getAttribute(qq.UploadButton.BUTTON_ID_ATTR_NAME); 1988 } 1989 inputs = fileBlobOrInput.getElementsByTagName("input"); 1990 qq.each(inputs, function(idx, input) { 1991 if (input.getAttribute("type") === "file") { 1992 fileInput = input; 1993 return false; 1994 } 1995 }); 1996 if (fileInput) { 1997 return fileInput.getAttribute(qq.UploadButton.BUTTON_ID_ATTR_NAME); 1998 } 1999 } 2000 }, 2001 _getCustomResumeData: function(fileId) { 2002 return this._customResumeDataStore.get(fileId); 2003 }, 2004 _getNotFinished: function() { 2005 return this._uploadData.retrieve({ 2006 status: [ qq.status.UPLOADING, qq.status.UPLOAD_RETRYING, qq.status.QUEUED, qq.status.SUBMITTING, qq.status.SUBMITTED, qq.status.PAUSED ] 2007 }).length; 2008 }, 2009 _getValidationBase: function(buttonId) { 2010 var extraButtonSpec = this._extraButtonSpecs[buttonId]; 2011 return extraButtonSpec ? extraButtonSpec.validation : this._options.validation; 2012 }, 2013 _getValidationDescriptor: function(fileWrapper) { 2014 if (fileWrapper.file instanceof qq.BlobProxy) { 2015 return { 2016 name: qq.getFilename(fileWrapper.file.referenceBlob), 2017 size: fileWrapper.file.referenceBlob.size 2018 }; 2019 } 2020 return { 2021 name: this.getUploads({ 2022 id: fileWrapper.id 2023 }).name, 2024 size: this.getUploads({ 2025 id: fileWrapper.id 2026 }).size 2027 }; 2028 }, 2029 _getValidationDescriptors: function(fileWrappers) { 2030 var self = this, fileDescriptors = []; 2031 qq.each(fileWrappers, function(idx, fileWrapper) { 2032 fileDescriptors.push(self._getValidationDescriptor(fileWrapper)); 2033 }); 2034 return fileDescriptors; 2035 }, 2036 _handleCameraAccess: function() { 2037 if (this._options.camera.ios && qq.ios()) { 2038 var acceptIosCamera = "image/*;capture=camera", button = this._options.camera.button, buttonId = button ? this._getButtonId(button) : this._defaultButtonId, optionRoot = this._options; 2039 if (buttonId && buttonId !== this._defaultButtonId) { 2040 optionRoot = this._extraButtonSpecs[buttonId]; 2041 } 2042 optionRoot.multiple = false; 2043 if (optionRoot.validation.acceptFiles === null) { 2044 optionRoot.validation.acceptFiles = acceptIosCamera; 2045 } else { 2046 optionRoot.validation.acceptFiles += "," + acceptIosCamera; 2047 } 2048 qq.each(this._buttons, function(idx, button) { 2049 if (button.getButtonId() === buttonId) { 2050 button.setMultiple(optionRoot.multiple); 2051 button.setAcceptFiles(optionRoot.acceptFiles); 2052 return false; 2053 } 2054 }); 2055 } 2056 }, 2057 _handleCheckedCallback: function(details) { 2058 var self = this, callbackRetVal = details.callback(); 2059 if (qq.isGenericPromise(callbackRetVal)) { 2060 this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier); 2061 return callbackRetVal.then(function(successParam) { 2062 self.log(details.name + " promise success for " + details.identifier); 2063 details.onSuccess(successParam); 2064 }, function() { 2065 if (details.onFailure) { 2066 self.log(details.name + " promise failure for " + details.identifier); 2067 details.onFailure(); 2068 } else { 2069 self.log(details.name + " promise failure for " + details.identifier); 2070 } 2071 }); 2072 } 2073 if (callbackRetVal !== false) { 2074 details.onSuccess(callbackRetVal); 2075 } else { 2076 if (details.onFailure) { 2077 this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback."); 2078 details.onFailure(); 2079 } else { 2080 this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed."); 2081 } 2082 } 2083 return callbackRetVal; 2084 }, 2085 _handleNewFile: function(file, batchId, newFileWrapperList) { 2086 var self = this, uuid = qq.getUniqueId(), size = -1, name = qq.getFilename(file), actualFile = file.blob || file, handler = this._customNewFileHandler ? this._customNewFileHandler : qq.bind(self._handleNewFileGeneric, self); 2087 if (!qq.isInput(actualFile) && actualFile.size >= 0) { 2088 size = actualFile.size; 2089 } 2090 handler(actualFile, name, uuid, size, newFileWrapperList, batchId, this._options.request.uuidName, { 2091 uploadData: self._uploadData, 2092 paramsStore: self._paramsStore, 2093 addFileToHandler: function(id, file) { 2094 self._handler.add(id, file); 2095 self._netUploadedOrQueued++; 2096 self._trackButton(id); 2097 } 2098 }); 2099 }, 2100 _handleNewFileGeneric: function(file, name, uuid, size, fileList, batchId) { 2101 var id = this._uploadData.addFile({ 2102 uuid: uuid, 2103 name: name, 2104 size: size, 2105 batchId: batchId, 2106 file: file 2107 }); 2108 this._handler.add(id, file); 2109 this._trackButton(id); 2110 this._netUploadedOrQueued++; 2111 fileList.push({ 2112 id: id, 2113 file: file 2114 }); 2115 }, 2116 _handlePasteSuccess: function(blob, extSuppliedName) { 2117 var extension = blob.type.split("/")[1], name = extSuppliedName; 2118 if (name == null) { 2119 name = this._options.paste.defaultName; 2120 } 2121 name += "." + extension; 2122 this.addFiles({ 2123 name: name, 2124 blob: blob 2125 }); 2126 }, 2127 _handleDeleteSuccess: function(id) { 2128 if (this.getUploads({ 2129 id: id 2130 }).status !== qq.status.DELETED) { 2131 var name = this.getName(id); 2132 this._netUploadedOrQueued--; 2133 this._netUploaded--; 2134 this._handler.expunge(id); 2135 this._uploadData.setStatus(id, qq.status.DELETED); 2136 this.log("Delete request for '" + name + "' has succeeded."); 2137 } 2138 }, 2139 _handleDeleteFailed: function(id, xhrOrXdr) { 2140 var name = this.getName(id); 2141 this._uploadData.setStatus(id, qq.status.DELETE_FAILED); 2142 this.log("Delete request for '" + name + "' has failed.", "error"); 2143 if (!xhrOrXdr || xhrOrXdr.withCredentials === undefined) { 2144 this._options.callbacks.onError(id, name, "Delete request failed", xhrOrXdr); 2145 } else { 2146 this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhrOrXdr.status, xhrOrXdr); 2147 } 2148 }, 2149 _initExtraButton: function(spec) { 2150 var button = this._createUploadButton({ 2151 accept: spec.validation.acceptFiles, 2152 allowedExtensions: spec.validation.allowedExtensions, 2153 element: spec.element, 2154 folders: spec.folders, 2155 multiple: spec.multiple, 2156 title: spec.fileInputTitle 2157 }); 2158 this._extraButtonSpecs[button.getButtonId()] = spec; 2159 }, 2160 _initFormSupportAndParams: function() { 2161 this._formSupport = qq.FormSupport && new qq.FormSupport(this._options.form, qq.bind(this.uploadStoredFiles, this), qq.bind(this.log, this)); 2162 if (this._formSupport && this._formSupport.attachedToForm) { 2163 this._paramsStore = this._createStore(this._options.request.params, this._formSupport.getFormInputsAsObject); 2164 this._options.autoUpload = this._formSupport.newAutoUpload; 2165 if (this._formSupport.newEndpoint) { 2166 this._options.request.endpoint = this._formSupport.newEndpoint; 2167 } 2168 } else { 2169 this._paramsStore = this._createStore(this._options.request.params); 2170 } 2171 }, 2172 _isDeletePossible: function() { 2173 if (!qq.DeleteFileAjaxRequester || !this._options.deleteFile.enabled) { 2174 return false; 2175 } 2176 if (this._options.cors.expected) { 2177 if (qq.supportedFeatures.deleteFileCorsXhr) { 2178 return true; 2179 } 2180 if (qq.supportedFeatures.deleteFileCorsXdr && this._options.cors.allowXdr) { 2181 return true; 2182 } 2183 return false; 2184 } 2185 return true; 2186 }, 2187 _isAllowedExtension: function(allowed, fileName) { 2188 var valid = false; 2189 if (!allowed.length) { 2190 return true; 2191 } 2192 qq.each(allowed, function(idx, allowedExt) { 2193 if (qq.isString(allowedExt)) { 2194 var extRegex = new RegExp("\\." + allowedExt + "$", "i"); 2195 if (fileName.match(extRegex) != null) { 2196 valid = true; 2197 return false; 2198 } 2199 } 2200 }); 2201 return valid; 2202 }, 2203 _itemError: function(code, maybeNameOrNames, item) { 2204 var message = this._options.messages[code], allowedExtensions = [], names = [].concat(maybeNameOrNames), name = names[0], buttonId = this._getButtonId(item), validationBase = this._getValidationBase(buttonId), extensionsForMessage, placeholderMatch; 2205 function r(name, replacement) { 2206 message = message.replace(name, replacement); 2207 } 2208 qq.each(validationBase.allowedExtensions, function(idx, allowedExtension) { 2209 if (qq.isString(allowedExtension)) { 2210 allowedExtensions.push(allowedExtension); 2211 } 2212 }); 2213 extensionsForMessage = allowedExtensions.join(", ").toLowerCase(); 2214 r("{file}", this._options.formatFileName(name)); 2215 r("{extensions}", extensionsForMessage); 2216 r("{sizeLimit}", this._formatSize(validationBase.sizeLimit)); 2217 r("{minSizeLimit}", this._formatSize(validationBase.minSizeLimit)); 2218 placeholderMatch = message.match(/(\{\w+\})/g); 2219 if (placeholderMatch !== null) { 2220 qq.each(placeholderMatch, function(idx, placeholder) { 2221 r(placeholder, names[idx]); 2222 }); 2223 } 2224 this._options.callbacks.onError(null, name, message, undefined); 2225 return message; 2226 }, 2227 _manualRetry: function(id, callback) { 2228 if (this._onBeforeManualRetry(id)) { 2229 this._netUploadedOrQueued++; 2230 this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING); 2231 if (callback) { 2232 callback(id); 2233 } else { 2234 this._handler.retry(id); 2235 } 2236 return true; 2237 } 2238 }, 2239 _maybeAllComplete: function(id, status) { 2240 var self = this, notFinished = this._getNotFinished(); 2241 if (status === qq.status.UPLOAD_SUCCESSFUL) { 2242 this._succeededSinceLastAllComplete.push(id); 2243 } else if (status === qq.status.UPLOAD_FAILED) { 2244 this._failedSinceLastAllComplete.push(id); 2245 } 2246 if (notFinished === 0 && (this._succeededSinceLastAllComplete.length || this._failedSinceLastAllComplete.length)) { 2247 setTimeout(function() { 2248 self._onAllComplete(self._succeededSinceLastAllComplete, self._failedSinceLastAllComplete); 2249 }, 0); 2250 } 2251 }, 2252 _maybeHandleIos8SafariWorkaround: function() { 2253 var self = this; 2254 if (this._options.workarounds.ios8SafariUploads && qq.ios800() && qq.iosSafari()) { 2255 setTimeout(function() { 2256 window.alert(self._options.messages.unsupportedBrowserIos8Safari); 2257 }, 0); 2258 throw new qq.Error(this._options.messages.unsupportedBrowserIos8Safari); 2259 } 2260 }, 2261 _maybeParseAndSendUploadError: function(id, name, response, xhr) { 2262 if (!response.success) { 2263 if (xhr && xhr.status !== 200 && !response.error) { 2264 this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr); 2265 } else { 2266 var errorReason = response.error ? response.error : this._options.text.defaultResponseError; 2267 this._options.callbacks.onError(id, name, errorReason, xhr); 2268 } 2269 } 2270 }, 2271 _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) { 2272 var self = this; 2273 if (items.length > index) { 2274 if (validItem || !this._options.validation.stopOnFirstInvalidFile) { 2275 setTimeout(function() { 2276 var validationDescriptor = self._getValidationDescriptor(items[index]), buttonId = self._getButtonId(items[index].file), button = self._getButton(buttonId); 2277 self._handleCheckedCallback({ 2278 name: "onValidate", 2279 callback: qq.bind(self._options.callbacks.onValidate, self, validationDescriptor, button), 2280 onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint), 2281 onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint), 2282 identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size 2283 }); 2284 }, 0); 2285 } else if (!validItem) { 2286 for (;index < items.length; index++) { 2287 self._fileOrBlobRejected(items[index].id); 2288 } 2289 } 2290 } 2291 }, 2292 _onAllComplete: function(successful, failed) { 2293 this._totalProgress && this._totalProgress.onAllComplete(successful, failed, this._preventRetries); 2294 this._options.callbacks.onAllComplete(qq.extend([], successful), qq.extend([], failed)); 2295 this._succeededSinceLastAllComplete = []; 2296 this._failedSinceLastAllComplete = []; 2297 }, 2298 _onAutoRetry: function(id, name, responseJSON, xhr, callback) { 2299 var self = this; 2300 self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty]; 2301 if (self._shouldAutoRetry(id)) { 2302 var retryWaitPeriod = self._options.retry.autoAttemptDelay * 1e3; 2303 self._maybeParseAndSendUploadError.apply(self, arguments); 2304 self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id]); 2305 self._onBeforeAutoRetry(id, name); 2306 self._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING); 2307 self._retryTimeouts[id] = setTimeout(function() { 2308 self.log("Starting retry for " + name + "..."); 2309 if (callback) { 2310 callback(id); 2311 } else { 2312 self._handler.retry(id); 2313 } 2314 }, retryWaitPeriod); 2315 return true; 2316 } 2317 }, 2318 _onBeforeAutoRetry: function(id, name) { 2319 this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "..."); 2320 }, 2321 _onBeforeManualRetry: function(id) { 2322 var itemLimit = this._currentItemLimit, fileName; 2323 if (this._preventRetries[id]) { 2324 this.log("Retries are forbidden for id " + id, "warn"); 2325 return false; 2326 } else if (this._handler.isValid(id)) { 2327 fileName = this.getName(id); 2328 if (this._options.callbacks.onManualRetry(id, fileName) === false) { 2329 return false; 2330 } 2331 if (itemLimit > 0 && this._netUploadedOrQueued + 1 > itemLimit) { 2332 this._itemError("retryFailTooManyItems"); 2333 return false; 2334 } 2335 this.log("Retrying upload for '" + fileName + "' (id: " + id + ")..."); 2336 return true; 2337 } else { 2338 this.log("'" + id + "' is not a valid file ID", "error"); 2339 return false; 2340 } 2341 }, 2342 _onCancel: function(id, name) { 2343 this._netUploadedOrQueued--; 2344 clearTimeout(this._retryTimeouts[id]); 2345 var storedItemIndex = qq.indexOf(this._storedIds, id); 2346 if (!this._options.autoUpload && storedItemIndex >= 0) { 2347 this._storedIds.splice(storedItemIndex, 1); 2348 } 2349 this._uploadData.setStatus(id, qq.status.CANCELED); 2350 }, 2351 _onComplete: function(id, name, result, xhr) { 2352 if (!result.success) { 2353 this._netUploadedOrQueued--; 2354 this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED); 2355 if (result[this._options.retry.preventRetryResponseProperty] === true) { 2356 this._preventRetries[id] = true; 2357 } 2358 } else { 2359 if (result.thumbnailUrl) { 2360 this._thumbnailUrls[id] = result.thumbnailUrl; 2361 } 2362 this._netUploaded++; 2363 this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL); 2364 } 2365 this._maybeParseAndSendUploadError(id, name, result, xhr); 2366 return result.success ? true : false; 2367 }, 2368 _onDelete: function(id) { 2369 this._uploadData.setStatus(id, qq.status.DELETING); 2370 }, 2371 _onDeleteComplete: function(id, xhrOrXdr, isError) { 2372 var name = this.getName(id); 2373 if (isError) { 2374 this._handleDeleteFailed(id, xhrOrXdr); 2375 } else { 2376 this._handleDeleteSuccess(id); 2377 } 2378 }, 2379 _onInputChange: function(input) { 2380 var fileIndex; 2381 if (qq.supportedFeatures.ajaxUploading) { 2382 for (fileIndex = 0; fileIndex < input.files.length; fileIndex++) { 2383 this._annotateWithButtonId(input.files[fileIndex], input); 2384 } 2385 this.addFiles(input.files); 2386 } else if (input.value.length > 0) { 2387 this.addFiles(input); 2388 } 2389 qq.each(this._buttons, function(idx, button) { 2390 button.reset(); 2391 }); 2392 }, 2393 _onProgress: function(id, name, loaded, total) { 2394 this._totalProgress && this._totalProgress.onIndividualProgress(id, loaded, total); 2395 }, 2396 _onSubmit: function(id, name) {}, 2397 _onSubmitCallbackSuccess: function(id, name) { 2398 this._onSubmit.apply(this, arguments); 2399 this._uploadData.setStatus(id, qq.status.SUBMITTED); 2400 this._onSubmitted.apply(this, arguments); 2401 if (this._options.autoUpload) { 2402 this._options.callbacks.onSubmitted.apply(this, arguments); 2403 this._uploadFile(id); 2404 } else { 2405 this._storeForLater(id); 2406 this._options.callbacks.onSubmitted.apply(this, arguments); 2407 } 2408 }, 2409 _onSubmitDelete: function(id, onSuccessCallback, additionalMandatedParams) { 2410 var uuid = this.getUuid(id), adjustedOnSuccessCallback; 2411 if (onSuccessCallback) { 2412 adjustedOnSuccessCallback = qq.bind(onSuccessCallback, this, id, uuid, additionalMandatedParams); 2413 } 2414 if (this._isDeletePossible()) { 2415 this._handleCheckedCallback({ 2416 name: "onSubmitDelete", 2417 callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id), 2418 onSuccess: adjustedOnSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, uuid, additionalMandatedParams), 2419 identifier: id 2420 }); 2421 return true; 2422 } else { 2423 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"); 2424 return false; 2425 } 2426 }, 2427 _onSubmitted: function(id) {}, 2428 _onTotalProgress: function(loaded, total) { 2429 this._options.callbacks.onTotalProgress(loaded, total); 2430 }, 2431 _onUploadPrep: function(id) {}, 2432 _onUpload: function(id, name) { 2433 this._uploadData.setStatus(id, qq.status.UPLOADING); 2434 }, 2435 _onUploadChunk: function(id, chunkData) {}, 2436 _onUploadChunkSuccess: function(id, chunkData) { 2437 if (!this._preventRetries[id] && this._options.retry.enableAuto) { 2438 this._autoRetries[id] = 0; 2439 } 2440 }, 2441 _onUploadStatusChange: function(id, oldStatus, newStatus) { 2442 if (newStatus === qq.status.PAUSED) { 2443 clearTimeout(this._retryTimeouts[id]); 2444 } 2445 }, 2446 _onValidateBatchCallbackFailure: function(fileWrappers) { 2447 var self = this; 2448 qq.each(fileWrappers, function(idx, fileWrapper) { 2449 self._fileOrBlobRejected(fileWrapper.id); 2450 }); 2451 }, 2452 _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint, button) { 2453 var errorMessage, itemLimit = this._currentItemLimit, proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued; 2454 if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) { 2455 if (items.length > 0) { 2456 this._handleCheckedCallback({ 2457 name: "onValidate", 2458 callback: qq.bind(this._options.callbacks.onValidate, this, validationDescriptors[0], button), 2459 onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint), 2460 onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint), 2461 identifier: "Item '" + items[0].file.name + "', size: " + items[0].file.size 2462 }); 2463 } else { 2464 this._itemError("noFilesError"); 2465 } 2466 } else { 2467 this._onValidateBatchCallbackFailure(items); 2468 errorMessage = this._options.messages.tooManyItemsError.replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued).replace(/\{itemLimit\}/g, itemLimit); 2469 this._batchError(errorMessage); 2470 } 2471 }, 2472 _onValidateCallbackFailure: function(items, index, params, endpoint) { 2473 var nextIndex = index + 1; 2474 this._fileOrBlobRejected(items[index].id, items[index].file.name); 2475 this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint); 2476 }, 2477 _onValidateCallbackSuccess: function(items, index, params, endpoint) { 2478 var self = this, nextIndex = index + 1, validationDescriptor = this._getValidationDescriptor(items[index]); 2479 this._validateFileOrBlobData(items[index], validationDescriptor).then(function() { 2480 self._upload(items[index].id, params, endpoint); 2481 self._maybeProcessNextItemAfterOnValidateCallback(true, items, nextIndex, params, endpoint); 2482 }, function() { 2483 self._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint); 2484 }); 2485 }, 2486 _prepareItemsForUpload: function(items, params, endpoint) { 2487 if (items.length === 0) { 2488 this._itemError("noFilesError"); 2489 return; 2490 } 2491 var validationDescriptors = this._getValidationDescriptors(items), buttonId = this._getButtonId(items[0].file), button = this._getButton(buttonId); 2492 this._handleCheckedCallback({ 2493 name: "onValidateBatch", 2494 callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors, button), 2495 onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint, button), 2496 onFailure: qq.bind(this._onValidateBatchCallbackFailure, this, items), 2497 identifier: "batch validation" 2498 }); 2499 }, 2500 _preventLeaveInProgress: function() { 2501 var self = this; 2502 this._disposeSupport.attach(window, "beforeunload", function(e) { 2503 if (self.getInProgress()) { 2504 e = e || window.event; 2505 e.returnValue = self._options.messages.onLeave; 2506 return self._options.messages.onLeave; 2507 } 2508 }); 2509 }, 2510 _refreshSessionData: function() { 2511 var self = this, options = this._options.session; 2512 if (qq.Session && this._options.session.endpoint != null) { 2513 if (!this._session) { 2514 qq.extend(options, { 2515 cors: this._options.cors 2516 }); 2517 options.log = qq.bind(this.log, this); 2518 options.addFileRecord = qq.bind(this._addCannedFile, this); 2519 this._session = new qq.Session(options); 2520 } 2521 setTimeout(function() { 2522 self._session.refresh().then(function(response, xhrOrXdr) { 2523 self._sessionRequestComplete(); 2524 self._options.callbacks.onSessionRequestComplete(response, true, xhrOrXdr); 2525 }, function(response, xhrOrXdr) { 2526 self._options.callbacks.onSessionRequestComplete(response, false, xhrOrXdr); 2527 }); 2528 }, 0); 2529 } 2530 }, 2531 _sessionRequestComplete: function() {}, 2532 _setSize: function(id, newSize) { 2533 this._uploadData.updateSize(id, newSize); 2534 this._totalProgress && this._totalProgress.onNewSize(id); 2535 }, 2536 _shouldAutoRetry: function(id) { 2537 var uploadData = this._uploadData.retrieve({ 2538 id: id 2539 }); 2540 if (!this._preventRetries[id] && this._options.retry.enableAuto && uploadData.status !== qq.status.PAUSED) { 2541 if (this._autoRetries[id] === undefined) { 2542 this._autoRetries[id] = 0; 2543 } 2544 if (this._autoRetries[id] < this._options.retry.maxAutoAttempts) { 2545 this._autoRetries[id] += 1; 2546 return true; 2547 } 2548 } 2549 return false; 2550 }, 2551 _storeForLater: function(id) { 2552 this._storedIds.push(id); 2553 }, 2554 _trackButton: function(id) { 2555 var buttonId; 2556 if (qq.supportedFeatures.ajaxUploading) { 2557 buttonId = this._handler.getFile(id).qqButtonId; 2558 } else { 2559 buttonId = this._getButtonId(this._handler.getInput(id)); 2560 } 2561 if (buttonId) { 2562 this._buttonIdsForFileIds[id] = buttonId; 2563 } 2564 }, 2565 _updateFormSupportAndParams: function(formElementOrId) { 2566 this._options.form.element = formElementOrId; 2567 this._formSupport = qq.FormSupport && new qq.FormSupport(this._options.form, qq.bind(this.uploadStoredFiles, this), qq.bind(this.log, this)); 2568 if (this._formSupport && this._formSupport.attachedToForm) { 2569 this._paramsStore.addReadOnly(null, this._formSupport.getFormInputsAsObject); 2570 this._options.autoUpload = this._formSupport.newAutoUpload; 2571 if (this._formSupport.newEndpoint) { 2572 this.setEndpoint(this._formSupport.newEndpoint); 2573 } 2574 } 2575 }, 2576 _upload: function(id, params, endpoint) { 2577 var name = this.getName(id); 2578 if (params) { 2579 this.setParams(params, id); 2580 } 2581 if (endpoint) { 2582 this.setEndpoint(endpoint, id); 2583 } 2584 this._handleCheckedCallback({ 2585 name: "onSubmit", 2586 callback: qq.bind(this._options.callbacks.onSubmit, this, id, name), 2587 onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name), 2588 onFailure: qq.bind(this._fileOrBlobRejected, this, id, name), 2589 identifier: id 2590 }); 2591 }, 2592 _uploadFile: function(id) { 2593 if (!this._handler.upload(id)) { 2594 this._uploadData.setStatus(id, qq.status.QUEUED); 2595 } 2596 }, 2597 _uploadStoredFiles: function() { 2598 var idToUpload, stillSubmitting, self = this; 2599 while (this._storedIds.length) { 2600 idToUpload = this._storedIds.shift(); 2601 this._uploadFile(idToUpload); 2602 } 2603 stillSubmitting = this.getUploads({ 2604 status: qq.status.SUBMITTING 2605 }).length; 2606 if (stillSubmitting) { 2607 qq.log("Still waiting for " + stillSubmitting + " files to clear submit queue. Will re-parse stored IDs array shortly."); 2608 setTimeout(function() { 2609 self._uploadStoredFiles(); 2610 }, 1e3); 2611 } 2612 }, 2613 _validateFileOrBlobData: function(fileWrapper, validationDescriptor) { 2614 var self = this, file = function() { 2615 if (fileWrapper.file instanceof qq.BlobProxy) { 2616 return fileWrapper.file.referenceBlob; 2617 } 2618 return fileWrapper.file; 2619 }(), name = validationDescriptor.name, size = validationDescriptor.size, buttonId = this._getButtonId(fileWrapper.file), validationBase = this._getValidationBase(buttonId), validityChecker = new qq.Promise(); 2620 validityChecker.then(function() {}, function() { 2621 self._fileOrBlobRejected(fileWrapper.id, name); 2622 }); 2623 if (qq.isFileOrInput(file) && !this._isAllowedExtension(validationBase.allowedExtensions, name)) { 2624 this._itemError("typeError", name, file); 2625 return validityChecker.failure(); 2626 } 2627 if (!this._options.validation.allowEmpty && size === 0) { 2628 this._itemError("emptyError", name, file); 2629 return validityChecker.failure(); 2630 } 2631 if (size > 0 && validationBase.sizeLimit && size > validationBase.sizeLimit) { 2632 this._itemError("sizeError", name, file); 2633 return validityChecker.failure(); 2634 } 2635 if (size > 0 && size < validationBase.minSizeLimit) { 2636 this._itemError("minSizeError", name, file); 2637 return validityChecker.failure(); 2638 } 2639 if (qq.ImageValidation && qq.supportedFeatures.imagePreviews && qq.isFile(file)) { 2640 new qq.ImageValidation(file, qq.bind(self.log, self)).validate(validationBase.image).then(validityChecker.success, function(errorCode) { 2641 self._itemError(errorCode + "ImageError", name, file); 2642 validityChecker.failure(); 2643 }); 2644 } else { 2645 validityChecker.success(); 2646 } 2647 return validityChecker; 2648 }, 2649 _wrapCallbacks: function() { 2650 var self, safeCallback, prop; 2651 self = this; 2652 safeCallback = function(name, callback, args) { 2653 var errorMsg; 2654 try { 2655 return callback.apply(self, args); 2656 } catch (exception) { 2657 errorMsg = exception.message || exception.toString(); 2658 self.log("Caught exception in '" + name + "' callback - " + errorMsg, "error"); 2659 } 2660 }; 2661 for (prop in this._options.callbacks) { 2662 (function() { 2663 var callbackName, callbackFunc; 2664 callbackName = prop; 2665 callbackFunc = self._options.callbacks[callbackName]; 2666 self._options.callbacks[callbackName] = function() { 2667 return safeCallback(callbackName, callbackFunc, arguments); 2668 }; 2669 })(); 2670 } 2671 } 2672 }; 2673 })(); 2674 (function() { 2675 "use strict"; 2676 qq.FineUploaderBasic = function(o) { 2677 var self = this; 2678 this._options = { 2679 debug: false, 2680 button: null, 2681 multiple: true, 2682 maxConnections: 3, 2683 disableCancelForFormUploads: false, 2684 autoUpload: true, 2685 warnBeforeUnload: true, 2686 request: { 2687 customHeaders: {}, 2688 endpoint: "/server/upload", 2689 filenameParam: "qqfilename", 2690 forceMultipart: true, 2691 inputName: "qqfile", 2692 method: "POST", 2693 omitDefaultParams: false, 2694 params: {}, 2695 paramsInBody: true, 2696 requireSuccessJson: true, 2697 totalFileSizeName: "qqtotalfilesize", 2698 uuidName: "qquuid" 2699 }, 2700 validation: { 2701 allowedExtensions: [], 2702 sizeLimit: 0, 2703 minSizeLimit: 0, 2704 itemLimit: 0, 2705 stopOnFirstInvalidFile: true, 2706 acceptFiles: null, 2707 image: { 2708 maxHeight: 0, 2709 maxWidth: 0, 2710 minHeight: 0, 2711 minWidth: 0 2712 }, 2713 allowEmpty: false 2714 }, 2715 callbacks: { 2716 onSubmit: function(id, name) {}, 2717 onSubmitted: function(id, name) {}, 2718 onComplete: function(id, name, responseJSON, maybeXhr) {}, 2719 onAllComplete: function(successful, failed) {}, 2720 onCancel: function(id, name) {}, 2721 onUpload: function(id, name) {}, 2722 onUploadChunk: function(id, name, chunkData) {}, 2723 onUploadChunkSuccess: function(id, chunkData, responseJSON, xhr) {}, 2724 onResume: function(id, fileName, chunkData, customResumeData) {}, 2725 onProgress: function(id, name, loaded, total) {}, 2726 onTotalProgress: function(loaded, total) {}, 2727 onError: function(id, name, reason, maybeXhrOrXdr) {}, 2728 onAutoRetry: function(id, name, attemptNumber) {}, 2729 onManualRetry: function(id, name) {}, 2730 onValidateBatch: function(fileOrBlobData) {}, 2731 onValidate: function(fileOrBlobData) {}, 2732 onSubmitDelete: function(id) {}, 2733 onDelete: function(id) {}, 2734 onDeleteComplete: function(id, xhrOrXdr, isError) {}, 2735 onPasteReceived: function(blob) {}, 2736 onStatusChange: function(id, oldStatus, newStatus) {}, 2737 onSessionRequestComplete: function(response, success, xhrOrXdr) {} 2738 }, 2739 messages: { 2740 typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.", 2741 sizeError: "{file} is too large, maximum file size is {sizeLimit}.", 2742 minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.", 2743 emptyError: "{file} is empty, please select files again without it.", 2744 noFilesError: "No files to upload.", 2745 tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.", 2746 maxHeightImageError: "Image is too tall.", 2747 maxWidthImageError: "Image is too wide.", 2748 minHeightImageError: "Image is not tall enough.", 2749 minWidthImageError: "Image is not wide enough.", 2750 retryFailTooManyItems: "Retry failed - you have reached your file limit.", 2751 onLeave: "The files are being uploaded, if you leave now the upload will be canceled.", 2752 unsupportedBrowserIos8Safari: "Unrecoverable error - this browser does not permit file uploading of any kind due to serious bugs in iOS8 Safari. Please use iOS8 Chrome until Apple fixes these issues." 2753 }, 2754 retry: { 2755 enableAuto: false, 2756 maxAutoAttempts: 3, 2757 autoAttemptDelay: 5, 2758 preventRetryResponseProperty: "preventRetry" 2759 }, 2760 classes: { 2761 buttonHover: "qq-upload-button-hover", 2762 buttonFocus: "qq-upload-button-focus" 2763 }, 2764 chunking: { 2765 enabled: false, 2766 concurrent: { 2767 enabled: false 2768 }, 2769 mandatory: false, 2770 paramNames: { 2771 partIndex: "qqpartindex", 2772 partByteOffset: "qqpartbyteoffset", 2773 chunkSize: "qqchunksize", 2774 totalFileSize: "qqtotalfilesize", 2775 totalParts: "qqtotalparts" 2776 }, 2777 partSize: function(id) { 2778 return 2e6; 2779 }, 2780 success: { 2781 endpoint: null, 2782 headers: function(id) { 2783 return null; 2784 }, 2785 jsonPayload: false, 2786 method: "POST", 2787 params: function(id) { 2788 return null; 2789 }, 2790 resetOnStatus: [] 2791 } 2792 }, 2793 resume: { 2794 enabled: false, 2795 recordsExpireIn: 7, 2796 paramNames: { 2797 resuming: "qqresume" 2798 }, 2799 customKeys: function(fileId) { 2800 return []; 2801 } 2802 }, 2803 formatFileName: function(fileOrBlobName) { 2804 return fileOrBlobName; 2805 }, 2806 text: { 2807 defaultResponseError: "Upload failure reason unknown", 2808 fileInputTitle: "file input", 2809 sizeSymbols: [ "kB", "MB", "GB", "TB", "PB", "EB" ] 2810 }, 2811 deleteFile: { 2812 enabled: false, 2813 method: "DELETE", 2814 endpoint: "/server/upload", 2815 customHeaders: {}, 2816 params: {} 2817 }, 2818 cors: { 2819 expected: false, 2820 sendCredentials: false, 2821 allowXdr: false 2822 }, 2823 blobs: { 2824 defaultName: "misc_data" 2825 }, 2826 paste: { 2827 targetElement: null, 2828 defaultName: "pasted_image" 2829 }, 2830 camera: { 2831 ios: false, 2832 button: null 2833 }, 2834 extraButtons: [], 2835 session: { 2836 endpoint: null, 2837 params: {}, 2838 customHeaders: {}, 2839 refreshOnReset: true 2840 }, 2841 form: { 2842 element: "qq-form", 2843 autoUpload: false, 2844 interceptSubmit: true 2845 }, 2846 scaling: { 2847 customResizer: null, 2848 sendOriginal: true, 2849 orient: true, 2850 defaultType: null, 2851 defaultQuality: 80, 2852 failureText: "Failed to scale", 2853 includeExif: false, 2854 sizes: [] 2855 }, 2856 workarounds: { 2857 iosEmptyVideos: true, 2858 ios8SafariUploads: true, 2859 ios8BrowserCrash: false 2860 } 2861 }; 2862 qq.extend(this._options, o, true); 2863 this._buttons = []; 2864 this._extraButtonSpecs = {}; 2865 this._buttonIdsForFileIds = []; 2866 this._wrapCallbacks(); 2867 this._disposeSupport = new qq.DisposeSupport(); 2868 this._storedIds = []; 2869 this._autoRetries = []; 2870 this._retryTimeouts = []; 2871 this._preventRetries = []; 2872 this._thumbnailUrls = []; 2873 this._netUploadedOrQueued = 0; 2874 this._netUploaded = 0; 2875 this._uploadData = this._createUploadDataTracker(); 2876 this._initFormSupportAndParams(); 2877 this._customHeadersStore = this._createStore(this._options.request.customHeaders); 2878 this._deleteFileCustomHeadersStore = this._createStore(this._options.deleteFile.customHeaders); 2879 this._deleteFileParamsStore = this._createStore(this._options.deleteFile.params); 2880 this._endpointStore = this._createStore(this._options.request.endpoint); 2881 this._deleteFileEndpointStore = this._createStore(this._options.deleteFile.endpoint); 2882 this._handler = this._createUploadHandler(); 2883 this._deleteHandler = qq.DeleteFileAjaxRequester && this._createDeleteHandler(); 2884 if (this._options.button) { 2885 this._defaultButtonId = this._createUploadButton({ 2886 element: this._options.button, 2887 title: this._options.text.fileInputTitle 2888 }).getButtonId(); 2889 } 2890 this._generateExtraButtonSpecs(); 2891 this._handleCameraAccess(); 2892 if (this._options.paste.targetElement) { 2893 if (qq.PasteSupport) { 2894 this._pasteHandler = this._createPasteHandler(); 2895 } else { 2896 this.log("Paste support module not found", "error"); 2897 } 2898 } 2899 this._options.warnBeforeUnload && this._preventLeaveInProgress(); 2900 this._imageGenerator = qq.ImageGenerator && new qq.ImageGenerator(qq.bind(this.log, this)); 2901 this._refreshSessionData(); 2902 this._succeededSinceLastAllComplete = []; 2903 this._failedSinceLastAllComplete = []; 2904 this._scaler = qq.Scaler && new qq.Scaler(this._options.scaling, qq.bind(this.log, this)) || {}; 2905 if (this._scaler.enabled) { 2906 this._customNewFileHandler = qq.bind(this._scaler.handleNewFile, this._scaler); 2907 } 2908 if (qq.TotalProgress && qq.supportedFeatures.progressBar) { 2909 this._totalProgress = new qq.TotalProgress(qq.bind(this._onTotalProgress, this), function(id) { 2910 var entry = self._uploadData.retrieve({ 2911 id: id 2912 }); 2913 return entry && entry.size || 0; 2914 }); 2915 } 2916 this._currentItemLimit = this._options.validation.itemLimit; 2917 this._customResumeDataStore = this._createStore(); 2918 }; 2919 qq.FineUploaderBasic.prototype = qq.basePublicApi; 2920 qq.extend(qq.FineUploaderBasic.prototype, qq.basePrivateApi); 2921 })(); 2922 qq.AjaxRequester = function(o) { 2923 "use strict"; 2924 var log, shouldParamsBeInQueryString, queue = [], requestData = {}, options = { 2925 acceptHeader: null, 2926 validMethods: [ "PATCH", "POST", "PUT" ], 2927 method: "POST", 2928 contentType: "application/x-www-form-urlencoded", 2929 maxConnections: 3, 2930 customHeaders: {}, 2931 endpointStore: {}, 2932 paramsStore: {}, 2933 mandatedParams: {}, 2934 allowXRequestedWithAndCacheControl: true, 2935 successfulResponseCodes: { 2936 DELETE: [ 200, 202, 204 ], 2937 PATCH: [ 200, 201, 202, 203, 204 ], 2938 POST: [ 200, 201, 202, 203, 204 ], 2939 PUT: [ 200, 201, 202, 203, 204 ], 2940 GET: [ 200 ] 2941 }, 2942 cors: { 2943 expected: false, 2944 sendCredentials: false 2945 }, 2946 log: function(str, level) {}, 2947 onSend: function(id) {}, 2948 onComplete: function(id, xhrOrXdr, isError) {}, 2949 onProgress: null 2950 }; 2951 qq.extend(options, o); 2952 log = options.log; 2953 if (qq.indexOf(options.validMethods, options.method) < 0) { 2954 throw new Error("'" + options.method + "' is not a supported method for this type of request!"); 2955 } 2956 function isSimpleMethod() { 2957 return qq.indexOf([ "GET", "POST", "HEAD" ], options.method) >= 0; 2958 } 2959 function containsNonSimpleHeaders(headers) { 2960 var containsNonSimple = false; 2961 qq.each(containsNonSimple, function(idx, header) { 2962 if (qq.indexOf([ "Accept", "Accept-Language", "Content-Language", "Content-Type" ], header) < 0) { 2963 containsNonSimple = true; 2964 return false; 2965 } 2966 }); 2967 return containsNonSimple; 2968 } 2969 function isXdr(xhr) { 2970 return options.cors.expected && xhr.withCredentials === undefined; 2971 } 2972 function getCorsAjaxTransport() { 2973 var xhrOrXdr; 2974 if (window.XMLHttpRequest || window.ActiveXObject) { 2975 xhrOrXdr = qq.createXhrInstance(); 2976 if (xhrOrXdr.withCredentials === undefined) { 2977 xhrOrXdr = new XDomainRequest(); 2978 xhrOrXdr.onload = function() {}; 2979 xhrOrXdr.onerror = function() {}; 2980 xhrOrXdr.ontimeout = function() {}; 2981 xhrOrXdr.onprogress = function() {}; 2982 } 2983 } 2984 return xhrOrXdr; 2985 } 2986 function getXhrOrXdr(id, suppliedXhr) { 2987 var xhrOrXdr = requestData[id] && requestData[id].xhr; 2988 if (!xhrOrXdr) { 2989 if (suppliedXhr) { 2990 xhrOrXdr = suppliedXhr; 2991 } else { 2992 if (options.cors.expected) { 2993 xhrOrXdr = getCorsAjaxTransport(); 2994 } else { 2995 xhrOrXdr = qq.createXhrInstance(); 2996 } 2997 } 2998 requestData[id].xhr = xhrOrXdr; 2999 } 3000 return xhrOrXdr; 3001 } 3002 function dequeue(id) { 3003 var i = qq.indexOf(queue, id), max = options.maxConnections, nextId; 3004 delete requestData[id]; 3005 queue.splice(i, 1); 3006 if (queue.length >= max && i < max) { 3007 nextId = queue[max - 1]; 3008 sendRequest(nextId); 3009 } 3010 } 3011 function onComplete(id, xdrError) { 3012 var xhr = getXhrOrXdr(id), method = options.method, isError = xdrError === true; 3013 dequeue(id); 3014 if (isError) { 3015 log(method + " request for " + id + " has failed", "error"); 3016 } else if (!isXdr(xhr) && !isResponseSuccessful(xhr.status)) { 3017 isError = true; 3018 log(method + " request for " + id + " has failed - response code " + xhr.status, "error"); 3019 } 3020 options.onComplete(id, xhr, isError); 3021 } 3022 function getParams(id) { 3023 var onDemandParams = requestData[id].additionalParams, mandatedParams = options.mandatedParams, params; 3024 if (options.paramsStore.get) { 3025 params = options.paramsStore.get(id); 3026 } 3027 if (onDemandParams) { 3028 qq.each(onDemandParams, function(name, val) { 3029 params = params || {}; 3030 params[name] = val; 3031 }); 3032 } 3033 if (mandatedParams) { 3034 qq.each(mandatedParams, function(name, val) { 3035 params = params || {}; 3036 params[name] = val; 3037 }); 3038 } 3039 return params; 3040 } 3041 function sendRequest(id, optXhr) { 3042 var xhr = getXhrOrXdr(id, optXhr), method = options.method, params = getParams(id), payload = requestData[id].payload, url; 3043 options.onSend(id); 3044 url = createUrl(id, params, requestData[id].additionalQueryParams); 3045 if (isXdr(xhr)) { 3046 xhr.onload = getXdrLoadHandler(id); 3047 xhr.onerror = getXdrErrorHandler(id); 3048 } else { 3049 xhr.onreadystatechange = getXhrReadyStateChangeHandler(id); 3050 } 3051 registerForUploadProgress(id); 3052 xhr.open(method, url, true); 3053 if (options.cors.expected && options.cors.sendCredentials && !isXdr(xhr)) { 3054 xhr.withCredentials = true; 3055 } 3056 setHeaders(id); 3057 log("Sending " + method + " request for " + id); 3058 if (payload) { 3059 xhr.send(payload); 3060 } else if (shouldParamsBeInQueryString || !params) { 3061 xhr.send(); 3062 } else if (params && options.contentType && options.contentType.toLowerCase().indexOf("application/x-www-form-urlencoded") >= 0) { 3063 xhr.send(qq.obj2url(params, "")); 3064 } else if (params && options.contentType && options.contentType.toLowerCase().indexOf("application/json") >= 0) { 3065 xhr.send(JSON.stringify(params)); 3066 } else { 3067 xhr.send(params); 3068 } 3069 return xhr; 3070 } 3071 function createUrl(id, params, additionalQueryParams) { 3072 var endpoint = options.endpointStore.get(id), addToPath = requestData[id].addToPath; 3073 if (addToPath != undefined) { 3074 endpoint += "/" + addToPath; 3075 } 3076 if (shouldParamsBeInQueryString && params) { 3077 endpoint = qq.obj2url(params, endpoint); 3078 } 3079 if (additionalQueryParams) { 3080 endpoint = qq.obj2url(additionalQueryParams, endpoint); 3081 } 3082 return endpoint; 3083 } 3084 function getXhrReadyStateChangeHandler(id) { 3085 return function() { 3086 if (getXhrOrXdr(id).readyState === 4) { 3087 onComplete(id); 3088 } 3089 }; 3090 } 3091 function registerForUploadProgress(id) { 3092 var onProgress = options.onProgress; 3093 if (onProgress) { 3094 getXhrOrXdr(id).upload.onprogress = function(e) { 3095 if (e.lengthComputable) { 3096 onProgress(id, e.loaded, e.total); 3097 } 3098 }; 3099 } 3100 } 3101 function getXdrLoadHandler(id) { 3102 return function() { 3103 onComplete(id); 3104 }; 3105 } 3106 function getXdrErrorHandler(id) { 3107 return function() { 3108 onComplete(id, true); 3109 }; 3110 } 3111 function setHeaders(id) { 3112 var xhr = getXhrOrXdr(id), customHeaders = options.customHeaders, onDemandHeaders = requestData[id].additionalHeaders || {}, method = options.method, allHeaders = {}; 3113 if (!isXdr(xhr)) { 3114 options.acceptHeader && xhr.setRequestHeader("Accept", options.acceptHeader); 3115 if (options.allowXRequestedWithAndCacheControl) { 3116 if (!options.cors.expected || (!isSimpleMethod() || containsNonSimpleHeaders(customHeaders))) { 3117 xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); 3118 xhr.setRequestHeader("Cache-Control", "no-cache"); 3119 } 3120 } 3121 if (options.contentType && (method === "POST" || method === "PUT")) { 3122 xhr.setRequestHeader("Content-Type", options.contentType); 3123 } 3124 qq.extend(allHeaders, qq.isFunction(customHeaders) ? customHeaders(id) : customHeaders); 3125 qq.extend(allHeaders, onDemandHeaders); 3126 qq.each(allHeaders, function(name, val) { 3127 xhr.setRequestHeader(name, val); 3128 }); 3129 } 3130 } 3131 function isResponseSuccessful(responseCode) { 3132 return qq.indexOf(options.successfulResponseCodes[options.method], responseCode) >= 0; 3133 } 3134 function prepareToSend(id, optXhr, addToPath, additionalParams, additionalQueryParams, additionalHeaders, payload) { 3135 requestData[id] = { 3136 addToPath: addToPath, 3137 additionalParams: additionalParams, 3138 additionalQueryParams: additionalQueryParams, 3139 additionalHeaders: additionalHeaders, 3140 payload: payload 3141 }; 3142 var len = queue.push(id); 3143 if (len <= options.maxConnections) { 3144 return sendRequest(id, optXhr); 3145 } 3146 } 3147 shouldParamsBeInQueryString = options.method === "GET" || options.method === "DELETE"; 3148 qq.extend(this, { 3149 initTransport: function(id) { 3150 var path, params, headers, payload, cacheBuster, additionalQueryParams; 3151 return { 3152 withPath: function(appendToPath) { 3153 path = appendToPath; 3154 return this; 3155 }, 3156 withParams: function(additionalParams) { 3157 params = additionalParams; 3158 return this; 3159 }, 3160 withQueryParams: function(_additionalQueryParams_) { 3161 additionalQueryParams = _additionalQueryParams_; 3162 return this; 3163 }, 3164 withHeaders: function(additionalHeaders) { 3165 headers = additionalHeaders; 3166 return this; 3167 }, 3168 withPayload: function(thePayload) { 3169 payload = thePayload; 3170 return this; 3171 }, 3172 withCacheBuster: function() { 3173 cacheBuster = true; 3174 return this; 3175 }, 3176 send: function(optXhr) { 3177 if (cacheBuster && qq.indexOf([ "GET", "DELETE" ], options.method) >= 0) { 3178 params.qqtimestamp = new Date().getTime(); 3179 } 3180 return prepareToSend(id, optXhr, path, params, additionalQueryParams, headers, payload); 3181 } 3182 }; 3183 }, 3184 canceled: function(id) { 3185 dequeue(id); 3186 } 3187 }); 3188 }; 3189 qq.UploadHandler = function(spec) { 3190 "use strict"; 3191 var proxy = spec.proxy, fileState = {}, onCancel = proxy.onCancel, getName = proxy.getName; 3192 qq.extend(this, { 3193 add: function(id, fileItem) { 3194 fileState[id] = fileItem; 3195 fileState[id].temp = {}; 3196 }, 3197 cancel: function(id) { 3198 var self = this, cancelFinalizationEffort = new qq.Promise(), onCancelRetVal = onCancel(id, getName(id), cancelFinalizationEffort); 3199 onCancelRetVal.then(function() { 3200 if (self.isValid(id)) { 3201 fileState[id].canceled = true; 3202 self.expunge(id); 3203 } 3204 cancelFinalizationEffort.success(); 3205 }); 3206 }, 3207 expunge: function(id) { 3208 delete fileState[id]; 3209 }, 3210 getThirdPartyFileId: function(id) { 3211 return fileState[id].key; 3212 }, 3213 isValid: function(id) { 3214 return fileState[id] !== undefined; 3215 }, 3216 reset: function() { 3217 fileState = {}; 3218 }, 3219 _getFileState: function(id) { 3220 return fileState[id]; 3221 }, 3222 _setThirdPartyFileId: function(id, thirdPartyFileId) { 3223 fileState[id].key = thirdPartyFileId; 3224 }, 3225 _wasCanceled: function(id) { 3226 return !!fileState[id].canceled; 3227 } 3228 }); 3229 }; 3230 qq.UploadHandlerController = function(o, namespace) { 3231 "use strict"; 3232 var controller = this, chunkingPossible = false, concurrentChunkingPossible = false, chunking, preventRetryResponse, log, handler, options = { 3233 paramsStore: {}, 3234 maxConnections: 3, 3235 chunking: { 3236 enabled: false, 3237 multiple: { 3238 enabled: false 3239 } 3240 }, 3241 log: function(str, level) {}, 3242 onProgress: function(id, fileName, loaded, total) {}, 3243 onComplete: function(id, fileName, response, xhr) {}, 3244 onCancel: function(id, fileName) {}, 3245 onUploadPrep: function(id) {}, 3246 onUpload: function(id, fileName) {}, 3247 onUploadChunk: function(id, fileName, chunkData) {}, 3248 onUploadChunkSuccess: function(id, chunkData, response, xhr) {}, 3249 onAutoRetry: function(id, fileName, response, xhr) {}, 3250 onResume: function(id, fileName, chunkData, customResumeData) {}, 3251 onUuidChanged: function(id, newUuid) {}, 3252 getName: function(id) {}, 3253 setSize: function(id, newSize) {}, 3254 isQueued: function(id) {}, 3255 getIdsInProxyGroup: function(id) {}, 3256 getIdsInBatch: function(id) {}, 3257 isInProgress: function(id) {} 3258 }, chunked = { 3259 done: function(id, chunkIdx, response, xhr) { 3260 var chunkData = handler._getChunkData(id, chunkIdx); 3261 handler._getFileState(id).attemptingResume = false; 3262 delete handler._getFileState(id).temp.chunkProgress[chunkIdx]; 3263 handler._getFileState(id).loaded += chunkData.size; 3264 options.onUploadChunkSuccess(id, handler._getChunkDataForCallback(chunkData), response, xhr); 3265 }, 3266 finalize: function(id) { 3267 var size = options.getSize(id), name = options.getName(id); 3268 log("All chunks have been uploaded for " + id + " - finalizing...."); 3269 handler.finalizeChunks(id).then(function(response, xhr) { 3270 log("Finalize successful for " + id); 3271 var normaizedResponse = upload.normalizeResponse(response, true); 3272 options.onProgress(id, name, size, size); 3273 handler._maybeDeletePersistedChunkData(id); 3274 upload.cleanup(id, normaizedResponse, xhr); 3275 }, function(response, xhr) { 3276 var normalizedResponse = upload.normalizeResponse(response, false); 3277 log("Problem finalizing chunks for file ID " + id + " - " + normalizedResponse.error, "error"); 3278 if (normalizedResponse.reset || xhr && options.chunking.success.resetOnStatus.indexOf(xhr.status) >= 0) { 3279 chunked.reset(id); 3280 } 3281 if (!options.onAutoRetry(id, name, normalizedResponse, xhr)) { 3282 upload.cleanup(id, normalizedResponse, xhr); 3283 } 3284 }); 3285 }, 3286 handleFailure: function(chunkIdx, id, response, xhr) { 3287 var name = options.getName(id); 3288 log("Chunked upload request failed for " + id + ", chunk " + chunkIdx); 3289 handler.clearCachedChunk(id, chunkIdx); 3290 var responseToReport = upload.normalizeResponse(response, false), inProgressIdx; 3291 if (responseToReport.reset) { 3292 chunked.reset(id); 3293 } else { 3294 var inProgressChunksArray = handler._getFileState(id).chunking.inProgress; 3295 inProgressIdx = inProgressChunksArray ? qq.indexOf(inProgressChunksArray, chunkIdx) : -1; 3296 if (inProgressIdx >= 0) { 3297 handler._getFileState(id).chunking.inProgress.splice(inProgressIdx, 1); 3298 handler._getFileState(id).chunking.remaining.unshift(chunkIdx); 3299 } 3300 } 3301 if (!handler._getFileState(id).temp.ignoreFailure) { 3302 if (concurrentChunkingPossible) { 3303 handler._getFileState(id).temp.ignoreFailure = true; 3304 log(qq.format("Going to attempt to abort these chunks: {}. These are currently in-progress: {}.", JSON.stringify(Object.keys(handler._getXhrs(id))), JSON.stringify(handler._getFileState(id).chunking.inProgress))); 3305 qq.each(handler._getXhrs(id), function(ckid, ckXhr) { 3306 log(qq.format("Attempting to abort file {}.{}. XHR readyState {}. ", id, ckid, ckXhr.readyState)); 3307 ckXhr.abort(); 3308 ckXhr._cancelled = true; 3309 }); 3310 handler.moveInProgressToRemaining(id); 3311 connectionManager.free(id, true); 3312 } 3313 if (!options.onAutoRetry(id, name, responseToReport, xhr)) { 3314 upload.cleanup(id, responseToReport, xhr); 3315 } 3316 } 3317 }, 3318 hasMoreParts: function(id) { 3319 return !!handler._getFileState(id).chunking.remaining.length; 3320 }, 3321 nextPart: function(id) { 3322 var nextIdx = handler._getFileState(id).chunking.remaining.shift(); 3323 if (nextIdx >= handler._getTotalChunks(id)) { 3324 nextIdx = null; 3325 } 3326 return nextIdx; 3327 }, 3328 reset: function(id) { 3329 log("Server or callback has ordered chunking effort to be restarted on next attempt for item ID " + id, "error"); 3330 handler._maybeDeletePersistedChunkData(id); 3331 handler.reevaluateChunking(id); 3332 handler._getFileState(id).loaded = 0; 3333 handler._getFileState(id).attemptingResume = false; 3334 }, 3335 sendNext: function(id) { 3336 var size = options.getSize(id), name = options.getName(id), chunkIdx = chunked.nextPart(id), chunkData = handler._getChunkData(id, chunkIdx), fileState = handler._getFileState(id), resuming = fileState.attemptingResume, inProgressChunks = fileState.chunking.inProgress || []; 3337 if (fileState.loaded == null) { 3338 fileState.loaded = 0; 3339 } 3340 if (resuming && options.onResume(id, name, chunkData, fileState.customResumeData) === false) { 3341 chunked.reset(id); 3342 chunkIdx = chunked.nextPart(id); 3343 chunkData = handler._getChunkData(id, chunkIdx); 3344 resuming = false; 3345 } 3346 if (chunkIdx == null && inProgressChunks.length === 0) { 3347 chunked.finalize(id); 3348 } else { 3349 inProgressChunks.push(chunkIdx); 3350 handler._getFileState(id).chunking.inProgress = inProgressChunks; 3351 if (concurrentChunkingPossible) { 3352 connectionManager.open(id, chunkIdx); 3353 } 3354 if (concurrentChunkingPossible && connectionManager.available() && handler._getFileState(id).chunking.remaining.length) { 3355 chunked.sendNext(id); 3356 } 3357 if (chunkData.blob.size === 0) { 3358 log(qq.format("Chunk {} for file {} will not be uploaded, zero sized chunk.", chunkIdx, id), "error"); 3359 chunked.handleFailure(chunkIdx, id, "File is no longer available", null); 3360 } 3361 var onUploadChunkPromise = options.onUploadChunk(id, name, handler._getChunkDataForCallback(chunkData)); 3362 onUploadChunkPromise.then(function(requestOverrides) { 3363 if (!options.isInProgress(id)) { 3364 log(qq.format("Not sending chunked upload request for item {}.{} - no longer in progress.", id, chunkIdx)); 3365 } else { 3366 log(qq.format("Sending chunked upload request for item {}.{}, bytes {}-{} of {}.", id, chunkIdx, chunkData.start + 1, chunkData.end, size)); 3367 var uploadChunkData = { 3368 chunkIdx: chunkIdx, 3369 id: id, 3370 overrides: requestOverrides, 3371 resuming: resuming 3372 }; 3373 handler.uploadChunk(uploadChunkData).then(function success(response, xhr) { 3374 log("Chunked upload request succeeded for " + id + ", chunk " + chunkIdx); 3375 handler.clearCachedChunk(id, chunkIdx); 3376 var inProgressChunks = handler._getFileState(id).chunking.inProgress || [], responseToReport = upload.normalizeResponse(response, true), inProgressChunkIdx = qq.indexOf(inProgressChunks, chunkIdx); 3377 log(qq.format("Chunk {} for file {} uploaded successfully.", chunkIdx, id)); 3378 chunked.done(id, chunkIdx, responseToReport, xhr); 3379 if (inProgressChunkIdx >= 0) { 3380 inProgressChunks.splice(inProgressChunkIdx, 1); 3381 } 3382 handler._maybePersistChunkedState(id); 3383 if (!chunked.hasMoreParts(id) && inProgressChunks.length === 0) { 3384 chunked.finalize(id); 3385 } else if (chunked.hasMoreParts(id)) { 3386 chunked.sendNext(id); 3387 } else { 3388 log(qq.format("File ID {} has no more chunks to send and these chunk indexes are still marked as in-progress: {}", id, JSON.stringify(inProgressChunks))); 3389 } 3390 }, function failure(response, xhr) { 3391 chunked.handleFailure(chunkIdx, id, response, xhr); 3392 }).done(function() { 3393 handler.clearXhr(id, chunkIdx); 3394 }); 3395 } 3396 }, function(error) { 3397 chunked.handleFailure(chunkIdx, id, error, null); 3398 }); 3399 } 3400 } 3401 }, connectionManager = { 3402 _open: [], 3403 _openChunks: {}, 3404 _waiting: [], 3405 available: function() { 3406 var max = options.maxConnections, openChunkEntriesCount = 0, openChunksCount = 0; 3407 qq.each(connectionManager._openChunks, function(fileId, openChunkIndexes) { 3408 openChunkEntriesCount++; 3409 openChunksCount += openChunkIndexes.length; 3410 }); 3411 return max - (connectionManager._open.length - openChunkEntriesCount + openChunksCount); 3412 }, 3413 free: function(id, dontAllowNext) { 3414 var allowNext = !dontAllowNext, waitingIndex = qq.indexOf(connectionManager._waiting, id), connectionsIndex = qq.indexOf(connectionManager._open, id), nextId; 3415 delete connectionManager._openChunks[id]; 3416 if (upload.getProxyOrBlob(id) instanceof qq.BlobProxy) { 3417 log("Generated blob upload has ended for " + id + ", disposing generated blob."); 3418 delete handler._getFileState(id).file; 3419 } 3420 if (waitingIndex >= 0) { 3421 connectionManager._waiting.splice(waitingIndex, 1); 3422 } else if (allowNext && connectionsIndex >= 0) { 3423 connectionManager._open.splice(connectionsIndex, 1); 3424 nextId = connectionManager._waiting.shift(); 3425 if (nextId >= 0) { 3426 connectionManager._open.push(nextId); 3427 upload.start(nextId); 3428 } 3429 } 3430 }, 3431 getWaitingOrConnected: function() { 3432 var waitingOrConnected = []; 3433 qq.each(connectionManager._openChunks, function(fileId, chunks) { 3434 if (chunks && chunks.length) { 3435 waitingOrConnected.push(parseInt(fileId)); 3436 } 3437 }); 3438 qq.each(connectionManager._open, function(idx, fileId) { 3439 if (!connectionManager._openChunks[fileId]) { 3440 waitingOrConnected.push(parseInt(fileId)); 3441 } 3442 }); 3443 waitingOrConnected = waitingOrConnected.concat(connectionManager._waiting); 3444 return waitingOrConnected; 3445 }, 3446 isUsingConnection: function(id) { 3447 return qq.indexOf(connectionManager._open, id) >= 0; 3448 }, 3449 open: function(id, chunkIdx) { 3450 if (chunkIdx == null) { 3451 connectionManager._waiting.push(id); 3452 } 3453 if (connectionManager.available()) { 3454 if (chunkIdx == null) { 3455 connectionManager._waiting.pop(); 3456 connectionManager._open.push(id); 3457 } else { 3458 (function() { 3459 var openChunksEntry = connectionManager._openChunks[id] || []; 3460 openChunksEntry.push(chunkIdx); 3461 connectionManager._openChunks[id] = openChunksEntry; 3462 })(); 3463 } 3464 return true; 3465 } 3466 return false; 3467 }, 3468 reset: function() { 3469 connectionManager._waiting = []; 3470 connectionManager._open = []; 3471 } 3472 }, simple = { 3473 send: function(id, name) { 3474 var fileState = handler._getFileState(id); 3475 if (!fileState) { 3476 log("Ignoring send request as this upload may have been cancelled, File ID " + id, "warn"); 3477 return; 3478 } 3479 fileState.loaded = 0; 3480 log("Sending simple upload request for " + id); 3481 handler.uploadFile(id).then(function(response, optXhr) { 3482 log("Simple upload request succeeded for " + id); 3483 var responseToReport = upload.normalizeResponse(response, true), size = options.getSize(id); 3484 options.onProgress(id, name, size, size); 3485 upload.maybeNewUuid(id, responseToReport); 3486 upload.cleanup(id, responseToReport, optXhr); 3487 }, function(response, optXhr) { 3488 log("Simple upload request failed for " + id); 3489 var responseToReport = upload.normalizeResponse(response, false); 3490 if (!options.onAutoRetry(id, name, responseToReport, optXhr)) { 3491 upload.cleanup(id, responseToReport, optXhr); 3492 } 3493 }); 3494 } 3495 }, upload = { 3496 cancel: function(id) { 3497 log("Cancelling " + id); 3498 options.paramsStore.remove(id); 3499 connectionManager.free(id); 3500 }, 3501 cleanup: function(id, response, optXhr) { 3502 var name = options.getName(id); 3503 options.onComplete(id, name, response, optXhr); 3504 if (handler._getFileState(id)) { 3505 handler._clearXhrs && handler._clearXhrs(id); 3506 } 3507 connectionManager.free(id); 3508 }, 3509 getProxyOrBlob: function(id) { 3510 return handler.getProxy && handler.getProxy(id) || handler.getFile && handler.getFile(id); 3511 }, 3512 initHandler: function() { 3513 var handlerType = namespace ? qq[namespace] : qq.traditional, handlerModuleSubtype = qq.supportedFeatures.ajaxUploading ? "Xhr" : "Form"; 3514 handler = new handlerType[handlerModuleSubtype + "UploadHandler"](options, { 3515 getCustomResumeData: options.getCustomResumeData, 3516 getDataByUuid: options.getDataByUuid, 3517 getName: options.getName, 3518 getSize: options.getSize, 3519 getUuid: options.getUuid, 3520 log: log, 3521 onCancel: options.onCancel, 3522 onProgress: options.onProgress, 3523 onUuidChanged: options.onUuidChanged, 3524 onFinalizing: function(id) { 3525 options.setStatus(id, qq.status.UPLOAD_FINALIZING); 3526 } 3527 }); 3528 if (handler._removeExpiredChunkingRecords) { 3529 handler._removeExpiredChunkingRecords(); 3530 } 3531 }, 3532 isDeferredEligibleForUpload: function(id) { 3533 return options.isQueued(id); 3534 }, 3535 maybeDefer: function(id, blob) { 3536 if (blob && !handler.getFile(id) && blob instanceof qq.BlobProxy) { 3537 options.onUploadPrep(id); 3538 log("Attempting to generate a blob on-demand for " + id); 3539 blob.create().then(function(generatedBlob) { 3540 log("Generated an on-demand blob for " + id); 3541 handler.updateBlob(id, generatedBlob); 3542 options.setSize(id, generatedBlob.size); 3543 handler.reevaluateChunking(id); 3544 upload.maybeSendDeferredFiles(id); 3545 }, function(errorMessage) { 3546 var errorResponse = {}; 3547 if (errorMessage) { 3548 errorResponse.error = errorMessage; 3549 } 3550 log(qq.format("Failed to generate blob for ID {}. Error message: {}.", id, errorMessage), "error"); 3551 options.onComplete(id, options.getName(id), qq.extend(errorResponse, preventRetryResponse), null); 3552 upload.maybeSendDeferredFiles(id); 3553 connectionManager.free(id); 3554 }); 3555 } else { 3556 return upload.maybeSendDeferredFiles(id); 3557 } 3558 return false; 3559 }, 3560 maybeSendDeferredFiles: function(id) { 3561 var idsInGroup = options.getIdsInProxyGroup(id), uploadedThisId = false; 3562 if (idsInGroup && idsInGroup.length) { 3563 log("Maybe ready to upload proxy group file " + id); 3564 qq.each(idsInGroup, function(idx, idInGroup) { 3565 if (upload.isDeferredEligibleForUpload(idInGroup) && !!handler.getFile(idInGroup)) { 3566 uploadedThisId = idInGroup === id; 3567 upload.now(idInGroup); 3568 } else if (upload.isDeferredEligibleForUpload(idInGroup)) { 3569 return false; 3570 } 3571 }); 3572 } else { 3573 uploadedThisId = true; 3574 upload.now(id); 3575 } 3576 return uploadedThisId; 3577 }, 3578 maybeNewUuid: function(id, response) { 3579 if (response.newUuid !== undefined) { 3580 options.onUuidChanged(id, response.newUuid); 3581 } 3582 }, 3583 normalizeResponse: function(originalResponse, successful) { 3584 var response = originalResponse; 3585 if (!qq.isObject(originalResponse)) { 3586 response = {}; 3587 if (qq.isString(originalResponse) && !successful) { 3588 response.error = originalResponse; 3589 } 3590 } 3591 response.success = successful; 3592 return response; 3593 }, 3594 now: function(id) { 3595 var name = options.getName(id); 3596 if (!controller.isValid(id)) { 3597 throw new qq.Error(id + " is not a valid file ID to upload!"); 3598 } 3599 options.onUpload(id, name).then(function(response) { 3600 if (response && response.pause) { 3601 options.setStatus(id, qq.status.PAUSED); 3602 handler.pause(id); 3603 connectionManager.free(id); 3604 } else { 3605 if (chunkingPossible && handler._shouldChunkThisFile(id)) { 3606 chunked.sendNext(id); 3607 } else { 3608 simple.send(id, name); 3609 } 3610 } 3611 }, function(error) { 3612 error = error || {}; 3613 log(id + " upload start aborted due to rejected onUpload Promise - details: " + error, "error"); 3614 if (!options.onAutoRetry(id, name, error.responseJSON || {})) { 3615 var response = upload.normalizeResponse(error.responseJSON, false); 3616 upload.cleanup(id, response); 3617 } 3618 }); 3619 }, 3620 start: function(id) { 3621 var blobToUpload = upload.getProxyOrBlob(id); 3622 if (blobToUpload) { 3623 return upload.maybeDefer(id, blobToUpload); 3624 } else { 3625 upload.now(id); 3626 return true; 3627 } 3628 } 3629 }; 3630 qq.extend(this, { 3631 add: function(id, file) { 3632 handler.add.apply(this, arguments); 3633 }, 3634 upload: function(id) { 3635 if (connectionManager.open(id)) { 3636 return upload.start(id); 3637 } 3638 return false; 3639 }, 3640 retry: function(id) { 3641 if (concurrentChunkingPossible) { 3642 handler._getFileState(id).temp.ignoreFailure = false; 3643 } 3644 if (connectionManager.isUsingConnection(id)) { 3645 return upload.start(id); 3646 } else { 3647 return controller.upload(id); 3648 } 3649 }, 3650 cancel: function(id) { 3651 var cancelRetVal = handler.cancel(id); 3652 if (qq.isGenericPromise(cancelRetVal)) { 3653 cancelRetVal.then(function() { 3654 upload.cancel(id); 3655 }); 3656 } else if (cancelRetVal !== false) { 3657 upload.cancel(id); 3658 } 3659 }, 3660 cancelAll: function() { 3661 var waitingOrConnected = connectionManager.getWaitingOrConnected(), i; 3662 if (waitingOrConnected.length) { 3663 for (i = waitingOrConnected.length - 1; i >= 0; i--) { 3664 controller.cancel(waitingOrConnected[i]); 3665 } 3666 } 3667 connectionManager.reset(); 3668 }, 3669 getFile: function(id) { 3670 if (handler.getProxy && handler.getProxy(id)) { 3671 return handler.getProxy(id).referenceBlob; 3672 } 3673 return handler.getFile && handler.getFile(id); 3674 }, 3675 isProxied: function(id) { 3676 return !!(handler.getProxy && handler.getProxy(id)); 3677 }, 3678 getInput: function(id) { 3679 if (handler.getInput) { 3680 return handler.getInput(id); 3681 } 3682 }, 3683 reset: function() { 3684 log("Resetting upload handler"); 3685 controller.cancelAll(); 3686 connectionManager.reset(); 3687 handler.reset(); 3688 }, 3689 expunge: function(id) { 3690 if (controller.isValid(id)) { 3691 return handler.expunge(id); 3692 } 3693 }, 3694 isValid: function(id) { 3695 return handler.isValid(id); 3696 }, 3697 hasResumeRecord: function(id) { 3698 var key = handler.isValid(id) && handler._getLocalStorageId && handler._getLocalStorageId(id); 3699 if (key) { 3700 return !!localStorage.getItem(key); 3701 } 3702 return false; 3703 }, 3704 getResumableFilesData: function() { 3705 if (handler.getResumableFilesData) { 3706 return handler.getResumableFilesData(); 3707 } 3708 return []; 3709 }, 3710 getThirdPartyFileId: function(id) { 3711 if (controller.isValid(id)) { 3712 return handler.getThirdPartyFileId(id); 3713 } 3714 }, 3715 pause: function(id) { 3716 if (controller.isResumable(id) && handler.pause && controller.isValid(id) && handler.pause(id)) { 3717 connectionManager.free(id); 3718 handler.moveInProgressToRemaining(id); 3719 return true; 3720 } 3721 return false; 3722 }, 3723 isAttemptingResume: function(id) { 3724 return !!handler.isAttemptingResume && handler.isAttemptingResume(id); 3725 }, 3726 isResumable: function(id) { 3727 return !!handler.isResumable && handler.isResumable(id); 3728 } 3729 }); 3730 qq.extend(options, o); 3731 log = options.log; 3732 chunkingPossible = options.chunking.enabled && qq.supportedFeatures.chunking; 3733 concurrentChunkingPossible = chunkingPossible && options.chunking.concurrent.enabled; 3734 preventRetryResponse = function() { 3735 var response = {}; 3736 response[options.preventRetryParam] = true; 3737 return response; 3738 }(); 3739 upload.initHandler(); 3740 }; 3741 qq.WindowReceiveMessage = function(o) { 3742 "use strict"; 3743 var options = { 3744 log: function(message, level) {} 3745 }, callbackWrapperDetachers = {}; 3746 qq.extend(options, o); 3747 qq.extend(this, { 3748 receiveMessage: function(id, callback) { 3749 var onMessageCallbackWrapper = function(event) { 3750 callback(event.data); 3751 }; 3752 if (window.postMessage) { 3753 callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper); 3754 } else { 3755 log("iframe message passing not supported in this browser!", "error"); 3756 } 3757 }, 3758 stopReceivingMessages: function(id) { 3759 if (window.postMessage) { 3760 var detacher = callbackWrapperDetachers[id]; 3761 if (detacher) { 3762 detacher(); 3763 } 3764 } 3765 } 3766 }); 3767 }; 3768 qq.FormUploadHandler = function(spec) { 3769 "use strict"; 3770 var options = spec.options, handler = this, proxy = spec.proxy, formHandlerInstanceId = qq.getUniqueId(), onloadCallbacks = {}, detachLoadEvents = {}, postMessageCallbackTimers = {}, isCors = options.isCors, inputName = options.inputName, getUuid = proxy.getUuid, log = proxy.log, corsMessageReceiver = new qq.WindowReceiveMessage({ 3771 log: log 3772 }); 3773 function expungeFile(id) { 3774 delete detachLoadEvents[id]; 3775 if (isCors) { 3776 clearTimeout(postMessageCallbackTimers[id]); 3777 delete postMessageCallbackTimers[id]; 3778 corsMessageReceiver.stopReceivingMessages(id); 3779 } 3780 var iframe = document.getElementById(handler._getIframeName(id)); 3781 if (iframe) { 3782 iframe.setAttribute("src", "javascript:false;"); 3783 qq(iframe).remove(); 3784 } 3785 } 3786 function getFileIdForIframeName(iframeName) { 3787 return iframeName.split("_")[0]; 3788 } 3789 function initIframeForUpload(name) { 3790 var iframe = qq.toElement("<iframe src='javascript:false;' name='" + name + "' />"); 3791 iframe.setAttribute("id", name); 3792 iframe.style.display = "none"; 3793 document.body.appendChild(iframe); 3794 return iframe; 3795 } 3796 function registerPostMessageCallback(iframe, callback) { 3797 var iframeName = iframe.id, fileId = getFileIdForIframeName(iframeName), uuid = getUuid(fileId); 3798 onloadCallbacks[uuid] = callback; 3799 detachLoadEvents[fileId] = qq(iframe).attach("load", function() { 3800 if (handler.getInput(fileId)) { 3801 log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")"); 3802 postMessageCallbackTimers[iframeName] = setTimeout(function() { 3803 var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName; 3804 log(errorMessage, "error"); 3805 callback({ 3806 error: errorMessage 3807 }); 3808 }, 1e3); 3809 } 3810 }); 3811 corsMessageReceiver.receiveMessage(iframeName, function(message) { 3812 log("Received the following window message: '" + message + "'"); 3813 var fileId = getFileIdForIframeName(iframeName), response = handler._parseJsonResponse(message), uuid = response.uuid, onloadCallback; 3814 if (uuid && onloadCallbacks[uuid]) { 3815 log("Handling response for iframe name " + iframeName); 3816 clearTimeout(postMessageCallbackTimers[iframeName]); 3817 delete postMessageCallbackTimers[iframeName]; 3818 handler._detachLoadEvent(iframeName); 3819 onloadCallback = onloadCallbacks[uuid]; 3820 delete onloadCallbacks[uuid]; 3821 corsMessageReceiver.stopReceivingMessages(iframeName); 3822 onloadCallback(response); 3823 } else if (!uuid) { 3824 log("'" + message + "' does not contain a UUID - ignoring."); 3825 } 3826 }); 3827 } 3828 qq.extend(this, new qq.UploadHandler(spec)); 3829 qq.override(this, function(super_) { 3830 return { 3831 add: function(id, fileInput) { 3832 super_.add(id, { 3833 input: fileInput 3834 }); 3835 fileInput.setAttribute("name", inputName); 3836 if (fileInput.parentNode) { 3837 qq(fileInput).remove(); 3838 } 3839 }, 3840 expunge: function(id) { 3841 expungeFile(id); 3842 super_.expunge(id); 3843 }, 3844 isValid: function(id) { 3845 return super_.isValid(id) && handler._getFileState(id).input !== undefined; 3846 } 3847 }; 3848 }); 3849 qq.extend(this, { 3850 getInput: function(id) { 3851 return handler._getFileState(id).input; 3852 }, 3853 _attachLoadEvent: function(iframe, callback) { 3854 var responseDescriptor; 3855 if (isCors) { 3856 registerPostMessageCallback(iframe, callback); 3857 } else { 3858 detachLoadEvents[iframe.id] = qq(iframe).attach("load", function() { 3859 log("Received response for " + iframe.id); 3860 if (!iframe.parentNode) { 3861 return; 3862 } 3863 try { 3864 if (iframe.contentDocument && iframe.contentDocument.body && iframe.contentDocument.body.innerHTML == "false") { 3865 return; 3866 } 3867 } catch (error) { 3868 log("Error when attempting to access iframe during handling of upload response (" + error.message + ")", "error"); 3869 responseDescriptor = { 3870 success: false 3871 }; 3872 } 3873 callback(responseDescriptor); 3874 }); 3875 } 3876 }, 3877 _createIframe: function(id) { 3878 var iframeName = handler._getIframeName(id); 3879 return initIframeForUpload(iframeName); 3880 }, 3881 _detachLoadEvent: function(id) { 3882 if (detachLoadEvents[id] !== undefined) { 3883 detachLoadEvents[id](); 3884 delete detachLoadEvents[id]; 3885 } 3886 }, 3887 _getIframeName: function(fileId) { 3888 return fileId + "_" + formHandlerInstanceId; 3889 }, 3890 _initFormForUpload: function(spec) { 3891 var method = spec.method, endpoint = spec.endpoint, params = spec.params, paramsInBody = spec.paramsInBody, targetName = spec.targetName, form = qq.toElement("<form method='" + method + "' enctype='multipart/form-data'></form>"), url = endpoint; 3892 if (paramsInBody) { 3893 qq.obj2Inputs(params, form); 3894 } else { 3895 url = qq.obj2url(params, endpoint); 3896 } 3897 form.setAttribute("action", url); 3898 form.setAttribute("target", targetName); 3899 form.style.display = "none"; 3900 document.body.appendChild(form); 3901 return form; 3902 }, 3903 _parseJsonResponse: function(innerHtmlOrMessage) { 3904 var response = {}; 3905 try { 3906 response = qq.parseJson(innerHtmlOrMessage); 3907 } catch (error) { 3908 log("Error when attempting to parse iframe upload response (" + error.message + ")", "error"); 3909 } 3910 return response; 3911 } 3912 }); 3913 }; 3914 qq.XhrUploadHandler = function(spec) { 3915 "use strict"; 3916 var handler = this, namespace = spec.options.namespace, proxy = spec.proxy, chunking = spec.options.chunking, getChunkSize = function(id) { 3917 var fileState = handler._getFileState(id); 3918 if (fileState.chunkSize) { 3919 return fileState.chunkSize; 3920 } else { 3921 var chunkSize = chunking.partSize; 3922 if (qq.isFunction(chunkSize)) { 3923 chunkSize = chunkSize(id, getSize(id)); 3924 } 3925 fileState.chunkSize = chunkSize; 3926 return chunkSize; 3927 } 3928 }, resume = spec.options.resume, chunkFiles = chunking && spec.options.chunking.enabled && qq.supportedFeatures.chunking, resumeEnabled = resume && spec.options.resume.enabled && chunkFiles && qq.supportedFeatures.resume, getName = proxy.getName, getSize = proxy.getSize, getUuid = proxy.getUuid, getEndpoint = proxy.getEndpoint, getDataByUuid = proxy.getDataByUuid, onUuidChanged = proxy.onUuidChanged, onProgress = proxy.onProgress, log = proxy.log, getCustomResumeData = proxy.getCustomResumeData; 3929 function abort(id) { 3930 qq.each(handler._getXhrs(id), function(xhrId, xhr) { 3931 var ajaxRequester = handler._getAjaxRequester(id, xhrId); 3932 xhr.onreadystatechange = null; 3933 xhr.upload.onprogress = null; 3934 xhr.abort(); 3935 ajaxRequester && ajaxRequester.canceled && ajaxRequester.canceled(id); 3936 }); 3937 } 3938 qq.extend(this, new qq.UploadHandler(spec)); 3939 qq.override(this, function(super_) { 3940 return { 3941 add: function(id, blobOrProxy) { 3942 if (qq.isFile(blobOrProxy) || qq.isBlob(blobOrProxy)) { 3943 super_.add(id, { 3944 file: blobOrProxy 3945 }); 3946 } else if (blobOrProxy instanceof qq.BlobProxy) { 3947 super_.add(id, { 3948 proxy: blobOrProxy 3949 }); 3950 } else { 3951 throw new Error("Passed obj is not a File, Blob, or proxy"); 3952 } 3953 handler._initTempState(id); 3954 resumeEnabled && handler._maybePrepareForResume(id); 3955 }, 3956 expunge: function(id) { 3957 abort(id); 3958 handler._maybeDeletePersistedChunkData(id); 3959 handler._clearXhrs(id); 3960 super_.expunge(id); 3961 } 3962 }; 3963 }); 3964 qq.extend(this, { 3965 clearCachedChunk: function(id, chunkIdx) { 3966 var fileState = handler._getFileState(id); 3967 if (fileState) { 3968 delete fileState.temp.cachedChunks[chunkIdx]; 3969 } 3970 }, 3971 clearXhr: function(id, chunkIdx) { 3972 var tempState = handler._getFileState(id).temp; 3973 if (tempState.xhrs) { 3974 delete tempState.xhrs[chunkIdx]; 3975 } 3976 if (tempState.ajaxRequesters) { 3977 delete tempState.ajaxRequesters[chunkIdx]; 3978 } 3979 }, 3980 finalizeChunks: function(id, responseParser) { 3981 var lastChunkIdx = handler._getTotalChunks(id) - 1, xhr = handler._getXhr(id, lastChunkIdx); 3982 if (responseParser) { 3983 return new qq.Promise().success(responseParser(xhr), xhr); 3984 } 3985 return new qq.Promise().success({}, xhr); 3986 }, 3987 getFile: function(id) { 3988 return handler.isValid(id) && handler._getFileState(id).file; 3989 }, 3990 getProxy: function(id) { 3991 return handler.isValid(id) && handler._getFileState(id).proxy; 3992 }, 3993 getResumableFilesData: function() { 3994 var resumableFilesData = []; 3995 handler._iterateResumeRecords(function(key, uploadData) { 3996 handler.moveInProgressToRemaining(null, uploadData.chunking.inProgress, uploadData.chunking.remaining); 3997 var data = { 3998 name: uploadData.name, 3999 remaining: uploadData.chunking.remaining, 4000 size: uploadData.size, 4001 uuid: uploadData.uuid 4002 }; 4003 if (uploadData.key) { 4004 data.key = uploadData.key; 4005 } 4006 if (uploadData.customResumeData) { 4007 data.customResumeData = uploadData.customResumeData; 4008 } 4009 resumableFilesData.push(data); 4010 }); 4011 return resumableFilesData; 4012 }, 4013 isAttemptingResume: function(id) { 4014 return handler._getFileState(id).attemptingResume; 4015 }, 4016 isResumable: function(id) { 4017 return !!chunking && handler.isValid(id) && !handler._getFileState(id).notResumable; 4018 }, 4019 moveInProgressToRemaining: function(id, optInProgress, optRemaining) { 4020 var fileState = handler._getFileState(id) || {}, chunkingState = fileState.chunking || {}, inProgress = optInProgress || chunkingState.inProgress, remaining = optRemaining || chunkingState.remaining; 4021 if (inProgress) { 4022 log(qq.format("Moving these chunks from in-progress {}, to remaining.", JSON.stringify(inProgress))); 4023 inProgress.reverse(); 4024 qq.each(inProgress, function(idx, chunkIdx) { 4025 remaining.unshift(chunkIdx); 4026 }); 4027 inProgress.length = 0; 4028 } 4029 }, 4030 pause: function(id) { 4031 if (handler.isValid(id)) { 4032 log(qq.format("Aborting XHR upload for {} '{}' due to pause instruction.", id, getName(id))); 4033 handler._getFileState(id).paused = true; 4034 abort(id); 4035 return true; 4036 } 4037 }, 4038 reevaluateChunking: function(id) { 4039 if (chunking && handler.isValid(id)) { 4040 var state = handler._getFileState(id), totalChunks, i; 4041 delete state.chunking; 4042 state.chunking = {}; 4043 totalChunks = handler._getTotalChunks(id); 4044 if (totalChunks > 1 || chunking.mandatory) { 4045 state.chunking.enabled = true; 4046 state.chunking.parts = totalChunks; 4047 state.chunking.remaining = []; 4048 for (i = 0; i < totalChunks; i++) { 4049 state.chunking.remaining.push(i); 4050 } 4051 handler._initTempState(id); 4052 } else { 4053 state.chunking.enabled = false; 4054 } 4055 } 4056 }, 4057 updateBlob: function(id, newBlob) { 4058 if (handler.isValid(id)) { 4059 handler._getFileState(id).file = newBlob; 4060 } 4061 }, 4062 _clearXhrs: function(id) { 4063 var tempState = handler._getFileState(id).temp; 4064 qq.each(tempState.ajaxRequesters, function(chunkId) { 4065 delete tempState.ajaxRequesters[chunkId]; 4066 }); 4067 qq.each(tempState.xhrs, function(chunkId) { 4068 delete tempState.xhrs[chunkId]; 4069 }); 4070 }, 4071 _createXhr: function(id, optChunkIdx) { 4072 return handler._registerXhr(id, optChunkIdx, qq.createXhrInstance()); 4073 }, 4074 _getAjaxRequester: function(id, optChunkIdx) { 4075 var chunkIdx = optChunkIdx == null ? -1 : optChunkIdx; 4076 return handler._getFileState(id).temp.ajaxRequesters[chunkIdx]; 4077 }, 4078 _getChunkData: function(id, chunkIndex) { 4079 var chunkSize = getChunkSize(id), fileSize = getSize(id), fileOrBlob = handler.getFile(id), startBytes = chunkSize * chunkIndex, endBytes = startBytes + chunkSize >= fileSize ? fileSize : startBytes + chunkSize, totalChunks = handler._getTotalChunks(id), cachedChunks = this._getFileState(id).temp.cachedChunks, blob = cachedChunks[chunkIndex] || qq.sliceBlob(fileOrBlob, startBytes, endBytes); 4080 cachedChunks[chunkIndex] = blob; 4081 return { 4082 part: chunkIndex, 4083 start: startBytes, 4084 end: endBytes, 4085 count: totalChunks, 4086 blob: blob, 4087 size: endBytes - startBytes 4088 }; 4089 }, 4090 _getChunkDataForCallback: function(chunkData) { 4091 return { 4092 partIndex: chunkData.part, 4093 startByte: chunkData.start + 1, 4094 endByte: chunkData.end, 4095 totalParts: chunkData.count 4096 }; 4097 }, 4098 _getLocalStorageId: function(id) { 4099 var formatVersion = "5.0", name = getName(id), size = getSize(id), chunkSize = getChunkSize(id), endpoint = getEndpoint(id), customKeys = resume.customKeys(id), localStorageId = qq.format("qq{}resume{}-{}-{}-{}-{}", namespace, formatVersion, name, size, chunkSize, endpoint); 4100 customKeys.forEach(function(key) { 4101 localStorageId += "-" + key; 4102 }); 4103 return localStorageId; 4104 }, 4105 _getMimeType: function(id) { 4106 return handler.getFile(id).type; 4107 }, 4108 _getPersistableData: function(id) { 4109 return handler._getFileState(id).chunking; 4110 }, 4111 _getTotalChunks: function(id) { 4112 if (chunking) { 4113 var fileSize = getSize(id), chunkSize = getChunkSize(id); 4114 return Math.ceil(fileSize / chunkSize); 4115 } 4116 }, 4117 _getXhr: function(id, optChunkIdx) { 4118 var chunkIdx = optChunkIdx == null ? -1 : optChunkIdx; 4119 return handler._getFileState(id).temp.xhrs[chunkIdx]; 4120 }, 4121 _getXhrs: function(id) { 4122 return handler._getFileState(id).temp.xhrs; 4123 }, 4124 _iterateResumeRecords: function(callback) { 4125 if (resumeEnabled) { 4126 qq.each(localStorage, function(key, item) { 4127 if (key.indexOf(qq.format("qq{}resume", namespace)) === 0) { 4128 var uploadData = JSON.parse(item); 4129 callback(key, uploadData); 4130 } 4131 }); 4132 } 4133 }, 4134 _initTempState: function(id) { 4135 handler._getFileState(id).temp = { 4136 ajaxRequesters: {}, 4137 chunkProgress: {}, 4138 xhrs: {}, 4139 cachedChunks: {} 4140 }; 4141 }, 4142 _markNotResumable: function(id) { 4143 handler._getFileState(id).notResumable = true; 4144 }, 4145 _maybeDeletePersistedChunkData: function(id) { 4146 var localStorageId; 4147 if (resumeEnabled && handler.isResumable(id)) { 4148 localStorageId = handler._getLocalStorageId(id); 4149 if (localStorageId && localStorage.getItem(localStorageId)) { 4150 localStorage.removeItem(localStorageId); 4151 return true; 4152 } 4153 } 4154 return false; 4155 }, 4156 _maybePrepareForResume: function(id) { 4157 var state = handler._getFileState(id), localStorageId, persistedData; 4158 if (resumeEnabled && state.key === undefined) { 4159 localStorageId = handler._getLocalStorageId(id); 4160 persistedData = localStorage.getItem(localStorageId); 4161 if (persistedData) { 4162 persistedData = JSON.parse(persistedData); 4163 if (getDataByUuid(persistedData.uuid)) { 4164 handler._markNotResumable(id); 4165 } else { 4166 log(qq.format("Identified file with ID {} and name of {} as resumable.", id, getName(id))); 4167 onUuidChanged(id, persistedData.uuid); 4168 state.key = persistedData.key; 4169 state.chunking = persistedData.chunking; 4170 state.loaded = persistedData.loaded; 4171 state.customResumeData = persistedData.customResumeData; 4172 state.attemptingResume = true; 4173 handler.moveInProgressToRemaining(id); 4174 } 4175 } 4176 } 4177 }, 4178 _maybePersistChunkedState: function(id) { 4179 var state = handler._getFileState(id), localStorageId, persistedData; 4180 if (resumeEnabled && handler.isResumable(id)) { 4181 var customResumeData = getCustomResumeData(id); 4182 localStorageId = handler._getLocalStorageId(id); 4183 persistedData = { 4184 name: getName(id), 4185 size: getSize(id), 4186 uuid: getUuid(id), 4187 key: state.key, 4188 chunking: state.chunking, 4189 loaded: state.loaded, 4190 lastUpdated: Date.now() 4191 }; 4192 if (customResumeData) { 4193 persistedData.customResumeData = customResumeData; 4194 } 4195 try { 4196 localStorage.setItem(localStorageId, JSON.stringify(persistedData)); 4197 } catch (error) { 4198 log(qq.format("Unable to save resume data for '{}' due to error: '{}'.", id, error.toString()), "warn"); 4199 } 4200 } 4201 }, 4202 _registerProgressHandler: function(id, chunkIdx, chunkSize) { 4203 var xhr = handler._getXhr(id, chunkIdx), name = getName(id), progressCalculator = { 4204 simple: function(loaded, total) { 4205 var fileSize = getSize(id); 4206 if (loaded === total) { 4207 onProgress(id, name, fileSize, fileSize); 4208 } else { 4209 onProgress(id, name, loaded >= fileSize ? fileSize - 1 : loaded, fileSize); 4210 } 4211 }, 4212 chunked: function(loaded, total) { 4213 var chunkProgress = handler._getFileState(id).temp.chunkProgress, totalSuccessfullyLoadedForFile = handler._getFileState(id).loaded, loadedForRequest = loaded, totalForRequest = total, totalFileSize = getSize(id), estActualChunkLoaded = loadedForRequest - (totalForRequest - chunkSize), totalLoadedForFile = totalSuccessfullyLoadedForFile; 4214 chunkProgress[chunkIdx] = estActualChunkLoaded; 4215 qq.each(chunkProgress, function(chunkIdx, chunkLoaded) { 4216 totalLoadedForFile += chunkLoaded; 4217 }); 4218 onProgress(id, name, totalLoadedForFile, totalFileSize); 4219 } 4220 }; 4221 xhr.upload.onprogress = function(e) { 4222 if (e.lengthComputable) { 4223 var type = chunkSize == null ? "simple" : "chunked"; 4224 progressCalculator[type](e.loaded, e.total); 4225 } 4226 }; 4227 }, 4228 _registerXhr: function(id, optChunkIdx, xhr, optAjaxRequester) { 4229 var xhrsId = optChunkIdx == null ? -1 : optChunkIdx, tempState = handler._getFileState(id).temp; 4230 tempState.xhrs = tempState.xhrs || {}; 4231 tempState.ajaxRequesters = tempState.ajaxRequesters || {}; 4232 tempState.xhrs[xhrsId] = xhr; 4233 if (optAjaxRequester) { 4234 tempState.ajaxRequesters[xhrsId] = optAjaxRequester; 4235 } 4236 return xhr; 4237 }, 4238 _removeExpiredChunkingRecords: function() { 4239 var expirationDays = resume.recordsExpireIn; 4240 handler._iterateResumeRecords(function(key, uploadData) { 4241 var expirationDate = new Date(uploadData.lastUpdated); 4242 expirationDate.setDate(expirationDate.getDate() + expirationDays); 4243 if (expirationDate.getTime() <= Date.now()) { 4244 log("Removing expired resume record with key " + key); 4245 localStorage.removeItem(key); 4246 } 4247 }); 4248 }, 4249 _shouldChunkThisFile: function(id) { 4250 var state = handler._getFileState(id); 4251 if (state) { 4252 if (!state.chunking) { 4253 handler.reevaluateChunking(id); 4254 } 4255 return state.chunking.enabled; 4256 } 4257 } 4258 }); 4259 }; 4260 qq.DeleteFileAjaxRequester = function(o) { 4261 "use strict"; 4262 var requester, options = { 4263 method: "DELETE", 4264 uuidParamName: "qquuid", 4265 endpointStore: {}, 4266 maxConnections: 3, 4267 customHeaders: function(id) { 4268 return {}; 4269 }, 4270 paramsStore: {}, 4271 cors: { 4272 expected: false, 4273 sendCredentials: false 4274 }, 4275 log: function(str, level) {}, 4276 onDelete: function(id) {}, 4277 onDeleteComplete: function(id, xhrOrXdr, isError) {} 4278 }; 4279 qq.extend(options, o); 4280 function getMandatedParams() { 4281 if (options.method.toUpperCase() === "POST") { 4282 return { 4283 _method: "DELETE" 4284 }; 4285 } 4286 return {}; 4287 } 4288 requester = qq.extend(this, new qq.AjaxRequester({ 4289 acceptHeader: "application/json", 4290 validMethods: [ "POST", "DELETE" ], 4291 method: options.method, 4292 endpointStore: options.endpointStore, 4293 paramsStore: options.paramsStore, 4294 mandatedParams: getMandatedParams(), 4295 maxConnections: options.maxConnections, 4296 customHeaders: function(id) { 4297 return options.customHeaders.get(id); 4298 }, 4299 log: options.log, 4300 onSend: options.onDelete, 4301 onComplete: options.onDeleteComplete, 4302 cors: options.cors 4303 })); 4304 qq.extend(this, { 4305 sendDelete: function(id, uuid, additionalMandatedParams) { 4306 var additionalOptions = additionalMandatedParams || {}; 4307 options.log("Submitting delete file request for " + id); 4308 if (options.method === "DELETE") { 4309 requester.initTransport(id).withPath(uuid).withParams(additionalOptions).send(); 4310 } else { 4311 additionalOptions[options.uuidParamName] = uuid; 4312 requester.initTransport(id).withParams(additionalOptions).send(); 4313 } 4314 } 4315 }); 4316 }; 4317 (function() { 4318 function detectSubsampling(img) { 4319 var iw = img.naturalWidth, ih = img.naturalHeight, canvas = document.createElement("canvas"), ctx; 4320 if (iw * ih > 1024 * 1024) { 4321 canvas.width = canvas.height = 1; 4322 ctx = canvas.getContext("2d"); 4323 ctx.drawImage(img, -iw + 1, 0); 4324 return ctx.getImageData(0, 0, 1, 1).data[3] === 0; 4325 } else { 4326 return false; 4327 } 4328 } 4329 function detectVerticalSquash(img, iw, ih) { 4330 var canvas = document.createElement("canvas"), sy = 0, ey = ih, py = ih, ctx, data, alpha, ratio; 4331 canvas.width = 1; 4332 canvas.height = ih; 4333 ctx = canvas.getContext("2d"); 4334 ctx.drawImage(img, 0, 0); 4335 data = ctx.getImageData(0, 0, 1, ih).data; 4336 while (py > sy) { 4337 alpha = data[(py - 1) * 4 + 3]; 4338 if (alpha === 0) { 4339 ey = py; 4340 } else { 4341 sy = py; 4342 } 4343 py = ey + sy >> 1; 4344 } 4345 ratio = py / ih; 4346 return ratio === 0 ? 1 : ratio; 4347 } 4348 function renderImageToDataURL(img, blob, options, doSquash) { 4349 var canvas = document.createElement("canvas"), mime = options.mime || "image/jpeg", promise = new qq.Promise(); 4350 renderImageToCanvas(img, blob, canvas, options, doSquash).then(function() { 4351 promise.success(canvas.toDataURL(mime, options.quality || .8)); 4352 }); 4353 return promise; 4354 } 4355 function maybeCalculateDownsampledDimensions(spec) { 4356 var maxPixels = 5241e3; 4357 if (!qq.ios()) { 4358 throw new qq.Error("Downsampled dimensions can only be reliably calculated for iOS!"); 4359 } 4360 if (spec.origHeight * spec.origWidth > maxPixels) { 4361 return { 4362 newHeight: Math.round(Math.sqrt(maxPixels * (spec.origHeight / spec.origWidth))), 4363 newWidth: Math.round(Math.sqrt(maxPixels * (spec.origWidth / spec.origHeight))) 4364 }; 4365 } 4366 } 4367 function renderImageToCanvas(img, blob, canvas, options, doSquash) { 4368 var iw = img.naturalWidth, ih = img.naturalHeight, width = options.width, height = options.height, ctx = canvas.getContext("2d"), promise = new qq.Promise(), modifiedDimensions; 4369 ctx.save(); 4370 if (options.resize) { 4371 return renderImageToCanvasWithCustomResizer({ 4372 blob: blob, 4373 canvas: canvas, 4374 image: img, 4375 imageHeight: ih, 4376 imageWidth: iw, 4377 orientation: options.orientation, 4378 resize: options.resize, 4379 targetHeight: height, 4380 targetWidth: width 4381 }); 4382 } 4383 if (!qq.supportedFeatures.unlimitedScaledImageSize) { 4384 modifiedDimensions = maybeCalculateDownsampledDimensions({ 4385 origWidth: width, 4386 origHeight: height 4387 }); 4388 if (modifiedDimensions) { 4389 qq.log(qq.format("Had to reduce dimensions due to device limitations from {}w / {}h to {}w / {}h", width, height, modifiedDimensions.newWidth, modifiedDimensions.newHeight), "warn"); 4390 width = modifiedDimensions.newWidth; 4391 height = modifiedDimensions.newHeight; 4392 } 4393 } 4394 transformCoordinate(canvas, width, height, options.orientation); 4395 if (qq.ios()) { 4396 (function() { 4397 if (detectSubsampling(img)) { 4398 iw /= 2; 4399 ih /= 2; 4400 } 4401 var d = 1024, tmpCanvas = document.createElement("canvas"), vertSquashRatio = doSquash ? detectVerticalSquash(img, iw, ih) : 1, dw = Math.ceil(d * width / iw), dh = Math.ceil(d * height / ih / vertSquashRatio), sy = 0, dy = 0, tmpCtx, sx, dx; 4402 tmpCanvas.width = tmpCanvas.height = d; 4403 tmpCtx = tmpCanvas.getContext("2d"); 4404 while (sy < ih) { 4405 sx = 0; 4406 dx = 0; 4407 while (sx < iw) { 4408 tmpCtx.clearRect(0, 0, d, d); 4409 tmpCtx.drawImage(img, -sx, -sy); 4410 ctx.drawImage(tmpCanvas, 0, 0, d, d, dx, dy, dw, dh); 4411 sx += d; 4412 dx += dw; 4413 } 4414 sy += d; 4415 dy += dh; 4416 } 4417 ctx.restore(); 4418 tmpCanvas = tmpCtx = null; 4419 })(); 4420 } else { 4421 ctx.drawImage(img, 0, 0, width, height); 4422 } 4423 canvas.qqImageRendered && canvas.qqImageRendered(); 4424 promise.success(); 4425 return promise; 4426 } 4427 function renderImageToCanvasWithCustomResizer(resizeInfo) { 4428 var blob = resizeInfo.blob, image = resizeInfo.image, imageHeight = resizeInfo.imageHeight, imageWidth = resizeInfo.imageWidth, orientation = resizeInfo.orientation, promise = new qq.Promise(), resize = resizeInfo.resize, sourceCanvas = document.createElement("canvas"), sourceCanvasContext = sourceCanvas.getContext("2d"), targetCanvas = resizeInfo.canvas, targetHeight = resizeInfo.targetHeight, targetWidth = resizeInfo.targetWidth; 4429 transformCoordinate(sourceCanvas, imageWidth, imageHeight, orientation); 4430 targetCanvas.height = targetHeight; 4431 targetCanvas.width = targetWidth; 4432 sourceCanvasContext.drawImage(image, 0, 0); 4433 resize({ 4434 blob: blob, 4435 height: targetHeight, 4436 image: image, 4437 sourceCanvas: sourceCanvas, 4438 targetCanvas: targetCanvas, 4439 width: targetWidth 4440 }).then(function success() { 4441 targetCanvas.qqImageRendered && targetCanvas.qqImageRendered(); 4442 promise.success(); 4443 }, promise.failure); 4444 return promise; 4445 } 4446 function transformCoordinate(canvas, width, height, orientation) { 4447 switch (orientation) { 4448 case 5: 4449 case 6: 4450 case 7: 4451 case 8: 4452 canvas.width = height; 4453 canvas.height = width; 4454 break; 4455 4456 default: 4457 canvas.width = width; 4458 canvas.height = height; 4459 } 4460 var ctx = canvas.getContext("2d"); 4461 switch (orientation) { 4462 case 2: 4463 ctx.translate(width, 0); 4464 ctx.scale(-1, 1); 4465 break; 4466 4467 case 3: 4468 ctx.translate(width, height); 4469 ctx.rotate(Math.PI); 4470 break; 4471 4472 case 4: 4473 ctx.translate(0, height); 4474 ctx.scale(1, -1); 4475 break; 4476 4477 case 5: 4478 ctx.rotate(.5 * Math.PI); 4479 ctx.scale(1, -1); 4480 break; 4481 4482 case 6: 4483 ctx.rotate(.5 * Math.PI); 4484 ctx.translate(0, -height); 4485 break; 4486 4487 case 7: 4488 ctx.rotate(.5 * Math.PI); 4489 ctx.translate(width, -height); 4490 ctx.scale(-1, 1); 4491 break; 4492 4493 case 8: 4494 ctx.rotate(-.5 * Math.PI); 4495 ctx.translate(-width, 0); 4496 break; 4497 4498 default: 4499 break; 4500 } 4501 } 4502 function MegaPixImage(srcImage, errorCallback) { 4503 var self = this; 4504 if (window.Blob && srcImage instanceof Blob) { 4505 (function() { 4506 var img = new Image(), URL = window.URL && window.URL.createObjectURL ? window.URL : window.webkitURL && window.webkitURL.createObjectURL ? window.webkitURL : null; 4507 if (!URL) { 4508 throw Error("No createObjectURL function found to create blob url"); 4509 } 4510 img.src = URL.createObjectURL(srcImage); 4511 self.blob = srcImage; 4512 srcImage = img; 4513 })(); 4514 } 4515 if (!srcImage.naturalWidth && !srcImage.naturalHeight) { 4516 srcImage.onload = function() { 4517 var listeners = self.imageLoadListeners; 4518 if (listeners) { 4519 self.imageLoadListeners = null; 4520 setTimeout(function() { 4521 for (var i = 0, len = listeners.length; i < len; i++) { 4522 listeners[i](); 4523 } 4524 }, 0); 4525 } 4526 }; 4527 srcImage.onerror = errorCallback; 4528 this.imageLoadListeners = []; 4529 } 4530 this.srcImage = srcImage; 4531 } 4532 MegaPixImage.prototype.render = function(target, options) { 4533 options = options || {}; 4534 var self = this, imgWidth = this.srcImage.naturalWidth, imgHeight = this.srcImage.naturalHeight, width = options.width, height = options.height, maxWidth = options.maxWidth, maxHeight = options.maxHeight, doSquash = !this.blob || this.blob.type === "image/jpeg", tagName = target.tagName.toLowerCase(), opt; 4535 if (this.imageLoadListeners) { 4536 this.imageLoadListeners.push(function() { 4537 self.render(target, options); 4538 }); 4539 return; 4540 } 4541 if (width && !height) { 4542 height = imgHeight * width / imgWidth << 0; 4543 } else if (height && !width) { 4544 width = imgWidth * height / imgHeight << 0; 4545 } else { 4546 width = imgWidth; 4547 height = imgHeight; 4548 } 4549 if (maxWidth && width > maxWidth) { 4550 width = maxWidth; 4551 height = imgHeight * width / imgWidth << 0; 4552 } 4553 if (maxHeight && height > maxHeight) { 4554 height = maxHeight; 4555 width = imgWidth * height / imgHeight << 0; 4556 } 4557 opt = { 4558 width: width, 4559 height: height 4560 }, qq.each(options, function(optionsKey, optionsValue) { 4561 opt[optionsKey] = optionsValue; 4562 }); 4563 if (tagName === "img") { 4564 (function() { 4565 var oldTargetSrc = target.src; 4566 renderImageToDataURL(self.srcImage, self.blob, opt, doSquash).then(function(dataUri) { 4567 target.src = dataUri; 4568 oldTargetSrc === target.src && target.onload(); 4569 }); 4570 })(); 4571 } else if (tagName === "canvas") { 4572 renderImageToCanvas(this.srcImage, this.blob, target, opt, doSquash); 4573 } 4574 if (typeof this.onrender === "function") { 4575 this.onrender(target); 4576 } 4577 }; 4578 qq.MegaPixImage = MegaPixImage; 4579 })(); 4580 qq.ImageGenerator = function(log) { 4581 "use strict"; 4582 function isImg(el) { 4583 return el.tagName.toLowerCase() === "img"; 4584 } 4585 function isCanvas(el) { 4586 return el.tagName.toLowerCase() === "canvas"; 4587 } 4588 function isImgCorsSupported() { 4589 return new Image().crossOrigin !== undefined; 4590 } 4591 function isCanvasSupported() { 4592 var canvas = document.createElement("canvas"); 4593 return canvas.getContext && canvas.getContext("2d"); 4594 } 4595 function determineMimeOfFileName(nameWithPath) { 4596 var pathSegments = nameWithPath.split("/"), name = pathSegments[pathSegments.length - 1].split("?")[0], extension = qq.getExtension(name); 4597 extension = extension && extension.toLowerCase(); 4598 switch (extension) { 4599 case "jpeg": 4600 case "jpg": 4601 return "image/jpeg"; 4602 4603 case "png": 4604 return "image/png"; 4605 4606 case "bmp": 4607 return "image/bmp"; 4608 4609 case "gif": 4610 return "image/gif"; 4611 4612 case "tiff": 4613 case "tif": 4614 return "image/tiff"; 4615 } 4616 } 4617 function isCrossOrigin(url) { 4618 var targetAnchor = document.createElement("a"), targetProtocol, targetHostname, targetPort; 4619 targetAnchor.href = url; 4620 targetProtocol = targetAnchor.protocol; 4621 targetPort = targetAnchor.port; 4622 targetHostname = targetAnchor.hostname; 4623 if (targetProtocol.toLowerCase() !== window.location.protocol.toLowerCase()) { 4624 return true; 4625 } 4626 if (targetHostname.toLowerCase() !== window.location.hostname.toLowerCase()) { 4627 return true; 4628 } 4629 if (targetPort !== window.location.port && !qq.ie()) { 4630 return true; 4631 } 4632 return false; 4633 } 4634 function registerImgLoadListeners(img, promise) { 4635 img.onload = function() { 4636 img.onload = null; 4637 img.onerror = null; 4638 promise.success(img); 4639 }; 4640 img.onerror = function() { 4641 img.onload = null; 4642 img.onerror = null; 4643 log("Problem drawing thumbnail!", "error"); 4644 promise.failure(img, "Problem drawing thumbnail!"); 4645 }; 4646 } 4647 function registerCanvasDrawImageListener(canvas, promise) { 4648 canvas.qqImageRendered = function() { 4649 promise.success(canvas); 4650 }; 4651 } 4652 function registerThumbnailRenderedListener(imgOrCanvas, promise) { 4653 var registered = isImg(imgOrCanvas) || isCanvas(imgOrCanvas); 4654 if (isImg(imgOrCanvas)) { 4655 registerImgLoadListeners(imgOrCanvas, promise); 4656 } else if (isCanvas(imgOrCanvas)) { 4657 registerCanvasDrawImageListener(imgOrCanvas, promise); 4658 } else { 4659 promise.failure(imgOrCanvas); 4660 log(qq.format("Element container of type {} is not supported!", imgOrCanvas.tagName), "error"); 4661 } 4662 return registered; 4663 } 4664 function draw(fileOrBlob, container, options) { 4665 var drawPreview = new qq.Promise(), identifier = new qq.Identify(fileOrBlob, log), maxSize = options.maxSize, orient = options.orient == null ? true : options.orient, megapixErrorHandler = function() { 4666 container.onerror = null; 4667 container.onload = null; 4668 log("Could not render preview, file may be too large!", "error"); 4669 drawPreview.failure(container, "Browser cannot render image!"); 4670 }; 4671 identifier.isPreviewable().then(function(mime) { 4672 var dummyExif = { 4673 parse: function() { 4674 return new qq.Promise().success(); 4675 } 4676 }, exif = orient ? new qq.Exif(fileOrBlob, log) : dummyExif, mpImg = new qq.MegaPixImage(fileOrBlob, megapixErrorHandler); 4677 if (registerThumbnailRenderedListener(container, drawPreview)) { 4678 exif.parse().then(function(exif) { 4679 var orientation = exif && exif.Orientation; 4680 mpImg.render(container, { 4681 maxWidth: maxSize, 4682 maxHeight: maxSize, 4683 orientation: orientation, 4684 mime: mime, 4685 resize: options.customResizeFunction 4686 }); 4687 }, function(failureMsg) { 4688 log(qq.format("EXIF data could not be parsed ({}). Assuming orientation = 1.", failureMsg)); 4689 mpImg.render(container, { 4690 maxWidth: maxSize, 4691 maxHeight: maxSize, 4692 mime: mime, 4693 resize: options.customResizeFunction 4694 }); 4695 }); 4696 } 4697 }, function() { 4698 log("Not previewable"); 4699 drawPreview.failure(container, "Not previewable"); 4700 }); 4701 return drawPreview; 4702 } 4703 function drawOnCanvasOrImgFromUrl(url, canvasOrImg, draw, maxSize, customResizeFunction) { 4704 var tempImg = new Image(), tempImgRender = new qq.Promise(); 4705 registerThumbnailRenderedListener(tempImg, tempImgRender); 4706 if (isCrossOrigin(url)) { 4707 tempImg.crossOrigin = "anonymous"; 4708 } 4709 tempImg.src = url; 4710 tempImgRender.then(function rendered() { 4711 registerThumbnailRenderedListener(canvasOrImg, draw); 4712 var mpImg = new qq.MegaPixImage(tempImg); 4713 mpImg.render(canvasOrImg, { 4714 maxWidth: maxSize, 4715 maxHeight: maxSize, 4716 mime: determineMimeOfFileName(url), 4717 resize: customResizeFunction 4718 }); 4719 }, draw.failure); 4720 } 4721 function drawOnImgFromUrlWithCssScaling(url, img, draw, maxSize) { 4722 registerThumbnailRenderedListener(img, draw); 4723 qq(img).css({ 4724 maxWidth: maxSize + "px", 4725 maxHeight: maxSize + "px" 4726 }); 4727 img.src = url; 4728 } 4729 function drawFromUrl(url, container, options) { 4730 var draw = new qq.Promise(), scale = options.scale, maxSize = scale ? options.maxSize : null; 4731 if (scale && isImg(container)) { 4732 if (isCanvasSupported()) { 4733 if (isCrossOrigin(url) && !isImgCorsSupported()) { 4734 drawOnImgFromUrlWithCssScaling(url, container, draw, maxSize); 4735 } else { 4736 drawOnCanvasOrImgFromUrl(url, container, draw, maxSize); 4737 } 4738 } else { 4739 drawOnImgFromUrlWithCssScaling(url, container, draw, maxSize); 4740 } 4741 } else if (isCanvas(container)) { 4742 drawOnCanvasOrImgFromUrl(url, container, draw, maxSize); 4743 } else if (registerThumbnailRenderedListener(container, draw)) { 4744 container.src = url; 4745 } 4746 return draw; 4747 } 4748 qq.extend(this, { 4749 generate: function(fileBlobOrUrl, container, options) { 4750 if (qq.isString(fileBlobOrUrl)) { 4751 log("Attempting to update thumbnail based on server response."); 4752 return drawFromUrl(fileBlobOrUrl, container, options || {}); 4753 } else { 4754 log("Attempting to draw client-side image preview."); 4755 return draw(fileBlobOrUrl, container, options || {}); 4756 } 4757 } 4758 }); 4759 this._testing = {}; 4760 this._testing.isImg = isImg; 4761 this._testing.isCanvas = isCanvas; 4762 this._testing.isCrossOrigin = isCrossOrigin; 4763 this._testing.determineMimeOfFileName = determineMimeOfFileName; 4764 }; 4765 qq.Exif = function(fileOrBlob, log) { 4766 "use strict"; 4767 var TAG_IDS = [ 274 ], TAG_INFO = { 4768 274: { 4769 name: "Orientation", 4770 bytes: 2 4771 } 4772 }; 4773 function parseLittleEndian(hex) { 4774 var result = 0, pow = 0; 4775 while (hex.length > 0) { 4776 result += parseInt(hex.substring(0, 2), 16) * Math.pow(2, pow); 4777 hex = hex.substring(2, hex.length); 4778 pow += 8; 4779 } 4780 return result; 4781 } 4782 function seekToApp1(offset, promise) { 4783 var theOffset = offset, thePromise = promise; 4784 if (theOffset === undefined) { 4785 theOffset = 2; 4786 thePromise = new qq.Promise(); 4787 } 4788 qq.readBlobToHex(fileOrBlob, theOffset, 4).then(function(hex) { 4789 var match = /^ffe([0-9])/.exec(hex), segmentLength; 4790 if (match) { 4791 if (match[1] !== "1") { 4792 segmentLength = parseInt(hex.slice(4, 8), 16); 4793 seekToApp1(theOffset + segmentLength + 2, thePromise); 4794 } else { 4795 thePromise.success(theOffset); 4796 } 4797 } else { 4798 thePromise.failure("No EXIF header to be found!"); 4799 } 4800 }); 4801 return thePromise; 4802 } 4803 function getApp1Offset() { 4804 var promise = new qq.Promise(); 4805 qq.readBlobToHex(fileOrBlob, 0, 6).then(function(hex) { 4806 if (hex.indexOf("ffd8") !== 0) { 4807 promise.failure("Not a valid JPEG!"); 4808 } else { 4809 seekToApp1().then(function(offset) { 4810 promise.success(offset); 4811 }, function(error) { 4812 promise.failure(error); 4813 }); 4814 } 4815 }); 4816 return promise; 4817 } 4818 function isLittleEndian(app1Start) { 4819 var promise = new qq.Promise(); 4820 qq.readBlobToHex(fileOrBlob, app1Start + 10, 2).then(function(hex) { 4821 promise.success(hex === "4949"); 4822 }); 4823 return promise; 4824 } 4825 function getDirEntryCount(app1Start, littleEndian) { 4826 var promise = new qq.Promise(); 4827 qq.readBlobToHex(fileOrBlob, app1Start + 18, 2).then(function(hex) { 4828 if (littleEndian) { 4829 return promise.success(parseLittleEndian(hex)); 4830 } else { 4831 promise.success(parseInt(hex, 16)); 4832 } 4833 }); 4834 return promise; 4835 } 4836 function getIfd(app1Start, dirEntries) { 4837 var offset = app1Start + 20, bytes = dirEntries * 12; 4838 return qq.readBlobToHex(fileOrBlob, offset, bytes); 4839 } 4840 function getDirEntries(ifdHex) { 4841 var entries = [], offset = 0; 4842 while (offset + 24 <= ifdHex.length) { 4843 entries.push(ifdHex.slice(offset, offset + 24)); 4844 offset += 24; 4845 } 4846 return entries; 4847 } 4848 function getTagValues(littleEndian, dirEntries) { 4849 var TAG_VAL_OFFSET = 16, tagsToFind = qq.extend([], TAG_IDS), vals = {}; 4850 qq.each(dirEntries, function(idx, entry) { 4851 var idHex = entry.slice(0, 4), id = littleEndian ? parseLittleEndian(idHex) : parseInt(idHex, 16), tagsToFindIdx = tagsToFind.indexOf(id), tagValHex, tagName, tagValLength; 4852 if (tagsToFindIdx >= 0) { 4853 tagName = TAG_INFO[id].name; 4854 tagValLength = TAG_INFO[id].bytes; 4855 tagValHex = entry.slice(TAG_VAL_OFFSET, TAG_VAL_OFFSET + tagValLength * 2); 4856 vals[tagName] = littleEndian ? parseLittleEndian(tagValHex) : parseInt(tagValHex, 16); 4857 tagsToFind.splice(tagsToFindIdx, 1); 4858 } 4859 if (tagsToFind.length === 0) { 4860 return false; 4861 } 4862 }); 4863 return vals; 4864 } 4865 qq.extend(this, { 4866 parse: function() { 4867 var parser = new qq.Promise(), onParseFailure = function(message) { 4868 log(qq.format("EXIF header parse failed: '{}' ", message)); 4869 parser.failure(message); 4870 }; 4871 getApp1Offset().then(function(app1Offset) { 4872 log(qq.format("Moving forward with EXIF header parsing for '{}'", fileOrBlob.name === undefined ? "blob" : fileOrBlob.name)); 4873 isLittleEndian(app1Offset).then(function(littleEndian) { 4874 log(qq.format("EXIF Byte order is {} endian", littleEndian ? "little" : "big")); 4875 getDirEntryCount(app1Offset, littleEndian).then(function(dirEntryCount) { 4876 log(qq.format("Found {} APP1 directory entries", dirEntryCount)); 4877 getIfd(app1Offset, dirEntryCount).then(function(ifdHex) { 4878 var dirEntries = getDirEntries(ifdHex), tagValues = getTagValues(littleEndian, dirEntries); 4879 log("Successfully parsed some EXIF tags"); 4880 parser.success(tagValues); 4881 }, onParseFailure); 4882 }, onParseFailure); 4883 }, onParseFailure); 4884 }, onParseFailure); 4885 return parser; 4886 } 4887 }); 4888 this._testing = {}; 4889 this._testing.parseLittleEndian = parseLittleEndian; 4890 }; 4891 qq.Identify = function(fileOrBlob, log) { 4892 "use strict"; 4893 function isIdentifiable(magicBytes, questionableBytes) { 4894 var identifiable = false, magicBytesEntries = [].concat(magicBytes); 4895 qq.each(magicBytesEntries, function(idx, magicBytesArrayEntry) { 4896 if (questionableBytes.indexOf(magicBytesArrayEntry) === 0) { 4897 identifiable = true; 4898 return false; 4899 } 4900 }); 4901 return identifiable; 4902 } 4903 qq.extend(this, { 4904 isPreviewable: function() { 4905 var self = this, identifier = new qq.Promise(), previewable = false, name = fileOrBlob.name === undefined ? "blob" : fileOrBlob.name; 4906 log(qq.format("Attempting to determine if {} can be rendered in this browser", name)); 4907 log("First pass: check type attribute of blob object."); 4908 if (this.isPreviewableSync()) { 4909 log("Second pass: check for magic bytes in file header."); 4910 qq.readBlobToHex(fileOrBlob, 0, 4).then(function(hex) { 4911 qq.each(self.PREVIEWABLE_MIME_TYPES, function(mime, bytes) { 4912 if (isIdentifiable(bytes, hex)) { 4913 if (mime !== "image/tiff" || qq.supportedFeatures.tiffPreviews) { 4914 previewable = true; 4915 identifier.success(mime); 4916 } 4917 return false; 4918 } 4919 }); 4920 log(qq.format("'{}' is {} able to be rendered in this browser", name, previewable ? "" : "NOT")); 4921 if (!previewable) { 4922 identifier.failure(); 4923 } 4924 }, function() { 4925 log("Error reading file w/ name '" + name + "'. Not able to be rendered in this browser."); 4926 identifier.failure(); 4927 }); 4928 } else { 4929 identifier.failure(); 4930 } 4931 return identifier; 4932 }, 4933 isPreviewableSync: function() { 4934 var fileMime = fileOrBlob.type, isRecognizedImage = qq.indexOf(Object.keys(this.PREVIEWABLE_MIME_TYPES), fileMime) >= 0, previewable = false, name = fileOrBlob.name === undefined ? "blob" : fileOrBlob.name; 4935 if (isRecognizedImage) { 4936 if (fileMime === "image/tiff") { 4937 previewable = qq.supportedFeatures.tiffPreviews; 4938 } else { 4939 previewable = true; 4940 } 4941 } 4942 !previewable && log(name + " is not previewable in this browser per the blob's type attr"); 4943 return previewable; 4944 } 4945 }); 4946 }; 4947 qq.Identify.prototype.PREVIEWABLE_MIME_TYPES = { 4948 "image/jpeg": "ffd8ff", 4949 "image/gif": "474946", 4950 "image/png": "89504e", 4951 "image/bmp": "424d", 4952 "image/tiff": [ "49492a00", "4d4d002a" ] 4953 }; 4954 qq.ImageValidation = function(blob, log) { 4955 "use strict"; 4956 function hasNonZeroLimits(limits) { 4957 var atLeastOne = false; 4958 qq.each(limits, function(limit, value) { 4959 if (value > 0) { 4960 atLeastOne = true; 4961 return false; 4962 } 4963 }); 4964 return atLeastOne; 4965 } 4966 function getWidthHeight() { 4967 var sizeDetermination = new qq.Promise(); 4968 new qq.Identify(blob, log).isPreviewable().then(function() { 4969 var image = new Image(), url = window.URL && window.URL.createObjectURL ? window.URL : window.webkitURL && window.webkitURL.createObjectURL ? window.webkitURL : null; 4970 if (url) { 4971 image.onerror = function() { 4972 log("Cannot determine dimensions for image. May be too large.", "error"); 4973 sizeDetermination.failure(); 4974 }; 4975 image.onload = function() { 4976 sizeDetermination.success({ 4977 width: this.width, 4978 height: this.height 4979 }); 4980 }; 4981 image.src = url.createObjectURL(blob); 4982 } else { 4983 log("No createObjectURL function available to generate image URL!", "error"); 4984 sizeDetermination.failure(); 4985 } 4986 }, sizeDetermination.failure); 4987 return sizeDetermination; 4988 } 4989 function getFailingLimit(limits, dimensions) { 4990 var failingLimit; 4991 qq.each(limits, function(limitName, limitValue) { 4992 if (limitValue > 0) { 4993 var limitMatcher = /(max|min)(Width|Height)/.exec(limitName), dimensionPropName = limitMatcher[2].charAt(0).toLowerCase() + limitMatcher[2].slice(1), actualValue = dimensions[dimensionPropName]; 4994 switch (limitMatcher[1]) { 4995 case "min": 4996 if (actualValue < limitValue) { 4997 failingLimit = limitName; 4998 return false; 4999 } 5000 break; 5001 5002 case "max": 5003 if (actualValue > limitValue) { 5004 failingLimit = limitName; 5005 return false; 5006 } 5007 break; 5008 } 5009 } 5010 }); 5011 return failingLimit; 5012 } 5013 this.validate = function(limits) { 5014 var validationEffort = new qq.Promise(); 5015 log("Attempting to validate image."); 5016 if (hasNonZeroLimits(limits)) { 5017 getWidthHeight().then(function(dimensions) { 5018 var failingLimit = getFailingLimit(limits, dimensions); 5019 if (failingLimit) { 5020 validationEffort.failure(failingLimit); 5021 } else { 5022 validationEffort.success(); 5023 } 5024 }, validationEffort.success); 5025 } else { 5026 validationEffort.success(); 5027 } 5028 return validationEffort; 5029 }; 5030 }; 5031 qq.Session = function(spec) { 5032 "use strict"; 5033 var options = { 5034 endpoint: null, 5035 params: {}, 5036 customHeaders: {}, 5037 cors: {}, 5038 addFileRecord: function(sessionData) {}, 5039 log: function(message, level) {} 5040 }; 5041 qq.extend(options, spec, true); 5042 function isJsonResponseValid(response) { 5043 if (qq.isArray(response)) { 5044 return true; 5045 } 5046 options.log("Session response is not an array.", "error"); 5047 } 5048 function handleFileItems(fileItems, success, xhrOrXdr, promise) { 5049 var someItemsIgnored = false; 5050 success = success && isJsonResponseValid(fileItems); 5051 if (success) { 5052 qq.each(fileItems, function(idx, fileItem) { 5053 if (fileItem.uuid == null) { 5054 someItemsIgnored = true; 5055 options.log(qq.format("Session response item {} did not include a valid UUID - ignoring.", idx), "error"); 5056 } else if (fileItem.name == null) { 5057 someItemsIgnored = true; 5058 options.log(qq.format("Session response item {} did not include a valid name - ignoring.", idx), "error"); 5059 } else { 5060 try { 5061 options.addFileRecord(fileItem); 5062 return true; 5063 } catch (err) { 5064 someItemsIgnored = true; 5065 options.log(err.message, "error"); 5066 } 5067 } 5068 return false; 5069 }); 5070 } 5071 promise[success && !someItemsIgnored ? "success" : "failure"](fileItems, xhrOrXdr); 5072 } 5073 this.refresh = function() { 5074 var refreshEffort = new qq.Promise(), refreshCompleteCallback = function(response, success, xhrOrXdr) { 5075 handleFileItems(response, success, xhrOrXdr, refreshEffort); 5076 }, requesterOptions = qq.extend({}, options), requester = new qq.SessionAjaxRequester(qq.extend(requesterOptions, { 5077 onComplete: refreshCompleteCallback 5078 })); 5079 requester.queryServer(); 5080 return refreshEffort; 5081 }; 5082 }; 5083 qq.SessionAjaxRequester = function(spec) { 5084 "use strict"; 5085 var requester, options = { 5086 endpoint: null, 5087 customHeaders: {}, 5088 params: {}, 5089 cors: { 5090 expected: false, 5091 sendCredentials: false 5092 }, 5093 onComplete: function(response, success, xhrOrXdr) {}, 5094 log: function(str, level) {} 5095 }; 5096 qq.extend(options, spec); 5097 function onComplete(id, xhrOrXdr, isError) { 5098 var response = null; 5099 if (xhrOrXdr.responseText != null) { 5100 try { 5101 response = qq.parseJson(xhrOrXdr.responseText); 5102 } catch (err) { 5103 options.log("Problem parsing session response: " + err.message, "error"); 5104 isError = true; 5105 } 5106 } 5107 options.onComplete(response, !isError, xhrOrXdr); 5108 } 5109 requester = qq.extend(this, new qq.AjaxRequester({ 5110 acceptHeader: "application/json", 5111 validMethods: [ "GET" ], 5112 method: "GET", 5113 endpointStore: { 5114 get: function() { 5115 return options.endpoint; 5116 } 5117 }, 5118 customHeaders: options.customHeaders, 5119 log: options.log, 5120 onComplete: onComplete, 5121 cors: options.cors 5122 })); 5123 qq.extend(this, { 5124 queryServer: function() { 5125 var params = qq.extend({}, options.params); 5126 options.log("Session query request."); 5127 requester.initTransport("sessionRefresh").withParams(params).withCacheBuster().send(); 5128 } 5129 }); 5130 }; 5131 qq.Scaler = function(spec, log) { 5132 "use strict"; 5133 var self = this, customResizeFunction = spec.customResizer, includeOriginal = spec.sendOriginal, orient = spec.orient, defaultType = spec.defaultType, defaultQuality = spec.defaultQuality / 100, failedToScaleText = spec.failureText, includeExif = spec.includeExif, sizes = this._getSortedSizes(spec.sizes); 5134 qq.extend(this, { 5135 enabled: qq.supportedFeatures.scaling && sizes.length > 0, 5136 getFileRecords: function(originalFileUuid, originalFileName, originalBlobOrBlobData) { 5137 var self = this, records = [], originalBlob = originalBlobOrBlobData.blob ? originalBlobOrBlobData.blob : originalBlobOrBlobData, identifier = new qq.Identify(originalBlob, log); 5138 if (identifier.isPreviewableSync()) { 5139 qq.each(sizes, function(idx, sizeRecord) { 5140 var outputType = self._determineOutputType({ 5141 defaultType: defaultType, 5142 requestedType: sizeRecord.type, 5143 refType: originalBlob.type 5144 }); 5145 records.push({ 5146 uuid: qq.getUniqueId(), 5147 name: self._getName(originalFileName, { 5148 name: sizeRecord.name, 5149 type: outputType, 5150 refType: originalBlob.type 5151 }), 5152 blob: new qq.BlobProxy(originalBlob, qq.bind(self._generateScaledImage, self, { 5153 customResizeFunction: customResizeFunction, 5154 maxSize: sizeRecord.maxSize, 5155 orient: orient, 5156 type: outputType, 5157 quality: defaultQuality, 5158 failedText: failedToScaleText, 5159 includeExif: includeExif, 5160 log: log 5161 })) 5162 }); 5163 }); 5164 records.push({ 5165 uuid: originalFileUuid, 5166 name: originalFileName, 5167 size: originalBlob.size, 5168 blob: includeOriginal ? originalBlob : null 5169 }); 5170 } else { 5171 records.push({ 5172 uuid: originalFileUuid, 5173 name: originalFileName, 5174 size: originalBlob.size, 5175 blob: originalBlob 5176 }); 5177 } 5178 return records; 5179 }, 5180 handleNewFile: function(file, name, uuid, size, fileList, batchId, uuidParamName, api) { 5181 var self = this, buttonId = file.qqButtonId || file.blob && file.blob.qqButtonId, scaledIds = [], originalId = null, addFileToHandler = api.addFileToHandler, uploadData = api.uploadData, paramsStore = api.paramsStore, proxyGroupId = qq.getUniqueId(); 5182 qq.each(self.getFileRecords(uuid, name, file), function(idx, record) { 5183 var blobSize = record.size, id; 5184 if (record.blob instanceof qq.BlobProxy) { 5185 blobSize = -1; 5186 } 5187 id = uploadData.addFile({ 5188 uuid: record.uuid, 5189 name: record.name, 5190 size: blobSize, 5191 batchId: batchId, 5192 proxyGroupId: proxyGroupId 5193 }); 5194 if (record.blob instanceof qq.BlobProxy) { 5195 scaledIds.push(id); 5196 } else { 5197 originalId = id; 5198 } 5199 if (record.blob) { 5200 addFileToHandler(id, record.blob); 5201 fileList.push({ 5202 id: id, 5203 file: record.blob 5204 }); 5205 } else { 5206 uploadData.setStatus(id, qq.status.REJECTED); 5207 } 5208 }); 5209 if (originalId !== null) { 5210 qq.each(scaledIds, function(idx, scaledId) { 5211 var params = { 5212 qqparentuuid: uploadData.retrieve({ 5213 id: originalId 5214 }).uuid, 5215 qqparentsize: uploadData.retrieve({ 5216 id: originalId 5217 }).size 5218 }; 5219 params[uuidParamName] = uploadData.retrieve({ 5220 id: scaledId 5221 }).uuid; 5222 uploadData.setParentId(scaledId, originalId); 5223 paramsStore.addReadOnly(scaledId, params); 5224 }); 5225 if (scaledIds.length) { 5226 (function() { 5227 var param = {}; 5228 param[uuidParamName] = uploadData.retrieve({ 5229 id: originalId 5230 }).uuid; 5231 paramsStore.addReadOnly(originalId, param); 5232 })(); 5233 } 5234 } 5235 } 5236 }); 5237 }; 5238 qq.extend(qq.Scaler.prototype, { 5239 scaleImage: function(id, specs, api) { 5240 "use strict"; 5241 if (!qq.supportedFeatures.scaling) { 5242 throw new qq.Error("Scaling is not supported in this browser!"); 5243 } 5244 var scalingEffort = new qq.Promise(), log = api.log, file = api.getFile(id), uploadData = api.uploadData.retrieve({ 5245 id: id 5246 }), name = uploadData && uploadData.name, uuid = uploadData && uploadData.uuid, scalingOptions = { 5247 customResizer: specs.customResizer, 5248 sendOriginal: false, 5249 orient: specs.orient, 5250 defaultType: specs.type || null, 5251 defaultQuality: specs.quality, 5252 failedToScaleText: "Unable to scale", 5253 sizes: [ { 5254 name: "", 5255 maxSize: specs.maxSize 5256 } ] 5257 }, scaler = new qq.Scaler(scalingOptions, log); 5258 if (!qq.Scaler || !qq.supportedFeatures.imagePreviews || !file) { 5259 scalingEffort.failure(); 5260 log("Could not generate requested scaled image for " + id + ". " + "Scaling is either not possible in this browser, or the file could not be located.", "error"); 5261 } else { 5262 qq.bind(function() { 5263 var record = scaler.getFileRecords(uuid, name, file)[0]; 5264 if (record && record.blob instanceof qq.BlobProxy) { 5265 record.blob.create().then(scalingEffort.success, scalingEffort.failure); 5266 } else { 5267 log(id + " is not a scalable image!", "error"); 5268 scalingEffort.failure(); 5269 } 5270 }, this)(); 5271 } 5272 return scalingEffort; 5273 }, 5274 _determineOutputType: function(spec) { 5275 "use strict"; 5276 var requestedType = spec.requestedType, defaultType = spec.defaultType, referenceType = spec.refType; 5277 if (!defaultType && !requestedType) { 5278 if (referenceType !== "image/jpeg") { 5279 return "image/png"; 5280 } 5281 return referenceType; 5282 } 5283 if (!requestedType) { 5284 return defaultType; 5285 } 5286 if (qq.indexOf(Object.keys(qq.Identify.prototype.PREVIEWABLE_MIME_TYPES), requestedType) >= 0) { 5287 if (requestedType === "image/tiff") { 5288 return qq.supportedFeatures.tiffPreviews ? requestedType : defaultType; 5289 } 5290 return requestedType; 5291 } 5292 return defaultType; 5293 }, 5294 _getName: function(originalName, scaledVersionProperties) { 5295 "use strict"; 5296 var startOfExt = originalName.lastIndexOf("."), versionType = scaledVersionProperties.type || "image/png", referenceType = scaledVersionProperties.refType, scaledName = "", scaledExt = qq.getExtension(originalName), nameAppendage = ""; 5297 if (scaledVersionProperties.name && scaledVersionProperties.name.trim().length) { 5298 nameAppendage = " (" + scaledVersionProperties.name + ")"; 5299 } 5300 if (startOfExt >= 0) { 5301 scaledName = originalName.substr(0, startOfExt); 5302 if (referenceType !== versionType) { 5303 scaledExt = versionType.split("/")[1]; 5304 } 5305 scaledName += nameAppendage + "." + scaledExt; 5306 } else { 5307 scaledName = originalName + nameAppendage; 5308 } 5309 return scaledName; 5310 }, 5311 _getSortedSizes: function(sizes) { 5312 "use strict"; 5313 sizes = qq.extend([], sizes); 5314 return sizes.sort(function(a, b) { 5315 if (a.maxSize > b.maxSize) { 5316 return 1; 5317 } 5318 if (a.maxSize < b.maxSize) { 5319 return -1; 5320 } 5321 return 0; 5322 }); 5323 }, 5324 _generateScaledImage: function(spec, sourceFile) { 5325 "use strict"; 5326 var self = this, customResizeFunction = spec.customResizeFunction, log = spec.log, maxSize = spec.maxSize, orient = spec.orient, type = spec.type, quality = spec.quality, failedText = spec.failedText, includeExif = spec.includeExif && sourceFile.type === "image/jpeg" && type === "image/jpeg", scalingEffort = new qq.Promise(), imageGenerator = new qq.ImageGenerator(log), canvas = document.createElement("canvas"); 5327 log("Attempting to generate scaled version for " + sourceFile.name); 5328 imageGenerator.generate(sourceFile, canvas, { 5329 maxSize: maxSize, 5330 orient: orient, 5331 customResizeFunction: customResizeFunction 5332 }).then(function() { 5333 var scaledImageDataUri = canvas.toDataURL(type, quality), signalSuccess = function() { 5334 log("Success generating scaled version for " + sourceFile.name); 5335 var blob = qq.dataUriToBlob(scaledImageDataUri); 5336 scalingEffort.success(blob); 5337 }; 5338 if (includeExif) { 5339 self._insertExifHeader(sourceFile, scaledImageDataUri, log).then(function(scaledImageDataUriWithExif) { 5340 scaledImageDataUri = scaledImageDataUriWithExif; 5341 signalSuccess(); 5342 }, function() { 5343 log("Problem inserting EXIF header into scaled image. Using scaled image w/out EXIF data.", "error"); 5344 signalSuccess(); 5345 }); 5346 } else { 5347 signalSuccess(); 5348 } 5349 }, function() { 5350 log("Failed attempt to generate scaled version for " + sourceFile.name, "error"); 5351 scalingEffort.failure(failedText); 5352 }); 5353 return scalingEffort; 5354 }, 5355 _insertExifHeader: function(originalImage, scaledImageDataUri, log) { 5356 "use strict"; 5357 var reader = new FileReader(), insertionEffort = new qq.Promise(), originalImageDataUri = ""; 5358 reader.onload = function() { 5359 originalImageDataUri = reader.result; 5360 insertionEffort.success(qq.ExifRestorer.restore(originalImageDataUri, scaledImageDataUri)); 5361 }; 5362 reader.onerror = function() { 5363 log("Problem reading " + originalImage.name + " during attempt to transfer EXIF data to scaled version.", "error"); 5364 insertionEffort.failure(); 5365 }; 5366 reader.readAsDataURL(originalImage); 5367 return insertionEffort; 5368 }, 5369 _dataUriToBlob: function(dataUri) { 5370 "use strict"; 5371 var byteString, mimeString, arrayBuffer, intArray; 5372 if (dataUri.split(",")[0].indexOf("base64") >= 0) { 5373 byteString = atob(dataUri.split(",")[1]); 5374 } else { 5375 byteString = decodeURI(dataUri.split(",")[1]); 5376 } 5377 mimeString = dataUri.split(",")[0].split(":")[1].split(";")[0]; 5378 arrayBuffer = new ArrayBuffer(byteString.length); 5379 intArray = new Uint8Array(arrayBuffer); 5380 qq.each(byteString, function(idx, character) { 5381 intArray[idx] = character.charCodeAt(0); 5382 }); 5383 return this._createBlob(arrayBuffer, mimeString); 5384 }, 5385 _createBlob: function(data, mime) { 5386 "use strict"; 5387 var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder, blobBuilder = BlobBuilder && new BlobBuilder(); 5388 if (blobBuilder) { 5389 blobBuilder.append(data); 5390 return blobBuilder.getBlob(mime); 5391 } else { 5392 return new Blob([ data ], { 5393 type: mime 5394 }); 5395 } 5396 } 5397 }); 5398 qq.ExifRestorer = function() { 5399 var ExifRestorer = {}; 5400 ExifRestorer.KEY_STR = "ABCDEFGHIJKLMNOP" + "QRSTUVWXYZabcdef" + "ghijklmnopqrstuv" + "wxyz0123456789+/" + "="; 5401 ExifRestorer.encode64 = function(input) { 5402 var output = "", chr1, chr2, chr3 = "", enc1, enc2, enc3, enc4 = "", i = 0; 5403 do { 5404 chr1 = input[i++]; 5405 chr2 = input[i++]; 5406 chr3 = input[i++]; 5407 enc1 = chr1 >> 2; 5408 enc2 = (chr1 & 3) << 4 | chr2 >> 4; 5409 enc3 = (chr2 & 15) << 2 | chr3 >> 6; 5410 enc4 = chr3 & 63; 5411 if (isNaN(chr2)) { 5412 enc3 = enc4 = 64; 5413 } else if (isNaN(chr3)) { 5414 enc4 = 64; 5415 } 5416 output = output + this.KEY_STR.charAt(enc1) + this.KEY_STR.charAt(enc2) + this.KEY_STR.charAt(enc3) + this.KEY_STR.charAt(enc4); 5417 chr1 = chr2 = chr3 = ""; 5418 enc1 = enc2 = enc3 = enc4 = ""; 5419 } while (i < input.length); 5420 return output; 5421 }; 5422 ExifRestorer.restore = function(origFileBase64, resizedFileBase64) { 5423 var expectedBase64Header = "data:image/jpeg;base64,"; 5424 if (!origFileBase64.match(expectedBase64Header)) { 5425 return resizedFileBase64; 5426 } 5427 var rawImage = this.decode64(origFileBase64.replace(expectedBase64Header, "")); 5428 var segments = this.slice2Segments(rawImage); 5429 var image = this.exifManipulation(resizedFileBase64, segments); 5430 return expectedBase64Header + this.encode64(image); 5431 }; 5432 ExifRestorer.exifManipulation = function(resizedFileBase64, segments) { 5433 var exifArray = this.getExifArray(segments), newImageArray = this.insertExif(resizedFileBase64, exifArray), aBuffer = new Uint8Array(newImageArray); 5434 return aBuffer; 5435 }; 5436 ExifRestorer.getExifArray = function(segments) { 5437 var seg; 5438 for (var x = 0; x < segments.length; x++) { 5439 seg = segments[x]; 5440 if (seg[0] == 255 & seg[1] == 225) { 5441 return seg; 5442 } 5443 } 5444 return []; 5445 }; 5446 ExifRestorer.insertExif = function(resizedFileBase64, exifArray) { 5447 var imageData = resizedFileBase64.replace("data:image/jpeg;base64,", ""), buf = this.decode64(imageData), separatePoint = buf.indexOf(255, 3), mae = buf.slice(0, separatePoint), ato = buf.slice(separatePoint), array = mae; 5448 array = array.concat(exifArray); 5449 array = array.concat(ato); 5450 return array; 5451 }; 5452 ExifRestorer.slice2Segments = function(rawImageArray) { 5453 var head = 0, segments = []; 5454 while (1) { 5455 if (rawImageArray[head] == 255 & rawImageArray[head + 1] == 218) { 5456 break; 5457 } 5458 if (rawImageArray[head] == 255 & rawImageArray[head + 1] == 216) { 5459 head += 2; 5460 } else { 5461 var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3], endPoint = head + length + 2, seg = rawImageArray.slice(head, endPoint); 5462 segments.push(seg); 5463 head = endPoint; 5464 } 5465 if (head > rawImageArray.length) { 5466 break; 5467 } 5468 } 5469 return segments; 5470 }; 5471 ExifRestorer.decode64 = function(input) { 5472 var output = "", chr1, chr2, chr3 = "", enc1, enc2, enc3, enc4 = "", i = 0, buf = []; 5473 var base64test = /[^A-Za-z0-9\+\/\=]/g; 5474 if (base64test.exec(input)) { 5475 throw new Error("There were invalid base64 characters in the input text. " + "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='"); 5476 } 5477 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 5478 do { 5479 enc1 = this.KEY_STR.indexOf(input.charAt(i++)); 5480 enc2 = this.KEY_STR.indexOf(input.charAt(i++)); 5481 enc3 = this.KEY_STR.indexOf(input.charAt(i++)); 5482 enc4 = this.KEY_STR.indexOf(input.charAt(i++)); 5483 chr1 = enc1 << 2 | enc2 >> 4; 5484 chr2 = (enc2 & 15) << 4 | enc3 >> 2; 5485 chr3 = (enc3 & 3) << 6 | enc4; 5486 buf.push(chr1); 5487 if (enc3 != 64) { 5488 buf.push(chr2); 5489 } 5490 if (enc4 != 64) { 5491 buf.push(chr3); 5492 } 5493 chr1 = chr2 = chr3 = ""; 5494 enc1 = enc2 = enc3 = enc4 = ""; 5495 } while (i < input.length); 5496 return buf; 5497 }; 5498 return ExifRestorer; 5499 }(); 5500 qq.TotalProgress = function(callback, getSize) { 5501 "use strict"; 5502 var perFileProgress = {}, totalLoaded = 0, totalSize = 0, lastLoadedSent = -1, lastTotalSent = -1, callbackProxy = function(loaded, total) { 5503 if (loaded !== lastLoadedSent || total !== lastTotalSent) { 5504 callback(loaded, total); 5505 } 5506 lastLoadedSent = loaded; 5507 lastTotalSent = total; 5508 }, noRetryableFiles = function(failed, retryable) { 5509 var none = true; 5510 qq.each(failed, function(idx, failedId) { 5511 if (qq.indexOf(retryable, failedId) >= 0) { 5512 none = false; 5513 return false; 5514 } 5515 }); 5516 return none; 5517 }, onCancel = function(id) { 5518 updateTotalProgress(id, -1, -1); 5519 delete perFileProgress[id]; 5520 }, onAllComplete = function(successful, failed, retryable) { 5521 if (failed.length === 0 || noRetryableFiles(failed, retryable)) { 5522 callbackProxy(totalSize, totalSize); 5523 this.reset(); 5524 } 5525 }, onNew = function(id) { 5526 var size = getSize(id); 5527 if (size > 0) { 5528 updateTotalProgress(id, 0, size); 5529 perFileProgress[id] = { 5530 loaded: 0, 5531 total: size 5532 }; 5533 } 5534 }, updateTotalProgress = function(id, newLoaded, newTotal) { 5535 var oldLoaded = perFileProgress[id] ? perFileProgress[id].loaded : 0, oldTotal = perFileProgress[id] ? perFileProgress[id].total : 0; 5536 if (newLoaded === -1 && newTotal === -1) { 5537 totalLoaded -= oldLoaded; 5538 totalSize -= oldTotal; 5539 } else { 5540 if (newLoaded) { 5541 totalLoaded += newLoaded - oldLoaded; 5542 } 5543 if (newTotal) { 5544 totalSize += newTotal - oldTotal; 5545 } 5546 } 5547 callbackProxy(totalLoaded, totalSize); 5548 }; 5549 qq.extend(this, { 5550 onAllComplete: onAllComplete, 5551 onStatusChange: function(id, oldStatus, newStatus) { 5552 if (newStatus === qq.status.CANCELED || newStatus === qq.status.REJECTED) { 5553 onCancel(id); 5554 } else if (newStatus === qq.status.SUBMITTING) { 5555 onNew(id); 5556 } 5557 }, 5558 onIndividualProgress: function(id, loaded, total) { 5559 updateTotalProgress(id, loaded, total); 5560 perFileProgress[id] = { 5561 loaded: loaded, 5562 total: total 5563 }; 5564 }, 5565 onNewSize: function(id) { 5566 onNew(id); 5567 }, 5568 reset: function() { 5569 perFileProgress = {}; 5570 totalLoaded = 0; 5571 totalSize = 0; 5572 } 5573 }); 5574 }; 5575 qq.PasteSupport = function(o) { 5576 "use strict"; 5577 var options, detachPasteHandler; 5578 options = { 5579 targetElement: null, 5580 callbacks: { 5581 log: function(message, level) {}, 5582 pasteReceived: function(blob) {} 5583 } 5584 }; 5585 function isImage(item) { 5586 return item.type && item.type.indexOf("image/") === 0; 5587 } 5588 function registerPasteHandler() { 5589 detachPasteHandler = qq(options.targetElement).attach("paste", function(event) { 5590 var clipboardData = event.clipboardData; 5591 if (clipboardData) { 5592 qq.each(clipboardData.items, function(idx, item) { 5593 if (isImage(item)) { 5594 var blob = item.getAsFile(); 5595 options.callbacks.pasteReceived(blob); 5596 } 5597 }); 5598 } 5599 }); 5600 } 5601 function unregisterPasteHandler() { 5602 if (detachPasteHandler) { 5603 detachPasteHandler(); 5604 } 5605 } 5606 qq.extend(options, o); 5607 registerPasteHandler(); 5608 qq.extend(this, { 5609 reset: function() { 5610 unregisterPasteHandler(); 5611 } 5612 }); 5613 }; 5614 qq.FormSupport = function(options, startUpload, log) { 5615 "use strict"; 5616 var self = this, interceptSubmit = options.interceptSubmit, formEl = options.element, autoUpload = options.autoUpload; 5617 qq.extend(this, { 5618 newEndpoint: null, 5619 newAutoUpload: autoUpload, 5620 attachedToForm: false, 5621 getFormInputsAsObject: function() { 5622 if (formEl == null) { 5623 return null; 5624 } 5625 return self._form2Obj(formEl); 5626 } 5627 }); 5628 function determineNewEndpoint(formEl) { 5629 if (formEl.getAttribute("action")) { 5630 self.newEndpoint = formEl.getAttribute("action"); 5631 } 5632 } 5633 function validateForm(formEl, nativeSubmit) { 5634 if (formEl.checkValidity && !formEl.checkValidity()) { 5635 log("Form did not pass validation checks - will not upload.", "error"); 5636 nativeSubmit(); 5637 } else { 5638 return true; 5639 } 5640 } 5641 function maybeUploadOnSubmit(formEl) { 5642 var nativeSubmit = formEl.submit; 5643 qq(formEl).attach("submit", function(event) { 5644 event = event || window.event; 5645 if (event.preventDefault) { 5646 event.preventDefault(); 5647 } else { 5648 event.returnValue = false; 5649 } 5650 validateForm(formEl, nativeSubmit) && startUpload(); 5651 }); 5652 formEl.submit = function() { 5653 validateForm(formEl, nativeSubmit) && startUpload(); 5654 }; 5655 } 5656 function determineFormEl(formEl) { 5657 if (formEl) { 5658 if (qq.isString(formEl)) { 5659 formEl = document.getElementById(formEl); 5660 } 5661 if (formEl) { 5662 log("Attaching to form element."); 5663 determineNewEndpoint(formEl); 5664 interceptSubmit && maybeUploadOnSubmit(formEl); 5665 } 5666 } 5667 return formEl; 5668 } 5669 formEl = determineFormEl(formEl); 5670 this.attachedToForm = !!formEl; 5671 }; 5672 qq.extend(qq.FormSupport.prototype, { 5673 _form2Obj: function(form) { 5674 "use strict"; 5675 var obj = {}, notIrrelevantType = function(type) { 5676 var irrelevantTypes = [ "button", "image", "reset", "submit" ]; 5677 return qq.indexOf(irrelevantTypes, type.toLowerCase()) < 0; 5678 }, radioOrCheckbox = function(type) { 5679 return qq.indexOf([ "checkbox", "radio" ], type.toLowerCase()) >= 0; 5680 }, ignoreValue = function(el) { 5681 if (radioOrCheckbox(el.type) && !el.checked) { 5682 return true; 5683 } 5684 return el.disabled && el.type.toLowerCase() !== "hidden"; 5685 }, selectValue = function(select) { 5686 var value = null; 5687 qq.each(qq(select).children(), function(idx, child) { 5688 if (child.tagName.toLowerCase() === "option" && child.selected) { 5689 value = child.value; 5690 return false; 5691 } 5692 }); 5693 return value; 5694 }; 5695 qq.each(form.elements, function(idx, el) { 5696 if ((qq.isInput(el, true) || el.tagName.toLowerCase() === "textarea") && notIrrelevantType(el.type) && !ignoreValue(el)) { 5697 obj[el.name] = el.value; 5698 } else if (el.tagName.toLowerCase() === "select" && !ignoreValue(el)) { 5699 var value = selectValue(el); 5700 if (value !== null) { 5701 obj[el.name] = value; 5702 } 5703 } 5704 }); 5705 return obj; 5706 } 5707 }); 5708 qq.traditional = qq.traditional || {}; 5709 qq.traditional.FormUploadHandler = function(options, proxy) { 5710 "use strict"; 5711 var handler = this, getName = proxy.getName, getUuid = proxy.getUuid, log = proxy.log; 5712 function getIframeContentJson(id, iframe) { 5713 var response, doc, innerHtml; 5714 try { 5715 doc = iframe.contentDocument || iframe.contentWindow.document; 5716 innerHtml = doc.body.innerHTML; 5717 log("converting iframe's innerHTML to JSON"); 5718 log("innerHTML = " + innerHtml); 5719 if (innerHtml && innerHtml.match(/^<pre/i)) { 5720 innerHtml = doc.body.firstChild.firstChild.nodeValue; 5721 } 5722 response = handler._parseJsonResponse(innerHtml); 5723 } catch (error) { 5724 log("Error when attempting to parse form upload response (" + error.message + ")", "error"); 5725 response = { 5726 success: false 5727 }; 5728 } 5729 return response; 5730 } 5731 function createForm(id, iframe) { 5732 var params = options.paramsStore.get(id), method = options.method.toLowerCase() === "get" ? "GET" : "POST", endpoint = options.endpointStore.get(id), name = getName(id); 5733 params[options.uuidName] = getUuid(id); 5734 params[options.filenameParam] = name; 5735 return handler._initFormForUpload({ 5736 method: method, 5737 endpoint: endpoint, 5738 params: params, 5739 paramsInBody: options.paramsInBody, 5740 targetName: iframe.name 5741 }); 5742 } 5743 this.uploadFile = function(id) { 5744 var input = handler.getInput(id), iframe = handler._createIframe(id), promise = new qq.Promise(), form; 5745 form = createForm(id, iframe); 5746 form.appendChild(input); 5747 handler._attachLoadEvent(iframe, function(responseFromMessage) { 5748 log("iframe loaded"); 5749 var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe); 5750 handler._detachLoadEvent(id); 5751 if (!options.cors.expected) { 5752 qq(iframe).remove(); 5753 } 5754 if (response.success) { 5755 promise.success(response); 5756 } else { 5757 promise.failure(response); 5758 } 5759 }); 5760 log("Sending upload request for " + id); 5761 form.submit(); 5762 qq(form).remove(); 5763 return promise; 5764 }; 5765 qq.extend(this, new qq.FormUploadHandler({ 5766 options: { 5767 isCors: options.cors.expected, 5768 inputName: options.inputName 5769 }, 5770 proxy: { 5771 onCancel: options.onCancel, 5772 getName: getName, 5773 getUuid: getUuid, 5774 log: log 5775 } 5776 })); 5777 }; 5778 qq.traditional = qq.traditional || {}; 5779 qq.traditional.XhrUploadHandler = function(spec, proxy) { 5780 "use strict"; 5781 var handler = this, getName = proxy.getName, getSize = proxy.getSize, getUuid = proxy.getUuid, log = proxy.log, multipart = spec.forceMultipart || spec.paramsInBody, addChunkingSpecificParams = function(id, params, chunkData) { 5782 var size = getSize(id), name = getName(id); 5783 if (!spec.omitDefaultParams) { 5784 params[spec.chunking.paramNames.partIndex] = chunkData.part; 5785 params[spec.chunking.paramNames.partByteOffset] = chunkData.start; 5786 params[spec.chunking.paramNames.chunkSize] = chunkData.size; 5787 params[spec.chunking.paramNames.totalParts] = chunkData.count; 5788 params[spec.totalFileSizeName] = size; 5789 } 5790 if (multipart && !spec.omitDefaultParams) { 5791 params[spec.filenameParam] = name; 5792 } 5793 }, allChunksDoneRequester = new qq.traditional.AllChunksDoneAjaxRequester({ 5794 cors: spec.cors, 5795 endpoint: spec.chunking.success.endpoint, 5796 headers: spec.chunking.success.headers, 5797 jsonPayload: spec.chunking.success.jsonPayload, 5798 log: log, 5799 method: spec.chunking.success.method, 5800 params: spec.chunking.success.params 5801 }), createReadyStateChangedHandler = function(id, xhr) { 5802 var promise = new qq.Promise(); 5803 xhr.onreadystatechange = function() { 5804 if (xhr.readyState === 4) { 5805 var result = onUploadOrChunkComplete(id, xhr); 5806 if (result.success) { 5807 promise.success(result.response, xhr); 5808 } else { 5809 promise.failure(result.response, xhr); 5810 } 5811 } 5812 }; 5813 return promise; 5814 }, getChunksCompleteParams = function(id) { 5815 var params = spec.paramsStore.get(id), name = getName(id), size = getSize(id); 5816 params[spec.uuidName] = getUuid(id); 5817 params[spec.filenameParam] = name; 5818 params[spec.totalFileSizeName] = size; 5819 params[spec.chunking.paramNames.totalParts] = handler._getTotalChunks(id); 5820 return params; 5821 }, isErrorUploadResponse = function(xhr, response) { 5822 return qq.indexOf([ 200, 201, 202, 203, 204 ], xhr.status) < 0 || spec.requireSuccessJson && !response.success || response.reset; 5823 }, onUploadOrChunkComplete = function(id, xhr) { 5824 var response; 5825 log("xhr - server response received for " + id); 5826 log("responseText = " + xhr.responseText); 5827 response = parseResponse(true, xhr); 5828 return { 5829 success: !isErrorUploadResponse(xhr, response), 5830 response: response 5831 }; 5832 }, parseResponse = function(upload, xhr) { 5833 var response = {}; 5834 try { 5835 log(qq.format("Received response status {} with body: {}", xhr.status, xhr.responseText)); 5836 response = qq.parseJson(xhr.responseText); 5837 } catch (error) { 5838 upload && spec.requireSuccessJson && log("Error when attempting to parse xhr response text (" + error.message + ")", "error"); 5839 } 5840 return response; 5841 }, sendChunksCompleteRequest = function(id) { 5842 var promise = new qq.Promise(); 5843 allChunksDoneRequester.complete(id, handler._createXhr(id), getChunksCompleteParams(id), spec.customHeaders.get(id)).then(function(xhr) { 5844 promise.success(parseResponse(false, xhr), xhr); 5845 }, function(xhr) { 5846 promise.failure(parseResponse(false, xhr), xhr); 5847 }); 5848 return promise; 5849 }, setParamsAndGetEntityToSend = function(entityToSendParams) { 5850 var fileOrBlob = entityToSendParams.fileOrBlob; 5851 var id = entityToSendParams.id; 5852 var xhr = entityToSendParams.xhr; 5853 var xhrOverrides = entityToSendParams.xhrOverrides || {}; 5854 var customParams = entityToSendParams.customParams || {}; 5855 var defaultParams = entityToSendParams.params || {}; 5856 var xhrOverrideParams = xhrOverrides.params || {}; 5857 var params; 5858 var formData = multipart ? new FormData() : null, method = xhrOverrides.method || spec.method, endpoint = xhrOverrides.endpoint || spec.endpointStore.get(id), name = getName(id), size = getSize(id); 5859 if (spec.omitDefaultParams) { 5860 params = qq.extend({}, customParams); 5861 qq.extend(params, xhrOverrideParams); 5862 } else { 5863 params = qq.extend({}, customParams); 5864 qq.extend(params, xhrOverrideParams); 5865 qq.extend(params, defaultParams); 5866 params[spec.uuidName] = getUuid(id); 5867 params[spec.filenameParam] = name; 5868 if (multipart) { 5869 params[spec.totalFileSizeName] = size; 5870 } else if (!spec.paramsInBody) { 5871 params[spec.inputName] = name; 5872 } 5873 } 5874 if (!spec.paramsInBody) { 5875 endpoint = qq.obj2url(params, endpoint); 5876 } 5877 xhr.open(method, endpoint, true); 5878 if (spec.cors.expected && spec.cors.sendCredentials) { 5879 xhr.withCredentials = true; 5880 } 5881 if (multipart) { 5882 if (spec.paramsInBody) { 5883 qq.obj2FormData(params, formData); 5884 } 5885 formData.append(spec.inputName, fileOrBlob); 5886 return formData; 5887 } 5888 return fileOrBlob; 5889 }, setUploadHeaders = function(headersOptions) { 5890 var headerOverrides = headersOptions.headerOverrides; 5891 var id = headersOptions.id; 5892 var xhr = headersOptions.xhr; 5893 if (headerOverrides) { 5894 qq.each(headerOverrides, function(headerName, headerValue) { 5895 xhr.setRequestHeader(headerName, headerValue); 5896 }); 5897 } else { 5898 var extraHeaders = spec.customHeaders.get(id), fileOrBlob = handler.getFile(id); 5899 xhr.setRequestHeader("Accept", "application/json"); 5900 xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); 5901 xhr.setRequestHeader("Cache-Control", "no-cache"); 5902 if (!multipart) { 5903 xhr.setRequestHeader("Content-Type", "application/octet-stream"); 5904 xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type); 5905 } 5906 qq.each(extraHeaders, function(name, val) { 5907 xhr.setRequestHeader(name, val); 5908 }); 5909 } 5910 }; 5911 qq.extend(this, { 5912 uploadChunk: function(uploadChunkParams) { 5913 var id = uploadChunkParams.id; 5914 var chunkIdx = uploadChunkParams.chunkIdx; 5915 var overrides = uploadChunkParams.overrides || {}; 5916 var resuming = uploadChunkParams.resuming; 5917 var chunkData = handler._getChunkData(id, chunkIdx), xhr = handler._createXhr(id, chunkIdx), promise, toSend, customParams, params = {}; 5918 promise = createReadyStateChangedHandler(id, xhr); 5919 handler._registerProgressHandler(id, chunkIdx, chunkData.size); 5920 customParams = spec.paramsStore.get(id); 5921 addChunkingSpecificParams(id, params, chunkData); 5922 if (resuming) { 5923 params[spec.resume.paramNames.resuming] = true; 5924 } 5925 toSend = setParamsAndGetEntityToSend({ 5926 fileOrBlob: chunkData.blob, 5927 id: id, 5928 customParams: customParams, 5929 params: params, 5930 xhr: xhr, 5931 xhrOverrides: overrides 5932 }); 5933 setUploadHeaders({ 5934 headerOverrides: overrides.headers, 5935 id: id, 5936 xhr: xhr 5937 }); 5938 xhr.send(toSend); 5939 return promise; 5940 }, 5941 uploadFile: function(id) { 5942 var fileOrBlob = handler.getFile(id), promise, xhr, customParams, toSend; 5943 xhr = handler._createXhr(id); 5944 handler._registerProgressHandler(id); 5945 promise = createReadyStateChangedHandler(id, xhr); 5946 customParams = spec.paramsStore.get(id); 5947 toSend = setParamsAndGetEntityToSend({ 5948 fileOrBlob: fileOrBlob, 5949 id: id, 5950 customParams: customParams, 5951 xhr: xhr 5952 }); 5953 setUploadHeaders({ 5954 id: id, 5955 xhr: xhr 5956 }); 5957 xhr.send(toSend); 5958 return promise; 5959 } 5960 }); 5961 qq.extend(this, new qq.XhrUploadHandler({ 5962 options: qq.extend({ 5963 namespace: "traditional" 5964 }, spec), 5965 proxy: qq.extend({ 5966 getEndpoint: spec.endpointStore.get 5967 }, proxy) 5968 })); 5969 qq.override(this, function(super_) { 5970 return { 5971 finalizeChunks: function(id) { 5972 proxy.onFinalizing(id); 5973 if (spec.chunking.success.endpoint) { 5974 return sendChunksCompleteRequest(id); 5975 } else { 5976 return super_.finalizeChunks(id, qq.bind(parseResponse, this, true)); 5977 } 5978 } 5979 }; 5980 }); 5981 }; 5982 qq.traditional.AllChunksDoneAjaxRequester = function(o) { 5983 "use strict"; 5984 var requester, options = { 5985 cors: { 5986 allowXdr: false, 5987 expected: false, 5988 sendCredentials: false 5989 }, 5990 endpoint: null, 5991 log: function(str, level) {}, 5992 method: "POST" 5993 }, promises = {}, endpointHandler = { 5994 get: function(id) { 5995 if (qq.isFunction(options.endpoint)) { 5996 return options.endpoint(id); 5997 } 5998 return options.endpoint; 5999 } 6000 }; 6001 qq.extend(options, o); 6002 requester = qq.extend(this, new qq.AjaxRequester({ 6003 acceptHeader: "application/json", 6004 contentType: options.jsonPayload ? "application/json" : "application/x-www-form-urlencoded", 6005 validMethods: [ options.method ], 6006 method: options.method, 6007 endpointStore: endpointHandler, 6008 allowXRequestedWithAndCacheControl: false, 6009 cors: options.cors, 6010 log: options.log, 6011 onComplete: function(id, xhr, isError) { 6012 var promise = promises[id]; 6013 delete promises[id]; 6014 if (isError) { 6015 promise.failure(xhr); 6016 } else { 6017 promise.success(xhr); 6018 } 6019 } 6020 })); 6021 qq.extend(this, { 6022 complete: function(id, xhr, params, headers) { 6023 var promise = new qq.Promise(); 6024 options.log("Submitting All Chunks Done request for " + id); 6025 promises[id] = promise; 6026 requester.initTransport(id).withParams(options.params(id) || params).withHeaders(options.headers(id) || headers).send(xhr); 6027 return promise; 6028 } 6029 }); 6030 }; 6031 qq.DragAndDrop = function(o) { 6032 "use strict"; 6033 var options, HIDE_ZONES_EVENT_NAME = "qq-hidezones", HIDE_BEFORE_ENTER_ATTR = "qq-hide-dropzone", uploadDropZones = [], droppedFiles = [], disposeSupport = new qq.DisposeSupport(); 6034 options = { 6035 dropZoneElements: [], 6036 allowMultipleItems: true, 6037 classes: { 6038 dropActive: null 6039 }, 6040 callbacks: new qq.DragAndDrop.callbacks() 6041 }; 6042 qq.extend(options, o, true); 6043 function uploadDroppedFiles(files, uploadDropZone) { 6044 var filesAsArray = Array.prototype.slice.call(files); 6045 options.callbacks.dropLog("Grabbed " + files.length + " dropped files."); 6046 uploadDropZone.dropDisabled(false); 6047 options.callbacks.processingDroppedFilesComplete(filesAsArray, uploadDropZone.getElement()); 6048 } 6049 function traverseFileTree(entry) { 6050 var parseEntryPromise = new qq.Promise(); 6051 if (entry.isFile) { 6052 entry.file(function(file) { 6053 file.qqPath = extractDirectoryPath(entry); 6054 droppedFiles.push(file); 6055 parseEntryPromise.success(); 6056 }, function(fileError) { 6057 options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error"); 6058 parseEntryPromise.failure(); 6059 }); 6060 } else if (entry.isDirectory) { 6061 getFilesInDirectory(entry).then(function allEntriesRead(entries) { 6062 var entriesLeft = entries.length; 6063 qq.each(entries, function(idx, entry) { 6064 traverseFileTree(entry).done(function() { 6065 entriesLeft -= 1; 6066 if (entriesLeft === 0) { 6067 parseEntryPromise.success(); 6068 } 6069 }); 6070 }); 6071 if (!entries.length) { 6072 parseEntryPromise.success(); 6073 } 6074 }, function readFailure(fileError) { 6075 options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error"); 6076 parseEntryPromise.failure(); 6077 }); 6078 } 6079 return parseEntryPromise; 6080 } 6081 function extractDirectoryPath(entry) { 6082 var name = entry.name, fullPath = entry.fullPath, indexOfNameInFullPath = fullPath.lastIndexOf(name); 6083 fullPath = fullPath.substr(0, indexOfNameInFullPath); 6084 if (fullPath.charAt(0) === "/") { 6085 fullPath = fullPath.substr(1); 6086 } 6087 return fullPath; 6088 } 6089 function getFilesInDirectory(entry, reader, accumEntries, existingPromise) { 6090 var promise = existingPromise || new qq.Promise(), dirReader = reader || entry.createReader(); 6091 dirReader.readEntries(function readSuccess(entries) { 6092 var newEntries = accumEntries ? accumEntries.concat(entries) : entries; 6093 if (entries.length) { 6094 setTimeout(function() { 6095 getFilesInDirectory(entry, dirReader, newEntries, promise); 6096 }, 0); 6097 } else { 6098 promise.success(newEntries); 6099 } 6100 }, promise.failure); 6101 return promise; 6102 } 6103 function handleDataTransfer(dataTransfer, uploadDropZone) { 6104 var pendingFolderPromises = [], handleDataTransferPromise = new qq.Promise(); 6105 options.callbacks.processingDroppedFiles(); 6106 uploadDropZone.dropDisabled(true); 6107 if (dataTransfer.files.length > 1 && !options.allowMultipleItems) { 6108 options.callbacks.processingDroppedFilesComplete([]); 6109 options.callbacks.dropError("tooManyFilesError", ""); 6110 uploadDropZone.dropDisabled(false); 6111 handleDataTransferPromise.failure(); 6112 } else { 6113 droppedFiles = []; 6114 if (qq.isFolderDropSupported(dataTransfer)) { 6115 qq.each(dataTransfer.items, function(idx, item) { 6116 var entry = item.webkitGetAsEntry(); 6117 if (entry) { 6118 if (entry.isFile) { 6119 droppedFiles.push(item.getAsFile()); 6120 } else { 6121 pendingFolderPromises.push(traverseFileTree(entry).done(function() { 6122 pendingFolderPromises.pop(); 6123 if (pendingFolderPromises.length === 0) { 6124 handleDataTransferPromise.success(); 6125 } 6126 })); 6127 } 6128 } 6129 }); 6130 } else { 6131 droppedFiles = dataTransfer.files; 6132 } 6133 if (pendingFolderPromises.length === 0) { 6134 handleDataTransferPromise.success(); 6135 } 6136 } 6137 return handleDataTransferPromise; 6138 } 6139 function setupDropzone(dropArea) { 6140 var dropZone = new qq.UploadDropZone({ 6141 HIDE_ZONES_EVENT_NAME: HIDE_ZONES_EVENT_NAME, 6142 element: dropArea, 6143 onEnter: function(e) { 6144 qq(dropArea).addClass(options.classes.dropActive); 6145 e.stopPropagation(); 6146 }, 6147 onLeaveNotDescendants: function(e) { 6148 qq(dropArea).removeClass(options.classes.dropActive); 6149 }, 6150 onDrop: function(e) { 6151 handleDataTransfer(e.dataTransfer, dropZone).then(function() { 6152 uploadDroppedFiles(droppedFiles, dropZone); 6153 }, function() { 6154 options.callbacks.dropLog("Drop event DataTransfer parsing failed. No files will be uploaded.", "error"); 6155 }); 6156 } 6157 }); 6158 disposeSupport.addDisposer(function() { 6159 dropZone.dispose(); 6160 }); 6161 qq(dropArea).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropArea).hide(); 6162 uploadDropZones.push(dropZone); 6163 return dropZone; 6164 } 6165 function isFileDrag(dragEvent) { 6166 var fileDrag; 6167 qq.each(dragEvent.dataTransfer.types, function(key, val) { 6168 if (val === "Files") { 6169 fileDrag = true; 6170 return false; 6171 } 6172 }); 6173 return fileDrag; 6174 } 6175 function leavingDocumentOut(e) { 6176 if (qq.safari()) { 6177 return e.x < 0 || e.y < 0; 6178 } 6179 return e.x === 0 && e.y === 0; 6180 } 6181 function setupDragDrop() { 6182 var dropZones = options.dropZoneElements, maybeHideDropZones = function() { 6183 setTimeout(function() { 6184 qq.each(dropZones, function(idx, dropZone) { 6185 qq(dropZone).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropZone).hide(); 6186 qq(dropZone).removeClass(options.classes.dropActive); 6187 }); 6188 }, 10); 6189 }; 6190 qq.each(dropZones, function(idx, dropZone) { 6191 var uploadDropZone = setupDropzone(dropZone); 6192 if (dropZones.length && qq.supportedFeatures.fileDrop) { 6193 disposeSupport.attach(document, "dragenter", function(e) { 6194 if (!uploadDropZone.dropDisabled() && isFileDrag(e)) { 6195 qq.each(dropZones, function(idx, dropZone) { 6196 if (dropZone instanceof HTMLElement && qq(dropZone).hasAttribute(HIDE_BEFORE_ENTER_ATTR)) { 6197 qq(dropZone).css({ 6198 display: "block" 6199 }); 6200 } 6201 }); 6202 } 6203 }); 6204 } 6205 }); 6206 disposeSupport.attach(document, "dragleave", function(e) { 6207 if (leavingDocumentOut(e)) { 6208 maybeHideDropZones(); 6209 } 6210 }); 6211 disposeSupport.attach(qq(document).children()[0], "mouseenter", function(e) { 6212 maybeHideDropZones(); 6213 }); 6214 disposeSupport.attach(document, "drop", function(e) { 6215 if (isFileDrag(e)) { 6216 e.preventDefault(); 6217 maybeHideDropZones(); 6218 } 6219 }); 6220 disposeSupport.attach(document, HIDE_ZONES_EVENT_NAME, maybeHideDropZones); 6221 } 6222 setupDragDrop(); 6223 qq.extend(this, { 6224 setupExtraDropzone: function(element) { 6225 options.dropZoneElements.push(element); 6226 setupDropzone(element); 6227 }, 6228 removeDropzone: function(element) { 6229 var i, dzs = options.dropZoneElements; 6230 for (i in dzs) { 6231 if (dzs[i] === element) { 6232 return dzs.splice(i, 1); 6233 } 6234 } 6235 }, 6236 dispose: function() { 6237 disposeSupport.dispose(); 6238 qq.each(uploadDropZones, function(idx, dropZone) { 6239 dropZone.dispose(); 6240 }); 6241 } 6242 }); 6243 this._testing = {}; 6244 this._testing.extractDirectoryPath = extractDirectoryPath; 6245 }; 6246 qq.DragAndDrop.callbacks = function() { 6247 "use strict"; 6248 return { 6249 processingDroppedFiles: function() {}, 6250 processingDroppedFilesComplete: function(files, targetEl) {}, 6251 dropError: function(code, errorSpecifics) { 6252 qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error"); 6253 }, 6254 dropLog: function(message, level) { 6255 qq.log(message, level); 6256 } 6257 }; 6258 }; 6259 qq.UploadDropZone = function(o) { 6260 "use strict"; 6261 var disposeSupport = new qq.DisposeSupport(), options, element, preventDrop, dropOutsideDisabled; 6262 options = { 6263 element: null, 6264 onEnter: function(e) {}, 6265 onLeave: function(e) {}, 6266 onLeaveNotDescendants: function(e) {}, 6267 onDrop: function(e) {} 6268 }; 6269 qq.extend(options, o); 6270 element = options.element; 6271 function dragoverShouldBeCanceled() { 6272 return qq.safari() || qq.firefox() && qq.windows(); 6273 } 6274 function disableDropOutside(e) { 6275 if (!dropOutsideDisabled) { 6276 if (dragoverShouldBeCanceled) { 6277 disposeSupport.attach(document, "dragover", function(e) { 6278 e.preventDefault(); 6279 }); 6280 } else { 6281 disposeSupport.attach(document, "dragover", function(e) { 6282 if (e.dataTransfer) { 6283 e.dataTransfer.dropEffect = "none"; 6284 e.preventDefault(); 6285 } 6286 }); 6287 } 6288 dropOutsideDisabled = true; 6289 } 6290 } 6291 function isValidFileDrag(e) { 6292 if (!qq.supportedFeatures.fileDrop) { 6293 return false; 6294 } 6295 var effectTest, dt = e.dataTransfer, isSafari = qq.safari(); 6296 effectTest = qq.ie() && qq.supportedFeatures.fileDrop ? true : dt.effectAllowed !== "none"; 6297 return dt && effectTest && (dt.files && dt.files.length || !isSafari && dt.types.contains && dt.types.contains("Files") || dt.types.includes && dt.types.includes("Files")); 6298 } 6299 function isOrSetDropDisabled(isDisabled) { 6300 if (isDisabled !== undefined) { 6301 preventDrop = isDisabled; 6302 } 6303 return preventDrop; 6304 } 6305 function triggerHidezonesEvent() { 6306 var hideZonesEvent; 6307 function triggerUsingOldApi() { 6308 hideZonesEvent = document.createEvent("Event"); 6309 hideZonesEvent.initEvent(options.HIDE_ZONES_EVENT_NAME, true, true); 6310 } 6311 if (window.CustomEvent) { 6312 try { 6313 hideZonesEvent = new CustomEvent(options.HIDE_ZONES_EVENT_NAME); 6314 } catch (err) { 6315 triggerUsingOldApi(); 6316 } 6317 } else { 6318 triggerUsingOldApi(); 6319 } 6320 document.dispatchEvent(hideZonesEvent); 6321 } 6322 function attachEvents() { 6323 disposeSupport.attach(element, "dragover", function(e) { 6324 if (!isValidFileDrag(e)) { 6325 return; 6326 } 6327 var effect = qq.ie() && qq.supportedFeatures.fileDrop ? null : e.dataTransfer.effectAllowed; 6328 if (effect === "move" || effect === "linkMove") { 6329 e.dataTransfer.dropEffect = "move"; 6330 } else { 6331 e.dataTransfer.dropEffect = "copy"; 6332 } 6333 e.stopPropagation(); 6334 e.preventDefault(); 6335 }); 6336 disposeSupport.attach(element, "dragenter", function(e) { 6337 if (!isOrSetDropDisabled()) { 6338 if (!isValidFileDrag(e)) { 6339 return; 6340 } 6341 options.onEnter(e); 6342 } 6343 }); 6344 disposeSupport.attach(element, "dragleave", function(e) { 6345 if (!isValidFileDrag(e)) { 6346 return; 6347 } 6348 options.onLeave(e); 6349 var relatedTarget = document.elementFromPoint(e.clientX, e.clientY); 6350 if (qq(this).contains(relatedTarget)) { 6351 return; 6352 } 6353 options.onLeaveNotDescendants(e); 6354 }); 6355 disposeSupport.attach(element, "drop", function(e) { 6356 if (!isOrSetDropDisabled()) { 6357 if (!isValidFileDrag(e)) { 6358 return; 6359 } 6360 e.preventDefault(); 6361 e.stopPropagation(); 6362 options.onDrop(e); 6363 triggerHidezonesEvent(); 6364 } 6365 }); 6366 } 6367 disableDropOutside(); 6368 attachEvents(); 6369 qq.extend(this, { 6370 dropDisabled: function(isDisabled) { 6371 return isOrSetDropDisabled(isDisabled); 6372 }, 6373 dispose: function() { 6374 disposeSupport.dispose(); 6375 }, 6376 getElement: function() { 6377 return element; 6378 } 6379 }); 6380 this._testing = {}; 6381 this._testing.isValidFileDrag = isValidFileDrag; 6382 }; 6383 (function() { 6384 "use strict"; 6385 qq.uiPublicApi = { 6386 addInitialFiles: function(cannedFileList) { 6387 this._parent.prototype.addInitialFiles.apply(this, arguments); 6388 this._templating.addCacheToDom(); 6389 }, 6390 clearStoredFiles: function() { 6391 this._parent.prototype.clearStoredFiles.apply(this, arguments); 6392 this._templating.clearFiles(); 6393 }, 6394 addExtraDropzone: function(element) { 6395 this._dnd && this._dnd.setupExtraDropzone(element); 6396 }, 6397 removeExtraDropzone: function(element) { 6398 if (this._dnd) { 6399 return this._dnd.removeDropzone(element); 6400 } 6401 }, 6402 getItemByFileId: function(id) { 6403 if (!this._templating.isHiddenForever(id)) { 6404 return this._templating.getFileContainer(id); 6405 } 6406 }, 6407 reset: function() { 6408 this._parent.prototype.reset.apply(this, arguments); 6409 this._templating.reset(); 6410 if (!this._options.button && this._templating.getButton()) { 6411 this._defaultButtonId = this._createUploadButton({ 6412 element: this._templating.getButton(), 6413 title: this._options.text.fileInputTitle 6414 }).getButtonId(); 6415 } 6416 if (this._dnd) { 6417 this._dnd.dispose(); 6418 this._dnd = this._setupDragAndDrop(); 6419 } 6420 this._totalFilesInBatch = 0; 6421 this._filesInBatchAddedToUi = 0; 6422 this._setupClickAndEditEventHandlers(); 6423 }, 6424 setName: function(id, newName) { 6425 var formattedFilename = this._options.formatFileName(newName); 6426 this._parent.prototype.setName.apply(this, arguments); 6427 this._templating.updateFilename(id, formattedFilename); 6428 }, 6429 pauseUpload: function(id) { 6430 var paused = this._parent.prototype.pauseUpload.apply(this, arguments); 6431 paused && this._templating.uploadPaused(id); 6432 return paused; 6433 }, 6434 continueUpload: function(id) { 6435 var continued = this._parent.prototype.continueUpload.apply(this, arguments); 6436 continued && this._templating.uploadContinued(id); 6437 return continued; 6438 }, 6439 getId: function(fileContainerOrChildEl) { 6440 return this._templating.getFileId(fileContainerOrChildEl); 6441 }, 6442 getDropTarget: function(fileId) { 6443 var file = this.getFile(fileId); 6444 return file.qqDropTarget; 6445 } 6446 }; 6447 qq.uiPrivateApi = { 6448 _getButton: function(buttonId) { 6449 var button = this._parent.prototype._getButton.apply(this, arguments); 6450 if (!button) { 6451 if (buttonId === this._defaultButtonId) { 6452 button = this._templating.getButton(); 6453 } 6454 } 6455 return button; 6456 }, 6457 _removeFileItem: function(fileId) { 6458 this._templating.removeFile(fileId); 6459 }, 6460 _setupClickAndEditEventHandlers: function() { 6461 this._fileButtonsClickHandler = qq.FileButtonsClickHandler && this._bindFileButtonsClickEvent(); 6462 this._focusinEventSupported = !qq.firefox(); 6463 if (this._isEditFilenameEnabled()) { 6464 this._filenameClickHandler = this._bindFilenameClickEvent(); 6465 this._filenameInputFocusInHandler = this._bindFilenameInputFocusInEvent(); 6466 this._filenameInputFocusHandler = this._bindFilenameInputFocusEvent(); 6467 } 6468 }, 6469 _setupDragAndDrop: function() { 6470 var self = this, dropZoneElements = this._options.dragAndDrop.extraDropzones, templating = this._templating, defaultDropZone = templating.getDropZone(); 6471 defaultDropZone && dropZoneElements.push(defaultDropZone); 6472 return new qq.DragAndDrop({ 6473 dropZoneElements: dropZoneElements, 6474 allowMultipleItems: this._options.multiple, 6475 classes: { 6476 dropActive: this._options.classes.dropActive 6477 }, 6478 callbacks: { 6479 processingDroppedFiles: function() { 6480 templating.showDropProcessing(); 6481 }, 6482 processingDroppedFilesComplete: function(files, targetEl) { 6483 templating.hideDropProcessing(); 6484 qq.each(files, function(idx, file) { 6485 file.qqDropTarget = targetEl; 6486 }); 6487 if (files.length) { 6488 self.addFiles(files, null, null); 6489 } 6490 }, 6491 dropError: function(code, errorData) { 6492 self._itemError(code, errorData); 6493 }, 6494 dropLog: function(message, level) { 6495 self.log(message, level); 6496 } 6497 } 6498 }); 6499 }, 6500 _bindFileButtonsClickEvent: function() { 6501 var self = this; 6502 return new qq.FileButtonsClickHandler({ 6503 templating: this._templating, 6504 log: function(message, lvl) { 6505 self.log(message, lvl); 6506 }, 6507 onDeleteFile: function(fileId) { 6508 self.deleteFile(fileId); 6509 }, 6510 onCancel: function(fileId) { 6511 self.cancel(fileId); 6512 }, 6513 onRetry: function(fileId) { 6514 self.retry(fileId); 6515 }, 6516 onPause: function(fileId) { 6517 self.pauseUpload(fileId); 6518 }, 6519 onContinue: function(fileId) { 6520 self.continueUpload(fileId); 6521 }, 6522 onGetName: function(fileId) { 6523 return self.getName(fileId); 6524 } 6525 }); 6526 }, 6527 _isEditFilenameEnabled: function() { 6528 return this._templating.isEditFilenamePossible() && !this._options.autoUpload && qq.FilenameClickHandler && qq.FilenameInputFocusHandler && qq.FilenameInputFocusHandler; 6529 }, 6530 _filenameEditHandler: function() { 6531 var self = this, templating = this._templating; 6532 return { 6533 templating: templating, 6534 log: function(message, lvl) { 6535 self.log(message, lvl); 6536 }, 6537 onGetUploadStatus: function(fileId) { 6538 return self.getUploads({ 6539 id: fileId 6540 }).status; 6541 }, 6542 onGetName: function(fileId) { 6543 return self.getName(fileId); 6544 }, 6545 onSetName: function(id, newName) { 6546 self.setName(id, newName); 6547 }, 6548 onEditingStatusChange: function(id, isEditing) { 6549 var qqInput = qq(templating.getEditInput(id)), qqFileContainer = qq(templating.getFileContainer(id)); 6550 if (isEditing) { 6551 qqInput.addClass("qq-editing"); 6552 templating.hideFilename(id); 6553 templating.hideEditIcon(id); 6554 } else { 6555 qqInput.removeClass("qq-editing"); 6556 templating.showFilename(id); 6557 templating.showEditIcon(id); 6558 } 6559 qqFileContainer.addClass("qq-temp").removeClass("qq-temp"); 6560 } 6561 }; 6562 }, 6563 _onUploadStatusChange: function(id, oldStatus, newStatus) { 6564 this._parent.prototype._onUploadStatusChange.apply(this, arguments); 6565 if (this._isEditFilenameEnabled()) { 6566 if (this._templating.getFileContainer(id) && newStatus !== qq.status.SUBMITTED) { 6567 this._templating.markFilenameEditable(id); 6568 this._templating.hideEditIcon(id); 6569 } 6570 } 6571 if (oldStatus === qq.status.UPLOAD_RETRYING && newStatus === qq.status.UPLOADING) { 6572 this._templating.hideRetry(id); 6573 this._templating.setStatusText(id); 6574 qq(this._templating.getFileContainer(id)).removeClass(this._classes.retrying); 6575 } else if (newStatus === qq.status.UPLOAD_FAILED) { 6576 this._templating.hidePause(id); 6577 } 6578 }, 6579 _bindFilenameInputFocusInEvent: function() { 6580 var spec = qq.extend({}, this._filenameEditHandler()); 6581 return new qq.FilenameInputFocusInHandler(spec); 6582 }, 6583 _bindFilenameInputFocusEvent: function() { 6584 var spec = qq.extend({}, this._filenameEditHandler()); 6585 return new qq.FilenameInputFocusHandler(spec); 6586 }, 6587 _bindFilenameClickEvent: function() { 6588 var spec = qq.extend({}, this._filenameEditHandler()); 6589 return new qq.FilenameClickHandler(spec); 6590 }, 6591 _storeForLater: function(id) { 6592 this._parent.prototype._storeForLater.apply(this, arguments); 6593 this._templating.hideSpinner(id); 6594 }, 6595 _onAllComplete: function(successful, failed) { 6596 this._parent.prototype._onAllComplete.apply(this, arguments); 6597 this._templating.resetTotalProgress(); 6598 }, 6599 _onSubmit: function(id, name) { 6600 var file = this.getFile(id); 6601 if (file && file.qqPath && this._options.dragAndDrop.reportDirectoryPaths) { 6602 this._paramsStore.addReadOnly(id, { 6603 qqpath: file.qqPath 6604 }); 6605 } 6606 this._parent.prototype._onSubmit.apply(this, arguments); 6607 this._addToList(id, name); 6608 }, 6609 _onSubmitted: function(id) { 6610 if (this._isEditFilenameEnabled()) { 6611 this._templating.markFilenameEditable(id); 6612 this._templating.showEditIcon(id); 6613 if (!this._focusinEventSupported) { 6614 this._filenameInputFocusHandler.addHandler(this._templating.getEditInput(id)); 6615 } 6616 } 6617 }, 6618 _onProgress: function(id, name, loaded, total) { 6619 this._parent.prototype._onProgress.apply(this, arguments); 6620 this._templating.updateProgress(id, loaded, total); 6621 if (total === 0 || Math.round(loaded / total * 100) === 100) { 6622 this._templating.hideCancel(id); 6623 this._templating.hidePause(id); 6624 this._templating.hideProgress(id); 6625 this._templating.setStatusText(id, this._options.text.waitingForResponse); 6626 this._displayFileSize(id); 6627 } else { 6628 this._displayFileSize(id, loaded, total); 6629 } 6630 }, 6631 _onTotalProgress: function(loaded, total) { 6632 this._parent.prototype._onTotalProgress.apply(this, arguments); 6633 this._templating.updateTotalProgress(loaded, total); 6634 }, 6635 _onComplete: function(id, name, result, xhr) { 6636 var parentRetVal = this._parent.prototype._onComplete.apply(this, arguments), templating = this._templating, fileContainer = templating.getFileContainer(id), self = this; 6637 function completeUpload(result) { 6638 if (!fileContainer) { 6639 return; 6640 } 6641 templating.setStatusText(id); 6642 qq(fileContainer).removeClass(self._classes.retrying); 6643 templating.hideProgress(id); 6644 if (self.getUploads({ 6645 id: id 6646 }).status !== qq.status.UPLOAD_FAILED) { 6647 templating.hideCancel(id); 6648 } 6649 templating.hideSpinner(id); 6650 if (result.success) { 6651 self._markFileAsSuccessful(id); 6652 } else { 6653 qq(fileContainer).addClass(self._classes.fail); 6654 templating.showCancel(id); 6655 if (templating.isRetryPossible() && !self._preventRetries[id]) { 6656 qq(fileContainer).addClass(self._classes.retryable); 6657 templating.showRetry(id); 6658 } 6659 self._controlFailureTextDisplay(id, result); 6660 } 6661 } 6662 if (parentRetVal instanceof qq.Promise) { 6663 parentRetVal.done(function(newResult) { 6664 completeUpload(newResult); 6665 }); 6666 } else { 6667 completeUpload(result); 6668 } 6669 return parentRetVal; 6670 }, 6671 _markFileAsSuccessful: function(id) { 6672 var templating = this._templating; 6673 if (this._isDeletePossible()) { 6674 templating.showDeleteButton(id); 6675 } 6676 qq(templating.getFileContainer(id)).addClass(this._classes.success); 6677 this._maybeUpdateThumbnail(id); 6678 }, 6679 _onUploadPrep: function(id) { 6680 this._parent.prototype._onUploadPrep.apply(this, arguments); 6681 this._templating.showSpinner(id); 6682 }, 6683 _onUpload: function(id, name) { 6684 var parentRetVal = this._parent.prototype._onUpload.apply(this, arguments); 6685 this._templating.showSpinner(id); 6686 return parentRetVal; 6687 }, 6688 _onUploadChunk: function(id, chunkData) { 6689 this._parent.prototype._onUploadChunk.apply(this, arguments); 6690 if (chunkData.partIndex > 0 && this._handler.isResumable(id)) { 6691 this._templating.allowPause(id); 6692 } 6693 }, 6694 _onCancel: function(id, name) { 6695 this._parent.prototype._onCancel.apply(this, arguments); 6696 this._removeFileItem(id); 6697 if (this._getNotFinished() === 0) { 6698 this._templating.resetTotalProgress(); 6699 } 6700 }, 6701 _onBeforeAutoRetry: function(id) { 6702 var retryNumForDisplay, maxAuto, retryNote; 6703 this._parent.prototype._onBeforeAutoRetry.apply(this, arguments); 6704 this._showCancelLink(id); 6705 if (this._options.retry.showAutoRetryNote) { 6706 retryNumForDisplay = this._autoRetries[id]; 6707 maxAuto = this._options.retry.maxAutoAttempts; 6708 retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay); 6709 retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto); 6710 this._templating.setStatusText(id, retryNote); 6711 qq(this._templating.getFileContainer(id)).addClass(this._classes.retrying); 6712 } 6713 }, 6714 _onBeforeManualRetry: function(id) { 6715 if (this._parent.prototype._onBeforeManualRetry.apply(this, arguments)) { 6716 this._templating.resetProgress(id); 6717 qq(this._templating.getFileContainer(id)).removeClass(this._classes.fail); 6718 this._templating.setStatusText(id); 6719 this._templating.showSpinner(id); 6720 this._showCancelLink(id); 6721 return true; 6722 } else { 6723 qq(this._templating.getFileContainer(id)).addClass(this._classes.retryable); 6724 this._templating.showRetry(id); 6725 return false; 6726 } 6727 }, 6728 _onSubmitDelete: function(id) { 6729 var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this); 6730 this._parent.prototype._onSubmitDelete.call(this, id, onSuccessCallback); 6731 }, 6732 _onSubmitDeleteSuccess: function(id, uuid, additionalMandatedParams) { 6733 if (this._options.deleteFile.forceConfirm) { 6734 this._showDeleteConfirm.apply(this, arguments); 6735 } else { 6736 this._sendDeleteRequest.apply(this, arguments); 6737 } 6738 }, 6739 _onDeleteComplete: function(id, xhr, isError) { 6740 this._parent.prototype._onDeleteComplete.apply(this, arguments); 6741 this._templating.hideSpinner(id); 6742 if (isError) { 6743 this._templating.setStatusText(id, this._options.deleteFile.deletingFailedText); 6744 this._templating.showDeleteButton(id); 6745 } else { 6746 this._removeFileItem(id); 6747 } 6748 }, 6749 _sendDeleteRequest: function(id, uuid, additionalMandatedParams) { 6750 this._templating.hideDeleteButton(id); 6751 this._templating.showSpinner(id); 6752 this._templating.setStatusText(id, this._options.deleteFile.deletingStatusText); 6753 this._deleteHandler.sendDelete.apply(this, arguments); 6754 }, 6755 _showDeleteConfirm: function(id, uuid, mandatedParams) { 6756 var fileName = this.getName(id), confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName), uuid = this.getUuid(id), deleteRequestArgs = arguments, self = this, retVal; 6757 retVal = this._options.showConfirm(confirmMessage); 6758 if (qq.isGenericPromise(retVal)) { 6759 retVal.then(function() { 6760 self._sendDeleteRequest.apply(self, deleteRequestArgs); 6761 }); 6762 } else if (retVal !== false) { 6763 self._sendDeleteRequest.apply(self, deleteRequestArgs); 6764 } 6765 }, 6766 _addToList: function(id, name, canned) { 6767 var prependData, prependIndex = 0, dontDisplay = this._handler.isProxied(id) && this._options.scaling.hideScaled, record; 6768 if (this._options.display.prependFiles) { 6769 if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) { 6770 prependIndex = this._filesInBatchAddedToUi - 1; 6771 } 6772 prependData = { 6773 index: prependIndex 6774 }; 6775 } 6776 if (!canned) { 6777 if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) { 6778 this._templating.disableCancel(); 6779 } 6780 if (!this._options.multiple) { 6781 record = this.getUploads({ 6782 id: id 6783 }); 6784 this._handledProxyGroup = this._handledProxyGroup || record.proxyGroupId; 6785 if (record.proxyGroupId !== this._handledProxyGroup || !record.proxyGroupId) { 6786 this._handler.cancelAll(); 6787 this._clearList(); 6788 this._handledProxyGroup = null; 6789 } 6790 } 6791 } 6792 if (canned) { 6793 this._templating.addFileToCache(id, this._options.formatFileName(name), prependData, dontDisplay); 6794 this._templating.updateThumbnail(id, this._thumbnailUrls[id], true, this._options.thumbnails.customResizer); 6795 } else { 6796 this._templating.addFile(id, this._options.formatFileName(name), prependData, dontDisplay); 6797 this._templating.generatePreview(id, this.getFile(id), this._options.thumbnails.customResizer); 6798 } 6799 this._filesInBatchAddedToUi += 1; 6800 if (canned || this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) { 6801 this._displayFileSize(id); 6802 } 6803 }, 6804 _clearList: function() { 6805 this._templating.clearFiles(); 6806 this.clearStoredFiles(); 6807 }, 6808 _displayFileSize: function(id, loadedSize, totalSize) { 6809 var size = this.getSize(id), sizeForDisplay = this._formatSize(size); 6810 if (size >= 0) { 6811 if (loadedSize !== undefined && totalSize !== undefined) { 6812 sizeForDisplay = this._formatProgress(loadedSize, totalSize); 6813 } 6814 this._templating.updateSize(id, sizeForDisplay); 6815 } 6816 }, 6817 _formatProgress: function(uploadedSize, totalSize) { 6818 var message = this._options.text.formatProgress; 6819 function r(name, replacement) { 6820 message = message.replace(name, replacement); 6821 } 6822 r("{percent}", Math.round(uploadedSize / totalSize * 100)); 6823 r("{total_size}", this._formatSize(totalSize)); 6824 return message; 6825 }, 6826 _controlFailureTextDisplay: function(id, response) { 6827 var mode, responseProperty, failureReason; 6828 mode = this._options.failedUploadTextDisplay.mode; 6829 responseProperty = this._options.failedUploadTextDisplay.responseProperty; 6830 if (mode === "custom") { 6831 failureReason = response[responseProperty]; 6832 if (!failureReason) { 6833 failureReason = this._options.text.failUpload; 6834 } 6835 this._templating.setStatusText(id, failureReason); 6836 if (this._options.failedUploadTextDisplay.enableTooltip) { 6837 this._showTooltip(id, failureReason); 6838 } 6839 } else if (mode === "default") { 6840 this._templating.setStatusText(id, this._options.text.failUpload); 6841 } else if (mode !== "none") { 6842 this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", "warn"); 6843 } 6844 }, 6845 _showTooltip: function(id, text) { 6846 this._templating.getFileContainer(id).title = text; 6847 }, 6848 _showCancelLink: function(id) { 6849 if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) { 6850 this._templating.showCancel(id); 6851 } 6852 }, 6853 _itemError: function(code, name, item) { 6854 var message = this._parent.prototype._itemError.apply(this, arguments); 6855 this._options.showMessage(message); 6856 }, 6857 _batchError: function(message) { 6858 this._parent.prototype._batchError.apply(this, arguments); 6859 this._options.showMessage(message); 6860 }, 6861 _setupPastePrompt: function() { 6862 var self = this; 6863 this._options.callbacks.onPasteReceived = function() { 6864 var message = self._options.paste.namePromptMessage, defaultVal = self._options.paste.defaultName; 6865 return self._options.showPrompt(message, defaultVal); 6866 }; 6867 }, 6868 _fileOrBlobRejected: function(id, name) { 6869 this._totalFilesInBatch -= 1; 6870 this._parent.prototype._fileOrBlobRejected.apply(this, arguments); 6871 }, 6872 _prepareItemsForUpload: function(items, params, endpoint) { 6873 this._totalFilesInBatch = items.length; 6874 this._filesInBatchAddedToUi = 0; 6875 this._parent.prototype._prepareItemsForUpload.apply(this, arguments); 6876 }, 6877 _maybeUpdateThumbnail: function(fileId) { 6878 var thumbnailUrl = this._thumbnailUrls[fileId], fileStatus = this.getUploads({ 6879 id: fileId 6880 }).status; 6881 if (fileStatus !== qq.status.DELETED && (thumbnailUrl || this._options.thumbnails.placeholders.waitUntilResponse || !qq.supportedFeatures.imagePreviews)) { 6882 this._templating.updateThumbnail(fileId, thumbnailUrl, this._options.thumbnails.customResizer); 6883 } 6884 }, 6885 _addCannedFile: function(sessionData) { 6886 var id = this._parent.prototype._addCannedFile.apply(this, arguments); 6887 this._addToList(id, this.getName(id), true); 6888 this._templating.hideSpinner(id); 6889 this._templating.hideCancel(id); 6890 this._markFileAsSuccessful(id); 6891 return id; 6892 }, 6893 _setSize: function(id, newSize) { 6894 this._parent.prototype._setSize.apply(this, arguments); 6895 this._templating.updateSize(id, this._formatSize(newSize)); 6896 }, 6897 _sessionRequestComplete: function() { 6898 this._templating.addCacheToDom(); 6899 this._parent.prototype._sessionRequestComplete.apply(this, arguments); 6900 } 6901 }; 6902 })(); 6903 qq.FineUploader = function(o, namespace) { 6904 "use strict"; 6905 var self = this; 6906 this._parent = namespace ? qq[namespace].FineUploaderBasic : qq.FineUploaderBasic; 6907 this._parent.apply(this, arguments); 6908 qq.extend(this._options, { 6909 element: null, 6910 button: null, 6911 listElement: null, 6912 dragAndDrop: { 6913 extraDropzones: [], 6914 reportDirectoryPaths: false 6915 }, 6916 text: { 6917 formatProgress: "{percent}% of {total_size}", 6918 failUpload: "Upload failed", 6919 waitingForResponse: "Processing...", 6920 paused: "Paused" 6921 }, 6922 template: "qq-template", 6923 classes: { 6924 retrying: "qq-upload-retrying", 6925 retryable: "qq-upload-retryable", 6926 success: "qq-upload-success", 6927 fail: "qq-upload-fail", 6928 editable: "qq-editable", 6929 hide: "qq-hide", 6930 dropActive: "qq-upload-drop-area-active" 6931 }, 6932 failedUploadTextDisplay: { 6933 mode: "default", 6934 responseProperty: "error", 6935 enableTooltip: true 6936 }, 6937 messages: { 6938 tooManyFilesError: "You may only drop one file", 6939 unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind." 6940 }, 6941 retry: { 6942 showAutoRetryNote: true, 6943 autoRetryNote: "Retrying {retryNum}/{maxAuto}..." 6944 }, 6945 deleteFile: { 6946 forceConfirm: false, 6947 confirmMessage: "Are you sure you want to delete {filename}?", 6948 deletingStatusText: "Deleting...", 6949 deletingFailedText: "Delete failed" 6950 }, 6951 display: { 6952 fileSizeOnSubmit: false, 6953 prependFiles: false 6954 }, 6955 paste: { 6956 promptForName: false, 6957 namePromptMessage: "Please name this image" 6958 }, 6959 thumbnails: { 6960 customResizer: null, 6961 maxCount: 0, 6962 placeholders: { 6963 waitUntilResponse: false, 6964 notAvailablePath: null, 6965 waitingPath: null 6966 }, 6967 timeBetweenThumbs: 750 6968 }, 6969 scaling: { 6970 hideScaled: false 6971 }, 6972 showMessage: function(message) { 6973 if (self._templating.hasDialog("alert")) { 6974 return self._templating.showDialog("alert", message); 6975 } else { 6976 setTimeout(function() { 6977 window.alert(message); 6978 }, 0); 6979 } 6980 }, 6981 showConfirm: function(message) { 6982 if (self._templating.hasDialog("confirm")) { 6983 return self._templating.showDialog("confirm", message); 6984 } else { 6985 return window.confirm(message); 6986 } 6987 }, 6988 showPrompt: function(message, defaultValue) { 6989 if (self._templating.hasDialog("prompt")) { 6990 return self._templating.showDialog("prompt", message, defaultValue); 6991 } else { 6992 return window.prompt(message, defaultValue); 6993 } 6994 } 6995 }, true); 6996 qq.extend(this._options, o, true); 6997 this._templating = new qq.Templating({ 6998 log: qq.bind(this.log, this), 6999 templateIdOrEl: this._options.template, 7000 containerEl: this._options.element, 7001 fileContainerEl: this._options.listElement, 7002 button: this._options.button, 7003 imageGenerator: this._imageGenerator, 7004 classes: { 7005 hide: this._options.classes.hide, 7006 editable: this._options.classes.editable 7007 }, 7008 limits: { 7009 maxThumbs: this._options.thumbnails.maxCount, 7010 timeBetweenThumbs: this._options.thumbnails.timeBetweenThumbs 7011 }, 7012 placeholders: { 7013 waitUntilUpdate: this._options.thumbnails.placeholders.waitUntilResponse, 7014 thumbnailNotAvailable: this._options.thumbnails.placeholders.notAvailablePath, 7015 waitingForThumbnail: this._options.thumbnails.placeholders.waitingPath 7016 }, 7017 text: this._options.text 7018 }); 7019 if (this._options.workarounds.ios8SafariUploads && qq.ios800() && qq.iosSafari()) { 7020 this._templating.renderFailure(this._options.messages.unsupportedBrowserIos8Safari); 7021 } else if (!qq.supportedFeatures.uploading || this._options.cors.expected && !qq.supportedFeatures.uploadCors) { 7022 this._templating.renderFailure(this._options.messages.unsupportedBrowser); 7023 } else { 7024 this._wrapCallbacks(); 7025 this._templating.render(); 7026 this._classes = this._options.classes; 7027 if (!this._options.button && this._templating.getButton()) { 7028 this._defaultButtonId = this._createUploadButton({ 7029 element: this._templating.getButton(), 7030 title: this._options.text.fileInputTitle 7031 }).getButtonId(); 7032 } 7033 this._setupClickAndEditEventHandlers(); 7034 if (qq.DragAndDrop && qq.supportedFeatures.fileDrop) { 7035 this._dnd = this._setupDragAndDrop(); 7036 } 7037 if (this._options.paste.targetElement && this._options.paste.promptForName) { 7038 if (qq.PasteSupport) { 7039 this._setupPastePrompt(); 7040 } else { 7041 this.log("Paste support module not found.", "error"); 7042 } 7043 } 7044 this._totalFilesInBatch = 0; 7045 this._filesInBatchAddedToUi = 0; 7046 } 7047 }; 7048 qq.extend(qq.FineUploader.prototype, qq.basePublicApi); 7049 qq.extend(qq.FineUploader.prototype, qq.basePrivateApi); 7050 qq.extend(qq.FineUploader.prototype, qq.uiPublicApi); 7051 qq.extend(qq.FineUploader.prototype, qq.uiPrivateApi); 7052 qq.Templating = function(spec) { 7053 "use strict"; 7054 var FILE_ID_ATTR = "qq-file-id", FILE_CLASS_PREFIX = "qq-file-id-", THUMBNAIL_MAX_SIZE_ATTR = "qq-max-size", THUMBNAIL_SERVER_SCALE_ATTR = "qq-server-scale", HIDE_DROPZONE_ATTR = "qq-hide-dropzone", DROPZPONE_TEXT_ATTR = "qq-drop-area-text", IN_PROGRESS_CLASS = "qq-in-progress", HIDDEN_FOREVER_CLASS = "qq-hidden-forever", fileBatch = { 7055 content: document.createDocumentFragment(), 7056 map: {} 7057 }, isCancelDisabled = false, generatedThumbnails = 0, thumbnailQueueMonitorRunning = false, thumbGenerationQueue = [], thumbnailMaxSize = -1, options = { 7058 log: null, 7059 limits: { 7060 maxThumbs: 0, 7061 timeBetweenThumbs: 750 7062 }, 7063 templateIdOrEl: "qq-template", 7064 containerEl: null, 7065 fileContainerEl: null, 7066 button: null, 7067 imageGenerator: null, 7068 classes: { 7069 hide: "qq-hide", 7070 editable: "qq-editable" 7071 }, 7072 placeholders: { 7073 waitUntilUpdate: false, 7074 thumbnailNotAvailable: null, 7075 waitingForThumbnail: null 7076 }, 7077 text: { 7078 paused: "Paused" 7079 } 7080 }, selectorClasses = { 7081 button: "qq-upload-button-selector", 7082 alertDialog: "qq-alert-dialog-selector", 7083 dialogCancelButton: "qq-cancel-button-selector", 7084 confirmDialog: "qq-confirm-dialog-selector", 7085 dialogMessage: "qq-dialog-message-selector", 7086 dialogOkButton: "qq-ok-button-selector", 7087 promptDialog: "qq-prompt-dialog-selector", 7088 uploader: "qq-uploader-selector", 7089 drop: "qq-upload-drop-area-selector", 7090 list: "qq-upload-list-selector", 7091 progressBarContainer: "qq-progress-bar-container-selector", 7092 progressBar: "qq-progress-bar-selector", 7093 totalProgressBarContainer: "qq-total-progress-bar-container-selector", 7094 totalProgressBar: "qq-total-progress-bar-selector", 7095 file: "qq-upload-file-selector", 7096 spinner: "qq-upload-spinner-selector", 7097 size: "qq-upload-size-selector", 7098 cancel: "qq-upload-cancel-selector", 7099 pause: "qq-upload-pause-selector", 7100 continueButton: "qq-upload-continue-selector", 7101 deleteButton: "qq-upload-delete-selector", 7102 retry: "qq-upload-retry-selector", 7103 statusText: "qq-upload-status-text-selector", 7104 editFilenameInput: "qq-edit-filename-selector", 7105 editNameIcon: "qq-edit-filename-icon-selector", 7106 dropText: "qq-upload-drop-area-text-selector", 7107 dropProcessing: "qq-drop-processing-selector", 7108 dropProcessingSpinner: "qq-drop-processing-spinner-selector", 7109 thumbnail: "qq-thumbnail-selector" 7110 }, previewGeneration = {}, cachedThumbnailNotAvailableImg = new qq.Promise(), cachedWaitingForThumbnailImg = new qq.Promise(), log, isEditElementsExist, isRetryElementExist, templateDom, container, fileList, showThumbnails, serverScale, cacheThumbnailPlaceholders = function() { 7111 var notAvailableUrl = options.placeholders.thumbnailNotAvailable, waitingUrl = options.placeholders.waitingForThumbnail, spec = { 7112 maxSize: thumbnailMaxSize, 7113 scale: serverScale 7114 }; 7115 if (showThumbnails) { 7116 if (notAvailableUrl) { 7117 options.imageGenerator.generate(notAvailableUrl, new Image(), spec).then(function(updatedImg) { 7118 cachedThumbnailNotAvailableImg.success(updatedImg); 7119 }, function() { 7120 cachedThumbnailNotAvailableImg.failure(); 7121 log("Problem loading 'not available' placeholder image at " + notAvailableUrl, "error"); 7122 }); 7123 } else { 7124 cachedThumbnailNotAvailableImg.failure(); 7125 } 7126 if (waitingUrl) { 7127 options.imageGenerator.generate(waitingUrl, new Image(), spec).then(function(updatedImg) { 7128 cachedWaitingForThumbnailImg.success(updatedImg); 7129 }, function() { 7130 cachedWaitingForThumbnailImg.failure(); 7131 log("Problem loading 'waiting for thumbnail' placeholder image at " + waitingUrl, "error"); 7132 }); 7133 } else { 7134 cachedWaitingForThumbnailImg.failure(); 7135 } 7136 } 7137 }, displayWaitingImg = function(thumbnail) { 7138 var waitingImgPlacement = new qq.Promise(); 7139 cachedWaitingForThumbnailImg.then(function(img) { 7140 maybeScalePlaceholderViaCss(img, thumbnail); 7141 if (!thumbnail.src) { 7142 thumbnail.src = img.src; 7143 thumbnail.onload = function() { 7144 thumbnail.onload = null; 7145 show(thumbnail); 7146 waitingImgPlacement.success(); 7147 }; 7148 } else { 7149 waitingImgPlacement.success(); 7150 } 7151 }, function() { 7152 hide(thumbnail); 7153 waitingImgPlacement.success(); 7154 }); 7155 return waitingImgPlacement; 7156 }, generateNewPreview = function(id, blob, spec) { 7157 var thumbnail = getThumbnail(id); 7158 log("Generating new thumbnail for " + id); 7159 blob.qqThumbnailId = id; 7160 return options.imageGenerator.generate(blob, thumbnail, spec).then(function() { 7161 generatedThumbnails++; 7162 show(thumbnail); 7163 previewGeneration[id].success(); 7164 }, function() { 7165 previewGeneration[id].failure(); 7166 if (!options.placeholders.waitUntilUpdate) { 7167 maybeSetDisplayNotAvailableImg(id, thumbnail); 7168 } 7169 }); 7170 }, generateNextQueuedPreview = function() { 7171 if (thumbGenerationQueue.length) { 7172 thumbnailQueueMonitorRunning = true; 7173 var queuedThumbRequest = thumbGenerationQueue.shift(); 7174 if (queuedThumbRequest.update) { 7175 processUpdateQueuedPreviewRequest(queuedThumbRequest); 7176 } else { 7177 processNewQueuedPreviewRequest(queuedThumbRequest); 7178 } 7179 } else { 7180 thumbnailQueueMonitorRunning = false; 7181 } 7182 }, getCancel = function(id) { 7183 return getTemplateEl(getFile(id), selectorClasses.cancel); 7184 }, getContinue = function(id) { 7185 return getTemplateEl(getFile(id), selectorClasses.continueButton); 7186 }, getDialog = function(type) { 7187 return getTemplateEl(container, selectorClasses[type + "Dialog"]); 7188 }, getDelete = function(id) { 7189 return getTemplateEl(getFile(id), selectorClasses.deleteButton); 7190 }, getDropProcessing = function() { 7191 return getTemplateEl(container, selectorClasses.dropProcessing); 7192 }, getEditIcon = function(id) { 7193 return getTemplateEl(getFile(id), selectorClasses.editNameIcon); 7194 }, getFile = function(id) { 7195 return fileBatch.map[id] || qq(fileList).getFirstByClass(FILE_CLASS_PREFIX + id); 7196 }, getFilename = function(id) { 7197 return getTemplateEl(getFile(id), selectorClasses.file); 7198 }, getPause = function(id) { 7199 return getTemplateEl(getFile(id), selectorClasses.pause); 7200 }, getProgress = function(id) { 7201 if (id == null) { 7202 return getTemplateEl(container, selectorClasses.totalProgressBarContainer) || getTemplateEl(container, selectorClasses.totalProgressBar); 7203 } 7204 return getTemplateEl(getFile(id), selectorClasses.progressBarContainer) || getTemplateEl(getFile(id), selectorClasses.progressBar); 7205 }, getRetry = function(id) { 7206 return getTemplateEl(getFile(id), selectorClasses.retry); 7207 }, getSize = function(id) { 7208 return getTemplateEl(getFile(id), selectorClasses.size); 7209 }, getSpinner = function(id) { 7210 return getTemplateEl(getFile(id), selectorClasses.spinner); 7211 }, getTemplateEl = function(context, cssClass) { 7212 return context && qq(context).getFirstByClass(cssClass); 7213 }, getThumbnail = function(id) { 7214 return showThumbnails && getTemplateEl(getFile(id), selectorClasses.thumbnail); 7215 }, hide = function(el) { 7216 el && qq(el).addClass(options.classes.hide); 7217 }, maybeScalePlaceholderViaCss = function(placeholder, thumbnail) { 7218 var maxWidth = placeholder.style.maxWidth, maxHeight = placeholder.style.maxHeight; 7219 if (maxHeight && maxWidth && !thumbnail.style.maxWidth && !thumbnail.style.maxHeight) { 7220 qq(thumbnail).css({ 7221 maxWidth: maxWidth, 7222 maxHeight: maxHeight 7223 }); 7224 } 7225 }, maybeSetDisplayNotAvailableImg = function(id, thumbnail) { 7226 var previewing = previewGeneration[id] || new qq.Promise().failure(), notAvailableImgPlacement = new qq.Promise(); 7227 cachedThumbnailNotAvailableImg.then(function(img) { 7228 previewing.then(function() { 7229 notAvailableImgPlacement.success(); 7230 }, function() { 7231 maybeScalePlaceholderViaCss(img, thumbnail); 7232 thumbnail.onload = function() { 7233 thumbnail.onload = null; 7234 notAvailableImgPlacement.success(); 7235 }; 7236 thumbnail.src = img.src; 7237 show(thumbnail); 7238 }); 7239 }); 7240 return notAvailableImgPlacement; 7241 }, parseAndGetTemplate = function() { 7242 var scriptEl, scriptHtml, fileListNode, tempTemplateEl, fileListEl, defaultButton, dropArea, thumbnail, dropProcessing, dropTextEl, uploaderEl; 7243 log("Parsing template"); 7244 if (options.templateIdOrEl == null) { 7245 throw new Error("You MUST specify either a template element or ID!"); 7246 } 7247 if (qq.isString(options.templateIdOrEl)) { 7248 scriptEl = document.getElementById(options.templateIdOrEl); 7249 if (scriptEl === null) { 7250 throw new Error(qq.format("Cannot find template script at ID '{}'!", options.templateIdOrEl)); 7251 } 7252 scriptHtml = scriptEl.innerHTML; 7253 } else { 7254 if (options.templateIdOrEl.innerHTML === undefined) { 7255 throw new Error("You have specified an invalid value for the template option! " + "It must be an ID or an Element."); 7256 } 7257 scriptHtml = options.templateIdOrEl.innerHTML; 7258 } 7259 scriptHtml = qq.trimStr(scriptHtml); 7260 tempTemplateEl = document.createElement("div"); 7261 tempTemplateEl.appendChild(qq.toElement(scriptHtml)); 7262 uploaderEl = qq(tempTemplateEl).getFirstByClass(selectorClasses.uploader); 7263 if (options.button) { 7264 defaultButton = qq(tempTemplateEl).getFirstByClass(selectorClasses.button); 7265 if (defaultButton) { 7266 qq(defaultButton).remove(); 7267 } 7268 } 7269 if (!qq.DragAndDrop || !qq.supportedFeatures.fileDrop) { 7270 dropProcessing = qq(tempTemplateEl).getFirstByClass(selectorClasses.dropProcessing); 7271 if (dropProcessing) { 7272 qq(dropProcessing).remove(); 7273 } 7274 } 7275 dropArea = qq(tempTemplateEl).getFirstByClass(selectorClasses.drop); 7276 if (dropArea && !qq.DragAndDrop) { 7277 log("DnD module unavailable.", "info"); 7278 qq(dropArea).remove(); 7279 } 7280 if (!qq.supportedFeatures.fileDrop) { 7281 uploaderEl.removeAttribute(DROPZPONE_TEXT_ATTR); 7282 if (dropArea && qq(dropArea).hasAttribute(HIDE_DROPZONE_ATTR)) { 7283 qq(dropArea).css({ 7284 display: "none" 7285 }); 7286 } 7287 } else if (qq(uploaderEl).hasAttribute(DROPZPONE_TEXT_ATTR) && dropArea) { 7288 dropTextEl = qq(dropArea).getFirstByClass(selectorClasses.dropText); 7289 dropTextEl && qq(dropTextEl).remove(); 7290 } 7291 thumbnail = qq(tempTemplateEl).getFirstByClass(selectorClasses.thumbnail); 7292 if (!showThumbnails) { 7293 thumbnail && qq(thumbnail).remove(); 7294 } else if (thumbnail) { 7295 thumbnailMaxSize = parseInt(thumbnail.getAttribute(THUMBNAIL_MAX_SIZE_ATTR)); 7296 thumbnailMaxSize = thumbnailMaxSize > 0 ? thumbnailMaxSize : null; 7297 serverScale = qq(thumbnail).hasAttribute(THUMBNAIL_SERVER_SCALE_ATTR); 7298 } 7299 showThumbnails = showThumbnails && thumbnail; 7300 isEditElementsExist = qq(tempTemplateEl).getByClass(selectorClasses.editFilenameInput).length > 0; 7301 isRetryElementExist = qq(tempTemplateEl).getByClass(selectorClasses.retry).length > 0; 7302 fileListNode = qq(tempTemplateEl).getFirstByClass(selectorClasses.list); 7303 if (fileListNode == null) { 7304 throw new Error("Could not find the file list container in the template!"); 7305 } 7306 fileListEl = fileListNode.children[0].cloneNode(true); 7307 fileListNode.innerHTML = ""; 7308 if (tempTemplateEl.getElementsByTagName("DIALOG").length) { 7309 document.createElement("dialog"); 7310 } 7311 log("Template parsing complete"); 7312 return { 7313 template: tempTemplateEl, 7314 fileTemplate: fileListEl 7315 }; 7316 }, prependFile = function(el, index, fileList) { 7317 var parentEl = fileList, beforeEl = parentEl.firstChild; 7318 if (index > 0) { 7319 beforeEl = qq(parentEl).children()[index].nextSibling; 7320 } 7321 parentEl.insertBefore(el, beforeEl); 7322 }, processNewQueuedPreviewRequest = function(queuedThumbRequest) { 7323 var id = queuedThumbRequest.id, optFileOrBlob = queuedThumbRequest.optFileOrBlob, relatedThumbnailId = optFileOrBlob && optFileOrBlob.qqThumbnailId, thumbnail = getThumbnail(id), spec = { 7324 customResizeFunction: queuedThumbRequest.customResizeFunction, 7325 maxSize: thumbnailMaxSize, 7326 orient: true, 7327 scale: true 7328 }; 7329 if (qq.supportedFeatures.imagePreviews) { 7330 if (thumbnail) { 7331 if (options.limits.maxThumbs && options.limits.maxThumbs <= generatedThumbnails) { 7332 maybeSetDisplayNotAvailableImg(id, thumbnail); 7333 generateNextQueuedPreview(); 7334 } else { 7335 displayWaitingImg(thumbnail).done(function() { 7336 previewGeneration[id] = new qq.Promise(); 7337 previewGeneration[id].done(function() { 7338 setTimeout(generateNextQueuedPreview, options.limits.timeBetweenThumbs); 7339 }); 7340 if (relatedThumbnailId != null) { 7341 useCachedPreview(id, relatedThumbnailId); 7342 } else { 7343 generateNewPreview(id, optFileOrBlob, spec); 7344 } 7345 }); 7346 } 7347 } else { 7348 generateNextQueuedPreview(); 7349 } 7350 } else if (thumbnail) { 7351 displayWaitingImg(thumbnail); 7352 generateNextQueuedPreview(); 7353 } 7354 }, processUpdateQueuedPreviewRequest = function(queuedThumbRequest) { 7355 var id = queuedThumbRequest.id, thumbnailUrl = queuedThumbRequest.thumbnailUrl, showWaitingImg = queuedThumbRequest.showWaitingImg, thumbnail = getThumbnail(id), spec = { 7356 customResizeFunction: queuedThumbRequest.customResizeFunction, 7357 scale: serverScale, 7358 maxSize: thumbnailMaxSize 7359 }; 7360 if (thumbnail) { 7361 if (thumbnailUrl) { 7362 if (options.limits.maxThumbs && options.limits.maxThumbs <= generatedThumbnails) { 7363 maybeSetDisplayNotAvailableImg(id, thumbnail); 7364 generateNextQueuedPreview(); 7365 } else { 7366 if (showWaitingImg) { 7367 displayWaitingImg(thumbnail); 7368 } 7369 return options.imageGenerator.generate(thumbnailUrl, thumbnail, spec).then(function() { 7370 show(thumbnail); 7371 generatedThumbnails++; 7372 setTimeout(generateNextQueuedPreview, options.limits.timeBetweenThumbs); 7373 }, function() { 7374 maybeSetDisplayNotAvailableImg(id, thumbnail); 7375 setTimeout(generateNextQueuedPreview, options.limits.timeBetweenThumbs); 7376 }); 7377 } 7378 } else { 7379 maybeSetDisplayNotAvailableImg(id, thumbnail); 7380 generateNextQueuedPreview(); 7381 } 7382 } 7383 }, setProgressBarWidth = function(id, percent) { 7384 var bar = getProgress(id), progressBarSelector = id == null ? selectorClasses.totalProgressBar : selectorClasses.progressBar; 7385 if (bar && !qq(bar).hasClass(progressBarSelector)) { 7386 bar = qq(bar).getFirstByClass(progressBarSelector); 7387 } 7388 if (bar) { 7389 qq(bar).css({ 7390 width: percent + "%" 7391 }); 7392 bar.setAttribute("aria-valuenow", percent); 7393 } 7394 }, show = function(el) { 7395 el && qq(el).removeClass(options.classes.hide); 7396 }, useCachedPreview = function(targetThumbnailId, cachedThumbnailId) { 7397 var targetThumbnail = getThumbnail(targetThumbnailId), cachedThumbnail = getThumbnail(cachedThumbnailId); 7398 log(qq.format("ID {} is the same file as ID {}. Will use generated thumbnail from ID {} instead.", targetThumbnailId, cachedThumbnailId, cachedThumbnailId)); 7399 previewGeneration[cachedThumbnailId].then(function() { 7400 generatedThumbnails++; 7401 previewGeneration[targetThumbnailId].success(); 7402 log(qq.format("Now using previously generated thumbnail created for ID {} on ID {}.", cachedThumbnailId, targetThumbnailId)); 7403 targetThumbnail.src = cachedThumbnail.src; 7404 show(targetThumbnail); 7405 }, function() { 7406 previewGeneration[targetThumbnailId].failure(); 7407 if (!options.placeholders.waitUntilUpdate) { 7408 maybeSetDisplayNotAvailableImg(targetThumbnailId, targetThumbnail); 7409 } 7410 }); 7411 }; 7412 qq.extend(options, spec); 7413 log = options.log; 7414 if (!qq.supportedFeatures.imagePreviews) { 7415 options.limits.timeBetweenThumbs = 0; 7416 options.limits.maxThumbs = 0; 7417 } 7418 container = options.containerEl; 7419 showThumbnails = options.imageGenerator !== undefined; 7420 templateDom = parseAndGetTemplate(); 7421 cacheThumbnailPlaceholders(); 7422 qq.extend(this, { 7423 render: function() { 7424 log("Rendering template in DOM."); 7425 generatedThumbnails = 0; 7426 container.appendChild(templateDom.template.cloneNode(true)); 7427 hide(getDropProcessing()); 7428 this.hideTotalProgress(); 7429 fileList = options.fileContainerEl || getTemplateEl(container, selectorClasses.list); 7430 log("Template rendering complete"); 7431 }, 7432 renderFailure: function(message) { 7433 var cantRenderEl = qq.toElement(message); 7434 container.innerHTML = ""; 7435 container.appendChild(cantRenderEl); 7436 }, 7437 reset: function() { 7438 container.innerHTML = ""; 7439 this.render(); 7440 }, 7441 clearFiles: function() { 7442 fileList.innerHTML = ""; 7443 }, 7444 disableCancel: function() { 7445 isCancelDisabled = true; 7446 }, 7447 addFile: function(id, name, prependInfo, hideForever, batch) { 7448 var fileEl = templateDom.fileTemplate.cloneNode(true), fileNameEl = getTemplateEl(fileEl, selectorClasses.file), uploaderEl = getTemplateEl(container, selectorClasses.uploader), fileContainer = batch ? fileBatch.content : fileList, thumb; 7449 if (batch) { 7450 fileBatch.map[id] = fileEl; 7451 } 7452 qq(fileEl).addClass(FILE_CLASS_PREFIX + id); 7453 uploaderEl.removeAttribute(DROPZPONE_TEXT_ATTR); 7454 if (fileNameEl) { 7455 qq(fileNameEl).setText(name); 7456 fileNameEl.setAttribute("title", name); 7457 } 7458 fileEl.setAttribute(FILE_ID_ATTR, id); 7459 if (prependInfo) { 7460 prependFile(fileEl, prependInfo.index, fileContainer); 7461 } else { 7462 fileContainer.appendChild(fileEl); 7463 } 7464 if (hideForever) { 7465 fileEl.style.display = "none"; 7466 qq(fileEl).addClass(HIDDEN_FOREVER_CLASS); 7467 } else { 7468 hide(getProgress(id)); 7469 hide(getSize(id)); 7470 hide(getDelete(id)); 7471 hide(getRetry(id)); 7472 hide(getPause(id)); 7473 hide(getContinue(id)); 7474 if (isCancelDisabled) { 7475 this.hideCancel(id); 7476 } 7477 thumb = getThumbnail(id); 7478 if (thumb && !thumb.src) { 7479 cachedWaitingForThumbnailImg.then(function(waitingImg) { 7480 thumb.src = waitingImg.src; 7481 if (waitingImg.style.maxHeight && waitingImg.style.maxWidth) { 7482 qq(thumb).css({ 7483 maxHeight: waitingImg.style.maxHeight, 7484 maxWidth: waitingImg.style.maxWidth 7485 }); 7486 } 7487 show(thumb); 7488 }); 7489 } 7490 } 7491 }, 7492 addFileToCache: function(id, name, prependInfo, hideForever) { 7493 this.addFile(id, name, prependInfo, hideForever, true); 7494 }, 7495 addCacheToDom: function() { 7496 fileList.appendChild(fileBatch.content); 7497 fileBatch.content = document.createDocumentFragment(); 7498 fileBatch.map = {}; 7499 }, 7500 removeFile: function(id) { 7501 qq(getFile(id)).remove(); 7502 }, 7503 getFileId: function(el) { 7504 var currentNode = el; 7505 if (currentNode) { 7506 while (currentNode.getAttribute(FILE_ID_ATTR) == null) { 7507 currentNode = currentNode.parentNode; 7508 } 7509 return parseInt(currentNode.getAttribute(FILE_ID_ATTR)); 7510 } 7511 }, 7512 getFileList: function() { 7513 return fileList; 7514 }, 7515 markFilenameEditable: function(id) { 7516 var filename = getFilename(id); 7517 filename && qq(filename).addClass(options.classes.editable); 7518 }, 7519 updateFilename: function(id, name) { 7520 var filenameEl = getFilename(id); 7521 if (filenameEl) { 7522 qq(filenameEl).setText(name); 7523 filenameEl.setAttribute("title", name); 7524 } 7525 }, 7526 hideFilename: function(id) { 7527 hide(getFilename(id)); 7528 }, 7529 showFilename: function(id) { 7530 show(getFilename(id)); 7531 }, 7532 isFileName: function(el) { 7533 return qq(el).hasClass(selectorClasses.file); 7534 }, 7535 getButton: function() { 7536 return options.button || getTemplateEl(container, selectorClasses.button); 7537 }, 7538 hideDropProcessing: function() { 7539 hide(getDropProcessing()); 7540 }, 7541 showDropProcessing: function() { 7542 show(getDropProcessing()); 7543 }, 7544 getDropZone: function() { 7545 return getTemplateEl(container, selectorClasses.drop); 7546 }, 7547 isEditFilenamePossible: function() { 7548 return isEditElementsExist; 7549 }, 7550 hideRetry: function(id) { 7551 hide(getRetry(id)); 7552 }, 7553 isRetryPossible: function() { 7554 return isRetryElementExist; 7555 }, 7556 showRetry: function(id) { 7557 show(getRetry(id)); 7558 }, 7559 getFileContainer: function(id) { 7560 return getFile(id); 7561 }, 7562 showEditIcon: function(id) { 7563 var icon = getEditIcon(id); 7564 icon && qq(icon).addClass(options.classes.editable); 7565 }, 7566 isHiddenForever: function(id) { 7567 return qq(getFile(id)).hasClass(HIDDEN_FOREVER_CLASS); 7568 }, 7569 hideEditIcon: function(id) { 7570 var icon = getEditIcon(id); 7571 icon && qq(icon).removeClass(options.classes.editable); 7572 }, 7573 isEditIcon: function(el) { 7574 return qq(el).hasClass(selectorClasses.editNameIcon, true); 7575 }, 7576 getEditInput: function(id) { 7577 return getTemplateEl(getFile(id), selectorClasses.editFilenameInput); 7578 }, 7579 isEditInput: function(el) { 7580 return qq(el).hasClass(selectorClasses.editFilenameInput, true); 7581 }, 7582 updateProgress: function(id, loaded, total) { 7583 var bar = getProgress(id), percent; 7584 if (bar && total > 0) { 7585 percent = Math.round(loaded / total * 100); 7586 if (percent === 100) { 7587 hide(bar); 7588 } else { 7589 show(bar); 7590 } 7591 setProgressBarWidth(id, percent); 7592 } 7593 }, 7594 updateTotalProgress: function(loaded, total) { 7595 this.updateProgress(null, loaded, total); 7596 }, 7597 hideProgress: function(id) { 7598 var bar = getProgress(id); 7599 bar && hide(bar); 7600 }, 7601 hideTotalProgress: function() { 7602 this.hideProgress(); 7603 }, 7604 resetProgress: function(id) { 7605 setProgressBarWidth(id, 0); 7606 this.hideTotalProgress(id); 7607 }, 7608 resetTotalProgress: function() { 7609 this.resetProgress(); 7610 }, 7611 showCancel: function(id) { 7612 if (!isCancelDisabled) { 7613 var cancel = getCancel(id); 7614 cancel && qq(cancel).removeClass(options.classes.hide); 7615 } 7616 }, 7617 hideCancel: function(id) { 7618 hide(getCancel(id)); 7619 }, 7620 isCancel: function(el) { 7621 return qq(el).hasClass(selectorClasses.cancel, true); 7622 }, 7623 allowPause: function(id) { 7624 show(getPause(id)); 7625 hide(getContinue(id)); 7626 }, 7627 uploadPaused: function(id) { 7628 this.setStatusText(id, options.text.paused); 7629 this.allowContinueButton(id); 7630 hide(getSpinner(id)); 7631 }, 7632 hidePause: function(id) { 7633 hide(getPause(id)); 7634 }, 7635 isPause: function(el) { 7636 return qq(el).hasClass(selectorClasses.pause, true); 7637 }, 7638 isContinueButton: function(el) { 7639 return qq(el).hasClass(selectorClasses.continueButton, true); 7640 }, 7641 allowContinueButton: function(id) { 7642 show(getContinue(id)); 7643 hide(getPause(id)); 7644 }, 7645 uploadContinued: function(id) { 7646 this.setStatusText(id, ""); 7647 this.allowPause(id); 7648 show(getSpinner(id)); 7649 }, 7650 showDeleteButton: function(id) { 7651 show(getDelete(id)); 7652 }, 7653 hideDeleteButton: function(id) { 7654 hide(getDelete(id)); 7655 }, 7656 isDeleteButton: function(el) { 7657 return qq(el).hasClass(selectorClasses.deleteButton, true); 7658 }, 7659 isRetry: function(el) { 7660 return qq(el).hasClass(selectorClasses.retry, true); 7661 }, 7662 updateSize: function(id, text) { 7663 var size = getSize(id); 7664 if (size) { 7665 show(size); 7666 qq(size).setText(text); 7667 } 7668 }, 7669 setStatusText: function(id, text) { 7670 var textEl = getTemplateEl(getFile(id), selectorClasses.statusText); 7671 if (textEl) { 7672 if (text == null) { 7673 qq(textEl).clearText(); 7674 } else { 7675 qq(textEl).setText(text); 7676 } 7677 } 7678 }, 7679 hideSpinner: function(id) { 7680 qq(getFile(id)).removeClass(IN_PROGRESS_CLASS); 7681 hide(getSpinner(id)); 7682 }, 7683 showSpinner: function(id) { 7684 qq(getFile(id)).addClass(IN_PROGRESS_CLASS); 7685 show(getSpinner(id)); 7686 }, 7687 generatePreview: function(id, optFileOrBlob, customResizeFunction) { 7688 if (!this.isHiddenForever(id)) { 7689 thumbGenerationQueue.push({ 7690 id: id, 7691 customResizeFunction: customResizeFunction, 7692 optFileOrBlob: optFileOrBlob 7693 }); 7694 !thumbnailQueueMonitorRunning && generateNextQueuedPreview(); 7695 } 7696 }, 7697 updateThumbnail: function(id, thumbnailUrl, showWaitingImg, customResizeFunction) { 7698 if (!this.isHiddenForever(id)) { 7699 thumbGenerationQueue.push({ 7700 customResizeFunction: customResizeFunction, 7701 update: true, 7702 id: id, 7703 thumbnailUrl: thumbnailUrl, 7704 showWaitingImg: showWaitingImg 7705 }); 7706 !thumbnailQueueMonitorRunning && generateNextQueuedPreview(); 7707 } 7708 }, 7709 hasDialog: function(type) { 7710 return qq.supportedFeatures.dialogElement && !!getDialog(type); 7711 }, 7712 showDialog: function(type, message, defaultValue) { 7713 var dialog = getDialog(type), messageEl = getTemplateEl(dialog, selectorClasses.dialogMessage), inputEl = dialog.getElementsByTagName("INPUT")[0], cancelBtn = getTemplateEl(dialog, selectorClasses.dialogCancelButton), okBtn = getTemplateEl(dialog, selectorClasses.dialogOkButton), promise = new qq.Promise(), closeHandler = function() { 7714 cancelBtn.removeEventListener("click", cancelClickHandler); 7715 okBtn && okBtn.removeEventListener("click", okClickHandler); 7716 promise.failure(); 7717 }, cancelClickHandler = function() { 7718 cancelBtn.removeEventListener("click", cancelClickHandler); 7719 dialog.close(); 7720 }, okClickHandler = function() { 7721 dialog.removeEventListener("close", closeHandler); 7722 okBtn.removeEventListener("click", okClickHandler); 7723 dialog.close(); 7724 promise.success(inputEl && inputEl.value); 7725 }; 7726 dialog.addEventListener("close", closeHandler); 7727 cancelBtn.addEventListener("click", cancelClickHandler); 7728 okBtn && okBtn.addEventListener("click", okClickHandler); 7729 if (inputEl) { 7730 inputEl.value = defaultValue; 7731 } 7732 messageEl.textContent = message; 7733 dialog.showModal(); 7734 return promise; 7735 } 7736 }); 7737 }; 7738 qq.UiEventHandler = function(s, protectedApi) { 7739 "use strict"; 7740 var disposer = new qq.DisposeSupport(), spec = { 7741 eventType: "click", 7742 attachTo: null, 7743 onHandled: function(target, event) {} 7744 }; 7745 qq.extend(this, { 7746 addHandler: function(element) { 7747 addHandler(element); 7748 }, 7749 dispose: function() { 7750 disposer.dispose(); 7751 } 7752 }); 7753 function addHandler(element) { 7754 disposer.attach(element, spec.eventType, function(event) { 7755 event = event || window.event; 7756 var target = event.target || event.srcElement; 7757 spec.onHandled(target, event); 7758 }); 7759 } 7760 qq.extend(protectedApi, { 7761 getFileIdFromItem: function(item) { 7762 return item.qqFileId; 7763 }, 7764 getDisposeSupport: function() { 7765 return disposer; 7766 } 7767 }); 7768 qq.extend(spec, s); 7769 if (spec.attachTo) { 7770 addHandler(spec.attachTo); 7771 } 7772 }; 7773 qq.FileButtonsClickHandler = function(s) { 7774 "use strict"; 7775 var inheritedInternalApi = {}, spec = { 7776 templating: null, 7777 log: function(message, lvl) {}, 7778 onDeleteFile: function(fileId) {}, 7779 onCancel: function(fileId) {}, 7780 onRetry: function(fileId) {}, 7781 onPause: function(fileId) {}, 7782 onContinue: function(fileId) {}, 7783 onGetName: function(fileId) {} 7784 }, buttonHandlers = { 7785 cancel: function(id) { 7786 spec.onCancel(id); 7787 }, 7788 retry: function(id) { 7789 spec.onRetry(id); 7790 }, 7791 deleteButton: function(id) { 7792 spec.onDeleteFile(id); 7793 }, 7794 pause: function(id) { 7795 spec.onPause(id); 7796 }, 7797 continueButton: function(id) { 7798 spec.onContinue(id); 7799 } 7800 }; 7801 function examineEvent(target, event) { 7802 qq.each(buttonHandlers, function(buttonType, handler) { 7803 var firstLetterCapButtonType = buttonType.charAt(0).toUpperCase() + buttonType.slice(1), fileId; 7804 if (spec.templating["is" + firstLetterCapButtonType](target)) { 7805 fileId = spec.templating.getFileId(target); 7806 qq.preventDefault(event); 7807 spec.log(qq.format("Detected valid file button click event on file '{}', ID: {}.", spec.onGetName(fileId), fileId)); 7808 handler(fileId); 7809 return false; 7810 } 7811 }); 7812 } 7813 qq.extend(spec, s); 7814 spec.eventType = "click"; 7815 spec.onHandled = examineEvent; 7816 spec.attachTo = spec.templating.getFileList(); 7817 qq.extend(this, new qq.UiEventHandler(spec, inheritedInternalApi)); 7818 }; 7819 qq.FilenameClickHandler = function(s) { 7820 "use strict"; 7821 var inheritedInternalApi = {}, spec = { 7822 templating: null, 7823 log: function(message, lvl) {}, 7824 classes: { 7825 file: "qq-upload-file", 7826 editNameIcon: "qq-edit-filename-icon" 7827 }, 7828 onGetUploadStatus: function(fileId) {}, 7829 onGetName: function(fileId) {} 7830 }; 7831 qq.extend(spec, s); 7832 function examineEvent(target, event) { 7833 if (spec.templating.isFileName(target) || spec.templating.isEditIcon(target)) { 7834 var fileId = spec.templating.getFileId(target), status = spec.onGetUploadStatus(fileId); 7835 if (status === qq.status.SUBMITTED) { 7836 spec.log(qq.format("Detected valid filename click event on file '{}', ID: {}.", spec.onGetName(fileId), fileId)); 7837 qq.preventDefault(event); 7838 inheritedInternalApi.handleFilenameEdit(fileId, target, true); 7839 } 7840 } 7841 } 7842 spec.eventType = "click"; 7843 spec.onHandled = examineEvent; 7844 qq.extend(this, new qq.FilenameEditHandler(spec, inheritedInternalApi)); 7845 }; 7846 qq.FilenameInputFocusInHandler = function(s, inheritedInternalApi) { 7847 "use strict"; 7848 var spec = { 7849 templating: null, 7850 onGetUploadStatus: function(fileId) {}, 7851 log: function(message, lvl) {} 7852 }; 7853 if (!inheritedInternalApi) { 7854 inheritedInternalApi = {}; 7855 } 7856 function handleInputFocus(target, event) { 7857 if (spec.templating.isEditInput(target)) { 7858 var fileId = spec.templating.getFileId(target), status = spec.onGetUploadStatus(fileId); 7859 if (status === qq.status.SUBMITTED) { 7860 spec.log(qq.format("Detected valid filename input focus event on file '{}', ID: {}.", spec.onGetName(fileId), fileId)); 7861 inheritedInternalApi.handleFilenameEdit(fileId, target); 7862 } 7863 } 7864 } 7865 spec.eventType = "focusin"; 7866 spec.onHandled = handleInputFocus; 7867 qq.extend(spec, s); 7868 qq.extend(this, new qq.FilenameEditHandler(spec, inheritedInternalApi)); 7869 }; 7870 qq.FilenameInputFocusHandler = function(spec) { 7871 "use strict"; 7872 spec.eventType = "focus"; 7873 spec.attachTo = null; 7874 qq.extend(this, new qq.FilenameInputFocusInHandler(spec, {})); 7875 }; 7876 qq.FilenameEditHandler = function(s, inheritedInternalApi) { 7877 "use strict"; 7878 var spec = { 7879 templating: null, 7880 log: function(message, lvl) {}, 7881 onGetUploadStatus: function(fileId) {}, 7882 onGetName: function(fileId) {}, 7883 onSetName: function(fileId, newName) {}, 7884 onEditingStatusChange: function(fileId, isEditing) {} 7885 }; 7886 function getFilenameSansExtension(fileId) { 7887 var filenameSansExt = spec.onGetName(fileId), extIdx = filenameSansExt.lastIndexOf("."); 7888 if (extIdx > 0) { 7889 filenameSansExt = filenameSansExt.substr(0, extIdx); 7890 } 7891 return filenameSansExt; 7892 } 7893 function getOriginalExtension(fileId) { 7894 var origName = spec.onGetName(fileId); 7895 return qq.getExtension(origName); 7896 } 7897 function handleNameUpdate(newFilenameInputEl, fileId) { 7898 var newName = newFilenameInputEl.value, origExtension; 7899 if (newName !== undefined && qq.trimStr(newName).length > 0) { 7900 origExtension = getOriginalExtension(fileId); 7901 if (origExtension !== undefined) { 7902 newName = newName + "." + origExtension; 7903 } 7904 spec.onSetName(fileId, newName); 7905 } 7906 spec.onEditingStatusChange(fileId, false); 7907 } 7908 function registerInputBlurHandler(inputEl, fileId) { 7909 inheritedInternalApi.getDisposeSupport().attach(inputEl, "blur", function() { 7910 handleNameUpdate(inputEl, fileId); 7911 }); 7912 } 7913 function registerInputEnterKeyHandler(inputEl, fileId) { 7914 inheritedInternalApi.getDisposeSupport().attach(inputEl, "keyup", function(event) { 7915 var code = event.keyCode || event.which; 7916 if (code === 13) { 7917 handleNameUpdate(inputEl, fileId); 7918 } 7919 }); 7920 } 7921 qq.extend(spec, s); 7922 spec.attachTo = spec.templating.getFileList(); 7923 qq.extend(this, new qq.UiEventHandler(spec, inheritedInternalApi)); 7924 qq.extend(inheritedInternalApi, { 7925 handleFilenameEdit: function(id, target, focusInput) { 7926 var newFilenameInputEl = spec.templating.getEditInput(id); 7927 spec.onEditingStatusChange(id, true); 7928 newFilenameInputEl.value = getFilenameSansExtension(id); 7929 if (focusInput) { 7930 newFilenameInputEl.focus(); 7931 } 7932 registerInputBlurHandler(newFilenameInputEl, id); 7933 registerInputEnterKeyHandler(newFilenameInputEl, id); 7934 } 7935 }); 7936 }; 7937})(window); 7938//# sourceMappingURL=jquery.fine-uploader.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.