* @returns {Object} jQuery object with the selector.
*/
findFormError($el) {
- var id = $el[0].id;
+ var id = $el.length ? $el[0].id : '';
var $error = $el.siblings(this.options.formErrorSelector);
if (!$error.length) {
return $(labels);
}
+ /**
+ * Get the set of labels associated with a set of checkbox els in this order
+ * 2. The <label> with the attribute `[for="someInputId"]`
+ * 3. The `.closest()` <label>
+ *
+ * @param {Object} $el - jQuery object to check for required attribute
+ * @returns {Boolean} Boolean value depends on whether or not attribute is checked or empty
+ */
+ findCheckboxLabels($els) {
+ var labels = $els.map((i, el) => {
+ var id = el.id;
+ var $label = this.$element.find(`label[for="${id}"]`);
+
+ if (!$label.length) {
+ $label = $(el).closest('label');
+ }
+ return $label[0];
+ });
+
+ return $(labels);
+ }
+
/**
* Adds the CSS error class as specified by the Abide settings to the label, input, and the form
* @param {Object} $el - jQuery object to add the class to
}
+ /**
+ * Remove CSS error classes etc from an entire checkbox group
+ * @param {String} groupName - A string that specifies the name of a checkbox group
+ *
+ */
+ removeCheckboxErrorClasses(groupName) {
+ var $els = this.$element.find(`:checkbox[name="${groupName}"]`);
+ var $labels = this.findCheckboxLabels($els);
+ var $formErrors = this.findFormError($els);
+
+ if ($labels.length) {
+ $labels.removeClass(this.options.labelErrorClass);
+ }
+
+ if ($formErrors.length) {
+ $formErrors.removeClass(this.options.formErrorClass);
+ }
+
+ $els.removeClass(this.options.inputErrorClass).attr({
+ 'data-invalid': null,
+ 'aria-invalid': null
+ });
+
+ }
+
/**
* Removes CSS error class as specified by the Abide settings from the label, input, and the form
* @param {Object} $el - jQuery object to remove the class from
if($el[0].type == 'radio') {
return this.removeRadioErrorClasses($el.attr('name'));
}
+ // checkboxes need to clear all of the els
+ else if($el[0].type == 'checkbox') {
+ return this.removeCheckboxErrorClasses($el.attr('name'));
+ }
var $label = this.findLabel($el);
var $formError = this.findFormError($el);
break;
case 'checkbox':
- validated = clearRequire;
+ validated = this.validateCheckbox($el.attr('name'));
+ clearRequire = true;
break;
case 'select':
validateForm() {
var acc = [];
var _this = this;
+ var checkboxGroupName;
+
+ // Remember first form submission to prevent specific checkbox validation (more than one required) until form got initially submitted
+ if (!this.initialized) {
+ this.initialized = true;
+ }
this.$inputs.each(function() {
+
+ // Only use one checkbox per group since validateCheckbox() iterates over all associated checkboxes
+ if ($(this)[0].type === 'checkbox') {
+ if ($(this).attr('name') === checkboxGroupName) return true;
+ checkboxGroupName = $(this).attr('name');
+ }
+
acc.push(_this.validateInput($(this)));
});
return valid;
}
+ /**
+ * Determines whether or a not a checkbox input is valid based on whether or not it is required and checked. Although the function targets a single `<input>`, it validates by checking the `required` and `checked` properties of all checkboxes in its group.
+ * @param {String} groupName - A string that specifies the name of a checkbox group
+ * @returns {Boolean} Boolean value depends on whether or not at least one checkbox input has been checked (if it's required)
+ */
+ validateCheckbox(groupName) {
+ // If at least one checkbox in the group has the `required` attribute, the group is considered required
+ // Per W3C spec, all checkboxes in a group should have `required`, but we're being nice
+ var $group = this.$element.find(`:checkbox[name="${groupName}"]`);
+ var valid = false, required = false, minRequired = 1, checked = 0;
+
+ // For the group to be required, at least one checkbox needs to be required
+ $group.each((i, e) => {
+ if ($(e).attr('required')) {
+ required = true;
+ }
+ });
+ if(!required) valid=true;
+
+ if (!valid) {
+ // Count checked checkboxes within the group
+ // Use data-min-required if available (default: 1)
+ $group.each((i, e) => {
+ if ($(e).prop('checked')) {
+ checked++;
+ }
+ if (typeof $(e).attr('data-min-required') !== 'undefined') {
+ minRequired = parseInt($(e).attr('data-min-required'));
+ }
+ });
+
+ // For the group to be valid, the minRequired amount of checkboxes have to be checked
+ if (checked >= minRequired) {
+ valid = true;
+ }
+ };
+
+ // Skip validation if more than 1 checkbox have to be checked AND if the form hasn't got submitted yet (otherwise it will already show an error during the first fill in)
+ if (this.initialized !== true && minRequired > 1) {
+ return true;
+ }
+
+ // Refresh error class for all input
+ $group.each((i, e) => {
+ if (!valid) {
+ this.addErrorClasses($(e));
+ } else {
+ this.removeErrorClasses($(e));
+ }
+ });
+
+ return valid;
+ }
+
/**
* Determines if a selected input passes a custom validation function. Multiple validations can be used, if passed to the element with `data-validator="foo bar baz"` in a space separated listed.
* @param {Object} $el - jQuery input element.
});
it('returns true for checked checkboxes', function() {
- $html = $("<form data-abide><input type='checkbox' required checked='checked'></form>").appendTo("body");
+ $html = $("<form data-abide novalidate><input type='checkbox' name='groupName' required checked='checked'></form>").appendTo("body");
plugin = new Foundation.Abide($html, {});
plugin.validateInput($html.find("input")).should.equal(true);
});
it('returns false for unchecked checkboxes', function() {
- $html = $("<form data-abide><input type='checkbox' required></form>").appendTo("body");
+ $html = $("<form data-abide novalidate><input type='checkbox' name='groupName' required></form>").appendTo("body");
plugin = new Foundation.Abide($html, {});
plugin.validateInput($html.find("input")).should.equal(false);
});
});
+ describe('removeCheckboxErrorClasses()', function() {
+ it('removes aria-invalid attribute from checkbox group', function() {
+ $html = $('<form data-abide><input type="checkbox" name="groupName"></form>').appendTo('body');
+ plugin = new Foundation.Abide($html, {});
+ // Add error classes first
+ plugin.addErrorClasses($html.find('input'));
+
+ plugin.removeCheckboxErrorClasses('groupName');
+
+ $html.find('input').should.not.have.attr('aria-invalid')
+ });
+ });
+
describe('resetForm()', function() {
it('removes aria-invalid attribute from elements', function() {
$html = $('<form data-abide><input type="text"></form>').appendTo('body');
--- /dev/null
+<!doctype html>
+<!--[if IE 9]><html class="lt-ie10" lang="en" > <![endif]-->
+<html class="no-js" lang="en" dir="ltr">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>Foundation for Sites Testing</title>
+ <link href="../assets/css/foundation.css" rel="stylesheet" />
+ </head>
+ <body>
+ <div class="grid-container">
+ <div class="grid-x grid-padding-x">
+ <div class="cell">
+ <h1>Abide Checkboxes</h1>
+
+ <p>This form has required checkboxes. If you try to submit without picking one, it
+ should show an error. When you then pick one, the error should clear and let you submit.</p>
+ <form id="form" data-abide novalidate>
+ <div class="alert callout hide" data-abide-error>
+ <p>This form has errors.</p>
+ </div>
+ <fieldset>
+ <legend>Fieldset Label</legend>
+ <input required type="checkbox" name="example1" value="yes" id="example1Yes" />
+ <label for="example1Yes">Yes</label>
+ <input required type="checkbox" name="example1" value="no" id="example1No" />
+ <label for="example1No">No</label>
+ </fieldset>
+ <button class="button" type="submit">Submit</button>
+ <button class="button" type="reset">Reset</button>
+ </form>
+
+ <hr>
+
+ <p>This form has <strong>one</strong> required checkbox. If you try to submit without picking one, it
+ should show an error. When you then pick one, the error should clear and let you submit.</p>
+ <form id="form" data-abide novalidate>
+ <div class="alert callout hide" data-abide-error>
+ <p>This form has errors.</p>
+ </div>
+ <fieldset>
+ <legend>Fieldset Label</legend>
+ <input type="checkbox" name="example3" value="yes" id="example3Yes" />
+ <label for="example3Yes">Yes</label>
+ <input required type="checkbox" name="example3" value="no" id="example3No" />
+ <label for="example3No">No</label>
+ <input type="checkbox" name="example3" value="maybe" id="example3Maybe" />
+ <label for="example3Maybe">Maybe</label>
+ </fieldset>
+ <button class="button" type="submit">Submit</button>
+ <button class="button" type="reset">Reset</button>
+ </form>
+
+ <hr>
+
+ <p>This form has optional checkboxes. It should let you submit with or without picking one.</p>
+ <form id="form" data-abide novalidate>
+ <div class="alert callout hide" data-abide-error>
+ <p>This form has errors.</p>
+ </div>
+ <fieldset>
+ <legend>Fieldset Label</legend>
+ <input type="checkbox" name="example2" value="yes" id="example2Yes" />
+ <label for="example2Yes">Yes</label>
+ <input type="checkbox" name="example2" value="no" id="example2No" />
+ <label for="example2No">No</label>
+ </fieldset>
+ <button class="button" type="submit">Submit</button>
+ <button class="button" type="reset">Reset</button>
+ </form>
+
+ <hr>
+
+ <p>This form has a required checkbox with a custom value for <strong>data-min-required</strong> that specifies how many checkboxes must be checked in the group. If you try to submit without checking at least 3 checkboxes, it
+ should show an error. When you then pick the required amount, the error should clear and let you submit.</p>
+ <form id="form" data-abide novalidate>
+ <div class="alert callout hide" data-abide-error>
+ <p>This form has errors.</p>
+ </div>
+ <fieldset>
+ <legend>Fieldset Label</legend>
+ <input type="checkbox" name="example4" value="yes" id="example4Yes" />
+ <label for="example4Yes">Yes</label>
+ <input required data-min-required="3" type="checkbox" name="example4" value="no" id="example4No" />
+ <label for="example4No">No</label>
+ <input type="checkbox" name="example4" value="maybe" id="example4Maybe" />
+ <label for="example4Maybe">Maybe</label>
+ <input type="checkbox" name="example4" value="some" id="example4Some" />
+ <label for="example4Some">Some</label>
+ <input type="checkbox" name="example4" value="more" id="example4More" />
+ <label for="example4More">More</label>
+ </fieldset>
+ <button class="button" type="submit">Submit</button>
+ <button class="button" type="reset">Reset</button>
+ </form>
+
+ <hr>
+ </div>
+ </div>
+ </div>
+
+ <script src="../assets/js/vendor.js"></script>
+ <script src="../assets/js/foundation.js"></script>
+ <script>
+ $(document).foundation();
+ </script>
+ </body>
\ No newline at end of file