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 if (document.readyState === "complete" || (document.readyState !== "loading" && !document.documentElement.doScroll)) { 20 listen(); 21 } else { 22 document.addEventListener("DOMContentLoaded", listen); 23 } 24 25 function listen() { 26 if (typeof umbracoFormsCollection === "undefined") { 27 //this will occur if this js file is loaded before the inline scripts, in which case 28 //we'll listen for the inline scripts to execute a custom event. 29 document.addEventListener("umbracoFormLoaded", init); 30 } 31 else { 32 initCollection(umbracoFormsCollection); 33 } 34 } 35 36 function initCollection(formsCollection) { 37 configureUmbracoFormsValidation(); 38 39 for (var i = 0; i < formsCollection.length; i++) { 40 init({ form: formsCollection[i] }); 41 } 42 } 43 44 function init(e) { 45 46 var formItem = JSON.parse(decodeURI(e.form)); 47 48 var forms = document.querySelectorAll('.umbraco-forms-form'); 49 50 for(var i = 0; i < forms.length; i++) { 51 var form = forms[i]; 52 53 dependencyCheck(form); 54 55 var page = form.querySelector('.umbraco-forms-page'); 56 var conditions = new UmbracoFormsConditions(page, 57 formItem.pageButtonConditions, 58 formItem.fieldSetConditions, 59 formItem.fieldConditions, 60 formItem.recordValues); 61 conditions.watch(); 62 63 applyFormAccessibility(form); 64 } 65 } 66 67 /** Configures the jquery validation for Umbraco forms */ 68 function configureUmbracoFormsValidation() { 69 70 if (window.aspnetValidation !== undefined) { 71 // Asp-net validation setup: 72 73 var validationService = new aspnetValidation.ValidationService(); 74 75 // TODO: equivilant to this: 76 /* 77 $.validator.setDefaults({ 78 ignore: ":hidden" 79 }); 80 */ 81 82 var required = function (value, element) { 83 // Handle single and multiple checkboxes: 84 if(element.type.toLowerCase() === "checkbox" || element.type.toLowerCase() === "radio") { 85 var allCheckboxesOfThisName = element.form.querySelectorAll("input[name='"+element.name+"']"); 86 for (var i=0; i<allCheckboxesOfThisName.length; i++) { 87 if (allCheckboxesOfThisName[i].checked === true) { 88 return true; 89 } 90 } 91 return false; 92 } 93 return Boolean(value); 94 } 95 validationService.addProvider("requiredcb", required); 96 validationService.addProvider("required", required);// this will go instead of the build-in required. 97 98 99 var umbracoforms_regex = function (value, element, params) { 100 if (!value || !params.pattern) { 101 return true; 102 } 103 104 var r = new RegExp(params.pattern); 105 return r.test(value); 106 } 107 validationService.addProvider("umbracoforms_regex", umbracoforms_regex); 108 109 var wrapProviderWithIgnorerBehaviour = function (provider) { 110 return function(value, element, params) { 111 112 // If field is hidden we ignorer the validation. 113 if (element.offsetParent === null) { 114 return true; 115 } 116 117 return provider(value, element, params); 118 } 119 } 120 121 // we can only incept with default validator if we do it after bootstrapping but before window load event triggers validationservice. 122 window.addEventListener('load', function() { 123 124 // Wrap all providers with ignorer hidden fields logic: 125 for (var key in validationService.providers) { 126 validationService.providers[key] = wrapProviderWithIgnorerBehaviour(validationService.providers[key]); 127 } 128 }); 129 130 // bootstrap validation service. 131 validationService.bootstrap(); 132
133 // Without jquery validation, the previous page submit button click isn't sent with it's name, 134 // so can't be used server-side to determine whether to go forward or back. 135 // Hence we'll use an alternate method, setting a hidden field that's also used in the check. 136 var handlePreviousClicked = function () { 137 this.form.elements["PreviousClicked"].value = "clicked"; 138 }; 139 var previousButtonElements = document.getElementsByClassName("prev cancel"); 140 for (var i = 0; i < previousButtonElements.length; i++) { 141 previousButtonElements[i].form.elements["PreviousClicked"].value = ""; 142 previousButtonElements[i].addEventListener('click', handlePreviousClicked, false); 143 } 144 145 } else if (typeof jQuery === "function" && $.validator) { 146 //Jquery validation setup 147 148 $.validator.setDefaults({ 149 ignore: ":hidden" 150 }); 151 152 $.validator.unobtrusive.adapters.addBool("requiredcb", "required"); 153 154 155 $.validator.addMethod("umbracoforms_regex", function (value, element) { 156 157 var regex = $(element).attr("data-regex"); 158 var val = $(element).val(); 159 if (val.length === 0) { 160 return true; 161 } 162 return val.match(regex); 163 }); 164 165 $.validator.unobtrusive.adapters.addBool("regex", "umbracoforms_regex"); 166 167 var submitInputs = document.querySelectorAll(".umbraco-forms-form input[type=submit]:not(.cancel)"); 168 for (let i = 0; i < submitInputs.length; i++) { 169 var input = submitInputs[i]; 170 input.addEventListener("click", function (evt) { 171 evt.preventDefault(); 172 var frm = $(this).closest("form"); 173 resetValidationMessages(frm[0]); 174 frm.validate(); 175 if (frm.valid()) { 176 frm.submit(); 177 this.setAttribute("disabled", "disabled"); 178 179 } 180 }.bind(input)); 181 } 182 } 183 184 } 185 186 /** 187 * method to determine if Umbraco Forms can run and has the required dependencies loaded 188 * @param {Form Element} formEl the element of the form 189 */ 190 function dependencyCheck(formEl) { 191 // Only perform check if the global 'Umbraco.Sys' is null/undefined. 192 // 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. 193 // Similarly we need a check for if running in a rich text editor. 194 var isBackOffice = function () { 195 return typeof Umbraco !== "undefined" && typeof Umbraco.Sys !== "undefined"; 196 }; 197 var isBackOfficeRte = function () { 198 return document.body.id === "tinymce"; 199 }; 200 if (isBackOffice() || isBackOfficeRte()) { 201 return; 202 } 203 204 var errorElement = document.createElement("div"); 205 errorElement.className = "umbraco-forms missing-library"; 206 errorElement.style.color = "#fff"; 207 errorElement.style.backgroundColor = "#9d261d"; 208 errorElement.style.padding = "15px"; 209 errorElement.style.margin = "10px 0"; 210 var errorMessage = ""; 211 212 //Ensure umbracoForm is not null 213 if (formEl) { 214 215 //Check to see if the message for the form has been inserted already 216 var checkForExistinhgErr = formEl.getElementsByClassName('umbraco-forms missing-library'); 217 if (checkForExistinhgErr.length > 0) { 218 return; 219 } 220 221 var hasValidationFramework = false; 222 223 if (window.jQuery && $ && $.validator !== undefined) { 224 hasValidationFramework = true; 225 } else if (window.aspnetValidation !== undefined) { 226 hasValidationFramework = true; 227 } 228 229 if(hasValidationFramework === false) { 230 errorMessage = errorMessage + "Umbraco Forms requires a validation framework to run, please read documentation for posible options."; 231 } 232 233 if (errorMessage !== "") { 234 errorElement.innerHTML = errorMessage + '<br/> <a href="https://our.umbraco.org/documentation/products/umbracoforms/developer/Prepping-Frontend/" target="_blank" style="text-decoration:underline;
234 color:#fff;">See Umbraco Forms Documentation</a>'; 235 236 formEl.insertBefore(errorElement, formEl.childNodes[0]); 237 } 238 } 239 } 240 241 /** 242 * Applies form accessibility improvements. 243 * @param {Element} formEl the element of the form. 244 */ 245 function applyFormAccessibility(formEl) { 246 setFocusToFirstElementOnValidationError(formEl); 247 } 248 249 /** 250 * Monitors for validation errors and when found sets the focus to the first field with an error. 251 * @param {Element} formEl the element of the form. 252 */ 253 function setFocusToFirstElementOnValidationError(formEl) { 254 if ("MutationObserver" in window === false) { 255 return; 256 } 257 258 // To implement this, we are relying on on monitoring for validation message elements, which only fires when there are changes. 259 // So if you have two errors, and fix the first one, it wouldn't then highlight the second one on re-submitting the form. 260 // Unless we reset the validation messages on submit, so they get changed back on errors. 261 if (window.aspnetValidation !== undefined) { 262 var form = formEl.getElementsByTagName('form')[0]; 263 var handleResetValidationMessages = function (event) { 264 resetValidationMessages(form); 265 }; 266 form.addEventListener('submit', handleResetValidationMessages, false); 267 } else { 268 // For jquery.validate, we need to hook this in as part of the submit handler coded in configureUmbracoFormsValidation(); 269 } 270 271 // Watch for changes to the validation error messages in the DOM tree using a MutationObserver. 272 // See: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver 273 var observer = new MutationObserver(function (mutationRecords) { 274 for (var i = 0; i < mutationRecords.length; i++) { 275 var mutationRecord = mutationRecords[i]; 276 if (mutationRecord.target.className === 'field-validation-error') { 277 setFocusOnFormField(mutationRecord.target); 278 break; 279 } 280 } 281 }); 282 283 observer.observe(formEl, { 284 attributes: true, 285 attributeFilter: ['class'], 286 childList: false, 287 characterData: false, 288 subtree: true 289 }); 290 } 291 292 /** 293 * Resets the validation messages for a form. 294 * @param {Element} formEl the element of the form. 295 */ 296 function resetValidationMessages(formEl) { 297 var validationErrorMessageElements = formEl.getElementsByClassName('field-validation-error'); 298 for (var i = 0; i < validationErrorMessageElements.length; i++) { 299 validationErrorMessageElements[i].className = 'field-validation-valid'; 300 } 301 } 302 303 /** 304 * Sets the focus to the form field input element associated with the provided validation message element. 305 * @param {Element} validationErrorEl the element of the validation error. 306 */ 307 function setFocusOnFormField(validationErrorEl) { 308 var formFieldElement = validationErrorEl.previousElementSibling; 309 while (formFieldElement) { 310 if (formFieldElement.tagName.toLowerCase() === 'input' || 311 formFieldElement.tagName.toLowerCase() === 'textarea' || 312 formFieldElement.tagName.toLowerCase() === 'select') { 313 formFieldElement.focus(); 314 break; 315 } 316 317 if (formFieldElement.classList.contains("radiobuttonlist") || 318 formFieldElement.classList.contains("checkboxlist")) { 319 for (var i = 0; i < formFieldElement.children.length; i++) { 320 var formFieldChildElement = formFieldElement.children[i]; 321 if (formFieldChildElement.tagName.toLowerCase() === 'input') { 322 formFieldChildElement.focus(); 323 break; 324 } 325 } 326 327 break; 328 } 329 330 formFieldElement = formFieldElement.previousElementSibling; 331 } 332 } 333 334 /** 335 * Class to handle Umbraco Forms conditional statements 336 * @param {any} form a reference to the form 337 * @param {any} pageButtonConditions a reference to the page button conditions 338 * @param {any} fieldsetConditions a reference to the fieldset conditions 339 * @param {any} fieldConditions a reference to the field conditions 340 * @param {any} values the form values 341 * @return {Object} reference to the created class 342 */ 343 function UmbracoFormsConditions(form, pageButtonConditions, fieldsetConditions, fieldConditions, values) { 344
345 //our conditions "class" - must always be newed to work as it uses a form instance to operate on 346 //load all the information from the dom and serverside info and then the class will take care of the rest 347 348 var self = {}; 349 self.form = form; 350 self.pageButtonConditions = pageButtonConditions; 351 self.fieldsetConditions = fieldsetConditions; 352 self.fieldConditions = fieldConditions; 353 self.values = values; 354 self.dataTypes = {}; 355 356 //Iterates through all the form elements found on the page to update the registered value 357 function populateFieldValues(page, formValues, dataTypes) { 358 359 var selectFields = page.querySelectorAll("select"); 360 for (let i = 0; i < selectFields.length; i++) { 361 const field = selectFields[i]; 362 formValues[field.getAttribute("id")] = field.value ? field.querySelector("option[value='" + field.value.replace(/'/g, "\\'") + "']").innerText : null; 363 dataTypes[field.getAttribute("id")] = "select"; 364 } 365 366 var textareaFields = page.querySelectorAll("textarea"); 367 for (let i=0; i<textareaFields.length; i++) { 368 const field = textareaFields[i]; 369 formValues[field.getAttribute("id")] = field.value; 370 dataTypes[field.getAttribute("id")] = "textarea"; 371 } 372 373 // clear out all saved checkbox values to we can safely append 374 var checkboxFields = page.querySelectorAll("input[type=checkbox]"); 375 for (let i=0; i<checkboxFields.length; i++) { 376 const field = checkboxFields[i]; 377 formValues[field.getAttribute("name")] = null; 378 dataTypes[field.getAttribute("id")] = "checkbox"; 379 } 380 381 //$page.find("input").each(function () { 382 var inputFields = page.querySelectorAll("input"); 383 for (let i=0; i<inputFields.length; i++) { 384 const field = inputFields[i]; 385 386 if (field.getAttribute('type') === "text" || 387 field.getAttribute('type') === "number" || 388 field.getAttribute('type') === "email" || 389 field.getAttribute('type') === "url" || 390 field.getAttribute('type') === "tel" || 391 field.getAttribute('type') === "date" || 392 field.getAttribute('type') === "datetime-local" || 393 field.getAttribute("type") === "hidden") { 394 formValues[field.getAttribute("id")] = field.value; 395 dataTypes[field.getAttribute("id")] = "text"; 396 } 397 398 if (field.getAttribute('type') === "radio") { 399 if (field.matches(':checked')) { 400 formValues[field.getAttribute("name")] = field.value; 401 dataTypes[field.getAttribute("id")] = "radio"; 402 } 403 } 404 405 if (field.getAttribute("type") === "checkbox") { 406 if (field.getAttribute("id") !== field.getAttribute("name")) { 407 if (field.matches(":checked")) { 408 if (formValues[field.getAttribute("name")] === null) { 409 formValues[field.getAttribute("name")] = field.value; 410 } 411 else { 412 formValues[field.getAttribute("name")] += ";;" + field.value; 413 } 414 } 415 } 416 else { 417 formValues[field.getAttribute("name")] = (field.matches(":checked") ? "true" : "false"); 418 } 419 } 420 } 421 } 422 423 /* Public api */ 424 425 self.operators = { 426 Is: function (value, expected, dataType) { 427 if ((value || "") === expected) { 428 return true; 429 } 430 if (value == null) { 431 return (expected == value); 432 } 433 434 if (dataType === "checkbox") { 435 if (expected.toUpperCase() === "TRUE" || expected.toUpperCase() === "ON") { 436 expected = "true" 437 } else if (expected.toUpperCase() === "FALSE" || expected.toUpperCase() === "OFF") { 438 expected = "false" 439 } 440 } 441 442 var values = value.split(';;'); 443 var matchingExpected = values.filter( 444 function (o) { 445 return o === expected; 446 }); 447 return matchingExpected.length > 0;
448 }, 449 IsNot: function (value, unexpected, dataType) { 450 if (value == null) { 451 return (unexpected != value); 452 } 453 var values = value.split(';;'); 454 var matchingUnexpected = values.filter( 455 function (o) { 456 return o === unexpected; 457 }); 458 459 if (dataType === "checkbox") { 460 if (unexpected.toUpperCase() === "TRUE" || unexpected.toUpperCase() === "ON") { 461 unexpected = "true" 462 } else if (unexpected.toUpperCase() === "FALSE" || unexpected.toUpperCase() === "OFF") { 463 unexpected = "false" 464 } 465 } 466 return (value || "") !== unexpected && matchingUnexpected.length === 0; 467 }, 468 GreaterThen: function (value, limit) { 469 return parseInt(value) > parseInt(limit); 470 }, 471 LessThen: function (value, limit) { 472 return parseInt(value) < parseInt(limit); 473 }, 474 StartsWith: function (value, criteria) { 475 return value && value.indexOf(criteria) === 0; 476 }, 477 EndsWith: function (value, criteria) { 478 return value && value.indexOf(criteria) === value.length - criteria.length; 479 }, 480 Contains: function (value, criteria) { 481 return value && value.indexOf(criteria) > -1; 482 } 483 }; 484 485 self.watch = function () { 486 // This is a special case for pikaday 487 // The only way around to pickup the value, for now, is to 488 // subscribe to blur events 489 var datepickerfields = self.form.querySelectorAll('.datepickerfield'); 490 for(let i = 0; i < datepickerfields.length; i++) { 491 const field = datepickerfields[i]; 492 field.addEventListener('blur', function () { 493 if(this.value===""){ 494 // Here comes the hack 495 // Force the hidden datepicker field the datepicker field 496 var id = this.getAttribute("id"); 497 var hiddenDatePickerField = id.substr(0, id.length-2); 498 self.values[hiddenDatePickerField]=""; 499 document.getElementById(hiddenDatePickerField).value="";// sadly we cant use querySelector with current mark-up (would need to prefix IDs) 500 } 501 502 populateFieldValues(self.form, self.values, self.dataTypes); 503 //process the conditions 504 self.run(); 505 }.bind(field)); 506 } 507 //subscribe to change events 508 var changeablefields = self.form.querySelectorAll("input, textarea, select"); 509 for(let i = 0; i < changeablefields.length; i++) { 510 const field = changeablefields[i]; 511 field.addEventListener("change", function () { 512 populateFieldValues(self.form, self.values, self.dataTypes); 513 //process the conditions 514 self.run(); 515 }.bind(field)); 516 } 517 518 //register all values from the current fields on the page 519 populateFieldValues(self.form, self.values, self.dataTypes); 520 521 //the initial run-through of all the conditions 522 self.run(); 523 }; 524 525 self.run = function () { 526 var fsId, 527 fieldId, 528 529 /* 530 fsConditions = params.fsConditions || {}, 531 fieldConditions = params.fieldConditions || {}, 532 values = params.values || {},*/ 533 534 cachedResults = {}; 535 536 function evaluateRuleInstance(rule) { 537 var value = self.values[rule.field], 538 dataType = self.dataTypes[rule.field], 539 func = self.operators[rule.operator], 540 result = value !== null && func(value, rule.value, dataType); 541 return result; 542 } 543 544 function evaluateRule(rule) { 545 var dependencyIsVisible = true; 546 547 if (self.fieldConditions[rule.field]) { 548 dependencyIsVisible = isVisible(rule.field, self.fieldConditions[rule.field]); 549 } 550 551 if (dependencyIsVisible) { 552 return evaluateRuleInstance(rule); 553 } 554 else { 555 return false; 556 } 557 } 558 559 function evaluateCondition(id, condition) { 560 // This was once pretty. Now it needs refactoring again. :) 561 562 var any = condition.logicType === "Any", 563 all = condition.logicType === "All", 564 fieldsetVisibilities = {}, 565 hasHiddenFieldset = false, 566 success = true, 567 rule, 568 i; 569 570 // If we don't have any rules defined, we must return false (as neither 'any' nor 'all' of the conditions 571 // can be considered passing). 572 if (condition.rules.length === 0) { 573 return false; 574 } 575 576 for (i = 0; i < condition.rules.length; i++) { 577 rule = condition.rules[i]; 578 579 if (id === rule.field || id === rule.fieldsetId) { 580 throw new Error("Field or fieldset " + id + " has a condition on itself."); 581 } 582 583 if (fieldsetVisibilities[rule.fieldsetId] !== undefined) { 584 continue; 585 } 586 587 if (self.fieldsetConditions[rule.fieldsetId]) { 588 589 fieldsetVisibilities[rule.fieldsetId] = 590 isVisible(rule.fieldsetId, self.fieldsetConditions[rule.fieldsetId]); 591 592 if (!fieldsetVisibilities[rule.fieldsetId]) { 593 hasHiddenFieldset = true; 594 } 595 } 596 else { 597 fieldsetVisibilities[rule.fieldsetId] = true; 598 } 599 } 600 601 if (all && hasHiddenFieldset) { 602 return false; 603 } 604 605 for (i = 0; i < condition.rules.length; i++) { 606 rule = condition.rules[i]; 607 608 if (fieldsetVisibilities[rule.fieldsetId]) { 609 success = evaluateRule(condition.rules[i]); 610 } 611 else { 612 success = false; 613 } 614 615 if (any && success) { 616 break; 617 } 618 if (all && !success) { 619 break; 620 } 621 } 622 return success; 623 } 624 625 function evaluateConditionVisibility(id, condition) { 626 var show = condition.actionType === "Show", 627 cachedResult = cachedResults[id]; 628 629 var success; 630 if (cachedResult === undefined) { 631 cachedResults[id] = show; // set default value to avoid circular issues 632 success = (cachedResults[id] = evaluateCondition(id, condition)); 633 } else { 634 success = cachedResult; 635 } 636 637 var visible = !(success ^ show); 638 return visible; 639 } 640 641 function isVisible(id, condition) { 642 if (condition) { 643 return evaluateConditionVisibility(id, condition); 644 } 645 return true; 646 } 647 648 function handleCondition(element, id, condition) { 649 var shouldShow = isVisible(id, condition); 650 if (element) { 651 if (shouldShow) { 652 element.style.display = ""; 653 } 654 else { 655 element.style.display = "none"; 656 } 657 } 658 } 659 660 for (pageId in self.pageButtonConditions) { 661 if (Object.prototype.hasOwnProperty.call(self.pageButtonConditions, pageId)) { 662 var pageElem = document.getElementById(pageId); 663 if (pageElem) { 664 handleCondition(pageElem.querySelector("input[name='__next'], button[name='__next']"), fsId, self.pageButtonConditions[pageId], "Page"); 665 } 666 } 667 } 668 669 for (fsId in self.fieldsetConditions) { 670 if (Object.prototype.hasOwnProperty.call(self.fieldsetConditions, fsId)) { 671 handleCondition(document.getElementById(fsId), fsId, self.fieldsetConditions[fsId], "Fieldset");// sadly we cant use querySelector with current mark-up (would need to prefix IDs) 672 } 673 } 674 675 for (fieldId in self.fieldConditions) { 676 if (Object.prototype.hasOwnProperty.call(self.fieldConditions, fieldId)) { 677 if (document.getElementById(fieldId)) { 678 handleCondition(document.getElementById(fieldId).closest(".umbraco-forms-field"),// sadly we cant use querySelector with current mark-up (would need to prefix IDs) 679 fieldId, 680 self.fieldConditions[fieldId], 681 "Field"); 682 } 683 } 684 } 685 }; 686 687 return self; 688 } 689})();
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.