PageSourceSearch

https://www.welcometosheffield.co.uk/App_Plugins/UmbracoForms/Assets/themes/default/umbracoforms.js?v=13.9.7

js welcometosheffield.co.uk collected 2026-10-01 11:28:58 UTC 47,156 bytes, 1,184 lines download raw bytes

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

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.