I have a set of checkboxes, and validation such that at least one of those checkboxes need to be checked on submit. But I want to be able to set a validation error if the user blurs out of the set of checkboxes. The problem is that if I do a blur for the last one it doesn't work because they could be shift-tabbing to the second from last checkbox. I've tried to but the onblur in the fieldset tag, but that didn't trigger a blur at all. Is this just a limitation that can't be overcome with plain html and vanilla JS?
CodePudding user response:
You can look at what the next element that is focused is and determine if they are under the same grouping.
document.querySelectorAll('input[type="checkbox"]').forEach((elem) => {
elem.addEventListener("blur", function (event) {
const nextElem = event.relatedTarget;
const fieldSet = elem.closest('fieldset');
const isValid = fieldSet.querySelector("input:checked") !== null;
if (nextElem?.closest('fieldset') !== fieldSet) {
fieldSet.classList.toggle("error", !isValid);
} else if (isValid) {
fieldSet.classList.remove("error");
}
});
});
.error {
color: red;
}
<form>
<fieldset>
<legend>Pizza One</legend>
<input type="checkbox" name="x1-1" id="x1-1">
<label for="x1-1">Cheese</label>
<input type="checkbox" name="x1-2" id="x1-2">
<label for="x1-2">Peppers</label>
<input type="checkbox" name="x1-3" id="x1-3">
<label for="x1=3">Mushrooms</label>
</fieldset>
<fieldset>
<legend>Pizza Two</legend>
<input type="checkbox" name="x2-1" id="x2-1">
<label for="x2-1">Cheese</label>
<input type="checkbox" name="x2-2" id="x2-2">
<label for="x2-2">Peppers</label>
<input type="checkbox" name="x2-3" id="x2-3">
<label for="x2-3">Mushrooms</label>
</fieldset>
<input type="submit" />
</form>
Adding in a trick to use HTML5 validation
document.querySelectorAll('input[type="checkbox"]').forEach((elem) => {
elem.addEventListener("blur", function(event) {
const nextElem = event.relatedTarget;
const fieldSet = elem.closest('fieldset');
const isValid = fieldSet.querySelector("input:checked") !== null;
if (nextElem?.closest('fieldset') !== fieldSet) {
fieldSet.classList.toggle("error", !isValid);
}
});
elem.addEventListener("change", function(event) {
const fieldSet = elem.closest('fieldset');
const isValid = fieldSet.querySelector("input:checked") !== null;
if (isValid) {
fieldSet.classList.remove("error");
fieldSet.querySelectorAll("input").forEach((cb) => {
cb.removeAttribute("required");
});
} else {
fieldSet.querySelectorAll("input").forEach((cb) => {
cb.setAttribute("required", "required");
});
}
});
const changeEvt = document.createEvent("HTMLEvents");
changeEvt.initEvent("change", false, true);
elem.dispatchEvent(changeEvt);
});
.error {
color: red;
}
<form>
<fieldset>
<legend>Pizza One</legend>
<input type="checkbox" name="x1-1" id="x1-1">
<label for="x1-1">Cheese</label>
<input type="checkbox" name="x1-2" id="x1-2">
<label for="x1-2">Peppers</label>
<input type="checkbox" name="x1-3" id="x1-3">
<label for="x1=3">Mushrooms</label>
</fieldset>
<fieldset>
<legend>Pizza Two</legend>
<input type="checkbox" name="x2-1" id="x2-1">
<label for="x2-1">Cheese</label>
<input type="checkbox" name="x2-2" id="x2-2">
<label for="x2-2">Peppers</label>
<input type="checkbox" name="x2-3" id="x2-3">
<label for="x2-3">Mushrooms</label>
</fieldset>
<input type="submit" />
</form>
CodePudding user response:
Based on epascarello's suggestion I was able to create a function that provided similar functionality to the one provided. I have an error span that's underneath the checkboxes. I wanted behavior that would immediately remove that if any checkbox was checked, but only add the error span if the user blurred out of the entire set of checkboxes. Note that there is only one fieldset on this page, and the error span has an specific id I could reference. Here's the function that I got working:
document.querySelectorAll('input[type="checkbox"]').forEach((elem) => {
elem.addEventListener("blur", function (event) {
var errorSpan = document.getElementById("checkboxesErrorSpan");
var isValid = document.querySelector("fieldset").querySelectorAll("input:checked").length > 0;
if (isValid) {
if ((errorSpan.style.display == "inline")) {
errorSpan.style.display = "none";
}
}
else {
if (event.relatedTarget.type != "checkbox") {
if ((errorSpan.style.display == "none")) {
errorSpan.style.display = "inline";
}
}
}
});
});