1/** 2 * http://github.com/Valums-File-Uploader/file-uploader 3 * 4 * Multiple file upload component with progress-bar, drag-and-drop. 5 * 6 * Have ideas for improving this JS for the general community? 7 * Submit your changes at: https://github.com/Valums-File-Uploader/file-uploader 8 * 9 * VERSION 2.0 beta 10 * Original version 1.0 © 2010 Andrew Valums ( andrew(at)valums.com ) 11 * 12 * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt. 13 */ 14var qq = qq || {}; 15qq.extend = function (first, second) { 16 for (var prop in second) { 17 first[prop] = second[prop]; 18 } 19}; 20qq.indexOf = function (arr, elt, from) { 21 if (arr.indexOf) return arr.indexOf(elt, from); 22 from = from || 0; 23 var len = arr.length; 24 if (from < 0) from += len; 25 for (; from < len; from++) { 26 if (from in arr && arr[from] === elt) { 27 return from; 28 } 29 } 30 return -1; 31}; 32qq.getUniqueId = (function () { 33 var id = 0; 34 return function () { 35 return id++; 36 }; 37})(); 38qq.ie = function () { 39 return navigator.userAgent.indexOf('MSIE') != -1; 40} 41qq.safari = function () { 42 return navigator.vendor != undefined && navigator.vendor.indexOf("Apple") != -1; 43} 44qq.chrome = function () { 45 return navigator.vendor != undefined && navigator.vendor.indexOf('Google') != -1; 46} 47qq.firefox = function () { 48 return (navigator.userAgent.indexOf('Mozilla') != -1 && navigator.vendor != undefined && navigator.vendor == ''); 49} 50qq.windows = function () { 51 return navigator.platform == "Win32"; 52} 53qq.attach = function (element, type, fn) { 54 if (element.addEventListener) { 55 element.addEventListener(type, fn, false); 56 } else if (element.attachEvent) { 57 element.attachEvent('on' + type, fn); 58 } 59 return function () { 60 qq.detach(element, type, fn) 61 } 62}; 63qq.detach = function (element, type, fn) { 64 if (element.removeEventListener) { 65 element.removeEventListener(type, fn, false); 66 } else if (element.attachEvent) { 67 element.detachEvent('on' + type, fn); 68 } 69}; 70qq.preventDefault = function (e) { 71 if (e.preventDefault) { 72 e.preventDefault(); 73 } else { 74 e.returnValue = false; 75 } 76}; 77qq.insertBefore = function (a, b) { 78 b.parentNode.insertBefore(a, b); 79}; 80qq.remove = function (element) { 81 element.parentNode.removeChild(element); 82}; 83qq.contains = function (parent, descendant) { 84 if (parent == descendant) return true; 85 if (parent.contains) { 86 return parent.contains(descendant); 87 } else { 88 return !!(descendant.compareDocumentPosition(parent) & 8); 89 } 90}; 91qq.toElement = (function () { 92 var div = document.createElement('div'); 93 return function (html) { 94 div.innerHTML = html; 95 var element = div.firstChild; 96 div.removeChild(element); 97 return element; 98 }; 99})(); 100qq.css = function (element, styles) { 101 if (styles.opacity != null) { 102 if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined') { 103 styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')'; 104 } 105 } 106 qq.extend(element.style, styles); 107}; 108qq.hasClass = function (element, name) { 109 var re = new RegExp('(^| )' + name + '( |$)'); 110 return re.test(element.className); 111}; 112qq.addClass = function (element, name) { 113 if (!qq.hasClass(element, name)) { 114 element.className += ' ' + name; 115 } 116}; 117qq.removeClass = function (element, name) { 118 var re = new RegExp('(^| )' + name + '( |$)'); 119 element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, ""); 120}; 121qq.setText = function (element, text) { 122 element.innerText = text; 123 element.textContent = text; 124}; 125qq.children = function (element) { 126 var children = [], child = element.firstChild; 127 while (child) { 128 if (child.nodeType == 1) { 129 children.push(child); 130 } 131 child = child.nextSibling; 132 } 133 return children; 134}; 135qq.getByClass = function (element, className) { 136 if (element.querySelectorAll) { 137 return element.querySelectorAll('.' + className); 138 } 139 var result = []; 140 var candidates = element.getElementsByTagName("*"); 141 var len = candidates.length; 142 for (var i = 0; i < len; i++) { 143 if (qq.hasClass(candidates[i], className)) { 144 result.push(candidates[i]); 145 } 146 } 147 return result; 148}; 149qq.obj2url = function (obj, temp, prefixDone) { 150 var uristrings = [], prefix = '&', add = function (nextObj, i) { 151 var nextTemp = temp ? (/\[\]$/.test(temp)) ? temp : temp + '[' + i + ']' : i; 152 if ((nextTemp != 'undefined') && (i != 'undefined')) { 153 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)); 154 } 155 }; 156 if (!prefixDone && temp) { 157 prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?'; 158 uristrings.push(temp); 159 uristrings.push(qq.obj2url(obj)); 160 } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined')) { 161 for (var i = 0, len = obj.length; i < len; ++i) { 162 add(obj[i], i); 163 } 164 } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")) { 165 for (var i in obj) { 166 add(obj[i], i); 167 } 168 } else { 169 uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj)); 170 } 171 return uristrings.join(prefix).replace(/^&/, '').replace(/%20/g, '+'); 172}; 173var qq = qq || {}; 174qq.FileUploaderBasic = function (o) { 175 var that = this; 176 this._options = { 177 debug: false, 178 action: '/server/upload', 179 params: {}, 180 customHeaders: {}, 181 button: null, 182 multiple: true, 183 maxConnections: 3, 184 disableCancelForFormUploads: false, 185 autoUpload: true, 186 allowedExtensions: [], 187 acceptFiles: null, 188 sizeLimit: 0, 189 minSizeLimit: 0, 190 onSubmit: function (id, fileName) { 191 }, 192 onComplete: function (id, fileName, responseJSON) { 193 }, 194 onCancel: function (id, fileName) { 195 }, 196 onUpload: function (id, fileName, xhr) { 197 }, 198 onProgress: function (id, fileName, loaded, total) { 199 }, 200 onError: function (id, fileName, xhr) { 201 }, 202 messages: { 203 typeError: "{file}
203 has an invalid extension. Only {extensions} {isAre} allowed.", 204 sizeError: "{file} is too large, maximum file size is {sizeLimit}.", 205 minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.", 206 emptyError: "{file} is empty, please select files again without it.", 207 noFilesError: "No files to upload.", 208 onLeave: "The files are being uploaded, if you leave now the upload will be cancelled." 209 }, 210 showMessage: function (message) { 211 alert(message); 212 }, 213 inputName: 'qqfile', 214 extraDropzones: [] 215 }; 216 qq.extend(this._options, o); 217 qq.extend(this, qq.DisposeSupport); 218 this._filesInProgress = 0; 219 this._storedFileIds = []; 220 this._handler = this._createUploadHandler(); 221 if (this._options.button) { 222 this._button = this._createUploadButton(this._options.button); 223 } 224 this._preventLeaveInProgress(); 225}; 226qq.FileUploaderBasic.prototype = { 227 setParams: function (params) { 228 this._options.params = params; 229 }, getInProgress: function () { 230 return this._filesInProgress; 231 }, uploadStoredFiles: function () { 232 for (var i = 0; i < this._storedFileIds.length; i++) { 233 this._filesInProgress++; 234 this._handler.upload(this._storedFileIds[i], this._options.params); 235 } 236 }, clearStoredFiles: function () { 237 this._storedFileIds = []; 238 }, _createUploadButton: function (element) { 239 var self = this; 240 var button = new qq.UploadButton({ 241 element: element, 242 multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(), 243 acceptFiles: this._options.acceptFiles, 244 onChange: function (input) { 245 self._onInputChange(input); 246 } 247 }); 248 this.addDisposer(function () { 249 button.dispose(); 250 }); 251 return button; 252 }, _createUploadHandler: function () { 253 var self = this, handlerClass; 254 if (qq.UploadHandlerXhr.isSupported()) { 255 handlerClass = 'UploadHandlerXhr'; 256 } else { 257 handlerClass = 'UploadHandlerForm'; 258 } 259 var handler = new qq[handlerClass]({ 260 debug: this._options.debug, 261 action: this._options.action, 262 encoding: this._options.encoding, 263 maxConnections: this._options.maxConnections, 264 customHeaders: this._options.customHeaders, 265 inputName: this._options.inputName, 266 extraDropzones: this._options.extraDropzones, 267 demoMode: this._options.demoMode, 268 onProgress: function (id, fileName, loaded, total) { 269 self._onProgress(id, fileName, loaded, total); 270 self._options.onProgress(id, fileName, loaded, total); 271 }, 272 onComplete: function (id, fileName, result) { 273 self._onComplete(id, fileName, result); 274 self._options.onComplete(id, fileName, result); 275 }, 276 onCancel: function (id, fileName) { 277 var indexToRemove = self._storedFileIds.indexOf(id); 278 if (indexToRemove >= 0) { 279 self._storedFileIds.splice(indexToRemove, 1); 280 } 281 self._onCancel(id, fileName); 282 self._options.onCancel(id, fileName); 283 }, 284 onError: self._options.onError, 285 onUpload: function (id, fileName, xhr) { 286 self._onUpload(id, fileName, xhr); 287 self._options.onUpload(id, fileName, xhr); 288 } 289 }); 290 return handler; 291 }, _preventLeaveInProgress: function () { 292 var self = this; 293 this._attach(window, 'beforeunload', function (e) { 294 if (!self._filesInProgress) { 295 return; 296 } 297 var e = e || window.event; 298 e.returnValue = self._options.messages.onLeave; 299 return self._options.messages.onLeave; 300 }); 301 }, _onSubmit: function (id, fileName) { 302 if (this._options.autoUpload) { 303 this._filesInProgress++; 304 } 305 }, _onProgress: function (id, fileName, loaded, total) { 306 }, _onComplete: function (id, fileName, result) { 307 var indexToRemove = this._storedFileIds.indexOf(id); 308 if (indexToRemove >= 0) { 309 this._storedFileIds.splice(indexToRemove, 1); 310 } 311 this._filesInProgress--; 312 if (result.error) { 313 this._options.showMessage(result.error); 314 } 315 }, _onCancel: function (id, fileName) { 316 if (this._options.autoUpload) { 317 this._filesInProgress--; 318 } 319 }, _onUpload: function (id, fileName, xhr) { 320 }, _onInputChange: function (input) { 321 if (this._handler instanceof qq.UploadHandlerXhr) { 322 this._uploadFileList(input.files); 323 } else {
324 if (this._validateFile(input)) { 325 this._uploadFile(input); 326 } 327 } 328 this._button.reset(); 329 }, _uploadFileList: function (files) { 330 if (files.length > 0) { 331 for (var i = 0; i < files.length; i++) { 332 if (!this._validateFile(files[i])) { 333 return; 334 } 335 } 336 for (var i = 0; i < files.length; i++) { 337 this._uploadFile(files[i]); 338 } 339 } 340 else { 341 this._error('noFilesError', ""); 342 } 343 }, _uploadFile: function (fileContainer) { 344 var id = this._handler.add(fileContainer); 345 var fileName = this._handler.getName(id); 346 if (this._options.onSubmit(id, fileName) !== false) { 347 this._onSubmit(id, fileName); 348 if (this._options.autoUpload) { 349 this._handler.upload(id, this._options.params); 350 } 351 else { 352 this._storeFileForLater(id); 353 } 354 } 355 }, _storeFileForLater: function (id) { 356 this._storedFileIds.push(id); 357 }, _validateFile: function (file) { 358 var name, size; 359 if (file.value) { 360 name = file.value.replace(/.*(\/|\\)/, ""); 361 } else { 362 name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name; 363 size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size; 364 } 365 if (!this._isAllowedExtension(name)) { 366 this._error('typeError', name); 367 return false; 368 } else if (size === 0) { 369 this._error('emptyError', name); 370 return false; 371 } else if (size && this._options.sizeLimit && size > this._options.sizeLimit) { 372 this._error('sizeError', name); 373 return false; 374 } else if (size && size < this._options.minSizeLimit) { 375 this._error('minSizeError', name); 376 return false; 377 } 378 return true; 379 }, _error: function (code, fileName) { 380 var message = this._options.messages[code]; 381 382 function r(name, replacement) { 383 message = message.replace(name, replacement); 384 } 385 386 var extensions = this._options.allowedExtensions.join(', '); 387 r('{file}', this._formatFileName(fileName)); 388 r('{extensions}', extensions); 389 r('{sizeLimit}', this._formatSize(this._options.sizeLimit)); 390 r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit)); 391 r('{isAre}', extensions.indexOf(",") != -1 ? "are" : "is"); 392 this._options.showMessage(message); 393 }, _formatFileName: function (name) { 394 if (name.length > 33) { 395 name = name.slice(0, 19) + '...' + name.slice(-13); 396 } 397 return name; 398 }, _isAllowedExtension: function (fileName) { 399 var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : ''; 400 var allowed = this._options.allowedExtensions; 401 if (!allowed.length) { 402 return true; 403 } 404 for (var i = 0; i < allowed.length; i++) { 405 if (allowed[i].toLowerCase() == ext) { 406 return true; 407 } 408 } 409 return false; 410 }, _formatSize: function (bytes) { 411 var i = -1; 412 do { 413 bytes = bytes / 1024; 414 i++; 415 } while (bytes > 99); 416 return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i]; 417 } 418}; 419qq.FileUploader = function (o) { 420 qq.FileUploaderBasic.apply(this, arguments); 421 qq.extend(this._options, { 422 element: null, 423 listElement: null, 424 dragText: 'Drop files here to upload', 425 uploadButtonText: 'Upload a file', 426 cancelButtonText: 'Cancel', 427 failUploadText: 'Upload failed', 428 template: '<div class="qq-uploader">' + '<div class="qq-upload-drop-area"><span>{dragText}</span></div>' + '<div class="qq-upload-button">{uploadButtonText}</div>' + '<ul class="qq-upload-list"></ul>' + '</div>', 429 fileTemplate: '<li>' + '<span class="qq-progress-bar"></span>' + '<span class="qq-upload-spinner"></span>' + '<span class="qq-upload-finished"></span>' + '<span class="qq-upload-file"></span>' + '<span class="qq-upload-size"></span>' + '<a class="qq-upload-cancel" href="#">{cancelButtonText}</a>' + '<span class="qq-upload-failed-text">{failUploadtext}</span>' + '</li>', 430 classes: { 431 button: 'qq-upload-button', 432 drop: 'qq-upload-drop-area', 433 dropActive: 'qq-upload-drop-area-active', 434 dropDisabled: 'qq-upload-drop-area-disabled', 435 list: 'qq-upload-list', 436 progressBar: 'qq-progress-bar', 437 file: 'qq-upload-file', 438 spinner: 'qq-upload-spinner', 439 finished: 'qq-upload-finished', 440 size: 'qq-upload-size', 441 cancel: 'qq-upload-cancel', 442 success: 'qq-upload-success', 443 fail: 'qq-upload-fail', 444 successIcon: null, 445 failIcon: null 446 } 447 });
448 qq.extend(this._options, o); 449 this._options.template = this._options.template.replace(/\{dragText\}/g, this._options.dragText); 450 this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.uploadButtonText); 451 this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.cancelButtonText); 452 this._options.fileTemplate = this._options.fileTemplate.replace(/\{failUploadtext\}/g, this._options.failUploadText); 453 this._element = this._options.element; 454 this._element.innerHTML = this._options.template; 455 this._listElement = this._options.listElement || this._find(this._element, 'list'); 456 this._classes = this._options.classes; 457 this._button = this._createUploadButton(this._find(this._element, 'button')); 458 this._bindCancelEvent(); 459 this._setupDragDrop(); 460}; 461qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype); 462qq.extend(qq.FileUploader.prototype, { 463 clearStoredFiles: function () { 464 qq.FileUploaderBasic.prototype.clearStoredFiles.apply(this, arguments); 465 this._listElement.innerHTML = ""; 466 }, addExtraDropzone: function (element) { 467 this._setupExtraDropzone(element); 468 }, removeExtraDropzone: function (element) { 469 var dzs = this._options.extraDropzones; 470 for (var i in dzs) if (dzs[i] === element) return this._options.extraDropzones.splice(i, 1); 471 }, _leaving_document_out: function (e) { 472 return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) || (qq.firefox() && !e.relatedTarget); 473 }, _storeFileForLater: function (id) { 474 qq.FileUploaderBasic.prototype._storeFileForLater.apply(this, arguments); 475 var item = this._getItemByFileId(id); 476 this._find(item, 'spinner').style.display = "none"; 477 }, _find: function (parent, type) { 478 var element = qq.getByClass(parent, this._options.classes[type])[0]; 479 if (!element) { 480 throw new Error('element not found ' + type); 481 } 482 return element; 483 }, _setupExtraDropzone: function (element) { 484 this._options.extraDropzones.push(element); 485 this._setupDropzone(element); 486 }, _setupDropzone: function (dropArea) { 487 var self = this; 488 var dz = new qq.UploadDropZone({ 489 element: dropArea, onEnter: function (e) { 490 qq.addClass(dropArea, self._classes.dropActive); 491 e.stopPropagation(); 492 }, onLeave: function (e) { 493 }, onLeaveNotDescendants: function (e) { 494 qq.removeClass(dropArea, self._classes.dropActive); 495 }, onDrop: function (e) { 496 dropArea.style.display = 'none'; 497 qq.removeClass(dropArea, self._classes.dropActive); 498 self._uploadFileList(e.dataTransfer.files); 499 } 500 }); 501 this.addDisposer(function () { 502 dz.dispose(); 503 }); 504 dropArea.style.display = 'none'; 505 }, _setupDragDrop: function () { 506 var dropArea = this._find(this._element, 'drop'); 507 var self = this; 508 this._options.extraDropzones.push(dropArea); 509 var dropzones = this._options.extraDropzones; 510 var i; 511 for (i = 0; i < dropzones.length; i++) { 512 this._setupDropzone(dropzones[i]); 513 } 514 if (!qq.ie()) { 515 this._attach(document, 'dragenter', function (e) { 516 if (qq.hasClass(dropArea, self._classes.dropDisabled)) return; 517 dropArea.style.display = 'block'; 518 for (i = 0; i < dropzones.length; i++) { 519 dropzones[i].style.display = 'block'; 520 } 521 }); 522 } 523 this._attach(document, 'dragleave', function (e) { 524 var relatedTarget = document.elementFromPoint(e.clientX, e.clientY); 525 if (qq.FileUploader.prototype._leaving_document_out(e)) { 526 for (i = 0; i < dropzones.length; i++) { 527 dropzones[i].style.display = 'none'; 528 } 529 } 530 }); 531 qq.attach(document, 'drop', function (e) { 532 for (i = 0; i < dropzones.length; i++) { 533 dropzones[i].style.display = 'none'; 534 } 535 e.preventDefault(); 536 }); 537 }, _onSubmit: function (id, fileName) { 538 qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments); 539 this._addToList(id, fileName); 540 }, _onProgress: function (id, fileName, loaded, total) { 541 qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments); 542 var item = this._getItemByFileId(id); 543 if (loaded === total) { 544 var cancelLink = this._find(item, 'cancel'); 545 cancelLink.style.display = 'none'; 546 } 547 var size = this._find(item, 'size'); 548 size.style.display = 'inline'; 549 var text; 550 var percent = Math.round(loaded / total * 100); 551 if (loaded != total) { 552 text = percent + '% from ' + this._formatSize(total); 553 } else { 554 text = this._formatSize(total); 555 } 556 this._find(item, 'progressBar').style.width = percent + '%'; 557 qq.setText(size, text); 558 }, _onComplete: function (id, fileName, result) { 559 qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments); 560 var item = this._getItemByFileId(id); 561 if (!this._options.disableCancelForFormUploads || qq.UploadHandlerXhr.isSupported()) { 562 qq.remove(this._find(item, 'cancel')); 563 } 564 qq.remove(this._find(item, 'spinner')); 565 if (result.success) { 566 qq.addClass(item, this._classes.success); 567 if (this._classes.successIcon) { 568 this._find(item, 'finished').style.display = "inline-block"; 569 qq.addClass(item, this._classes.successIcon) 570 } 571 } else { 572 var errorReason = result.reason ? result.reason : "Unknown error"; 573 qq.addClass(item, this._classes.fail); 574 if (this._classes.failIcon) { 575 this._find(item, 'finished').style.display = "inline-block"; 576 qq.addClass(item, this._classes.failIcon) 577 } 578 item.title = errorReason; 579 } 580 }, _addToList: function (id, fileName) { 581 var item = qq.toElement(this._options.fileTemplate); 582 if (this._options.disableCancelForFormUploads && !qq.UploadHandlerXhr.isSupported()) { 583 var cancelLink = this._find(item, 'cancel'); 584 qq.remove(cancelLink); 585 } 586 item.qqFileId = id; 587 var fileElement = this._find(item, 'file'); 588 qq.setText(fileElement, this._formatFileName(fileName)); 589 this._find(item, 'size').style.display = 'none'; 590 if (!this._options.multiple) this._clearList(); 591 this._listElement.appendChild(item); 592 }, _clearList: function () { 593 this._listElement.innerHTML = ''; 594 this.clearStoredFiles(); 595 }, _getItemByFileId: function (id) { 596 var item = this._listElement.firstChild; 597 while (item) { 598 if (item.qqFileId == id) return item; 599 item = item.nextSibling; 600 } 601 }, _bindCancelEvent: function () { 602 var self = this, list = this._listElement; 603 this._attach(list, 'click', function (e) { 604 e = e || window.event; 605 var target = e.target || e.srcElement; 606 if (qq.hasClass(target, self._classes.cancel)) { 607 qq.preventDefault(e); 608 var item = target.parentNode; 609 self._handler.cancel(item.qqFileId); 610 qq.remove(item); 611 } 612 }); 613 } 614}); 615qq.UploadDropZone = function (o) { 616 this._options = { 617 element: null, onEnter: function (e) { 618 }, onLeave: function (e) { 619 }, onLeaveNotDescendants: function (e) { 620 }, onDrop: function (e) { 621 } 622 }; 623 qq.extend(this._options, o); 624 qq.extend(this, qq.DisposeSupport); 625 this._element = this._options.element; 626 this._disableDropOutside(); 627 this._attachEvents(); 628}; 629qq.UploadDropZone.prototype = { 630 _dragover_should_be_canceled: function () { 631 return qq.safari() || (qq.firefox() && qq.windows()); 632 }, _disableDropOutside: function (e) { 633 if (!qq.UploadDropZone.dropOutsideDisabled) { 634 if (this._dragover_should_be_canceled) { 635 qq.attach(document, 'dragover', function (e) { 636 e.preventDefault(); 637 }); 638 } else { 639 qq.attach(document, 'dragover', function (e) { 640 if (e.dataTransfer) { 641 e.dataTransfer.dropEffect = 'none'; 642 e.preventDefault(); 643 } 644 }); 645 } 646 qq.UploadDropZone.dropOutsideDisabled = true; 647 } 648 }, _attachEvents: function () { 649 var self = this; 650 self._attach(self._element, 'dragover', function (e) { 651 if (!self._isValidFileDrag(e)) return; 652 var effect = qq.ie() ? null : e.dataTransfer.effectAllowed; 653 if (effect == 'move' || effect == 'linkMove') { 654 e.dataTransfer.dropEffect = 'move'; 655 } else { 656 e.dataTransfer.dropEffect = 'copy'; 657 } 658 e.stopPropagation(); 659 e.preventDefault(); 660 }); 661 self._attach(self._element, 'dragenter', function (e) { 662 if (!self._isValidFileDrag(e)) return; 663 self._options.onEnter(e); 664 }); 665 self._attach(self._element, 'dragleave', function (e) { 666 if (!self._isValidFileDrag(e)) return; 667 self._options.onLeave(e); 668 var relatedTarget = document.elementFromPoint(e.clientX, e.clientY); 669 if (qq.contains(this, relatedTarget)) return; 670 self._options.onLeaveNotDescendants(e); 671 }); 672 self._attach(self._element, 'drop', function (e) { 673 if (!self._isValidFileDrag(e)) return; 674 e.preventDefault(); 675 self._options.onDrop(e); 676 }); 677 }, _isValidFileDrag: function (e) { 678 if (qq.ie()) return false; 679 var dt = e.dataTransfer, isSafari = qq.safari(); 680 return dt && dt.effectAllowed != 'none' && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files'))); 681 } 682}; 683qq.UploadButton = function (o) { 684 this._options = { 685 element: null, multiple: false, acceptFiles: null, name: 'file', onChange: function (input) { 686 }, hoverClass: 'qq-upload-button-hover', focusClass: 'qq-upload-button-focus' 687 }; 688 qq.extend(this._options, o); 689 qq.extend(this, qq.DisposeSupport); 690 this._element = this._options.element; 691 qq.css(this._element, {position: 'relative', overflow: 'hidden', direction: 'ltr'}); 692 this._input = this._createInput(); 693}; 694qq.UploadButton.prototype = { 695 getInput: function () { 696 return this._input; 697 }, reset: function () { 698 if (this._input.parentNode) { 699 qq.remove(this._input); 700 } 701 qq.removeClass(this._element, this._options.focusClass); 702 this._input = this._createInput(); 703 }, _createInput: function () { 704 var input = document.createElement("input"); 705 if (this._options.multiple) { 706 input.setAttribute("multiple", "multiple"); 707 } 708 if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles); 709 input.setAttribute("type", "file"); 710 input.setAttribute("name", this._options.name); 711 qq.css(input, { 712 position: 'absolute', 713 right: 0, 714 top: 0, 715 fontFamily: 'Arial', 716 fontSize: '118px', 717 margin: 0, 718 padding: 0, 719 cursor: 'pointer', 720 opacity: 0 721 }); 722 this._element.appendChild(input); 723 var self = this; 724 this._attach(input, 'change', function () { 725 self._options.onChange(input); 726 }); 727 this._attach(input, 'mouseover', function () { 728 qq.addClass(self._element, self._options.hoverClass); 729 }); 730 this._attach(input, 'mouseout', function () { 731 qq.removeClass(self._element, self._options.hoverClass); 732 }); 733 this._attach(input, 'focus', function () { 734 qq.addClass(self._element, self._options.focusClass); 735 }); 736 this._attach(input, 'blur', function () { 737 qq.removeClass(self._element, self._options.focusClass); 738 }); 739 if (window.attachEvent) { 740 input.setAttribute('tabIndex', "-1"); 741 } 742 return input; 743 } 744}; 745qq.UploadHandlerAbstract = function (o) { 746 this._options = { 747 debug: false, 748 action: '/upload.php', 749 maxConnections: 999, 750 onProgress: function (id, fileName, loaded, total) { 751 }, 752 onComplete: function (id, fileName, response) { 753 }, 754 onCancel: function (id, fileName) { 755 }, 756 onUpload: function (id, fileName, xhr) { 757 } 758 }; 759 qq.extend(this._options, o); 760 this._queue = []; 761 this._params = []; 762}; 763qq.UploadHandlerAbstract.prototype = { 764 log: function (str) { 765 if (this._options.debug && window.console) console.log('[uploader] ' + str); 766 }, add: function (file) { 767 }, upload: function (id, params) { 768 var len = this._queue.push(id); 769 var copy = {}; 770 qq.extend(copy, params); 771 this._params[id] = copy; 772 if (len <= this._options.maxConnections) { 773 this._upload(id, this._params[id]); 774 } 775 }, cancel: function (id) { 776 this._cancel(id); 777 this._dequeue(id); 778 }, cancelAll: function () { 779 for (var i = 0; i < this._queue.length; i++) { 780 this._cancel(this._queue[i]); 781 } 782 this._queue = []; 783 }, getName: function (id) { 784 }, getSize: function (id) { 785 }, getQueue: function () { 786 return this._queue; 787 }, _upload: function (id) { 788 }, _cancel: function (id) { 789 }, _dequeue: function (id) { 790 var i = qq.indexOf(this._queue, id); 791 this._queue.splice(i, 1); 792 var max = this._options.maxConnections; 793 if (this._queue.length >= max && i < max) { 794 var nextId = this._queue[max - 1]; 795 this._upload(nextId, this._params[nextId]); 796 } 797 } 798}; 799qq.UploadHandlerForm = function (o) { 800 qq.UploadHandlerAbstract.apply(this, arguments); 801 this._inputs = {}; 802 this._detach_load_events = {}; 803};
804qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype); 805qq.extend(qq.UploadHandlerForm.prototype, { 806 add: function (fileInput) { 807 fileInput.setAttribute('name', this._options.inputName); 808 var id = 'qq-upload-handler-iframe' + qq.getUniqueId(); 809 this._inputs[id] = fileInput; 810 if (fileInput.parentNode) { 811 qq.remove(fileInput); 812 } 813 return id; 814 }, getName: function (id) { 815 return this._inputs[id].value.replace(/.*(\/|\\)/, ""); 816 }, _cancel: function (id) { 817 this._options.onCancel(id, this.getName(id)); 818 delete this._inputs[id]; 819 delete this._detach_load_events[id]; 820 var iframe = document.getElementById(id); 821 if (iframe) { 822 iframe.setAttribute('src', 'javascript:false;'); 823 qq.remove(iframe); 824 } 825 }, _upload: function (id, params) { 826 this._options.onUpload(id, this.getName(id), false); 827 var input = this._inputs[id]; 828 if (!input) { 829 throw new Error('file with passed id was not added, or already uploaded or cancelled'); 830 } 831 var fileName = this.getName(id); 832 var iframe = this._createIframe(id); 833 var form = this._createForm(iframe, params); 834 form.appendChild(input); 835 var self = this; 836 this._attachLoadEvent(iframe, function () { 837 self.log('iframe loaded'); 838 var response = self._getIframeContentJSON(iframe); 839 self._options.onComplete(id, fileName, response); 840 self._dequeue(id); 841 delete self._inputs[id]; 842 setTimeout(function () { 843 self._detach_load_events[id](); 844 delete self._detach_load_events[id]; 845 qq.remove(iframe); 846 }, 1); 847 }); 848 form.submit(); 849 qq.remove(form); 850 return id; 851 }, _attachLoadEvent: function (iframe, callback) { 852 this._detach_load_events[iframe.id] = qq.attach(iframe, 'load', function () { 853 if (!iframe.parentNode) { 854 return; 855 } 856 try { 857 if (iframe.contentDocument && iframe.contentDocument.body && iframe.contentDocument.body.innerHTML == "false") { 858 return; 859 } 860 } 861 catch (error) { 862 } 863 callback(); 864 }); 865 }, _getIframeContentJSON: function (iframe) { 866 try { 867 var doc = iframe.contentDocument ? iframe.contentDocument : iframe.contentWindow.document, response; 868 var innerHTML = doc.body.innerHTML; 869 this.log("converting iframe's innerHTML to JSON"); 870 this.log("innerHTML = " + innerHTML); 871 if (innerHTML.slice(0, 5).toLowerCase() == '<pre>' && innerHTML.slice(-6).toLowerCase() == '</pre>') { 872 innerHTML = doc.body.firstChild.firstChild.nodeValue; 873 } 874 response = eval("(" + innerHTML + ")"); 875 } catch (err) { 876 response = {success: false}; 877 } 878 return response; 879 }, _createIframe: function (id) { 880 var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />'); 881 iframe.setAttribute('id', id); 882 iframe.style.display = 'none'; 883 document.body.appendChild(iframe); 884 return iframe; 885 }, _createForm: function (iframe, params) { 886 var protocol = this._options.demoMode ? "GET" : "POST" 887 var form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>'); 888 var queryString = qq.obj2url(params, this._options.action); 889 form.setAttribute('action', queryString); 890 form.setAttribute('target', iframe.name); 891 form.style.display = 'none'; 892 document.body.appendChild(form); 893 return form; 894 } 895}); 896qq.UploadHandlerXhr = function (o) { 897 qq.UploadHandlerAbstract.apply(this, arguments); 898 this._files = []; 899 this._xhrs = []; 900 this._loaded = []; 901}; 902qq.UploadHandlerXhr.isSupported = function () { 903 var input = document.createElement('input'); 904 input.type = 'file'; 905 return ('multiple' in input && typeof File != "undefined" && typeof FormData != "undefined" && typeof(new XMLHttpRequest()).upload != "undefined"); 906}; 907qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype) 908qq.extend(qq.UploadHandlerXhr.prototype, { 909 add: function (file) { 910 if (!(file instanceof File)) { 911 throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)'); 912 } 913 return this._files.push(file) - 1; 914 }, getName: function (id) { 915 var file = this._files[id]; 916 return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name; 917 }, getSize: function (id) { 918 var file = this._files[id]; 919 return file.fileSize != null ? file.fileSize : file.size; 920 }, getLoaded: function (id) { 921 return this._loaded[id] || 0; 922 }, _upload: function (id, params) { 923 this._options.onUpload(id, this.getName(id), true); 924 var file = this._files[id], name = this.getName(id), size = this.get
924Size(id); 925 this._loaded[id] = 0; 926 var xhr = this._xhrs[id] = new XMLHttpRequest(); 927 var self = this; 928 xhr.upload.onprogress = function (e) { 929 if (e.lengthComputable) { 930 self._loaded[id] = e.loaded; 931 self._options.onProgress(id, name, e.loaded, e.total); 932 } 933 }; 934 xhr.onreadystatechange = function () { 935 if (xhr.readyState == 4) { 936 self._onComplete(id, xhr); 937 } 938 }; 939 params = params || {}; 940 params[this._options.inputName] = name; 941 var queryString = qq.obj2url(params, this._options.action); 942 var protocol = this._options.demoMode ? "GET" : "POST"; 943 xhr.open(protocol, queryString, true); 944 xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); 945 xhr.setRequestHeader("X-File-Name", encodeURIComponent(name)); 946 if (this._options.encoding == 'multipart') { 947 var formData = new FormData(); 948 formData.append(this._options.inputName, file); 949 file = formData; 950 } else { 951 xhr.setRequestHeader("Content-Type", "application/octet-stream"); 952 xhr.setRequestHeader("X-Mime-Type", file.type); 953 } 954 for (key in this._options.customHeaders) { 955 xhr.setRequestHeader(key, this._options.customHeaders[key]); 956 } 957 ;xhr.send(file); 958 }, _onComplete: function (id, xhr) { 959 if (!this._files[id]) return; 960 var name = this.getName(id); 961 var size = this.getSize(id); 962 this._options.onProgress(id, name, size, size); 963 if (xhr.status == 200) { 964 this.log("xhr - server response received"); 965 this.log("responseText = " + xhr.responseText); 966 var response; 967 try { 968 response = eval("(" + xhr.responseText + ")"); 969 } catch (err) { 970 response = {}; 971 } 972 this._options.onComplete(id, name, response); 973 } else { 974 this._options.onError(id, name, xhr); 975 this._options.onComplete(id, name, {}); 976 } 977 this._xhrs[id] = null; 978 this._dequeue(id); 979 }, _cancel: function (id) { 980 this._options.onCancel(id, this.getName(id)); 981 this._files[id] = null; 982 if (this._xhrs[id]) { 983 this._xhrs[id].abort(); 984 this._xhrs[id] = null; 985 } 986 } 987}); 988qq.DisposeSupport = { 989 _disposers: [], dispose: function () { 990 var disposer; 991 while (disposer = this._disposers.shift()) { 992 disposer(); 993 } 994 }, addDisposer: function (disposeFunction) { 995 this._disposers.push(disposeFunction); 996 }, _attach: function () { 997 this.addDisposer(qq.attach.apply(this, arguments)); 998 } 999};
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.