Skip to content

Commit

Permalink
Merge pull request #11698 from soylent/feat/validator-error-messages
Browse files Browse the repository at this point in the history
feat(Abide): add validator-specific error messages
  • Loading branch information
joeworkman committed Feb 3, 2020
2 parents 8c51e69 + 5eac681 commit 5a41377
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 25 deletions.
22 changes: 22 additions & 0 deletions docs/pages/abide.md
Expand Up @@ -168,6 +168,28 @@ When the Form Errors cannot be placed next to its field, like in an Input Group,
</form>
```

You can specify validator-specific error messages using `[data-form-error-on]`
attribute, for example:

- `data-form-error-on="required"`
- `data-form-error-on="pattern"`
- `data-form-error-on="equalTo"`
- `data-form-error-on="your_custom_validator"`

```html_example
<form data-abide novalidate>
<label>Email Required
<input type="text" required pattern="email">
<span class="form-error" data-form-error-on="required">
Yo, you had better fill this out, it's required.
</span>
<span class="form-error" data-form-error-on="pattern">
Invalid Email
</span>
</label>
<button class="button" type="submit" value="Submit">Submit</button>
</form>
```

## Initial State

Expand Down
64 changes: 39 additions & 25 deletions js/foundation.abide.js
Expand Up @@ -173,9 +173,10 @@ class Abide extends Plugin {
* This allows for multiple form errors per input, though if none are found, no form errors will be shown.
*
* @param {Object} $el - jQuery object to use as reference to find the form error selector.
* @param {String[]} [failedValidators] - List of failed validators.
* @returns {Object} jQuery object with the selector.
*/
findFormError($el) {
findFormError($el, failedValidators) {
var id = $el.length ? $el[0].id : '';
var $error = $el.siblings(this.options.formErrorSelector);

Expand All @@ -187,6 +188,15 @@ class Abide extends Plugin {
$error = $error.add(this.$element.find(`[data-form-error-for="${id}"]`));
}

if (!!failedValidators) {
$error = $error.not('[data-form-error-on]')

failedValidators.forEach((v) => {
$error = $error.add($el.siblings(`[data-form-error-on="${v}"]`));
$error = $error.add(this.$element.find(`[data-form-error-for="${id}"][data-form-error-on="${v}"]`));
});
}

return $error;
}

Expand Down Expand Up @@ -256,10 +266,11 @@ class Abide extends Plugin {
/**
* 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
* @param {String[]} [failedValidators] - List of failed validators.
*/
addErrorClasses($el) {
addErrorClasses($el, failedValidators) {
var $label = this.findLabel($el);
var $formError = this.findFormError($el);
var $formError = this.findFormError($el, failedValidators);

if ($label.length) {
$label.addClass(this.options.labelErrorClass);
Expand Down Expand Up @@ -422,10 +433,9 @@ class Abide extends Plugin {
*/
validateInput($el) {
var clearRequire = this.requiredCheck($el),
validated = false,
customValidator = true,
validator = $el.attr('data-validator'),
equalTo = true;
failedValidators = [],
manageErrorClasses = true;

// skip validation if disabled
if (this._validationIsDisabled()) {
Expand All @@ -439,34 +449,39 @@ class Abide extends Plugin {

switch ($el[0].type) {
case 'radio':
validated = this.validateRadio($el.attr('name'));
this.validateRadio($el.attr('name')) || failedValidators.push('required');
break;

case 'checkbox':
validated = this.validateCheckbox($el.attr('name'));
clearRequire = true;
this.validateCheckbox($el.attr('name')) || failedValidators.push('required');
// validateCheckbox() adds/removes error classes
manageErrorClasses = false;
break;

case 'select':
case 'select-one':
case 'select-multiple':
validated = clearRequire;
clearRequire || failedValidators.push('required');
break;

default:
validated = this.validateText($el);
clearRequire || failedValidators.push('required');
this.validateText($el) || failedValidators.push('pattern');
}

if (validator) {
customValidator = this.matchValidation($el, validator, $el.attr('required'));
const required = $el.attr('required') ? true : false;

validator.split(' ').forEach((v) => {
this.options.validators[v]($el, required, $el.parent()) || failedValidators.push(v);
});
}

if ($el.attr('data-equalto')) {
equalTo = this.options.validators.equalTo($el);
this.options.validators.equalTo($el) || failedValidators.push('equalTo');
}


var goodToGo = [clearRequire, validated, customValidator, equalTo].indexOf(false) === -1;
var goodToGo = failedValidators.length === 0;
var message = (goodToGo ? 'valid' : 'invalid') + '.zf.abide';

if (goodToGo) {
Expand All @@ -482,7 +497,13 @@ class Abide extends Plugin {
}
}

this[goodToGo ? 'removeErrorClasses' : 'addErrorClasses']($el);
if (manageErrorClasses) {
this.removeErrorClasses($el);

if (!goodToGo) {
this.addErrorClasses($el, failedValidators);
}
}

/**
* Fires when the input is done checking for validation. Event trigger is either `valid.zf.abide` or `invalid.zf.abide`
Expand Down Expand Up @@ -559,7 +580,7 @@ class Abide extends Plugin {
// A pattern can be passed to this function, or it will be infered from the input's "pattern" attribute, or it's "type" attribute
pattern = (pattern || $el.attr('data-pattern') || $el.attr('pattern') || $el.attr('type'));
var inputText = $el.val();
var valid = false;
var valid = true;

if (inputText.length) {
// If the pattern attribute on the element is in Abide's list of patterns, then test that regexp
Expand All @@ -570,13 +591,6 @@ class Abide extends Plugin {
else if (pattern !== $el.attr('type')) {
valid = new RegExp(pattern).test(inputText);
}
else {
valid = true;
}
}
// An empty field is valid if it's not required
else if (!$el.prop('required')) {
valid = true;
}

return valid;
Expand Down Expand Up @@ -658,7 +672,7 @@ class Abide extends Plugin {
// Refresh error class for all input
$group.each((i, e) => {
if (!valid) {
this.addErrorClasses($(e));
this.addErrorClasses($(e), ['required']);
} else {
this.removeErrorClasses($(e));
}
Expand Down
120 changes: 120 additions & 0 deletions test/visual/abide/error-messages.html
@@ -0,0 +1,120 @@
<!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">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<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: Error Messages</h1>

<p>This form has two different error messages.</p>

<form data-abide novalidate>
<label>
<input required type="email" placeholder="Email Required">
<span class="form-error" data-form-error-on="required">Required</span>
<span class="form-error" data-form-error-on="pattern">Invalid</span>
</label>
<button type="submit" class="button">Submit</button>
<button type="reset" class="button">Reset</button>
</form>

<hr>

<p>This form has two different error messages and uses the [data-form-error-for] attribute to specify the target input.</p>

<form data-abide novalidate>
<label for="url">Url
<input required id="url" type="url" placeholder="Url Required">
</label>
<span class="form-error" data-form-error-for="url" data-form-error-on="required">Required</span>
<span class="form-error" data-form-error-for="url" data-form-error-on="pattern">Invalid</span>
<button type="submit" class="button">Submit</button>
<button type="reset" class="button">Reset</button>
</form>

<hr>

<p>This form has one generic and one validator-specific error message.</p>

<form data-abide novalidate>
<label>
<input required type="email" placeholder="Email Required">
<span class="form-error">Error</span>
<span class="form-error" data-form-error-on="pattern">Invalid</span>
</label>
<button type="submit" class="button">Submit</button>
<button type="reset" class="button">Reset</button>
</form>

<hr>

<p>This form uses the equalTo validator.</p>

<form data-abide novalidate>
<label>
<input required id="email" type="email" placeholder="Email Required">
<span class="form-error">Error</span>
</label>
</label>
<input type="email" placeholder="Email Confirmation" data-equalto="email">
<span class="form-error" data-form-error-on="pattern">Invalid</span>
<span class="form-error" data-form-error-on="equalTo">Mismatch</span>
</label>
<button type="submit" class="button">Submit</button>
<button type="reset" class="button">Reset</button>
</form>

<hr>

<p>This form has two input fields.</p>

<form data-abide novalidate>
<label>Email
<input required type="email" placeholder="Email Required">
<span class="form-error" data-form-error-on="required">Required</span>
<span class="form-error" data-form-error-on="pattern">Invalid</span>
</label>
<label>Url
<input required type="url" placeholder="Url Required">
<span class="form-error" data-form-error-on="required">Required</span>
<span class="form-error" data-form-error-on="pattern">Invalid</span>
</label>
<button type="submit" class="button">Submit</button>
<button type="reset" class="button">Reset</button>
</form>

<hr>

<p>This form uses a custom validator.</p>

<form data-abide novalidate>
<label>
<input required type="number" placeholder="Positive Number Required" data-validator="positive">
<span class="form-error" data-form-error-on="required">Required</span>
<span class="form-error" data-form-error-on="positive">Must be positive</span>
</label>
<button type="submit" class="button">Submit</button>
<button type="reset" class="button">Reset</button>
</form>
</div>
</div>
</div>

<script src="../assets/js/vendor.js"></script>
<script src="../assets/js/foundation.js"></script>
<script>
Foundation.Abide.defaults.validators['positive'] = function($el) {
return parseInt($el.val()) > 0;
}
$(document).foundation();
</script>
</body>
</html>

0 comments on commit 5a41377

Please sign in to comment.