1 2if (typeof AcDocuments === "undefined") { 3 4 function AcDocuments(step, config) { 5 var defaultConfig = { 6 rootSelector: '[data-ov-widget="file-upload"]', 7 msg: { 8 }, 9 urls: { 10 addFile: null, 11 deleteFile: null 12 }, 13 validate: { 14 minSizes: { 15 'image/jpeg': 300, 16 'image/png': 80, 17 'application/pdf': 5120, 18 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 10000, 19 'application/msword': 20000 20 } 21 }, 22 animate: { 23 duration: { 24 show: 400, 25 hide: 900 26 }, 27 timeout: { 28 hide: 4000 29 } 30 }, 31 reloadOnAuthError: false 32 }; 33 34 // AcTramiteStep instance 35 this.step = step; 36 this.config = jQuery.extend({}, defaultConfig, config || {}); 37 this.pendingOperations = 0; 38 39 if (this.findElements().length != 1) { 40 throw new Error('Invalid root selector: ' + this.config.rootSelector); 41 } 42 } 43 44 AcDocuments.prototype.reloadIfAuthError = function(timeout) { 45 if (this.config.reloadOnAuthError === true) { 46 // fixme: Reload should not be done on last page 47 // setTimeout(window.location.reload, timeout ? timeout : 500); 48 } 49 } 50 51 AcDocuments.prototype.add = function(id, fileName, mimeType) { 52 this.documents[id] = { 53 file: fileName, 54 mime: mimeType 55 }; 56 } 57 58 AcDocuments.prototype.init = function() { 59 if (typeof this.config.init === "function") { 60 this.config.init(this); 61 } 62 } 63 64 AcDocuments.prototype.findElements = function(groupId, selector) { 65 var result = jQuery(this.config.rootSelector).first(); 66 67 if (typeof groupId === "string" && groupId.trim()) { 68 result = result.find('[data-ov-file-upload-group-id="' + groupId + '"]').first(); 69 } 70 71 if (typeof selector === "string" && selector.trim()) { 72 result = result.find(selector.trim()); 73 } 74 75 return result; 76 } 77 78 AcDocuments.prototype.findElement = function(groupId, selector) { 79 return this.findElements(groupId, selector).first(); 80 } 81 82 AcDocuments.prototype.createFileElement = function(groupId, fileId, file) { 83 var fileElem = jQuery('<span class="fileImage"></span>'); 84 fileElem.attr("data-ov-file-upload-document-id", fileId); 85 var icon = jQuery('<span class="document file-type-icon"></span>'); 86 87 if (this.isImage(file)) { 88 icon.addClass('file-type-image'); 89 var img = document.createElement('img'); 90 img.src = URL.createObjectURL(file); 91 img.alt = ""; 92 img.onLoad = function() { 93 URL.revokeObjectURL(this.src); 94 }; 95 icon.append(img); 96 } else if (this.isPdf(file)) { 97 icon.addClass('file-type-pdf'); 98 } else if (file && file.name.indexOf('.') >= 0) { 99 var ext = file.name.toLowerCase().split('.').slice(-1); 100 icon.addClass('file-type-' + ext); 101 } else { 102 icon.addClass('file-type-any'); 103 } 104 105 fileElem.append(icon); 106 fileElem.append(jQuery('<span class="name">' + file.name + '</span>')); 107 fileElem.append(jQuery('<span class="progress"><span class="progress-bar"></span></span>')); 108 109 this.findElement(groupId, ".fileList").append(fileElem); 110 this.findElement(groupId, '.placeholder').addClass('hide'); 111 112 return fileElem; 113 } 114 115 AcDocuments.prototype.addFileElementDeleteLink = function(groupId, fileId, replaceJElem) { 116 var deleteButton = jQuery('<span class="delete action-button" aria-role="button">' + this.config.msg.eliminarFichero + '</span>'); 117 118 if (typeof replaceJElem === "object") { 119 replaceJElem.replaceWith(deleteButton); 120 } else { 121 var fileElem = this.findElement(groupId, '[data-ov-file-upload-document-id="' + fileId + '"]'); 122 123 if (fileElem.find('.delete').length > 0) { 124 return; 125 } 126 127 fileElem.append(deleteButton); 128 } 129 130 this.attachEventHandlers(deleteButton); 131 } 132 133 AcDocuments.prototype.deleteFileElement = function(groupId, fileId) { 134 var document = this.findElement(groupId, '[data-ov-file-upload-document-id="' + fileId + '"]'); 135 document.find('.delete').off('click.dnd'); 136 document.remove(); 137 138 if (this.findElement(groupId, '.fileList .fileImage').length == 0) { 139 this.findElement(groupId, '.placeholder').removeClass('hide'); 140 } 141 } 142 143 /** 144 * Add a file details to the list after being processed in backend 145 * 146 * @param groupId Group Id 147 * @param tmpFileId Unique file Id used to create the progress 148 * @param fileData File details obtained from the call: fileData.id, fileData.simpleType, fileData.fileName, 149 * fileData.mimeType, fileData.base64Thumbnail 150 * 151 */
152 AcDocuments.prototype.updateFileInList = function(groupId, tmpFileId, fileData) { 153 154 var fileElem = this.findElement(groupId, '[data-ov-file-upload-document-id="' + tmpFileId + '"]'); 155 156 if (fileElem.length == 0) { 157 fileElem = this.createFileElement(groupId, fileData.id); 158 } else { 159 fileElem.attr('data-ov-file-upload-document-id', fileData.id); 160 } 161 162 fileElem.find('.name').text(fileData.fileName); 163 164 var icon = fileElem.find('.document.file-type-icon').addClass('.file-type-' + fileData.simpleType) 165 .removeClass('.file-type-any'); 166 167 if (fileData.mimeType && fileData.base64Thumbnail) { 168 var img = icon.find('img'); 169 var dataUrl = 'data:' + fileData.mimeType + ";base64," + fileData.base64Thumbnail; 170 171 if (img.length === 0) { 172 img = jQuery('<img src="' + dataUrl + '" alt="">'); 173 icon.append(img); 174 } else { 175 img.attr('src', dataUrl).removeClass('hide'); 176 } 177 } 178 179 return fileElem; 180 } 181 182 AcDocuments.prototype.attachEventHandlers = function(itemRootElem) { 183 var $this = this; 184 var targetElem = itemRootElem.find('.delete'); 185 186 if (itemRootElem.hasClass('delete') && itemRootElem.hasClass('action-button')) { 187 targetElem = itemRootElem.first(); 188 } 189 190 targetElem.off('click.dnd').on('click.dnd', function(ev){ 191 $this.handleDeleteEvent(this, ev); 192 }); 193 } 194 195 AcDocuments.prototype.handleDeleteEvent = function(domElem, ev) { 196 ev.stopPropagation(); 197 ev.preventDefault(); 198 var fileElem = jQuery(domElem).closest('[data-ov-file-upload-document-id]'); 199 var fileId = fileElem.attr('data-ov-file-upload-document-id'); 200 201 if (fileId) { 202 var groupId = this.getGroupId(domElem); 203 this.ajaxDeleteFile(groupId, fileId, fileElem); 204 } 205 } 206 207 AcDocuments.prototype.ajaxDeleteFile = function(groupId, fileId, fileElem) { 208 var $this = this; 209 210 var data = {}; 211 data[this.step.config.nmsp + "groupId"] = groupId; 212 data[this.step.config.nmsp + "fileId"] = fileId; 213 214 $this.onBackgroundOperationStart(); 215 216 jQuery.ajax({ 217 url: this.config.url.deleteFile, 218 method: 'POST', 219 data: data, 220 cache: false, 221 dataType: 'json' 222 }).done(function(result, textStatus, jqXHR){ 223 $this.internalFileDelete(groupId, fileElem, result.success, result.status); 224 }).fail(function(jqXHR, textStatus, errorThrown){ 225 $this.internalFileDelete(groupId, null, false, textStatus); 226 }).always(function(){ 227 $this.onBackgroundOperationEnd(); 228 }); 229 } 230 231 AcDocuments.prototype.internalFileDelete = function (groupId, fileElem, success, statusOrMsg){ 232 if (success) { 233 fileElem.remove(); 234 var fileList = this.findElements(groupId, '.fileList'); 235 if (fileList.children().length == 0) { 236 fileList.parent().find('.placeholder').removeClass('hide').show(); 237 } 238 } else { 239 if (statusOrMsg && statusOrMsg.code === "401") { 240 // Session token expired, must reload page 241 this.reloadIfAuthError(); 242 } 243 244 this.showError(groupId, statusOrMsg); 245 } 246 } 247 248 AcDocuments.prototype.normalizeToFileArray = function (files) { 249 var result = []; 250 251 if (files instanceof Array || files instanceof FileList || files instanceof DataTransferItemList) { 252 for (var index = 0; index < files.length; index++) { 253 var file = files[index]; 254 255 if (file instanceof DataTransferItem) { 256 file = file.getAsFile(); 257 } 258 259 if (file instanceof File) { 260 result.push(file); 261 } else { 262 result = result.concat(this.normalizeToFileArray(file)); 263 } 264 } 265 } else if (files instanceof DataTransferItem) { 266 result.push(files.getAsFile()); 267 } else if (files instanceof File) { 268 result.push(files); 269 } else if (file) { 270 throw new Error("Cannot normalize to File[]: " + files); 271 } 272 273 return result; 274 } 275 276 AcDocuments.prototype.ajaxAddFiles = function(groupId, fileKey, fileValues, fileAddedCallback, finalCallback) { 277 var files = this.normalizeToFileArray(fileValues); 278 var count = files.length; 279 var order = 0; 280 281 for (var index = 0; index < files.length; index++) { 282 var file = files[index]; 283 order++; 284 285 this.ajaxAddFile(groupId, fileKey, file, function(finalGroupId, finalFileId){ 286 count--; 287 288 if (typeof fileAddedCallback === 'function') { 289 fileAddedCallback(finalGroupId, finalFileId, order, activeUploads); 290 } 291 292 if (count == 0 && typeof finalCallback === 'function') { 293 finalCallback(finalGroupId, finalFileId); 294 } 295 }) 296 } 297 } 298 299 AcDocuments.prototype.generateFileId = function(groupId, file) { 300 return (new Date()).getTime() + "" + parseInt(Math.random() * 1000000); 301 } 302 303 AcDocuments.prototype.ajaxAddFile = function(groupId, fileKey, fileValue, finalCallback) { 304 var tmpFileId = this.generateFileId(groupId, fileValue); 305 306 var fileElem = this.createFileElement(groupId, tmpFileId, fileValue); 307 this.startProgressAnimation(fileElem); 308 309 var formData = new FormData(); 310 formData.append(fileKey, fileValue) 311 formData.append(this.step.config.nmsp + "groupId", groupId); 312 formData.append(this.step.config.nmsp + "tmpFileId", tmpFileId); 313 314 var $this = this; 315
316 $this.onBackgroundOperationStart(); 317 318 jQuery.ajax({ 319 url: this.config.url.addFile, 320 method: 'POST', 321 data: formData, 322 cache: false, 323 contentType: false, 324 processData: false, 325 dataType: 'json', 326 timeout: 50000, 327 beforeSend: function() { 328 329 } 330 }).done(function(result, textStatus, jqXHR) { 331 var data = result && result.success && result.data ? result.data : null; 332 $this.handleAjaxFileUploadResult(result.success && data, result.status, fileElem, groupId, tmpFileId, data, finalCallback); 333 }).fail(function(jqXHR, textStatus, errorThrown){ 334 $this.handleAjaxFileUploadResult(false, textStatus, fileElem, groupId, tmpFileId, null, finalCallback); 335 }).always(function(){ 336 $this.onBackgroundOperationEnd(); 337 }); 338 } 339 340 AcDocuments.prototype.handleAjaxFileUploadResult = function (success, statusOrMsg, fileElem, groupId, tmpFileId, data, finalCallback){ 341 var $this = this; 342 343 this.stopProgressAnimation(fileElem, function(progress){ 344 if (success) { 345 $this.updateFileInList(groupId, tmpFileId, data); 346 $this.addFileElementDeleteLink(groupId, data.id, progress); 347 348 if (typeof finalCallback === "function") { 349 finalCallback(groupId, data.id); 350 } 351 } else { 352 $this.deleteFileElement(groupId, tmpFileId); 353 354 if (statusOrMsg && statusOrMsg.code === "401") { 355 // Session token expired, must reload page 356 $this.reloadIfAuthError(); 357 } 358 359 $this.showError(groupId, statusOrMsg); 360 361 if (typeof finalCallback === "function") { 362 finalCallback(groupId, undefined); 363 } 364 } 365 }); 366 } 367 368 AcDocuments.prototype.onBackgroundOperationStart = function() { 369 this.pendingOperations++; 370 this.step.disableUserInteraction(); 371 } 372 373 AcDocuments.prototype.onBackgroundOperationEnd = function() { 374 this.pendingOperations--; 375 376 if (this.pendingOperations === 0) { 377 this.step.enableUserInteraction(); 378 } 379 } 380 381 AcDocuments.prototype.filterMsgToKey = function(msg) { 382 var safeMsg = ""; 383 384 if (typeof msg === "object") { 385 if ("status" in msg && typeof msg.status.message === "string") { 386 safeMsg = msg.status.message; 387 } else if (typeof msg === "object" && "message" in msg) { 388 safeMsg = msg.message; 389 } 390 391 safeMsg = safeMsg.replace(" ", "_"); 392 } else if (typeof msg === "string") { 393 safeMsg = msg; 394 } 395 396 var diacriticsRegex = new RegExp("\\p{Diacritic}", "gu"); 397 safeMsg = safeMsg.trim().normalize("NFD").replace(diacriticsRegex, ""); 398 safeMsg = safeMsg.toLowerCase().replace(/[ \t\r\n\-]+/, "-"); 399 400 return safeMsg; 401 } 402 403 AcDocuments.prototype.getMessageObj = function(msgKey, msg) { 404 var result = { 405 title: this.config.msg.genericErrorTitle, 406 description: this.config.msg.genericErrorDescription 407 }; 408 409 var unhandledErrors = ["timeout", "parsererror", "abort", "error", "authorization_required"]; 410 411 if (msgKey === "invalid-size" || msgKey == "error-file-upload-max-size") { 412 result.title = this.config.msg.invalidSizeTitle; 413 result.description = this.config.msg.invalidSizeDescription; 414 } else if (msgKey === "invalid-format" || msgKey == "error-file-upload-format") { 415 result.title = this.config.msg.invalidFormatTitle; 416 result.description = this.config.msg.invalidFormatDescription; 417 } else if (msgKey === "invalid-file" || msgKey == "error-file-corrupted" || msgKey == "error-file-truncated") { 418 result.title = this.config.msg.fileCorruptedTitle; 419 result.description = this.config.msg.fileCorruptedDescription; 420 } else if (unhandledErrors.indexOf(msgKey) >= 0) { 421 var errorKey = "ajaxError"; 422 423 if (msgKey === "Error") { 424 errorKey += "Generic"; 425 } else { 426 // convert msgKey to camelCase and append to errorKey 427 var parts = msgKey.split("-"); 428 for (var index = 0; index < parts.length; index++) { 429 var part = parts[index]; 430 errorKey += part[0].toUpperCase() + part.substring(1); 431 } 432 } 433 434 if (errorKey in this.config.msg) { 435 result.description = this.config.msg[errorKey]; 436 } 437 438 } else if (msgKey.length > 0) { 439 result.description = msgKey.trim().replace(/[\-_]+/, " "); 440 result.description = result.description[0].toUpperCase() + result.description.substr(1); 441 } 442 443 return result; 444 } 445 446 AcDocuments.prototype.showError = function(groupId, msg) { 447 var msgKey = this.filterMsgToKey(msg); 448 var msgObj = this.getMessageObj(msgKey, msg); 449 450 var errorContainer = this.findElement(groupId, '.file-upload-error-message'); 451 errorContainer.find('.title').text(msgObj.title); 452 errorContainer.find('.description').text(msgObj.description); 453 var $this = this; 454 var showTime = this.config.animate.duration.show || 'slow'; 455 var hideTimeout = this.config.animate.timeout.hide || 4000; 456 457 errorContainer 458 .attr("aria-role", "alert") 459 .addClass("current-error") 460 .show(showTime, function(){ 461 setTimeout(function(){ 462 $this.hideError(groupId); 463 }, hideTimeout); 464 }); 465 } 466 467 AcDocuments.prototype.hideError = function(groupId) { 468 var errorContainer = this.findElement(groupId, '.file-upload-error-message'); 469 var hideTime = this.config.animate.duration.hide || 'fast'; 470 errorContainer 471 .removeAttr("aria-role") 472 .removeClass("current-error") 473 .hide(hideTime); 474 } 475 476 AcDocuments.prototype.initDnD = function() { 477 var rootElem = this.findElement(); 478 this.initValidation(rootElem); 479 this.init(); 480 481 // Ensure there is a file input and that it contains all the required stuff 482 rootElem.find('[data-ov-file-upload-group-id] input[type="file"]') 483 .attr('accept', this.config.validate.accepts.join(',')) 484 // Ignore file elements in built-in validation as they are handled by this pseudo-class directly 485 .attr('data-ov-validation-ignore', true) 486 .attr('data-ov-validation', 'valid') 487 .prop('multiple', true) 488 .show() 489 .addClass('ov-hidden-accesible-block'); 490 491 this.handleEvents(rootElem); 492 } 493 494 AcDocuments.prototype.handleEvents = function(rootElem) { 495 var $this = this; 496 rootElem.find('[data-ov-file-upload-group-id] [data-ov-file-upload-document-id]').each(function(pos, elem){ 497 var jElem = jQuery(elem); 498 $this.attachEventHandlers(jElem); 499 }); 500 501 rootElem.find('[data-ov-file-upload-group-id] .drop_zone') 502 .on('dragover.dnd dragleave.dnd', function(ev) { 503 ev.stopPropagation(); 504 ev.preventDefault(); 505 $this.hideError($this.getGroupId(this)); 506 }).on('drop.dnd', function(ev) { 507 $this.onDrop(this, ev); 508 }); 509 510 rootElem.find('[data-ov-file-upload-group-id] input[type="file"]') 511 .on('change.dnd', function(ev){ 512 $this.onChange(this, ev); 513 }); 514 } 515 516 AcDocuments.prototype.initValidation = function(rootElem) { 517 var accepts = rootElem.attr("data-ov-upload-file-accepts"); 518 accepts = accepts ? accepts.trim().toLowerCase().split(/[,\ ]+/) : []; 519 520 var maxSize = rootElem.attr("data-ov-upload-file-max-size"); 521 maxSize = maxSize ? parseInt(maxSize.trim()) : 0; 522 523 if (isNaN(maxSize) || maxSize < 0) { 524 maxSize = 0; 525 } 526 527 this.config.validate.maxSize = maxSize; 528 this.config.validate.accepts = accepts; 529 } 530 531 AcDocuments.prototype.getGroupId = function(elem) { 532 var jElem = jQuery(elem); 533 var groupId = jElem.attr('data-ov-file-upload-group-id'); 534 535 if (!groupId) { 536 jElem = jElem.closest('[data-ov-file-upload-group-id]'); 537 538 if (jElem.length >= 0) { 539 groupId = jElem.attr('data-ov-file-upload-group-id'); 540 } 541 } 542 543 return groupId ? groupId : undefined; 544 } 545 546 AcDocuments.prototype.getFileInputByElem = function(domElement) { 547 var groupId = this.getGroupId(domElement); 548 549 if (!groupId) { 550 throw new Error('No groupId found by DOM element', domElement); 551 } 552 553 var fileInput = this.findElement(groupId, 'input[type="file"]'); 554 555 return fileInput && fileInput.length > 0 ? fileInput : undefined; 556 } 557 558 AcDocuments.prototype.onDrop = function(domElement, ev) { 559 ev.stopPropagation(); 560 ev.preventDefault(); 561 562 // Normalize to a File array. 563 564 var dataTransferItems = (ev.dataTransfer && ev.dataTransfer.items) 565 || (ev.originalEvent.dataTransfer && ev.originalEvent.dataTransfer.items) 566 || []; 567 var dataFiles = ev.target.files || ev.originalEvent.target.files || []; 568 569 var files = this.normalizeToFileArray([].concat(dataTransferItems).concat(dataFiles)); 570 571 var fileInput = this.getFileInputByElem(domElement); 572 573 if (!fileInput) { 574 throw new Error('No file input found by group ' + groupId); 575 } 576 577 this.handleFilesForUpload(domElement, fileInput.attr("name"), files); 578 } 579 580 AcDocuments.prototype.onChange = function(targetElem, ev) { 581 ev.preventDefault(); 582 ev.stopPropagation(); 583 584 var fileUploadRow = jQuery(targetElem).closest('.file-upload-row'); 585 586 if (fileUploadRow.length === 0) { 587 throw new Error('Invalid target element is not inside the upload row'); 588 } 589 590 var files = this.normalizeToFileArray(ev.target.files); 591 this.handleFilesForUpload(fileUploadRow[0], ev.target.name, files); 592 } 593 594 AcDocuments.prototype.handleFilesForUpload = function(targetElem, inputName, files) { 595 var groupId = this.getGroupId(targetElem); 596 597 if (!groupId) { 598 throw new Error("No group found for target element"); 599 } 600 601 for (var index = 0; index < files.length; index++) { 602 var candidate = files[index];
603 var validation = this.validateFile(candidate); 604 605 if (validation !== "") { 606 console && console.error("Invalid file " + candidate.name + ". Validation: " + validation); 607 this.showError(groupId, validation); 608 return; 609 } 610 } 611 612 var $this = this; 613 614 this.ajaxAddFiles(groupId, inputName, files, null, function(finalGroupId, finalFileId){ 615 var input = $this.findElement(finalGroupId, 'input[type="file"]'); 616 617 if (input.length === 1) { 618 $this.clearFileInput(input); 619 } else if (input.length > 1) { 620 throw new Error("Found multiple inputs in group " + finalGroupId); 621 } else if (input.length === 0) { 622 throw new Error("No file input found in group " + finalGroupId); 623 } 624 }); 625 } 626 627 AcDocuments.prototype.clearFileInput = function(input) { 628 // clear validation state 629 input.val('').attr("data-ov-validation", "valid")[0].removeAttribute("aria-invalid"); 630 // Clear the file input replacing with a deep clone while keeping all event handlers 631 var newInput = input.clone(true); 632 input.replaceWith(newInput); 633 // Do validation over the new empty one 634 newInput.change(); 635 } 636 637 AcDocuments.prototype.validateFileNotCorrupted = function(file) { 638 var key = (file && file.type.toLowerCase()) || "none"; 639 var minSize = 0; 640 641 if (key in this.config.validate.minSizes) { 642 minSize = this.config.validate.minSizes[key]; 643 } 644 645 return AcValidations.validateFileNotEmptyAndMime(file, minSize); 646 } 647 648 AcDocuments.prototype.validateFileSize = function(file) { 649 return AcValidations.validateFileSize(file, this.config.validate.maxSize); 650 } 651 652 AcDocuments.prototype.validateFileAccept = function(file) { 653 return AcValidations.validateFileAccept(file, this.config.validate.accepts); 654 } 655 656 AcDocuments.prototype.validateFile = function(file) { 657 658 if (!this.validateFileNotCorrupted(file)) { 659 return "invalid-file"; 660 } else if (!this.validateFileSize(file)) { 661 return "invalid-size"; 662 } else if (!this.validateFileAccept(file)) { 663 return "invalid-format"; 664 } 665 666 return ''; 667 } 668 669 AcDocuments.prototype.isImage = function(file) { 670 return file && file.type && file.type.startsWith('image/'); 671 } 672 673 AcDocuments.prototype.isPdf = function(file) { 674 return file && file.type && file.type === 'application/pdf'; 675 } 676 677 AcDocuments.prototype.startProgressAnimation = function(fileElem, onEnd) { 678 var progress = fileElem.find('.progress'); 679 var domElem = progress.get(0); 680 var totalWidth = parseInt(progress.css('width')); 681 var maxTimeUnits = 120; 682 var stepTime = 400; 683 684 var fill = progress.find('.progress-bar'); 685 var currentWidth = parseInt(fill.css('width')); 686 687 var stepWidth = (totalWidth - currentWidth)/maxTimeUnits; 688 var $this = this; 689 690 var updateFill = function() { 691 clearTimeout(domElem.progressTimer); 692 693 if (currentWidth >= totalWidth) { 694 $this.showError(fileElem.attr('data-ov-file-upload-group-id')); 695 $this.stopProgressAnimation(fileElem, onEnd); 696 } else { 697 currentWidth += stepWidth; 698 fill.css('width', currentWidth + 'px'); 699 700 if (currentWidth > (totalWidth / 4)) { 701 stepTime = 300; 702 stepWidth = stepWidth / 2; 703 } else if (currentWidth > (totalWidth / 2)) { 704 stepTime = 200; 705 stepWidth = stepWidth / 2; 706 } 707 708 domElem.progressTimer = setTimeout(updateFill, stepTime); 709 } 710 }; 711 712 updateFill(); 713 } 714 715 AcDocuments.prototype.stopProgressAnimation = function(fileElem, onEnd) { 716 var progress = fileElem.find('.progress'); 717 718 if (progress.length > 0) { 719 var domElem = progress.get(0); 720 clearTimeout(domElem.progressTimer); 721 progress.find('.progress-bar').css('width', '100%'); 722 } 723 724 if (typeof onEnd === 'function') { 725 setTimeout(function(){ 726 onEnd(progress); 727 }, 100); 728 } 729 } 730} 731 732/* Definition of class AcTramiteStep */ 733 734if (typeof AcTramiteStep === 'undefined') { 735 function AcTramiteStep(config) { 736 737 var baseSelector = ".ac-tramite.tramite-cambio-titular"; 738 var sectionSelector = baseSelector + " .tramite-wrapper"; 739 740 var defaultConfig = { 741 nmsp: "", 742 validation: {}, 743 selectors: { 744 base: baseSelector, 745 section: sectionSelector, 746 form: sectionSelector + " form.tramite-form" 747 }, 748 }; 749 750 this.config = jQuery.extend({}, defaultConfig, config || {}); 751 752 this.name = "tramite-step"; 753 var parts = this.getCurrentStep().split("-"); 754 755 for (var index = 0; index < parts.length; index++) { 756 var part = parts[index]; 757 if (part.length > 0) { 758 this.name += part[0].toUpperCase() + part.substr(1); 759 } 760 } 761 762 // jQuery validate validator object 763 this.validator = null; 764 // Flag to avoid recursion between checks inside enableSubmitButton and handlers that call it 765 this.enableButtonPending = false;
766 this.debug = false; 767 } 768 769 /** 770 * Returns the portvar namespace 771 */ 772 AcTramiteStep.prototype.getNmsp = function() { 773 return this.config.nmsp; 774 } 775 776 AcTramiteStep.prototype.getName = function () { 777 return this.name; 778 } 779 780 /** 781 * Returns the validator object returned by jQuery.validate 782 * 783 * @see https://jqueryvalidation.org/documentation/ 784 * @example 785 * Validator.form() – Validates the form. 786 * Validator.element() – Validates a single element. 787 * Validator.resetForm() – Resets the controlled form. 788 * Validator.showErrors() – Show the specified messages. 789 * Validator.numberOfInvalids() – Returns the number of invalid fields. 790 * Validator.destroy() – Destroys this instance of validator. 791 * 792 */ 793 AcTramiteStep.prototype.getValidator = function() { 794 return this.validator; 795 } 796 797 /** 798 * 799 */ 800 AcTramiteStep.prototype._getElement = function(type) { 801 var elem = null; 802 803 if (type in this.config.selectors) { 804 elem = jQuery(this.config.selectors[type]); 805 806 if (typeof elem === "undefined" || !elem.length) { 807 typeof window.console === "object" 808 && console.error("No element found by selector type " + type + ": " + this.config.selectors[type]); 809 } 810 } else { 811 typeof window.console === "object" 812 && console.error("Invalid selector type " + type); 813 } 814 815 return elem; 816 } 817 818 AcTramiteStep.prototype.getSectionElement = function() { 819 return this._getElement("section"); 820 } 821 822 /** 823 * Gets the tramite form jQuery object 824 */ 825 AcTramiteStep.prototype.getForm = function() { 826 return this._getElement("form"); 827 } 828 829 /** 830 * Gets the DOM element of a form input by name 831 */ 832 AcTramiteStep.prototype.getFormElement = function (elemName) { 833 if (typeof elemName !== "string" || !elemName) { 834 return null; 835 } 836 837 return this.getForm().find('[name="' + elemName + '"]'); 838 } 839 840 /** 841 * Already valid elements 842 */ 843 AcTramiteStep.prototype.getValidElements = function () { 844 return this._getValidationElements("valid"); 845 } 846 847 /**
848 * Already invalid elements 849 */ 850 AcTramiteStep.prototype.getInvalidElements = function () { 851 return this._getValidationElements("invalid"); 852 } 853 854 /** 855 * Elements marked as required but not validated 856 */ 857 AcTramiteStep.prototype.getMissingElements = function () { 858 return this._getValidationElements("missing"); 859 } 860 861 /** 862 * Elements that haven't been validated whether they are required or not 863 */ 864 AcTramiteStep.prototype.getAllElements = function () { 865 return this._getValidationElements("all"); 866 } 867 868 AcTramiteStep.prototype._getValidationElements = function (validFlag) { 869 870 var filterType = ""; 871 872 if (validFlag === true || validFlag === "true") { 873 // Valid if not invalid 874 filterType = "valid"; 875 } else if (validFlag === false || validFlag === "false") { 876 // Invalid if not valid 877 filterType = "invalid"; 878 } else if (validFlag === "missing") { 879 // Elements required but not already filled but they are also not marked as invalid 880 filterType = "missing"; 881 } else if (validFlag === undefined || validFlag === "not-validated") {
882 // Elements not validated whether already invalid or not 883 filterType = "not-validated"; 884 } else if (validFlag === "validated") { 885 // Elements not validated whether already invalid or not 886 filterType = "not-validated"; 887 } else { 888 // all, no selector 889 filterType = "all"; 890 } 891 892 var result = this.getValidator().elements(); 893 var $this = this; 894 895 if (filterType !== "all") { 896 result = result.filter(function(pos, elem){ 897 return $this._is(jQuery(elem), filterType); 898 }); 899 } 900 901 return result; 902 } 903 904 AcTramiteStep.prototype.getElementsSummary = function() { 905 var summary = { 906 total: 0, 907 required: 0, 908 missing: 0, 909 validated: 0, 910 notValidated: 0, 911 valid: 0, 912 invalid: 0 913 }; 914 915 var $this = this; 916 917 this.getValidator().elements().each(function(pos, elem){ 918 var jElem = jQuery(elem); 919 summary.total++; 920 summary.required += jElem.is(':required') ? 1: 0; 921 summary.missing += $this._is(jElem, "missing") ? 1 : 0; 922 summary.validated += $this._is(jElem, "validated") ? 1 : 0; 923 summary.notValidated += $this._is(jElem, "not-validated") ? 1 : 0; 924 summary.valid += $this._is(jElem, "valid") ? 1 : 0; 925 summary.invalid += $this._is(jElem, "invalid") ? 1 : 0; 926 }); 927 928 return summary; 929 } 930 931 AcTramiteStep.prototype._is = function(jElem, filterType) { 932 if (jElem.is(":hidden") || jElem.prop("readonly")) { 933 return false; 934 } 935 936 switch (filterType) { 937 case "valid": 938 return jElem.is('[data-ov-validation="valid"]'); 939 case "invalid": 940 return jElem.is('[data-ov-validation="invalid"]'); 941 case "missing": 942 return this.validator.isMissing(jElem[0]); 943 case "not-validated": 944 return jElem.is('[data-ov-validation="pending"]'); 945 case "validated": 946 return jElem.is(':not([data-ov-validation="pending"])'); 947 default: 948 return true; 949 } 950 } 951 952 /** 953 * Gets if the current form is valid and can be submitted 954 * 955 * Checks if all required fields are filled and valid and that no invalid fields have been found. 956 */ 957 AcTramiteStep.prototype.isValid = function() { 958 var summary = this.getElementsSummary(); 959 960 var formIsValid = summary.missing === 0 && summary.invalid === 0; 961 962 if (this.debug) { 963 typeof window.console === "object" 964 && console.debug("isValid::" + formIsValid, summary) 965 && console.debug(this.getForm().find(':focus').first()); 966 } 967 968 return formIsValid; 969 } 970 971 /** 972 * Gets the tramite submit button jQuery object or null 973 */ 974 AcTramiteStep.prototype.getSubmitButton = function(form) { 975 form = form || this.getForm(); 976 return form.find('.btn-tramite[type="submit"]'); 977 } 978 979 AcTramiteStep.prototype._delayedEnableSubmitButton = function(enable, form) { 980 var $this = this; 981 982 if (this._delayedEnableSubmitButtonTimeout !== null) { 983 clearTimeout(this._delayedEnableSubmitButtonTimeout); 984 this._delayedEnableSubmitButtonTimeout = null; 985 } 986 987 this._delayedEnableSubmitButtonTimeout = setTimeout(function(){ 988 this._delayedEnableSubmitButtonTimeout = null; 989 $this.enableSubmitButton(enable, form); 990 }, 50); 991 } 992 993 AcTramiteStep.prototype.enableSubmitButton = function(enable, form) { 994 if (typeof enable === "undefined" || enable === null) { 995 enable = this.isValid(); 996 } 997 998 var propValue = typeof enable === "boolean" && enable; 999 this.getSubmitButton(form).prop("disabled", !propValue); 1000 } 1001 1002 AcTramiteStep.prototype.hideErrorsBlock = function(time) { 1003 if (this.getInvalidElements().length === 0 1004 && jQuery('#errores-formulario').is(":visible")) { 1005 // No invalids, errors shown, then clear them. 1006 jQuery('#errores-formulario').hide(time || 800); 1007 } 1008 } 1009 1010 /** 1011 * Gets the current step key 1012 */ 1013 AcTramiteStep.prototype.getCurrentStep = function() { 1014 var section = this.getSectionElement(); 1015 var data = section.attr("data-tramite-step"); 1016 1017 if (typeof data === "string" && data.trim()) { 1018 return data.trim(); 1019 } 1020 1021 return ""; 1022 } 1023 1024 /** 1025 * Gets the name of the current step 1026 */ 1027 AcTramiteStep.prototype.getCurrentStepName = function() { 1028 var step = this.getCurrentStep(); 1029 var name = ""; 1030 1031 if (step) { 1032 var parts = step.split("-"); 1033 for (var index = 0; index < parts.length; index++) { 1034 if (index == 0) { 1035 name += parts[index]; 1036 } else { 1037 name += parts[index][0].toUpperCase() 1038 + parts[index].substr(1); 1039 } 1040 } 1041 } 1042 1043 return name; 1044 } 1045 1046 /** 1047 * Returns default validation object to configure jQuery validate plugin. 1048 */ 1049 AcTramiteStep.prototype.getDefaultValidation = function() { 1050 var $this = this; 1051 1052 return { 1053 errorClass: "has-error", 1054 validClass: "is-valid", 1055 success: "is-valid", 1056 errorElement: "span", 1057 errorPlacement: function(error, element) { 1058 jQuery(element) 1059 .closest(".ov-form-field") 1060 .find(".field-error") 1061 .empty() 1062 .append(error); 1063 }, 1064 highlight: function(element, errorClass, validClass) { 1065 jQuery(element) 1066 .closest(".ov-form-field") 1067 .addClass(errorClass) 1068 .removeClass(validClass); 1069 }, 1070 unhighlight: function(element, errorClass, validClass) { 1071 jQuery(element) 1072 .closest(".ov-form-field") 1073 .removeClass(errorClass) 1074 .addClass(validClass); 1075 }, 1076 invalidHandler: function(event, validator) { 1077 // 'this' refers to the form 1078 $this.enableSubmitButton(false); 1079 }, 1080 afterElementValidated: function (element, valid) { 1081 $this._delayedEnableSubmitButton(); 1082 } 1083 } 1084 } 1085 1086 AcTramiteStep.prototype.destroyValidator = function() { 1087 if (this.validator != null) {
1088 var validator = this.validator; 1089 this.validator = null; 1090 validator.destroy(); 1091 } 1092 } 1093 1094 AcTramiteStep.prototype.getOrCreateWaitLoad = function() { 1095 var section = this.getSectionElement(); 1096 var wait = section.find("#wait-load"); 1097 1098 if (wait && wait.length === 0) { 1099 wait = section.find(".spinner.wait-load"); 1100 } 1101 1102 if (!wait || wait.length === 0) { 1103 wait = jQuery('<div class="spinner wait-load" style="display: none;"><div class="double-bounce1"></div><div class="double-bounce2"></div></div>'); 1104 section.append(wait); 1105 } 1106 1107 return wait; 1108 } 1109 1110 AcTramiteStep.prototype.disableUserInteraction = function(focus, disableAllInputs) { 1111 var wait = this.getOrCreateWaitLoad(); 1112 1113 if (wait && wait.length === 1 && !wait.is(':visible')) { 1114 wait.show(); 1115 if (focus) { 1116 wait.attr("tabindex", 0)[0].scrollIntoView() 1117 } 1118 } else { 1119 return; 1120 } 1121 1122 var form = this.getForm(); 1123 form.attr("data-ov-disabled", "true"); 1124 1125 if (disableAllInputs) { 1126 form.find("input:not(:hidden)").each(function(pos, elem){ 1127 var jElem = jQuery(elem); 1128 if (!jElem.prop("disabled")) { 1129 jElem.attr("data-ov-disabled", 'true'); 1130 jElem.prop("disabled", true); 1131 } 1132 }); 1133 } 1134 1135 form.find('button[type="submit"]').each(function(pos, elem){ 1136 var jElem = jQuery(elem); 1137 var disabledPrev = jElem.prop('disabled') === true; 1138 jElem.attr("data-ov-disabled-prev", disabledPrev); 1139 jElem.prop('disabled', true); 1140 }); 1141 } 1142 1143 AcTramiteStep.prototype.enableUserInteraction = function() { 1144 var section = this.getSectionElement(); 1145 var wait = section.find("#wait-load"); 1146 1147 if (wait && wait.length === 0) { 1148 wait = section.find(".spinner.wait-load"); 1149 } 1150 1151 if (wait && wait.length === 1 && wait.is(':visible')) { 1152 wait.hide(); 1153 } 1154 1155 var form = this.getForm(); 1156 form.removeAttr("data-ov-disabled"); 1157 1158 form.find('[data-ov-disabled="true"]').each(function(pos, elem){ 1159 jQuery(elem).removeAttr("data-ov-disabled").prop('disabled', false); 1160 }); 1161 1162 form.find('button[type="submit"]').each(function(pos, elem){ 1163 var jElem = jQuery(elem); 1164 var disabledPrev = jElem.attr('data-ov-disabled-prev') === 'true'; 1165 jElem.removeAttr('data-ov-disabled-prev'); 1166 1167 if (!disabledPrev) { 1168 jElem.prop('disabled', false); 1169 } 1170 }); 1171 } 1172 1173 /** 1174 * Initializes generic validation with jQuery validate plugin 1175 * 1176 * Merges the customValidation argument over the default validation and uses it 1177 * 1178 */ 1179 AcTramiteStep.prototype.initValidation = function(customValidation) { 1180 1181 if (this.validator != null) { 1182 return; 1183 } 1184 1185 customValidation = customValidation || {}; 1186 var defaultValidation = this.getDefaultValidation(); 1187 var validation = jQuery.extend({}, defaultValidation, this.config.validation, customValidation); 1188 1189 var form = this.getForm(); 1190 this.validator = form.validate(validation); 1191 var $this = this; 1192 1193 // Handle change on elements that toggle parts of the form and revalidate those parts 1194 form.find('input[data-ov-toggle-visibility], select[data-ov-toggle-visibility], textarea[data-ov-toggle-visibility]').change(function(ev){ 1195 $this.toggleVisibility(this); 1196 }); 1197 1198 // Force a change to set initial state 1199 jQuery("[data-ov-toggle-visibility]").change(); 1200 1201 // After initializing validate the form if some data is present and hide/show the button 1202 if (this.validator.elementsFilled().length > 0) { 1203 var valid = form.valid(); 1204 1205 this.validator.showErrors(); 1206 1207 this.enableSubmitButton(valid, form); 1208 } else { 1209 this.validator.initElements(); 1210 } 1211 } 1212 1213 AcTramiteStep.prototype.toggleVisibility = function(elem) { 1214 var jElem = jQuery(elem); 1215 var toggleSource = jElem.closest(".row--panel-toggle-source"); 1216 var visible = jElem.is(":checked"); 1217 1218 if (visible) { 1219 toggleSource.addClass("visible"); 1220 } else { 1221 toggleSource.removeClass("visible"); 1222 } 1223 1224 var targets = jElem.attr('data-ov-toggle-visibility').split(' '); 1225 1226 for (var index = 0; index < targets.length; index++) { 1227 var targetId = targets[index]; 1228 // Search only under the form element 1229 var target = this.getForm().find('[id="' + targetId + '"]').first(); 1230 1231 if (!visible) { 1232 this.hideToggleTarget(target); 1233 } else { 1234 this.showToggleTarget(target); 1235 } 1236 } 1237 } 1238 1239 AcTramiteStep.prototype.getTargetElements = function(target) { 1240 return jQuery(target) 1241 .find('input, select, textarea') 1242 .not(':submit, :reset, :image, :disabled, [type="hidden"], [readonly]') 1243 ; 1244 } 1245 1246 AcTramiteStep.prototype.hideToggleTarget = function(target) { 1247 var $this = this; 1248 1249 target.hide("fast", "swing", function() { 1250 $this.validator.resetElements($this.getTargetElements(target)); 1251 $this._delayedEnableSubmitButton(); 1252 }); 1253 } 1254 1255 AcTramiteStep.prototype.showToggleTarget = function(target) { 1256 var $this = this; 1257 1258 target.show(400, "swing", function() { 1259 // Validate shown elements only if at least one is filled 1260 var enableValidation = false;
1261 1262 var elements = $this.getTargetElements(target); 1263 1264 for (var index = 0; index < elements.length; index++) { 1265 var jElem = jQuery(elements[index]); 1266 1267 if (jElem.is(":visible") && !jElem.is(":blank")) { 1268 enableValidation = true; 1269 break; 1270 } 1271 } 1272 1273 if (enableValidation) { 1274 elements.each(function(pos, targetElement){ 1275 // Validate on show only those visible inputs with content or marked as invalid 1276 $this.validator.element(targetElement); 1277 }); 1278 } else { 1279 $this.validator.resetElements(elements); 1280 } 1281 1282 $this._delayedEnableSubmitButton(); 1283 }); 1284 } 1285} 1286 1287 1288if (typeof AcTramite === 'undefined') { 1289 function AcTramite(config) { 1290 this.steps = {}; 1291 this.msg = {}; 1292 this.funcs = {}; 1293 1294 var defaultConfig = { 1295 nmsp: "", 1296 name: "tramite-generic", 1297 defaultStep: "default-step" 1298 }; 1299 1300 this.config = jQuery.extend({}, defaultConfig, config || {}); 1301 } 1302 1303 AcTramite.prototype.getCurrentStep = function() { 1304 var currentStep = jQuery('.ac-tramite.tramite-' + this.config.name + ' .tramite-wrapper').attr('data-tramite-step'); 1305 1306 if (!currentStep) { 1307 currentStep = this.config.defaultStep; 1308 } 1309 1310 return currentStep; 1311 }; 1312 1313 AcTramite.prototype.createStep = function(stepConfig) { 1314 var config = stepConfig || {}; 1315 1316 var defaultConfig = { 1317 nmsp: this.config.nmsp, 1318 }; 1319 1320 var finalConfig = jQuery.extend(defaultConfig, config); 1321 var step = new AcTramiteStep(finalConfig); 1322 1323 return step; 1324 } 1325 1326 AcTramite.prototype.init = function () { 1327 var currentStep = this.getCurrentStep(); 1328 1329 if (typeof this.funcs['before-init'] === "function") { 1330 this.funcs['before-init'](currentStep); 1331 } 1332 1333 if (typeof this.funcs['init-' + currentStep]) { 1334 this.funcs['init-' + currentStep](); 1335 } 1336 1337 if (typeof this.funcs['after-init']) { 1338 this.funcs['after-init'](currentStep); 1339 } 1340 } 1341 1342 AcTramite.prototype.initStep = function (stepName) { 1343 1344 }; 1345 1346} 1347 1348 1349if (typeof AcValidations === "undefined") { 1350 function AcValidations() { 1351 1352 } 1353 1354 AcValidations.validateFileNotEmptyAndMime = function (file, minSize) { 1355 if (!file) { 1356 return true; 1357 } 1358 1359 var safeMinSize = parseInt(minSize); 1360 if (isNaN(safeMinSize) || safeMinSize < 0) { 1361 safeMinSize = 0; 1362 } 1363 1364 return file.size > safeMinSize 1365 && file.type !== "" 1366 && file.name.indexOf(".") > 0; 1367 } 1368 1369 AcValidations.validateFileSize = function (file, maximumSize) { 1370 var fileSizeLimit = parseInt(maximumSize); 1371 1372 if (!file || isNaN(fileSizeLimit) || fileSizeLimit <= 0) { 1373 return true; 1374 } 1375 1376 return file.size <= fileSizeLimit; 1377 } 1378 1379 AcValidations.validateFileAccept = function (file, accept) { 1380 if (!file || (typeof accept === "string" && accept.trim() === "") 1381 || (accept instanceof Array && accept.length === 0)) { 1382 return true; 1383 } 1384 1385 var expressions = null; 1386 1387 if (typeof accept === "string") { 1388 expressions = accept.split(","); 1389 } else if (accept instanceof Array) { 1390 expressions = accept; 1391 } else { 1392 return true; 1393 } 1394 1395 var fileExt = file.name.toLowerCase().substring(file.name.lastIndexOf(".")); 1396 var fileType = file.type.toLowerCase(); 1397 1398 for (var index = 0; index < expressions.length; index++) { 1399 var expression = expressions[index].trim().toLowerCase(); 1400 1401 if (expression.length === 0) { 1402 continue; 1403 } 1404 1405 var checkTypeEnds = false; 1406 1407 if (expression.endsWith("*")) { 1408 expression = expression.substring(0, expression.length - 1); 1409 checkTypeEnds = true; 1410 } 1411 1412 if (expression === fileExt 1413 || (!checkTypeEnds && fileType === expression) 1414 || (checkTypeEnds && fileType.startsWith(expression))) { 1415 return true; 1416 } 1417 } 1418 1419 return false; 1420 } 1421} 1422
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.