Skip to content
This repository has been archived by the owner on Mar 23, 2023. It is now read-only.

Commit

Permalink
v1.2.0 – Add support for both blur and keyup events. (#31)
Browse files Browse the repository at this point in the history
We've added f-validate to a new project, however there's a few things we need to add before we get feature parity with solution we're replacing.

The previous solution uses [jQuery Validate](https://github.com/jquery-validation/jquery-validation/). The default behaviour of this library is to delay validation until the input blur event has fired once. This provides an UX improvement of not seeing a validation error before the user has finished typing. This is most useful when the validation type is email or min-length. This PR attempts to recreate that logic.

A few things:

- I couldn't think of a better name than `hybridMode` which I appreciate isn't great 😄 
- I've tried to make minimal changes to the existing code
- There's not really any tests around any of this yet. If this approach looks OK, I'll add additional tests to cover these scenarios


## UI Review Checks

- [x] UI Documentation has been [updated]
- [x] JavaScript Tests have been [updated]
- [x] Module has been tested in Global Documentation

## Browsers Tested

- [x] Chrome
- [x] Edge
- [ ] Internet Explorer 11
- [x] Mobile (i.e. iPhone/Android - please list device)

### List any other browsers that this PR has been tested in:

- [View our supported browser list](https://fozzie.just-eat.com/documentation/general/browser-support)
  • Loading branch information
tompoole authored and ashleynolan committed Mar 21, 2019
1 parent e30114e commit 19ad2b8
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 3 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

v1.2.0
------------------------------
*March 21, 2019*

### Added
- Support for `hybridValidation` mode which binds both `keyup` and `blur` events


v1.1.0
------------------------------
Expand Down
28 changes: 28 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module.exports = {
moduleFileExtensions: [
'js',
'json',
'vue'
],

moduleNameMapper: {
'\\.(jpg|jpeg|png|gif||svg|)$': '<rootDir>/fileMock.js',
'\\.(css|scss)$': '<rootDir>/styleMock.js'
},

transform: {
'^.+\\.js$': 'babel-jest'
},

collectCoverageFrom: [
'assets/js/**/*.js',
'!assets/js/shims/**',
'!**/locales/**',
'!**/node_modules/**'
],

testURL: 'http://localhost/',

snapshotSerializers: [
]
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@justeat/f-validate",
"description": "Fozzie vanilla JS Validation Component",
"version": "1.1.0",
"version": "1.2.0",
"main": "dist/index.js",
"files": [
"dist"
Expand Down
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ export default {
escapeChars: /[|\\{}()[\]^$+*?.]/g,
fieldValues: `input, select, textarea, [${validationGroup}]`,
validationGroup,
blurredAttr: 'data-blurred',
validateOnOptions: ['blur', 'keyup']
};
45 changes: 43 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export const defaultOptions = {
successClass: CONSTANTS.cssClasses.hasSuccess,
focus: false,
groupErrorPlacement: false,
enableHTML5Validation: false
enableHTML5Validation: false,
hybridMode: false // setups both onblur and onkeydown events
};

const getForm = descriptor => {
Expand Down Expand Up @@ -95,6 +96,9 @@ export default class FormValidation {
if (this.options.validateOn) {
this.validateOn();
}
if (this.options.hybridMode) {
this.setupHybridValidate();
}

this.setFormNoValidate();
this.form.addEventListener('submit', this.isValid.bind(this));
Expand Down Expand Up @@ -150,7 +154,7 @@ export default class FormValidation {
* @param {object} currentElement
* @returns {boolean}
*/
isValid (event, currentElement) {
isValid (event, currentElement, eventType) {

let formValid = true;
this.errorMessages = [];
Expand All @@ -163,6 +167,11 @@ export default class FormValidation {
return;
}

// if hybrid validation is active, give the user a chance to input a value before we start validating
if (this.options.hybridMode && currentElement && eventType === 'keyup' && !field.hasAttribute(CONSTANTS.blurredAttr)) {
return;
}

let errorMessage = '';

// This needs to be set outside of the forEach loop, as otherwise only the final rule will apply the state
Expand Down Expand Up @@ -351,4 +360,36 @@ export default class FormValidation {
});
}

markFieldAsBlurred (field) {
if (!field.hasAttribute(CONSTANTS.blurredAttr)) {
field.setAttribute(CONSTANTS.blurredAttr, '');
}
}

/**
* Validates form field(s) on both blur and keyup events.
* Initial validate is delayed until user blurs to avoid untimely validation errors.
*
* example:
* this.validation = new FormValidation(this.form, {
* hybridMode: true
* });
*/
setupHybridValidate () {
if (this.options.groupErrorPlacement) {
throw new Error('f-validate: hybridMode cannot be used if errors are grouped');
}

if (this.options.validateOn) {
throw new Error('f-validate: hybridMode cannot be used with the validateOn option');
}

// 'hybridMode' listens to both keyup and blur events, delaying validation until the first blur event
this.fields.forEach(field => {
field.addEventListener('blur', this.isValid.bind(this, null, { field }, 'blur'));
field.addEventListener('blur', this.markFieldAsBlurred.bind(this, field));
field.addEventListener('keyup', this.isValid.bind(this, null, { field }, 'keyup'));
});
}

}
20 changes: 20 additions & 0 deletions test/__snapshots__/hybridMode.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`hybridMode should not validate on keydown before initial blur 1`] = `
"<form novalidate=\\"\\">
<input type=\\"email\\" name=\\"email\\">
</form>"
`;
exports[`hybridMode should validate on form submit 1`] = `
"<form novalidate=\\"\\" class=\\"has-error\\">
<input type=\\"text\\" required=\\"\\" class=\\"has-error\\"><p class=\\"form-error\\">This field is required.</p>
<input type=\\"email\\" name=\\"email\\" class=\\"has-error\\" data-blurred=\\"\\"><p class=\\"form-error\\">This field must contain a valid email address.</p>
</form>"
`;
exports[`hybridMode should validate on keydown after initial blur 1`] = `
"<form novalidate=\\"\\" class=\\"has-error\\">
<input type=\\"email\\" name=\\"email\\" class=\\"has-error\\" data-blurred=\\"\\"><p class=\\"form-error\\">This field must contain a valid email address.</p>
</form>"
`;
148 changes: 148 additions & 0 deletions test/hybridMode.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import TestUtils from 'js-test-buddy';
import FormValidation from '../src';

describe('hybridMode', () => {

it('should throw error if hyrbid mode is used with a validateOn event', () => {

// Arrange
TestUtils.setBodyHtml(`<form>
<input required value="x" />
<input required>
</form>`);
const form = document.querySelector('form');

// Act & Assert
expect(() => {
// eslint-disable-next-line no-new
new FormValidation(form, {
hybridMode: true,
validateOn: 'blur'
});
}).toThrowError('f-validate: hybridMode cannot be used with the validateOn option');

});

it('should throw error if hyrbid mode is used with grouped errors', () => {

// Arrange
TestUtils.setBodyHtml(`<form>
<input required value="x" />
<input required>
</form>`);
const form = document.querySelector('form');

// Act & Assert
expect(() => {
// eslint-disable-next-line no-new
new FormValidation(form, {
hybridMode: true,
groupErrorPlacement: true
});
}).toThrowError('f-validate: hybridMode cannot be used if errors are grouped');

});

it('should bind events if configuration is valid', () => {

// Arrange
TestUtils.setBodyHtml(`<form><input required value="x"></form>`);
const form = document.querySelector('form');
const input = form.querySelector('input');

const isValidSpy = jest.fn();
const markFieldAsBlurredSpy = jest.fn();

const validation = new FormValidation(form, {
hybridMode: true
});

validation.isValid = isValidSpy;
validation.markFieldAsBlurred = markFieldAsBlurredSpy;
validation.setupHybridValidate(); // need to re-run setup as functions have changed

// Act
TestUtils.dispatchEvent(input, 'keydown');
TestUtils.dispatchEvent(input, 'blur');

// Assert
expect(isValidSpy.mock.calls.length).toBe(1);
expect(markFieldAsBlurredSpy.mock.calls.length).toBe(1);
});

it('should not validate on keydown before initial blur', () => {

// Arrange
TestUtils.setBodyHtml(`<form>
<input type="email" name="email" />
</form>`);
const form = document.querySelector('form');
const input = form.querySelector('input');

// eslint-disable-next-line no-new
new FormValidation(form, {
hybridMode: true
});

// Act
input.value = 'not-a-valid-email';
TestUtils.dispatchEvent(input, 'keydown');

// Assert
const html = TestUtils.getBodyHtml();
expect(html).toMatchSnapshot();

});

it('should validate on keydown after initial blur', () => {

// Arrange
TestUtils.setBodyHtml(`<form>
<input type="email" name="email" />
</form>`);
const form = document.querySelector('form');
const input = form.querySelector('input');

// eslint-disable-next-line no-new
new FormValidation(form, {
hybridMode: true
});

// Act
input.value = 'not-a-valid-email';
TestUtils.dispatchEvent(input, 'keydown');
TestUtils.dispatchEvent(input, 'blur');
TestUtils.dispatchEvent(input, 'keydown');

// Assert
const html = TestUtils.getBodyHtml();
expect(html).toMatchSnapshot();

});

it('should validate on form submit', () => {

// Arrange
TestUtils.setBodyHtml(`<form>
<input type="text" required />
<input type="email" name="email" />
</form>`);
const form = document.querySelector('form');
const inputEmail = form.querySelector('input[type=email]');

// eslint-disable-next-line no-new
new FormValidation(form, {
hybridMode: true
});

// Act
inputEmail.value = 'not-a-valid-email';
TestUtils.dispatchEvent(inputEmail, 'blur');
TestUtils.dispatchEvent(form, 'submit');

// Assert
const html = TestUtils.getBodyHtml();
expect(html).toMatchSnapshot();
});

});

0 comments on commit 19ad2b8

Please sign in to comment.