1// The only change in this file is the change of listening to DOMContentLoaded to BaselineLoaded. 2// In the future, please make sure this file is updated and then listen to the correct event. 3// Get a new version of this file at: 4// https://localhost:44389/App_Plugins/UmbracoForms/Assets/themes/default/umbracoforms.js?v=13.5.0 (update the version number) 5 6(function () { 7 // polyfill for matches and closest 8 if (!Element.prototype.matches) 9 Element.prototype.matches = Element.prototype.msMatchesSelector; 10 if (!Element.prototype.closest) 11 Element.prototype.closest = function (selector) { 12 var el = this; 13 while (el) { 14 if (el.matches(selector)) { 15 return el; 16 } 17 el = el.parentElement; 18 } 19 }; 20 21 //execute init() on document ready 22 document.addEventListener("BaselineLoaded", listen); 23 24 function listen() { 25 // Inline script setting umbracoFormsCollection was removed in 8.11/9.3, and replaced by config elements. 26 // So if we have them, use it, otherwise fall-back to legacy method. 27 var configElements = document.getElementsByClassName( 28 "umbraco-forms-form-config" 29 ); 30 if (configElements.length > 0) { 31 var umbracoFormsCollectionFromConfig = []; 32 for (var i = 0; i < configElements.length; i++) { 33 var configElement = configElements[i]; 34 var form = { 35 formId: configElement.dataset.id, 36 pageButtonConditions: JSON.parse( 37 configElement.dataset.serializedPageButtonConditions 38 ), 39 fieldSetConditions: JSON.parse( 40 configElement.dataset.serializedFieldsetConditions 41 ), 42 fieldConditions: JSON.parse( 43 configElement.dataset.serializedFieldConditions 44 ), 45 triggerConditionsCheckOn: 46 configElement.dataset.triggerConditionsCheckOn, 47 disableValidationDependencyCheck: 48 configElement.dataset.disableValidationDependencyCheck === "true", 49 recordValues: JSON.parse( 50 configElement.dataset.serializedFieldsNotDisplayed 51 ), 52 formElementHtmlIdPrefix: 53 configElement.dataset.formElementHtmlIdPrefix, 54 validationRules: configElement.dataset.serializedValidationRules 55 ? JSON.parse(configElement.dataset.serializedValidationRules) 56 : [], 57 }; 58 umbracoFormsCollectionFromConfig.push(form); 59 } 60 initCollection(umbracoFormsCollectionFromConfig, true); // Set the 'jsonParsed' flag, indicating that we already have a JSON structure, which we don't have with the legacy, inline script method. 61 } else { 62 if (typeof umbracoFormsCollection === "undefined") { 63 //this will occur if this js file is loaded before the inline scripts, in which case 64 //we'll listen for the inline scripts to execute a custom event. 65 document.addEventListener("umbracoFormLoaded", init); 66 } else { 67 initCollection(umbracoFormsCollection); 68 } 69 } 70 71 // Scrolling to the submitted form was also moved from an inline script in 8.11/9.3. 72 var submittedFormElement = document.getElementById( 73 "umbraco-forms-form-submitted" 74 ); 75 if (submittedFormElement) { 76 var formClientId = submittedFormElement.dataset.formClientId; 77 scrollToSubmittedForm(formClientId); 78 } 79 } 80 81 function initCollection(formsCollection, jsonParsed) { 82 configureUmbracoFormsValidation(formsCollection); 83 84 for (var i = 0; i < formsCollection.length; i++) { 85 init({ form: formsCollection[i] }, jsonParsed); 86 } 87 } 88 89 function init(e, jsonParsed) { 90 var formItem = jsonParsed ? e.form : JSON.parse(decodeURI(e.form)); 91 92 var forms = document.querySelectorAll(".umbraco-forms-form"); 93 94 for (var i = 0; i < forms.length; i++) { 95 var form = forms[i]; 96 97 if (!formItem.disableValidationDependencyCheck) { 98 dependencyCheck(form); 99 } 100 101 var page = form.querySelector(".umbraco-forms-page"); 102 var conditions = new UmbracoFormsConditions( 103 page, 104 formItem.pageButtonConditions, 105 formItem.fieldSetConditions, 106 formItem.fieldConditions, 107 formItem.triggerConditionsCheckOn && 108 formItem.triggerConditionsCheckOn.length > 0 109 ? formItem.triggerConditionsCheckOn 110 : "change", 111 formItem.recordValues, 112 formItem.formElementHtmlIdPrefix 113 ); 114 conditions.watch(); 115 116 applyFormAccessibility(form); 117 } 118 } 119 120 function scrollToSubmittedForm(formClientId) { 121 document.onreadystatechange = function () { 122 if (document.readyState == "complete") { 123 // We have various cases where we want to scroll to the form rendered on the page. 124 // - When we have a server-side validation error, we should scroll to the first error element (which class 'field-validation-error'). 125 // - When the form has been submitted, we should scroll to the displayed message (which will have class 'umbraco-forms-submitmessage' or 'umbraco-forms-submitmessage-html'). 126 // - When navigating back and forth on a multi-page form, we should scroll to the start of the form page (which will have an id of #umbraco_form_[form client id]).
127 var validationsMessageElements = document.querySelectorAll( 128 ".field-validation-error" 129 ); 130 var messageOnSubmitElement = document.querySelectorAll( 131 ".umbraco-forms-submitmessage" 132 ); 133 var messageOnSubmitHtmlElement = document.querySelectorAll( 134 ".umbraco-forms-submitmessage-html" 135 ); 136 var submittedFormElement = document.getElementById( 137 "umbraco_form_" + formClientId 138 ); 139 if (validationsMessageElements.length > 0) { 140 scrollToFirstFieldWithValidationError(validationsMessageElements); 141 } else if (messageOnSubmitElement.length > 0) { 142 scrollElementIntoView(messageOnSubmitElement[0].parentElement); 143 } else if (messageOnSubmitHtmlElement.length > 0) { 144 scrollElementIntoView(messageOnSubmitHtmlElement[0]); 145 } else if (submittedFormElement) { 146 scrollElementIntoView(submittedFormElement); 147 } 148 } 149 }; 150 151 function scrollToFirstFieldWithValidationError(validationsMessageElements) { 152 var scrollY = 0; 153 for (var i = 0; i < validationsMessageElements.length; i++) { 154 var node = getFormFieldElement(validationsMessageElements[i]); 155 var offset = node.getBoundingClientRect().top; 156 if (0 < offset && (offset < scrollY || scrollY === 0)) { 157 scrollY = offset; 158 } 159 } 160 161 if (scrollY > 0) { 162 window.scrollTo(0, scrollY); 163 } 164 } 165 166 function scrollElementIntoView(element) { 167 element.scrollIntoView({ 168 behavior: "auto", 169 block: "start", 170 }); 171 } 172 173 function getFormFieldElement(node) { 174 var runner = node; 175 while (runner.tagName !== "BODY") { 176 if (runner.classList.contains("umbraco-forms-field")) { 177 return runner; 178 } 179 180 runner = runner.parentNode; 181 } 182 183 return node; 184 } 185 } 186 187 /** Configures the jquery validation for Umbraco forms */ 188 function configureUmbracoFormsValidation(formsCollection) { 189 var submitInputs, input; 190 191 if (window.aspnetValidation !== undefined) { 192 // Asp-net validation setup: 193 194 var validationService = new aspnetValidation.ValidationService(); 195 var required = function (value, element) { 196 // Handle single and multiple checkboxes: 197 if ( 198 element.type.toLowerCase() === "checkbox" || 199 element.type.toLowerCase() === "radio" 200 ) { 201 var allCheckboxesOfThisName = element.form.querySelectorAll( 202 "input[name='" + element.name + "']" 203 ); 204 for (var i = 0; i < allCheckboxesOfThisName.length; i++) { 205 if (allCheckboxesOfThisName[i].checked === true) { 206 return true; 207 } 208 } 209 return false; 210 } 211 return Boolean(value); 212 }; 213 validationService.addProvider("requiredcb", required); 214 validationService.addProvider("required", required); // this will go instead of the build-in required. 215 216 var umbracoforms_regex = function (value, element, params) { 217 if (!value || !params.pattern) { 218 return true; 219 } 220 221 var r = new RegExp(params.pattern); 222 return r.test(value); 223 }; 224 validationService.addProvider("umbracoforms_regex", umbracoforms_regex); 225 226 var wrapProviderWithIgnorerBehaviour = function (provider) { 227 return function (value, element, params) { 228 // If field is hidden we ignore the validation. 229 if (element.offsetParent === null) { 230 return true; 231 } 232 233 return provider(value, element, params); 234 }; 235 }; 236 237 // we can only incept with default validator if we do it after bootstrapping but before window load event triggers validationservice. 238 window.addEventListener("load", function () { 239 // Wrap all providers with ignorer hidden fields logic: 240 for (var key in validationService.providers) { 241 validationService.providers[key] = wrapProviderWithIgnorerBehaviour( 242 validationService.providers[key] 243 ); 244 } 245 }); 246 247 // bootstrap validation service. 248 validationService.bootstrap(); 249 250 // make validation service availalble globally for custom client-side code. 251 window.umbracoFormsValidationService = validationService; 252
253 // Without jquery validation, the previous page submit button click isn't sent with it's name, 254 // so can't be used server-side to determine whether to go forward or back. 255 // Hence we'll use an alternate method, setting a hidden field that's also used in the check. 256 var handlePreviousClicked = function () { 257 this.form.elements["PreviousClicked"].value = "clicked"; 258 }; 259 260 // - previous buttons are indicated by a data attribute of data-umb="prev-forms-form", introduced in 8.12/9.4. 261 var previousButtonElements = document.querySelectorAll( 262 "[data-umb='prev-forms-form']" 263 ); 264 265 // - prior to that classes were used to identify them, so for compatibility with custom themes, we'll check for those too 266 // if we haven't found any via the data attribute. 267 if (previousButtonElements.length === 0) { 268 previousButtonElements = document.getElementsByClassName("prev cancel"); 269 } 270 271 for (var i = 0; i < previousButtonElements.length; i++) { 272 previousButtonElements[i].form.elements["PreviousClicked"].value = ""; 273 previousButtonElements[i].addEventListener( 274 "click", 275 handlePreviousClicked, 276 false 277 ); 278 } 279 280 // Apply protection for multiple form submissions using the aspnet validation validator. 281 submitInputs = getSubmitInputs(); 282 for (let i = 0; i < submitInputs.length; i++) { 283 input = submitInputs[i]; 284 input.addEventListener("click", function (e) { 285 e.preventDefault(); 286 var frm = e.currentTarget.form; 287 var validationRules = getValidationRulesForForm(frm, formsCollection); 288 validationService.validateForm(frm, (valid) => { 289 var formRulesValid = validateFormRules( 290 frm, 291 validationRules, 292 "aspnet-client-validation", 293 validationService 294 ); 295 valid = valid && formRulesValid; 296 if (valid) { 297 submitValidatedForm(frm); 298 var submitInputsForForm = frm.querySelectorAll( 299 "[type=submit]:not(.cancel)" 300 ); 301 for (let j = 0; j < submitInputsForForm.length; j++) { 302 submitInputsForForm[j].setAttribute("disabled", "disabled"); 303 } 304 } 305 }); 306 }); 307 } 308 } else if ( 309 typeof jQuery === "function" && 310 $.validator && 311 $.validator.unobtrusive 312 ) { 313 //Jquery validation setup 314 315 $.validator.setDefaults({ 316 ignore: ":hidden", 317 }); 318 319 $.validator.unobtrusive.adapters.addBool("requiredcb", "required"); 320 321 $.validator.addMethod("umbracoforms_regex", function (value, element) { 322 var regex = $(element).attr("data-regex"); 323 var val = $(element).val(); 324 if (val.length === 0) { 325 return true; 326 } 327 return val.match(regex); 328 }); 329 330 $.validator.unobtrusive.adapters.addBool("regex", "umbracoforms_regex"); 331 332 // Apply protection for multiple form submissions using the jquery validator. 333 submitInputs = getSubmitInputs(); 334 for (let i = 0; i < submitInputs.length; i++) { 335 input = submitInputs[i]; 336 input.addEventListener( 337 "click", 338 function (evt) { 339 evt.preventDefault(); 340 var frm = $(this).closest("form"); 341 resetValidationMessages(frm[0]); 342 var validator = frm.validate(); 343 var validationRules = getValidationRulesForForm( 344 frm[0], 345 formsCollection 346 ); 347 var formValid = frm.valid(); 348 var formRulesValid = validateFormRules( 349 frm[0], 350 validationRules, 351 "jquery", 352 validator 353 ); 354 if (formValid && formRulesValid) { 355 submitValidatedForm(frm); 356 this.setAttribute("disabled", "disabled"); 357 } 358 }.bind(input) 359 ); 360 } 361 } 362 } 363 364 function getSubmitInputs() { 365 return document.querySelectorAll( 366 ".umbraco-forms-form [type=submit]:not(.cancel)" 367 ); 368 } 369 370 function getValidationRulesForForm(formElement, formsCollection) { 371 var filteredForms = formsCollection.filter( 372 (f) => f.formId === formElement["FormId"].value.replaceAll("-", "") 373 ); 374 if (filteredForms.length > 0 && filteredForms[0].validationRules) { 375 // Work on a clone of the rules, so we don't modify in the originals and can still access the definitions that refer to 376 // field names. 377 var clonedRules = structuredClone(filteredForms[0].validationRules); 378 var formData = new FormData(formElement); 379 return clonedRules.map((r) => getRuleWithFieldValues(r, formData)); 380 } 381 382 return []; 383 } 384 385 function getRuleWithFieldValues(validationRule, formData) { 386 var replacedRule = validationRule.rule; 387 for (var item of formData) { 388 replacedRule = replacedRule.replaceAll("{" + item[0] + "}", item[1]); 389 } 390 validationRule.rule = replacedRule; 391 return validationRule; 392 } 393 394 function validateFormRules( 395 formElement, 396 validationRules, 397 validationLibrary, 398 validator 399 ) { 400 if (typeof jsonLogic === "undefined") { 401 // If we don't have the dependency, form rules aren't validated. 402 return true; 403 } 404 405 var rulesResult = true; 406 for (var i = 0; i < validationRules.length; i++) {
407 var validationRule = validationRules[i]; 408 409 // Skip if the rule contains unresolved field references. 410 var re = /{({*[\w]*}*)}/; 411 if (validationRule.rule.match(re)) { 412 continue; 413 } 414 415 var ruleAsJson; 416 try { 417 ruleAsJson = JSON.parse(validationRule.rule); 418 } catch (e) { 419 continue; // Skip also on invalid JSON. 420 } 421 422 var ruleResult = jsonLogic.apply(ruleAsJson); 423 indicateFormRuleValidation( 424 formElement, 425 validationLibrary, 426 validator, 427 ruleResult, 428 validationRule 429 ); 430 rulesResult = rulesResult && ruleResult; 431 } 432 433 return rulesResult; 434 } 435 436 function indicateFormRuleValidation( 437 formElement, 438 validationLibrary, 439 validator, 440 result, 441 validationRule 442 ) { 443 if (validationRule.fieldId) { 444 // Show or hide field error dependending on result. 445 var inputElement = formElement[validationRule.fieldId]; 446 447 // If we have more than one element, e.g. for the date picker, use the one also matching the id. 448 if (inputElement.length && inputElement.length > 0) { 449 inputElement = Array.from(inputElement).filter( 450 (e) => e.id === validationRule.fieldId 451 )[0]; 452 } 453 454 if (validationLibrary === "aspnet-client-validation") { 455 if (result) { 456 // Only remove if it's the same message that we've added (could be other validation rules on the field we would want to keep). 457 var validationFieldMessageElement = getValidationFieldMessageElement( 458 inputElement, 459 "error" 460 ); 461 if ( 462 validationFieldMessageElement && 463 validationFieldMessageElement.innerText === 464 validationRule.errorMessage 465 ) { 466 validator.removeError(inputElement); 467 } 468 } else { 469 validator.addError(inputElement, validationRule.errorMessage); 470 } 471 }
471 else if (validationLibrary === "jquery") { 472 var validationSummaryElement = formElement.querySelector( 473 ".validation-summary-errors ul" 474 ); 475 if (result) { 476 var validationFieldMessageElement = getValidationFieldMessageElement( 477 inputElement, 478 "error" 479 ); 480 481 // Only remove if it's the same message that we've added (could be other validation rules on the field we would want to keep). 482 if ( 483 validationFieldMessageElement && 484 validationFieldMessageElement.innerText === 485 validationRule.errorMessage 486 ) { 487 validationFieldMessageElement.classList.remove( 488 "field-validation-error" 489 ); 490 validationFieldMessageElement.classList.add( 491 "field-validation-valid" 492 ); 493 validationFieldMessageElement.innerText = ""; 494 } 495 496 removeValidationSummaryMessage( 497 validationSummaryElement, 498 validationRule.errorMessage 499 ); 500 } else { 501 var validationFieldMessageElement = getValidationFieldMessageElement( 502 inputElement, 503 "valid" 504 ); 505 if (validationFieldMessageElement) { 506 validationFieldMessageElement.classList.remove( 507 "field-validation-valid" 508 ); 509 validationFieldMessageElement.classList.add( 510 "field-validation-error" 511 ); 512 validationFieldMessageElement.innerText = 513 validationRule.errorMessage; 514 } 515 516 addValidationSummaryMessage( 517 validationSummaryElement, 518 validationRule.errorMessage 519 ); 520 } 521 } 522 } 523 } 524 525 function getValidationFieldMessageElement(inputElement, state) { 526 return inputElement 527 .closest(".umbraco-forms-field-wrapper") 528 .querySelector(".field-validation-" + state); 529 } 530 531 function removeValidationSummaryMessage( 532 validationSummaryElement, 533 errorMessage 534 ) { 535 if (validationSummaryElement) { 536 validationSummaryElement.querySelectorAll("li").forEach((li) => { 537 if (li.innerText === errorMessage) { 538 li.remove(); 539 } 540 }); 541 } 542 } 543 544 function addValidationSummaryMessage(validationSummaryElement, errorMessage) { 545 removeValidationSummaryMessage(validationSummaryElement, errorMessage); 546 if (validationSummaryElement) { 547 var validationSummaryItemElement = document.createElement("li"); 548 validationSummaryItemElement.appendChild( 549 document.createTextNode(validationRule.errorMessage) 550 ); 551 validationSummaryElement.appendChild(validationSummaryItemElement); 552 } 553 } 554 555 function submitValidatedForm(form) { 556 // Use requestSubmit if available (see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit#usage_notes) 557 if (form.requestSubmit) { 558 form.requestSubmit(); 559 } else { 560 form.submit(); 561 } 562 } 563 564 /** 565 * method to determine if Umbraco Forms can run and has the required dependencies loaded 566 * @param {Form Element} formEl the element of the form 567 */ 568 function dependencyCheck(formEl) { 569 // Only perform check if the global 'Umbraco.Sys' is null/undefined. 570 // If present means we are in backoffice & that this is being rendered as a macro preview and we do not need to perform this check here. 571 // Similarly we need a check for if running in a rich text editor. 572 var isBackOffice = function () { 573 return ( 574 typeof Umbraco !== "undefined" && typeof Umbraco.Sys !== "undefined" 575 ); 576 }; 577 var isBackOfficeRte = function () { 578 return document.body.id === "tinymce"; 579 }; 580 581 if (isBackOffice() || isBackOfficeRte()) { 582 return; 583 } 584 585 var errorElement = document.createElement("div"); 586 errorElement.className = "umbraco-forms missing-library"; 587 errorElement.style.color = "#fff"; 588 errorElement.style.backgroundColor = "#9d261d"; 589 errorElement.style.padding = "15px"; 590 errorElement.style.margin = "10px 0"; 591 var errorMessage = ""; 592 593 //Ensure umbracoForm is not null 594 if (formEl) { 595 //Check to see if the message for the form has been inserted already 596 var checkForExistinhgErr = formEl.getElementsByClassName( 597 "umbraco-forms missing-library" 598 ); 599 if (checkForExistinhgErr.length > 0) { 600 return; 601 } 602 603 var hasValidationFramework = false; 604 605 if (window.jQuery && $ && $.validator !== undefined) { 606 hasValidationFramework = true; 607 } else if (window.aspnetValidation !== undefined) { 608 hasValidationFramework = true; 609 } 610 611 if (hasValidationFramework === false) { 612 errorMessage = 613 errorMessage + 614 "Umbraco Forms requires a validation framework to run, please read documentation for posible options."; 615 } 616 617 if (errorMessage !== "") { 618 errorElement.innerHTML = 619 errorMessage + 620 '<br/> <a href="https://docs.umbraco.com/umbraco-forms/developer/prepping-frontend" target="_blank" style="text-decoration:underline;
620 color:#fff;">See Umbraco Forms Documentation</a>'; 621 622 formEl.insertBefore(errorElement, formEl.childNodes[0]); 623 } 624 } 625 } 626 627 /** 628 * Applies form accessibility improvements. 629 * @param {Element} formEl the element of the form. 630 */ 631 function applyFormAccessibility(formEl) { 632 setFocusToFirstElementOnValidationError(formEl); 633 } 634 635 /** 636 * Monitors for validation errors and when found sets the focus to the first field with an error. 637 * @param {Element} formEl the element of the form. 638 */ 639 function setFocusToFirstElementOnValidationError(formEl) { 640 if ("MutationObserver" in window === false) { 641 return; 642 } 643 644 // To implement this, we are relying on on monitoring for validation message elements, which only fires when there are changes. 645 // So if you have two errors, and fix the first one, it wouldn't then highlight the second one on re-submitting the form. 646 // Unless we reset the validation messages on submit, so they get changed back on errors. 647 if (window.aspnetValidation !== undefined) { 648 var form = formEl.getElementsByTagName("form")[0]; 649 var handleResetValidationMessages = function () { 650 resetValidationMessages(form); 651 }; 652 form.addEventListener("submit", handleResetValidationMessages, false); 653 } else { 654 // For jquery.validate, we need to hook this in as part of the submit handler coded in configureUmbracoFormsValidation(); 655 } 656 657 // Watch for changes to the validation error messages in the DOM tree using a MutationObserver. 658 // See: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver 659 var observer = new MutationObserver(function (mutationRecords) { 660 for (var i = 0; i < mutationRecords.length; i++) { 661 var mutationRecord = mutationRecords[i]; 662 if (mutationRecord.target.className === "field-validation-error") { 663 setFocusOnFormField(mutationRecord.target); 664 break; 665 } 666 } 667 }); 668 669 observer.observe(formEl, { 670 attributes: true, 671 attributeFilter: ["class"], 672 childList: false, 673 characterData: false, 674 subtree: true, 675 }); 676 } 677 678 /** 679 * Resets the validation messages for a form. 680 * @param {Element} formEl the element of the form. 681 */ 682 function resetValidationMessages(formEl) { 683 var validationErrorMessageElements = formEl.getElementsByClassName( 684 "field-validation-error" 685 ); 686 for (var i = 0; i < validationErrorMessageElements.length; i++) { 687 validationErrorMessageElements[i].className = "field-validation-valid"; 688 } 689 } 690 691 /** 692 * Sets the focus to the form field input element associated with the provided validation message element. 693 * @param {Element} validationErrorEl the element of the validation error. 694 */ 695 function setFocusOnFormField(validationErrorEl) { 696 var formFieldElement = validationErrorEl.previousElementSibling; 697 while (formFieldElement) { 698 if ( 699 formFieldElement.tagName.toLowerCase() === "input" || 700 formFieldElement.tagName.toLowerCase() === "textarea" || 701 formFieldElement.tagName.toLowerCase() === "select" 702 ) { 703 formFieldElement.focus(); 704 break; 705 } 706 707 if ( 708 formFieldElement.classList.contains("radiobuttonlist") || 709 formFieldElement.classList.contains("checkboxlist") 710 ) { 711 for (var i = 0; i < formFieldElement.children.length; i++) { 712 var formFieldChildElement = formFieldElement.children[i]; 713 if (formFieldChildElement.tagName.toLowerCase() === "input") { 714 formFieldChildElement.focus(); 715 break; 716 } 717 } 718 719 break; 720 } 721 722 formFieldElement = formFieldElement.previousElementSibling; 723 } 724 } 725 726 /** 727 * Class to handle Umbraco Forms conditional statements 728 * @param {any} form a reference to the form 729 * @param {any} pageButtonConditions a reference to the page button conditions 730 * @param {any} fieldsetConditions a reference to the fieldset conditions 731 * @param {any} fieldConditions a reference to the field conditions 732 * @param {any} triggerConditionsCheckOn the client-side event on which condition checks should be triggered 733 * @param {any} values the form values 734 * @param {any} formElementHtmlIdPrefix the HTML ID prefix used when rendering the form 735 * @return {Object} reference to the created class 736 */ 737 function UmbracoFormsConditions( 738 form, 739 pageButtonConditions, 740 fieldsetConditions, 741 fieldConditions, 742 triggerConditionsCheckOn, 743 values, 744 formElementHtmlIdPrefix 745 ) {
746 //our conditions "class" - must always be newed to work as it uses a form instance to operate on 747 //load all the information from the dom and serverside info and then the class will take care of the rest 748 749 var self = {}; 750 self.form = form; 751 self.pageButtonConditions = pageButtonConditions; 752 self.fieldsetConditions = fieldsetConditions; 753 self.fieldConditions = fieldConditions; 754 self.triggerConditionsCheckOn = triggerConditionsCheckOn; 755 self.values = values; 756 self.formElementHtmlIdPrefix = formElementHtmlIdPrefix; 757 self.dataTypes = {}; 758 759 //Iterates through all the form elements found on the page to update the registered value 760 function populateFieldValues(page, formValues, dataTypes) { 761 var selectFields = page.querySelectorAll("select"); 762 for (let i = 0; i < selectFields.length; i++) { 763 const field = selectFields[i]; 764 formValues[field.getAttribute("id")] = field.value; 765 dataTypes[field.getAttribute("id")] = "select"; 766 } 767 768 var textareaFields = page.querySelectorAll("textarea"); 769 for (let i = 0; i < textareaFields.length; i++) { 770 const field = textareaFields[i]; 771 formValues[field.getAttribute("id")] = field.value; 772 dataTypes[field.getAttribute("id")] = "textarea"; 773 } 774 775 var getKeyForRadioOrCheckBoxListElement = function (field) { 776 const idParts = field.getAttribute("id").split("_"); 777 return idParts.slice(0, idParts.length - 1).join("_"); 778 }; 779 780 // clear out all saved checkbox values to we can safely append 781 var checkboxFields = page.querySelectorAll("input[type=checkbox]"); 782 for (let i = 0; i < checkboxFields.length; i++) { 783 const field = checkboxFields[i]; 784 785 // Need to handle both a single checkbox, and a checkbox list. We can distinguish by comparing the field's 786 // id and name attributes. If they are the same, accounting for a custom prefix, then we have a single checkbox. 787 if ( 788 field.getAttribute("id") !== 789 self.formElementHtmlIdPrefix + field.getAttribute("name") 790 ) { 791 // Handle checkbox list. 792 // For a checkbox in a list we need to remove the suffix from the Id (which will be '_0', '_1' etc.). 793 const checkboxKey = getKeyForRadioOrCheckBoxListElement(field); 794 formValues[checkboxKey] = null; 795 dataTypes[checkboxKey] = "checkbox"; 796 } else { 797 // Handle single checkbox. 798 formValues[field.getAttribute("id")] = null; 799 dataTypes[field.getAttribute("id")] = "checkbox"; 800 } 801 } 802 803 var inputFields = page.querySelectorAll("input"); 804 for (let i = 0; i < inputFields.length; i++) { 805 const field = inputFields[i]; 806 807 if ( 808 field.getAttribute("type") === "text" || 809 field.getAttribute("type") === "number" || 810 field.getAttribute("type") === "email" || 811 field.getAttribute("type") === "url" || 812 field.getAttribute("type") === "tel" || 813 field.getAttribute("type") === "time" || 814 field.getAttribute("type") === "date" || 815 field.getAttribute("type") === "datetime-local" || 816 field.getAttribute("type") === "hidden" 817 ) { 818 formValues[field.getAttribute("id")] = field.value; 819 dataTypes[field.getAttribute("id")] = "text"; 820 } 821 822 if (field.getAttribute("type") === "radio") { 823 if (field.matches(":checked")) { 824 // For a radio button in a list we need to remove the suffix from the Id (which will be '_0', '_1' etc.). 825 const radioKey = getKeyForRadioOrCheckBoxListElement(field); 826 formValues[radioKey] = field.value; 827 dataTypes[radioKey] = "radio"; 828 } 829 } 830 831 if (field.getAttribute("type") === "checkbox") { 832 // Again, need to handle both a single checkbox, and a checkbox list. 833 if ( 834 field.getAttribute("id") !== 835 self.formElementHtmlIdPrefix + field.getAttribute("name") 836 ) { 837 // Handle checkbox list. 838 // For a checkbox in a list we need to remove the suffix from the Id (which will be '_0', '_1' etc.). 839 const checkboxKey = getKeyForRadioOrCheckBoxListElement(field); 840 if (field.matches(":checked")) { 841 if (formValues[checkboxKey] === null) { 842 formValues[checkboxKey] = field.value; 843 } else { 844 formValues[checkboxKey] += ";;" + field.value; 845 } 846 } 847 } else { 848 // Handle single checkbox. 849 formValues[field.getAttribute("id")] = field.matches(":checked") 850 ? "true" 851 : "false"; 852 } 853 } 854 } 855 } 856 857 /* Public api */ 858 859 self.operators = { 860 Is: function (value, expected) { 861 if ((value || "") === expected) { 862 return true; 863 } 864 if (value == null) { 865 return expected == value; 866 } 867 868 // Handle special cases where we case insensitively compare for expected values of "true" and "on", which we 869 // expect to have come from checkbox conditions. 870 if ( 871 expected.toUpperCase() === "TRUE" || 872 expected.toUpperCase() === "ON" 873 ) { 874 expected = "true"; 875 } else if ( 876 expected.toUpperCase() === "FALSE" || 877 expected.toUpperCase() === "OFF" 878 ) { 879 expected = "false"; 880 } 881 882 // Handle single checked checkbox, where we'll get two values provided. 883 if (value === "true;;false") { 884 value = "true"; 885 } 886 887 var values = value.split(";;"); 888 889 var matchingExpected = values.filter(function (o) { 890 return o === expected; 891 }); 892 return matchingExpected.length === values.length; // If there are multiple values (e.g. for a checkbox list), "is" only matches if 893 // a single value only matches the expected. Contains should be used if matching any value. 894 }, 895 IsNot: function (value, unexpected, dataType) { 896 if (value == null) { 897 return unexpected != value; 898 } 899 900 // Handle single checked checkbox, where we'll get two values provided. 901 if (value === "true;;false") { 902 value = "true"; 903 } 904 905 var values = value.split(";;"); 906 var matchingUnexpected = values.filter(function (o) { 907 return o === unexpected; 908 }); 909 910 if (dataType === "checkbox") { 911 if ( 912 unexpected.toUpperCase() === "TRUE" || 913 unexpected.toUpperCase() === "ON" 914 ) { 915 unexpected = "true"; 916 } else if ( 917 unexpected.toUpperCase() === "FALSE" || 918 unexpected.toUpperCase() === "OFF" 919 ) { 920 unexpected = "false"; 921 } 922 } 923 return ( 924 (value || "") !== unexpected && 925 matchingUnexpected.length != values.length 926 ); 927 }, 928 GreaterThen: function (value, limit) { 929 if (isNaN(value) || isNaN(limit)) { 930 return value > limit; 931 } 932 933 return parseInt(value) > parseInt(limit); 934 }, 935 LessThen: function (value, limit) { 936 if (isNaN(value) || isNaN(limit)) { 937 return value < limit; 938 } 939 940 return parseInt(value) < parseInt(limit); 941 }, 942 StartsWith: function (value, criteria) { 943 return value && value.startsWith(criteria); 944 }, 945 EndsWith: function (value, criteria) { 946 return value && value.endsWith(criteria); 947 }, 948 Contains: function (value, criteria) { 949 return value && value.indexOf(criteria) > -1; 950 }, 951 ContainsIgnoreCase: function (value, criteria) { 952 return ( 953 value && 954 criteria && 955 value.toUpperCase().indexOf(criteria.toUpperCase()) >= 0 956 ); 957 }, 958 StartsWithIgnoreCase: function (value, criteria) { 959 return ( 960 value && 961 criteria && 962 value.toUpperCase().startsWith(criteria.toUpperCase()) 963 ); 964 }, 965 EndsWithIgnoreCase: function (value, criteria) { 966 return ( 967 value && 968 criteria && 969 value.toUpperCase().endsWith(criteria.toUpperCase()) 970 ); 971 }, 972 NotContains: function (value, criteria) { 973 return !value || (criteria && value.indexOf(criteria) < 0); 974 }, 975 NotStartsWith: function (value, criteria) { 976 return !value || (criteria && !value.startsWith(criteria)); 977 }, 978 NotEndsWith: function (value, criteria) { 979 return !value || (criteria && !value.endsWith(criteria)); 980 }, 981 NotContainsIgnoreCase: function (value, criteria) { 982 return ( 983 !value || 984 (criteria && value.toUpperCase().indexOf(criteria.toUpperCase()) < 0) 985 ); 986 }, 987 NotStartsWithIgnoreCase: function (value, criteria) { 988 return ( 989 !value || 990 (criteria && !value.toUpperCase().startsWith(criteria.toUpperCase())) 991 ); 992 }, 993 NotEndsWithIgnoreCase: function (value, criteria) { 994 return ( 995 !value || 996 (criteria && !value.toUpperCase().endsWith(criteria.toUpperCase())) 997 ); 998 }, 999 }; 1000 1001 self.watch = function () { 1002 // This is a special case for pikaday 1003 // The only way around to pickup the value, for now, is to 1004 // subscribe to blur events 1005 var datepickerfields = self.form.querySelectorAll(".datepickerfield"); 1006 for (let i = 0; i < datepickerfields.length; i++) { 1007 const field = datepickerfields[i]; 1008 field.addEventListener( 1009 "blur", 1010 function () { 1011 if (this.value === "") { 1012 // Here comes the hack 1013 // Force the hidden datepicker field the datepicker field 1014 var id = this.getAttribute("id"); 1015 var hiddenDatePickerField = id + "_1"; 1016 self.values[hiddenDatePickerField] = "";
1017 document.getElementById(hiddenDatePickerField).value = ""; // sadly we cant use querySelector with current mark-up (would need to prefix IDs) 1018 } 1019 1020 populateFieldValues(self.form, self.values, self.dataTypes); 1021 //process the conditions 1022 self.run(); 1023 }.bind(field) 1024 ); 1025 } 1026 //subscribe to change or input event as per configuration 1027 var changeablefields = self.form.querySelectorAll( 1028 "input, textarea, select" 1029 ); 1030 for (let i = 0; i < changeablefields.length; i++) { 1031 const field = changeablefields[i]; 1032 field.addEventListener( 1033 self.triggerConditionsCheckOn, 1034 function () { 1035 populateFieldValues(self.form, self.values, self.dataTypes); 1036 //process the conditions 1037 self.run(); 1038 }.bind(field) 1039 ); 1040 } 1041 1042 //register all values from the current fields on the page 1043 populateFieldValues(self.form, self.values, self.dataTypes); 1044 1045 //the initial run-through of all the conditions 1046 self.run(); 1047 }; 1048 1049 self.run = function () { 1050 var pageId, 1051 htmlFieldId, 1052 fsId, 1053 fieldId, 1054 /* 1055 fsConditions = params.fsConditions || {}, 1056 fieldConditions = params.fieldConditions || {}, 1057 values = params.values || {},*/ 1058 1059 cachedResults = {}; 1060 1061 function evaluateRuleInstance(rule) { 1062 var value = self.values[self.formElementHtmlIdPrefix + rule.field], 1063 func = self.operators[rule.operator], 1064 result = value !== null && func(value, rule.value); 1065 return result; 1066 } 1067 1068 function evaluateRule(rule) { 1069 var dependencyIsVisible = true; 1070 1071 if (self.fieldConditions[rule.field]) { 1072 dependencyIsVisible = isVisible( 1073 rule.field, 1074 self.fieldConditions[rule.field] 1075 ); 1076 } 1077 1078 if (dependencyIsVisible) { 1079 return evaluateRuleInstance(rule); 1080 } else { 1081 return false; 1082 } 1083 } 1084 1085 function evaluateCondition(id, condition) { 1086 // This was once pretty. Now it needs refactoring again. :) 1087 1088 var any = condition.logicType === "Any", 1089 all = condition.logicType === "All", 1090 fieldsetVisibilities = {}, 1091 hasHiddenFieldset = false, 1092 success = true, 1093 rule, 1094 i; 1095 1096 // If we don't have any rules defined, we must return false (as neither 'any' nor 'all' of the conditions 1097 // can be considered passing). 1098 if (condition.rules.length === 0) { 1099 return false; 1100 } 1101 1102 for (i = 0; i < condition.rules.length; i++) { 1103 rule = condition.rules[i]; 1104 1105 if (id === rule.field || id === rule.fieldsetId) { 1106 throw new Error( 1107 "Field or fieldset " + id + " has a condition on itself." 1108 ); 1109 } 1110 1111 if (fieldsetVisibilities[rule.fieldsetId] !== undefined) { 1112 continue; 1113 } 1114 1115 if (self.fieldsetConditions[rule.fieldsetId]) { 1116 fieldsetVisibilities[rule.fieldsetId] = isVisible( 1117 rule.fieldsetId, 1118 self.fieldsetConditions[rule.fieldsetId] 1119 ); 1120 1121 if (!fieldsetVisibilities[rule.fieldsetId]) { 1122 hasHiddenFieldset = true; 1123 } 1124 } else { 1125 fieldsetVisibilities[rule.fieldsetId] = true; 1126 } 1127 } 1128 1129 if (all && hasHiddenFieldset) { 1130 return false; 1131 } 1132 1133 for (i = 0; i < condition.rules.length; i++) { 1134 rule = condition.rules[i]; 1135 1136 if (fieldsetVisibilities[rule.fieldsetId]) { 1137 success = evaluateRule(condition.rules[i]); 1138 } else { 1139 success = false; 1140 } 1141 1142 if (any && success) { 1143 break; 1144 } 1145 if (all && !success) { 1146 break; 1147 } 1148 } 1149 return success; 1150 } 1151 1152 function evaluateConditionVisibility(id, condition) { 1153 var show = condition.actionType === "Show", 1154 cachedResult = cachedResults[id]; 1155 1156 var success; 1157 if (cachedResult === undefined) { 1158 cachedResults[id] = show; // set default value to avoid circular issues 1159 success = cachedResults[id] = evaluateCondition(id, condition); 1160 } else { 1161 success = cachedResult; 1162 } 1163 1164 var visible = !(success ^ show); 1165 return visible; 1166 } 1167 1168 function isVisible(id, condition) { 1169 if (condition) { 1170 return evaluateConditionVisibility(id, condition); 1171 } 1172 return true; 1173 } 1174 1175 function handleCondition(element, id, condition) { 1176 var shouldShow = isVisible(id, condition); 1177 if (element) { 1178 if (shouldShow) { 1179 element.classList.remove("umbraco-forms-hidden"); 1180 } else { 1181 element.classList.add("umbraco-forms-hidden"); 1182 } 1183 } 1184 } 1185 1186 for (pageId in self.pageButtonConditions) { 1187 if ( 1188 Object.prototype.hasOwnProperty.call( 1189 self.pageButtonConditions, 1190 pageId 1191 ) 1192 ) { 1193 var pageElem = document.getElementById(pageId); 1194 if (pageElem) { 1195 handleCondition( 1196 pageElem.querySelector( 1197 "input[name='__next'], button[name='__next'], input[data-form-navigate='next'], button[data-form-navigate='next']" 1198 ), 1199 pageId, 1200 self.pageButtonConditions[pageId], 1201 "Page" 1202 ); 1203 } 1204 } 1205 } 1206 1207 for (fsId in self.fieldsetConditions) { 1208 if ( 1209 Object.prototype.hasOwnProperty.call(self.fieldsetConditions, fsId) 1210 ) { 1211 htmlFieldId = self.formElementHtmlIdPrefix + fsId; 1212 handleCondition( 1213 document.getElementById(htmlFieldId), 1214 fsId, 1215 self.fieldsetConditions[fsId], 1216 "Fieldset" 1217 ); // sadly we cant use querySelector with current mark-up (would need to prefix IDs) 1218 } 1219 } 1220 1221 for (fieldId in self.fieldConditions) { 1222 if ( 1223 Object.prototype.hasOwnProperty.call(self.fieldConditions, fieldId) 1224 ) { 1225 htmlFieldId = self.formElementHtmlIdPrefix + fieldId; 1226 if (document.getElementById(htmlFieldId)) { 1227 handleCondition( 1228 document 1229 .getElementById(htmlFieldId) 1230 .closest(".umbraco-forms-field"), // sadly we cant use querySelector with current mark-up (would need to prefix IDs) 1231 fieldId, 1232 self.fieldConditions[fieldId], 1233 "Field" 1234 ); 1235 } 1236 } 1237 } 1238 }; 1239 1240 return self; 1241 } 1242})();
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.