1(function () { 2 3 4 // polyfill for matches and closest 5 if (!Element.prototype.matches) Element.prototype.matches = Element.prototype.msMatchesSelector; 6 if (!Element.prototype.closest) Element.prototype.closest = function (selector) { 7 var el = this; 8 while (el) { 9 if (el.matches(selector)) { 10 return el; 11 } 12 el = el.parentElement; 13 } 14 }; 15 16 17 18 //execute init() on document ready 19 document.addEventListener("DOMContentLoaded", listen); 20 21 function listen() { 22 23 // Inline script setting umbracoFormsCollection was removed in 8.11/9.3, and replaced by config elements. 24 // So if we have them, use it, otherwise fall-back to legacy method. 25 var configElements = document.getElementsByClassName("umbraco-forms-form-config"); 26 if (configElements.length > 0) { 27 var umbracoFormsCollectionFromConfig = []; 28 for (var i = 0; i < configElements.length; i++) { 29 var configElement = configElements[i]; 30 var form = { 31 formId: configElement.dataset.id, 32 pageButtonConditions: JSON.parse(configElement.dataset.serializedPageButtonConditions), 33 fieldSetConditions: JSON.parse(configElement.dataset.serializedFieldsetConditions), 34 fieldConditions: JSON.parse(configElement.dataset.serializedFieldConditions), 35 triggerConditionsCheckOn: configElement.dataset.triggerConditionsCheckOn, 36 disableValidationDependencyCheck: configElement.dataset.disableValidationDependencyCheck === "true", 37 recordValues: JSON.parse(configElement.dataset.serializedFieldsNotDisplayed), 38 formElementHtmlIdPrefix: configElement.dataset.formElementHtmlIdPrefix, 39 }; 40 umbracoFormsCollectionFromConfig.push(form); 41 } 42 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. 43 } else { 44 if (typeof umbracoFormsCollection === "undefined") { 45 //this will occur if this js file is loaded before the inline scripts, in which case 46 //we'll listen for the inline scripts to execute a custom event. 47 document.addEventListener("umbracoFormLoaded", init); 48 } 49 else { 50 initCollection(umbracoFormsCollection); 51 } 52 } 53 54 // Scrolling to the submitted form was also moved from an inline script in 8.11/9.3. 55 var submittedFormElement = document.getElementById("umbraco-forms-form-submitted"); 56 if (submittedFormElement) { 57 var formClientId = submittedFormElement.dataset.formClientId; 58 scrollToSubmittedForm(formClientId); 59 } 60 } 61 62 function initCollection(formsCollection, jsonParsed) { 63 configureUmbracoFormsValidation(); 64 65 for (var i = 0; i < formsCollection.length; i++) { 66 init({ form: formsCollection[i] }, jsonParsed); 67 } 68 } 69 70 function init(e, jsonParsed) { 71 72 var formItem = jsonParsed ? e.form : JSON.parse(decodeURI(e.form)); 73 74 var forms = document.querySelectorAll('.umbraco-forms-form'); 75 76 for (var i = 0; i < forms.length; i++) { 77 var form = forms[i]; 78 79 if (!formItem.disableValidationDependencyCheck) { 80 dependencyCheck(form); 81 } 82 83 var page = form.querySelector('.umbraco-forms-page'); 84 var conditions = new UmbracoFormsConditions(page, 85 formItem.pageButtonConditions, 86 formItem.fieldSetConditions, 87 formItem.fieldConditions, 88 formItem.triggerConditionsCheckOn && formItem.triggerConditionsCheckOn.length > 0 ? formItem.triggerConditionsCheckOn : "change", 89 formItem.recordValues, 90 formItem.formElementHtmlIdPrefix); 91 conditions.watch(); 92 93 applyFormAccessibility(form); 94 } 95 } 96 97 function scrollToSubmittedForm(formClientId) { 98 document.onreadystatechange = function () { 99 100 if (document.readyState == "complete") { 101 102 // We have various cases where we want to scroll to the form rendered on the page. 103 // - When we have a server-side validation error, we should scroll to the first error element (which class 'field-validation-error'). 104 // - 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'). 105 // - 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]).
106 var validationsMessageElements = document.querySelectorAll(".field-validation-error"); 107 var messageOnSubmitElement = document.querySelectorAll(".umbraco-forms-submitmessage"); 108 var messageOnSubmitHtmlElement = document.querySelectorAll(".umbraco-forms-submitmessage-html"); 109 var submittedFormElement = document.getElementById("umbraco_form_" + formClientId); 110 if (validationsMessageElements.length > 0) { 111 scrollToFirstFieldWithValidationError(validationsMessageElements); 112 } else if (messageOnSubmitElement.length > 0) { 113 scrollElementIntoView(messageOnSubmitElement[0].parentElement); 114 } else if (messageOnSubmitHtmlElement.length > 0) { 115 scrollElementIntoView(messageOnSubmitHtmlElement[0]); 116 } else if (submittedFormElement) { 117 scrollElementIntoView(submittedFormElement); 118 } 119 } 120 } 121 122 function scrollToFirstFieldWithValidationError(validationsMessageElements) { 123 var scrollY = 0; 124 for (var i = 0; i < validationsMessageElements.length; i++) { 125 var node = getFormFieldElement(validationsMessageElements[i]); 126 var offset = node.getBoundingClientRect().top; 127 if (0 < offset && (offset < scrollY || scrollY === 0)) { 128 scrollY = offset; 129 } 130 } 131 132 if (scrollY > 0) { 133 window.scrollTo(0, scrollY); 134 } 135 } 136 137 function scrollElementIntoView(element) { 138 element.scrollIntoView({ 139 behavior: "auto", 140 block: "start", 141 }); 142 } 143 144 function getFormFieldElement(node) { 145 var runner = node; 146 while (runner.tagName !== "BODY") { 147 if (runner.classList.contains("umbraco-forms-field")) { 148 return runner; 149 } 150 151 runner = runner.parentNode; 152 } 153 154 return node; 155 } 156 } 157 158 /** Configures the jquery validation for Umbraco forms */ 159 function configureUmbracoFormsValidation() { 160 161 if (window.aspnetValidation !== undefined) { 162 // Asp-net validation setup: 163 164 var validationService = new aspnetValidation.ValidationService(); 165 166 // TODO: equivilant to this: 167 /* 168 $.validator.setDefaults({ 169 ignore: ":hidden" 170 }); 171 */ 172 173 var required = function (value, element) { 174 // Handle single and multiple checkboxes: 175 if (element.type.toLowerCase() === "checkbox" || element.type.toLowerCase() === "radio") { 176 var allCheckboxesOfThisName = element.form.querySelectorAll("input[name='" + element.name + "']"); 177 for (var i = 0; i < allCheckboxesOfThisName.length; i++) { 178 if (allCheckboxesOfThisName[i].checked === true) { 179 return true; 180 } 181 } 182 return false; 183 } 184 return Boolean(value); 185 } 186 validationService.addProvider("requiredcb", required); 187 validationService.addProvider("required", required);// this will go instead of the build-in required. 188 189 190 var umbracoforms_regex = function (value, element, params) { 191 if (!value || !params.pattern) { 192 return true; 193 } 194 195 var r = new RegExp(params.pattern); 196 return r.test(value); 197 } 198 validationService.addProvider("umbracoforms_regex", umbracoforms_regex); 199 200 var wrapProviderWithIgnorerBehaviour = function (provider) { 201 return function (value, element, params) { 202 203 // If field is hidden we ignorer the validation. 204 if (element.offsetParent === null) { 205 return true; 206 } 207 208 return provider(value, element, params); 209 } 210 } 211 212 // we can only incept with default validator if we do it after bootstrapping but before window load event triggers validationservice. 213 window.addEventListener('load', function () { 214 215 // Wrap all providers with ignorer hidden fields logic: 216 for (var key in validationService.providers) { 217 validationService.providers[key] = wrapProviderWithIgnorerBehaviour(validationService.providers[key]); 218 } 219 }); 220 221 // bootstrap validation service. 222 validationService.bootstrap(); 223 224 // make validation service avaialble globally for custom client-side code. 225 window.umbracoFormsValidationService = validationService; 226
227 // Without jquery validation, the previous page submit button click isn't sent with it's name, 228 // so can't be used server-side to determine whether to go forward or back. 229 // Hence we'll use an alternate method, setting a hidden field that's also used in the check. 230 var handlePreviousClicked = function () { 231 this.form.elements["PreviousClicked"].value = "clicked"; 232 }; 233 234 // - previous buttons are indicated by a data attribute of data-umb="prev-forms-form", introduced in 8.12/9.4. 235 var previousButtonElements = document.querySelectorAll("[data-umb='prev-forms-form']"); 236 237 // - prior to that classes were used to identify them, so for compatibility with custom themes, we'll check for those too 238 // if we haven't found any via the data attribute. 239 if (previousButtonElements.length === 0) { 240 previousButtonElements = document.getElementsByClassName("prev cancel"); 241 } 242 243 for (var i = 0; i < previousButtonElements.length; i++) { 244 previousButtonElements[i].form.elements["PreviousClicked"].value = ""; 245 previousButtonElements[i].addEventListener('click', handlePreviousClicked, false); 246 } 247 248 } else if (typeof jQuery === "function" && $.validator && $.validator.unobtrusive) { 249 //Jquery validation setup 250 251 $.validator.setDefaults({ 252 ignore: ":hidden" 253 }); 254 255 $.validator.unobtrusive.adapters.addBool("requiredcb", "required"); 256 257 258 $.validator.addMethod("umbracoforms_regex", function (value, element) { 259 260 var regex = $(element).attr("data-regex"); 261 var val = $(element).val(); 262 if (val.length === 0) { 263 return true; 264 } 265 return val.match(regex); 266 }); 267 268 $.validator.unobtrusive.adapters.addBool("regex", "umbracoforms_regex"); 269 270 var submitInputs = document.querySelectorAll(".umbraco-forms-form input[type=submit]:not(.cancel)"); 271 for (let i = 0; i < submitInputs.length; i++) { 272 var input = submitInputs[i]; 273 input.addEventListener("click", function (evt) { 274 evt.preventDefault(); 275 var frm = $(this).closest("form"); 276 resetValidationMessages(frm[0]); 277 frm.validate(); 278 if (frm.valid()) { 279 frm.submit(); 280 this.setAttribute("disabled", "disabled"); 281 282 } 283 }.bind(input)); 284 } 285 } 286 287 } 288 289 /** 290 * method to determine if Umbraco Forms can run and has the required dependencies loaded 291 * @param {Form Element} formEl the element of the form 292 */ 293 function dependencyCheck(formEl) { 294 // Only perform check if the global 'Umbraco.Sys' is null/undefined. 295 // 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. 296 // Similarly we need a check for if running in a rich text editor. 297 var isBackOffice = function () { 298 return typeof Umbraco !== "undefined" && typeof Umbraco.Sys !== "undefined"; 299 }; 300 var isBackOfficeRte = function () { 301 return document.body.id === "tinymce"; 302 }; 303 304 if (isBackOffice() || isBackOfficeRte()) { 305 return; 306 } 307 308 var errorElement = document.createElement("div"); 309 errorElement.className = "umbraco-forms missing-library"; 310 errorElement.style.color = "#fff"; 311 errorElement.style.backgroundColor = "#9d261d"; 312 errorElement.style.padding = "15px"; 313 errorElement.style.margin = "10px 0"; 314 var errorMessage = ""; 315 316 //Ensure umbracoForm is not null 317 if (formEl) { 318 319 //Check to see if the message for the form has been inserted already 320 var checkForExistinhgErr = formEl.getElementsByClassName('umbraco-forms missing-library'); 321 if (checkForExistinhgErr.length > 0) { 322 return; 323 } 324 325 var hasValidationFramework = false;
326 327 if (window.jQuery && $ && $.validator !== undefined) { 328 hasValidationFramework = true; 329 } else if (window.aspnetValidation !== undefined) { 330 hasValidationFramework = true; 331 } 332 333 if (hasValidationFramework === false) { 334 errorMessage = errorMessage + "Umbraco Forms requires a validation framework to run, please read documentation for posible options."; 335 } 336 337 if (errorMessage !== "") { 338 errorElement.innerHTML = errorMessage + '<br/> <a href="https://docs.umbraco.com/umbraco-forms/developer/prepping-frontend" target="_blank" style="text-decoration:underline; color:#fff;">See Umbraco Forms Documentation</a>'; 339 340 formEl.insertBefore(errorElement, formEl.childNodes[0]); 341 } 342 } 343 } 344 345 /** 346 * Applies form accessibility improvements. 347 * @param {Element} formEl the element of the form. 348 */ 349 function applyFormAccessibility(formEl) { 350 setFocusToFirstElementOnValidationError(formEl); 351 } 352 353 /** 354 * Monitors for validation errors and when found sets the focus to the first field with an error. 355 * @param {Element} formEl the element of the form. 356 */ 357 function setFocusToFirstElementOnValidationError(formEl) { 358 if ("MutationObserver" in window === false) { 359 return; 360 } 361 362 // To implement this, we are relying on on monitoring for validation message elements, which only fires when there are changes. 363 // So if you have two errors, and fix the first one, it wouldn't then highlight the second one on re-submitting the form. 364 // Unless we reset the validation messages on submit, so they get changed back on errors. 365 if (window.aspnetValidation !== undefined) { 366 var form = formEl.getElementsByTagName('form')[0]; 367 var handleResetValidationMessages = function () { 368 resetValidationMessages(form); 369 }; 370 form.addEventListener('submit', handleResetValidationMessages, false); 371 } else { 372 // For jquery.validate, we need to hook this in as part of the submit handler coded in configureUmbracoFormsValidation(); 373 } 374 375 // Watch for changes to the validation error messages in the DOM tree using a MutationObserver. 376 // See: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver 377 var observer = new MutationObserver(function (mutationRecords) { 378 for (var i = 0; i < mutationRecords.length; i++) { 379 var mutationRecord = mutationRecords[i]; 380 if (mutationRecord.target.className === 'field-validation-error') { 381 setFocusOnFormField(mutationRecord.target); 382 break; 383 } 384 } 385 }); 386 387 observer.observe(formEl, { 388 attributes: true, 389 attributeFilter: ['class'], 390 childList: false, 391 characterData: false, 392 subtree: true 393 }); 394 } 395 396 /** 397 * Resets the validation messages for a form. 398 * @param {Element} formEl the element of the form. 399 */ 400 function resetValidationMessages(formEl) { 401 var validationErrorMessageElements = formEl.getElementsByClassName('field-validation-error'); 402 for (var i = 0; i < validationErrorMessageElements.length; i++) { 403 validationErrorMessageElements[i].className = 'field-validation-valid'; 404 } 405 } 406 407 /** 408 * Sets the focus to the form field input element associated with the provided validation message element. 409 * @param {Element} validationErrorEl the element of the validation error. 410 */ 411 function setFocusOnFormField(validationErrorEl) { 412 var formFieldElement = validationErrorEl.previousElementSibling; 413 while (formFieldElement) { 414 if (formFieldElement.tagName.toLowerCase() === 'input' || 415 formFieldElement.tagName.toLowerCase() === 'textarea' || 416 formFieldElement.tagName.toLowerCase() === 'select') { 417 formFieldElement.focus(); 418 break; 419 } 420 421 if (formFieldElement.classList.contains("radiobuttonlist") || 422 formFieldElement.classList.contains("checkboxlist")) { 423 for (var i = 0; i < formFieldElement.children.length; i++) { 424 var formFieldChildElement = formFieldElement.children[i]; 425 if (formFieldChildElement.tagName.toLowerCase() === 'input') { 426 formFieldChildElement.focus(); 427 break; 428 } 429 } 430 431 break; 432 } 433 434 formFieldElement = formFieldElement.previousElementSibling; 435 } 436 } 437 438 /** 439 * Class to handle Umbraco Forms conditional statements 440 * @param {any} form a reference to the form 441 * @param {any} pageButtonConditions a reference to the page button conditions 442 * @param {any} fieldsetConditions a reference to the fieldset conditions 443 * @param {any} fieldConditions a reference to the field conditions 444 * @param {any} triggerConditionsCheckOn the client-side event on which condition checks should be triggered 445 * @param {any} values the form values 446 * @param {any} formElementHtmlIdPrefix the HTML ID prefix used when rendering the form 447 * @return {Object} reference to the created class 448 */ 449 function UmbracoFormsConditions(form, pageButtonConditions, fieldsetConditions, fieldConditions, triggerConditionsCheckOn, values, formElementHtmlIdPrefix) { 450
451 //our conditions "class" - must always be newed to work as it uses a form instance to operate on 452 //load all the information from the dom and serverside info and then the class will take care of the rest 453 454 var self = {}; 455 self.form = form; 456 self.pageButtonConditions = pageButtonConditions; 457 self.fieldsetConditions = fieldsetConditions; 458 self.fieldConditions = fieldConditions; 459 self.triggerConditionsCheckOn = triggerConditionsCheckOn; 460 self.values = values; 461 self.formElementHtmlIdPrefix = formElementHtmlIdPrefix; 462 self.dataTypes = {}; 463 464 //Iterates through all the form elements found on the page to update the registered value 465 function populateFieldValues(page, formValues, dataTypes) { 466 467 var selectFields = page.querySelectorAll("select"); 468 for (let i = 0; i < selectFields.length; i++) { 469 const field = selectFields[i]; 470 formValues[field.getAttribute("id")] = field.value; 471 dataTypes[field.getAttribute("id")] = "select"; 472 } 473 474 var textareaFields = page.querySelectorAll("textarea"); 475 for (let i = 0; i < textareaFields.length; i++) { 476 const field = textareaFields[i]; 477 formValues[field.getAttribute("id")] = field.value; 478 dataTypes[field.getAttribute("id")] = "textarea"; 479 } 480 481 var getKeyForRadioOrCheckBoxListElement = function (field) { 482 const idParts = field.getAttribute("id").split("_"); 483 return idParts.slice(0, idParts.length - 1).join("_"); 484 } 485 486 // clear out all saved checkbox values to we can safely append 487 var checkboxFields = page.querySelectorAll("input[type=checkbox]"); 488 for (let i = 0; i < checkboxFields.length; i++) { 489 const field = checkboxFields[i]; 490 491 // Need to handle both a single checkbox, and a checkbox list. We can distinguish by comparing the field's 492 // id and name attributes. If they are the same, accounting for a custom prefix, then we have a single checkbox. 493 if (field.getAttribute("id") !== self.formElementHtmlIdPrefix + field.getAttribute("name")) { 494 // Handle checkbox list. 495 // For a checkbox in a list we need to remove the suffix from the Id (which will be '_0', '_1' etc.). 496 const checkboxKey = getKeyForRadioOrCheckBoxListElement(field); 497 formValues[checkboxKey] = null; 498 dataTypes[checkboxKey] = "checkbox"; 499 } else { 500 // Handle single checkbox. 501 formValues[field.getAttribute("id")] = null; 502 dataTypes[field.getAttribute("id")] = "checkbox"; 503 } 504 } 505 506 var inputFields = page.querySelectorAll("input"); 507 for (let i = 0; i < inputFields.length; i++) { 508 const field = inputFields[i]; 509 510 if (field.getAttribute('type') === "text" || 511 field.getAttribute('type') === "number" || 512 field.getAttribute('type') === "email" || 513 field.getAttribute('type') === "url" || 514 field.getAttribute('type') === "tel" || 515 field.getAttribute('type') === "time" || 516 field.getAttribute('type') === "date" || 517 field.getAttribute('type') === "datetime-local" || 518 field.getAttribute("type") === "hidden") { 519 formValues[field.getAttribute("id")] = field.value; 520 dataTypes[field.getAttribute("id")] = "text"; 521 } 522 523 if (field.getAttribute('type') === "radio") { 524 if (field.matches(':checked')) { 525 // For a radio button in a list we need to remove the suffix from the Id (which will be '_0', '_1' etc.). 526 const radioKey = getKeyForRadioOrCheckBoxListElement(field); 527 formValues[radioKey] = field.value; 528 dataTypes[radioKey] = "radio"; 529 } 530 } 531 532 if (field.getAttribute("type") === "checkbox") {
533 // Again, need to handle both a single checkbox, and a checkbox list. 534 if (field.getAttribute("id") !== self.formElementHtmlIdPrefix + field.getAttribute("name")) { 535 // Handle checkbox list. 536 // For a checkbox in a list we need to remove the suffix from the Id (which will be '_0', '_1' etc.). 537 const checkboxKey = getKeyForRadioOrCheckBoxListElement(field); 538 if (field.matches(":checked")) { 539 if (formValues[checkboxKey] === null) { 540 formValues[checkboxKey] = field.value; 541 } 542 else { 543 formValues[checkboxKey] += ";;" + field.value; 544 } 545 } 546 } 547 else { 548 // Handle single checkbox. 549 formValues[field.getAttribute("id")] = (field.matches(":checked") ? "true" : "false"); 550 } 551 } 552 } 553 } 554 555 /* Public api */ 556 557 self.operators = { 558 Is: function (value, expected, dataType) { 559 if ((value || "") === expected) { 560 return true; 561 } 562 if (value == null) { 563 return (expected == value); 564 } 565 566 if (dataType === "checkbox") { 567 if (expected.toUpperCase() === "TRUE" || expected.toUpperCase() === "ON") { 568 expected = "true" 569 } else if (expected.toUpperCase() === "FALSE" || expected.toUpperCase() === "OFF") { 570 expected = "false" 571 } 572 } 573 574 var values = value.split(';;'); 575 var matchingExpected = values.filter( 576 function (o) { 577 return o === expected; 578 }); 579 return matchingExpected.length > 0; 580 }, 581 IsNot: function (value, unexpected, dataType) { 582 if (value == null) { 583 return (unexpected != value); 584 } 585 var values = value.split(';;'); 586 var matchingUnexpected = values.filter( 587 function (o) { 588 return o === unexpected; 589 }); 590 591 if (dataType === "checkbox") { 592 if (unexpected.toUpperCase() === "TRUE" || unexpected.toUpperCase() === "ON") { 593 unexpected = "true" 594 } else if (unexpected.toUpperCase() === "FALSE" || unexpected.toUpperCase() === "OFF") { 595 unexpected = "false" 596 } 597 } 598 return (value || "") !== unexpected && matchingUnexpected.length === 0; 599 }, 600 GreaterThen: function (value, limit) { 601 if (isNaN(value) || isNaN(limit)) { 602 return value > limit; 603 } 604 605 return parseInt(value) > parseInt(limit); 606 }, 607 LessThen: function (value, limit) { 608 if (isNaN(value) || isNaN(limit)) { 609 return value < limit; 610 } 611 612 return parseInt(value) < parseInt(limit); 613 }, 614 StartsWith: function (value, criteria) { 615 return value && value.startsWith(criteria); 616 }, 617 EndsWith: function (value, criteria) { 618 return value && value.endsWith(criteria); 619 }, 620 Contains: function (value, criteria) { 621 return value && value.indexOf(criteria) > -1; 622 }, 623 ContainsIgnoreCase: function (value, criteria) { 624 return value && criteria && value.toUpperCase().indexOf(criteria.toUpperCase()) >= 0; 625 }, 626 StartsWithIgnoreCase: function (value, criteria) { 627 return value && criteria && value.toUpperCase().startsWith(criteria.toUpperCase()); 628 }, 629 EndsWithIgnoreCase: function (value, criteria) { 630 return value && criteria && value.toUpperCase().endsWith(criteria.toUpperCase()); 631 }, 632 NotContains: function (value, criteria) { 633 return !value || (criteria && value.indexOf(criteria) < 0); 634 }, 635 NotStartsWith: function (value, criteria) { 636 return !value || (criteria && !value.startsWith(criteria)); 637 }, 638 NotEndsWith: function (value, criteria) { 639 return !value || (criteria && !value.endsWith(criteria)); 640 }, 641 NotContainsIgnoreCase: function (value, criteria) { 642 return !value || (criteria && value.toUpperCase().indexOf(criteria.toUpperCase()) < 0); 643 }, 644 NotStartsWithIgnoreCase: function (value, criteria) { 645 return !value || (criteria && !value.toUpperCase().startsWith(criteria.toUpperCase())); 646 }, 647 NotEndsWithIgnoreCase: function (value, criteria) { 648 return !value || (criteria && !value.toUpperCase().endsWith(criteria.toUpperCase())); 649 }, 650 }; 651 652 self.watch = function () { 653 // This is a special case for pikaday 654 // The only way around to pickup the value, for now, is to 655 // subscribe to blur events 656 var datepickerfields = self.form.querySelectorAll('.datepickerfield'); 657 for (let i = 0; i < datepickerfields.length; i++) { 658 const field = datepickerfields[i]; 659 field.addEventListener('blur', function () { 660 if (this.value === "") { 661 // Here comes the hack 662 // Force the hidden datepicker field the datepicker field 663 var id = this.getAttribute("id"); 664 var hiddenDatePickerField = id.substr(0, id.length - 2); 665 self.values[hiddenDatePickerField] = "";
666 document.getElementById(hiddenDatePickerField).value = "";// sadly we cant use querySelector with current mark-up (would need to prefix IDs) 667 } 668 669 populateFieldValues(self.form, self.values, self.dataTypes); 670 //process the conditions 671 self.run(); 672 }.bind(field)); 673 } 674 //subscribe to change or input event as per configuration 675 var changeablefields = self.form.querySelectorAll("input, textarea, select"); 676 for (let i = 0; i < changeablefields.length; i++) { 677 const field = changeablefields[i]; 678 field.addEventListener(self.triggerConditionsCheckOn, function () { 679 populateFieldValues(self.form, self.values, self.dataTypes); 680 //process the conditions 681 self.run(); 682 }.bind(field)); 683 } 684 685 //register all values from the current fields on the page 686 populateFieldValues(self.form, self.values, self.dataTypes); 687 688 //the initial run-through of all the conditions 689 self.run(); 690 }; 691 692 self.run = function () { 693 var pageId, 694 htmlFieldId, 695 fsId, 696 fieldId, 697 698 /* 699 fsConditions = params.fsConditions || {}, 700 fieldConditions = params.fieldConditions || {}, 701 values = params.values || {},*/ 702 703 cachedResults = {}; 704 705 function evaluateRuleInstance(rule) { 706 var value = self.values[self.formElementHtmlIdPrefix + rule.field], 707 dataType = self.dataTypes[rule.field], 708 func = self.operators[rule.operator], 709 result = value !== null && func(value, rule.value, dataType); 710 return result; 711 } 712 713 function evaluateRule(rule) { 714 var dependencyIsVisible = true; 715 716 if (self.fieldConditions[rule.field]) { 717 dependencyIsVisible = isVisible(rule.field, self.fieldConditions[rule.field]); 718 } 719 720 if (dependencyIsVisible) { 721 return evaluateRuleInstance(rule); 722 } 723 else { 724 return false; 725 } 726 } 727 728 function evaluateCondition(id, condition) { 729 // This was once pretty. Now it needs refactoring again. :) 730 731 var any = condition.logicType === "Any", 732 all = condition.logicType === "All", 733 fieldsetVisibilities = {}, 734 hasHiddenFieldset = false, 735 success = true, 736 rule, 737 i; 738 739 // If we don't have any rules defined, we must return false (as neither 'any' nor 'all' of the conditions 740 // can be considered passing). 741 if (condition.rules.length === 0) { 742 return false; 743 } 744 745 for (i = 0; i < condition.rules.length; i++) { 746 rule = condition.rules[i]; 747 748 if (id === rule.field || id === rule.fieldsetId) { 749 throw new Error("Field or fieldset " + id + " has a condition on itself."); 750 } 751 752 if (fieldsetVisibilities[rule.fieldsetId] !== undefined) { 753 continue; 754 } 755 756 if (self.fieldsetConditions[rule.fieldsetId]) { 757 758 fieldsetVisibilities[rule.fieldsetId] = 759 isVisible(rule.fieldsetId, self.fieldsetConditions[rule.fieldsetId]); 760 761 if (!fieldsetVisibilities[rule.fieldsetId]) { 762 hasHiddenFieldset = true; 763 } 764 } 765 else { 766 fieldsetVisibilities[rule.fieldsetId] = true; 767 } 768 } 769 770 if (all && hasHiddenFieldset) { 771 return false;
772 } 773 774 for (i = 0; i < condition.rules.length; i++) { 775 rule = condition.rules[i]; 776 777 if (fieldsetVisibilities[rule.fieldsetId]) { 778 success = evaluateRule(condition.rules[i]); 779 } 780 else { 781 success = false; 782 } 783 784 if (any && success) { 785 break; 786 } 787 if (all && !success) { 788 break; 789 } 790 } 791 return success; 792 } 793 794 function evaluateConditionVisibility(id, condition) { 795 var show = condition.actionType === "Show", 796 cachedResult = cachedResults[id]; 797 798 var success; 799 if (cachedResult === undefined) { 800 cachedResults[id] = show; // set default value to avoid circular issues 801 success = (cachedResults[id] = evaluateCondition(id, condition)); 802 } else { 803 success = cachedResult; 804 } 805 806 var visible = !(success ^ show); 807 return visible; 808 } 809 810 function isVisible(id, condition) { 811 if (condition) { 812 return evaluateConditionVisibility(id, condition); 813 } 814 return true; 815 } 816 817 function handleCondition(element, id, condition) { 818 var shouldShow = isVisible(id, condition); 819 if (element) { 820 if (shouldShow) { 821 element.classList.remove("umbraco-forms-hidden"); 822 } 823 else { 824 element.classList.add("umbraco-forms-hidden"); 825 } 826 } 827 } 828 829 for (pageId in self.pageButtonConditions) { 830 if (Object.prototype.hasOwnProperty.call(self.pageButtonConditions, pageId)) { 831 var pageElem = document.getElementById(pageId); 832 if (pageElem) { 833 handleCondition(pageElem.querySelector("input[name='__next'], button[name='__next'], input[data-form-navigate='next'], button[data-form-navigate='next']"), pageId, self.pageButtonConditions[pageId], "Page"); 834 } 835 } 836 } 837 838 for (fsId in self.fieldsetConditions) { 839 if (Object.prototype.hasOwnProperty.call(self.fieldsetConditions, fsId)) { 840 htmlFieldId = self.formElementHtmlIdPrefix + fsId; 841 handleCondition(document.getElementById(htmlFieldId), fsId, self.fieldsetConditions[fsId], "Fieldset");// sadly we cant use querySelector with current mark-up (would need to prefix IDs) 842 } 843 } 844 845 for (fieldId in self.fieldConditions) { 846 if (Object.prototype.hasOwnProperty.call(self.fieldConditions, fieldId)) { 847 htmlFieldId = self.formElementHtmlIdPrefix + fieldId; 848 if (document.getElementById(htmlFieldId)) { 849 handleCondition(document.getElementById(htmlFieldId).closest(".umbraco-forms-field"),// sadly we cant use querySelector with current mark-up (would need to prefix IDs) 850 fieldId, 851 self.fieldConditions[fieldId], 852 "Field"); 853 } 854 } 855 } 856 }; 857 858 return self; 859 } 860})();
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.