Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Listen for removed inputs and unregister them #67

Merged
merged 3 commits into from
Aug 15, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 71 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,13 +518,17 @@ export class ValidationService {
/**
* Scans document for all validation message <span> 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<HTMLElement>('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);
}
}
}

Expand All @@ -540,7 +544,11 @@ export class ValidationService {
let validationMessageElements = Array.from(form.querySelectorAll<HTMLElement>('[data-valmsg-for]'));

for (let span of validationMessageElements) {
this.pushValidationMessageSpan(form, span);
if (remove) {
this.removeValidationMessageSpan(form, span);
} else {
this.pushValidationMessageSpan(form, span);
}
}
}
}
Expand All @@ -557,6 +565,19 @@ 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] || (this.messageFor[name] = []);
LiteracyFanatic marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -649,7 +670,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);
}
Comment on lines +676 to +679
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree it's curious that inputUID doesn't seem to have been deleted, but guarding against undefined also doesn't both me.

}

let tasks = formValidators.map(factory => factory());
Expand Down Expand Up @@ -895,6 +919,20 @@ export class ValidationService {
this.elementEvents[formUID] = cb;
}

private untrackFormInput(form: HTMLFormElement, inputUID: string) {
let formUID = this.getElementUID(form);
if (!this.formInputs[formUID]) {
this.formInputs[formUID] = [];
}
LiteracyFanatic marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down Expand Up @@ -935,10 +973,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<ValidatableElement>(validatableSelector('[data-val="true"]')));

// querySelectorAll does not include the root element itself.
Expand All @@ -949,7 +999,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);
}
}
}

Expand Down Expand Up @@ -1210,10 +1265,10 @@ export class ValidationService {
/**
* Scans the provided root element for any validation directives and attaches behavior to them.
*/
scan(root: ParentNode) {
scan(root: ParentNode, remove: boolean = false) {
LiteracyFanatic marked this conversation as resolved.
Show resolved Hide resolved
this.logger.log('Scanning', root);
this.scanMessages(root);
this.scanInputs(root);
this.scanMessages(root, remove);
this.scanInputs(root, remove);
}

/**
Expand Down Expand Up @@ -1243,6 +1298,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.scan(node, true);
}
}
} else if (mutation.type === 'attributes') {
if (mutation.target instanceof HTMLElement) {
const oldValue = mutation.oldValue ?? '';
Expand Down