diff --git a/src/index.ts b/src/index.ts index 2713e30..876ec75 100644 --- a/src/index.ts +++ b/src/index.ts @@ -518,13 +518,17 @@ export class ValidationService { /** * Scans document for all validation message generated by ASP.NET Core MVC, then tracks them. */ - private scanMessages(root: ParentNode) { + private scanMessages(root: ParentNode, remove: boolean = false) { /* If a validation span explicitly declares a form, we group the span with that form. */ let validationMessageElements = Array.from(root.querySelectorAll('span[form]')); for (let span of validationMessageElements) { let form = document.getElementById(span.getAttribute('form')); if (form) { - this.pushValidationMessageSpan(form, span); + if (remove) { + this.removeValidationMessageSpan(form, span); + } else { + this.pushValidationMessageSpan(form, span); + } } } @@ -540,7 +544,11 @@ export class ValidationService { let validationMessageElements = Array.from(form.querySelectorAll('[data-valmsg-for]')); for (let span of validationMessageElements) { - this.pushValidationMessageSpan(form, span); + if (remove) { + this.removeValidationMessageSpan(form, span); + } else { + this.pushValidationMessageSpan(form, span); + } } } } @@ -557,6 +565,22 @@ export class ValidationService { } } + private removeValidationMessageSpan(form: HTMLElement, span: HTMLElement) { + let formId = this.getElementUID(form); + let name = `${formId}:${span.getAttribute('data-valmsg-for')}`; + let spans = this.messageFor[name]; + if (!spans) { + return; + } + let index = spans.indexOf(span); + if (index >= 0) { + spans.splice(index, 1); + } + else { + this.logger.log("Validation element for '%s' was already removed", name, span); + } + } + /** * Given attribute map for an HTML input, returns the validation directives to be executed. * @param attributes @@ -649,7 +673,10 @@ export class ValidationService { for (let i = 0; i < formInputUIDs.length; i++) { let inputUID = formInputUIDs[i]; - formValidators.push(this.validators[inputUID]); + const validator = this.validators[inputUID]; + if (validator) { + formValidators.push(validator); + } } let tasks = formValidators.map(factory => factory()); @@ -895,6 +922,20 @@ export class ValidationService { this.elementEvents[formUID] = cb; } + private untrackFormInput(form: HTMLFormElement, inputUID: string) { + let formUID = this.getElementUID(form); + if (!this.formInputs[formUID]) { + return; + } + let indexToRemove = this.formInputs[formUID].indexOf(inputUID); + if (indexToRemove >= 0) { + this.formInputs[formUID].splice(indexToRemove, 1); + } + else { + this.logger.log("Form input for UID '%s' was already removed", inputUID); + } + } + /** * Adds an input element to be managed and validated by the service. * Triggers a debounced live validation when input value changes. @@ -935,10 +976,22 @@ export class ValidationService { this.elementEvents[uid] = cb; } + removeInput(input: ValidatableElement) { + let uid = this.getElementUID(input); + + delete this.summary[uid]; + delete this.elementEvents[uid]; + delete this.validators[uid]; + + if (input.form) { + this.untrackFormInput(input.form, uid); + } + } + /** * Scans the entire document for input elements to be validated. */ - private scanInputs(root: ParentNode) { + private scanInputs(root: ParentNode, remove: boolean = false) { let inputs = Array.from(root.querySelectorAll(validatableSelector('[data-val="true"]'))); // querySelectorAll does not include the root element itself. @@ -949,7 +1002,12 @@ export class ValidationService { for (let i = 0; i < inputs.length; i++) { let input = inputs[i]; - this.addInput(input); + if (remove) { + this.removeInput(input); + } + else { + this.addInput(input); + } } } @@ -1216,6 +1274,15 @@ export class ValidationService { this.scanInputs(root); } + /** + * Scans the provided root element for any validation directives and removes behavior from them. + */ + remove(root: ParentNode) { + this.logger.log('Removing', root); + this.scanMessages(root, true); + this.scanInputs(root, true); + } + /** * Watches the provided root element for mutations, and scans for new validation directives to attach behavior. * @param root The root element to use, defaults to the document.documentElement. @@ -1243,6 +1310,13 @@ export class ValidationService { this.scan(node); } } + for (let i = 0; i < mutation.removedNodes.length; i++) { + let node = mutation.removedNodes[i]; + this.logger.log('Removed node', node); + if (node instanceof HTMLElement) { + this.remove(node); + } + } } else if (mutation.type === 'attributes') { if (mutation.target instanceof HTMLElement) { const oldValue = mutation.oldValue ?? '';