Skip to content

Commit

Permalink
Refactor form validation
Browse files Browse the repository at this point in the history
**Why**: To consolidate the various client side methods we are using.
  • Loading branch information
hursey013 committed Aug 3, 2016
1 parent de4e411 commit a3cf4c1
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 95 deletions.
2 changes: 1 addition & 1 deletion app/assets/javascripts/app/form-field-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function formatForm() {
input.className += ' monospace';

/* eslint-disable no-new */
new Formatter(input, { pattern: ptrn, persistent: true });
new Formatter(input, { pattern: ptrn });
}
});
}
Expand Down
85 changes: 76 additions & 9 deletions app/assets/javascripts/app/form-validation.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,83 @@
import 'classlist.js';
import gf from 'gentleform';
import h5f from 'h5f';


function validateForm() {
const form = document.querySelector('form[novalidate]');
const validate = {
msgs: {
missing: 'Please fill in all required fields.',
mismatch: 'Please match the requested format.',
},

if (form) {
gf(form, function onSubmit(e) {
if (!this.isValid()) e.preventDefault();
init() {
this.form = document.querySelector('form');
if (!this.form) return;
this.btn = this.form.querySelector('[type=submit]');

h5f.setup(this.form, {
validClass: 'valid',
invalidClass: 'invalid',
requiredClass: 'required',
placeholderClass: 'placeholder',
});
}
}

this.addEvents();
},

addEvents() {
this.form.addEventListener('invalid', e => e.preventDefault(), true);
this.form.addEventListener('change', e => this.validateField(e.target));
this.form.addEventListener('submit', e => this.submitForm(e));
this.btn.addEventListener('click', () => this.validateForm());
},

submitForm(e) {
if (!this.form.checkValidity()) e.preventDefault();
},

validateForm() {
const fields = this.form.querySelectorAll('.field');
for (let i = 0; i < fields.length; i++) {
this.validateField(fields[i]);
}

// add focus to first invalid input
const invalidField = this.form.querySelector(':invalid');
if (invalidField) invalidField.focus();
},

validateField(f) {
f.classList.add('interacted');

const parent = f.parentNode;
const errorMsg = parent.querySelector('.error-message');

if (errorMsg !== null) parent.removeChild(errorMsg);

if (!f.validity.valid) this.addInvalidMarkup(f);
else this.removeInvalidMarkup(f);
},

addInvalidMarkup(f) {
f.setAttribute('aria-invalid', 'true');
f.setAttribute('aria-describedby', `alert_${f.id}`);

if (f.validity.valueMissing) f.setCustomValidity(this.msgs.missing);
else if (f.validity.typeMismatch) f.setCustomValidity(this.msgs.mismatch);
else f.setCustomValidity('');

f.insertAdjacentHTML(
'afterend',
`<div role='alert' class='error-message red h5' id='alert_${f.id}'>
${f.validationMessage}
</div>`
);
},

removeInvalidMarkup(f) {
f.removeAttribute('aria-invalid');
f.removeAttribute('aria-describedby');
},
};


document.addEventListener('DOMContentLoaded', validateForm);
document.addEventListener('DOMContentLoaded', () => validate.init());
26 changes: 0 additions & 26 deletions app/assets/javascripts/app/utils.js

This file was deleted.

1 change: 0 additions & 1 deletion app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'app/utils';
import 'app/pw-toggle';
import 'app/form-validation';
import 'app/form-field-format';
3 changes: 2 additions & 1 deletion app/assets/stylesheets/components/_form.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ $radio-checkbox-space: 1.5rem;
font-weight: $bold-font-weight;
}

.is-invalid {
.invalid,
.interacted.required:invalid {
border-color: $red;
}

Expand Down
38 changes: 4 additions & 34 deletions app/helpers/form_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# rubocop:disable ModuleLength
# TODO(sbc): Refactor to address rubocop warning
# :reek:DataClump
module FormHelper
def app_setting_value_field_for(app_setting, f)
if app_setting.boolean?
Expand All @@ -11,46 +8,19 @@ def app_setting_value_field_for(app_setting, f)
end

def block_text_field_tag(name, value, options = {})
text_field_tag(name, value, options.merge(class: 'block col-12 mb2 field')) +
form_input_error_messages(name, options)
text_field_tag(name, value, options.merge(class: 'block col-12 field'))
end

def block_date_field_tag(name, value, options = {})
date_field_tag(name, value, options.merge(class: 'block col-12 mb2 field')) +
form_input_error_messages(name, options)
date_field_tag(name, value, options.merge(class: 'block col-12 field'))
end

# rubocop:disable MethodLength
# TODO(sbc): Refactor to address rubocop warning
def form_input_error_messages(name, options = {})
content_tag(:div, nil, class: 'bold red mb2', data: { 'errors-for' => name }) do
if options[:required]
concat content_tag(
:div,
t('forms.value_missing'),
style: 'display: none',
data: { 'errors-when' => 'valueMissing' }
)
end
if options[:pattern]
concat content_tag(
:div,
options[:'data-custom-message'] || t('forms.pattern_mismatch'),
style: 'display: none',
data: { 'errors-when' => 'patternMismatch' }
)
end
end
end
# rubocop:enable MethodLength

def us_states_territories_select_tag(options = {})
select_tag(
'state',
options_for_select(us_states_territories),
options.merge(class: 'block col-12 mb2 field')
) +
form_input_error_messages('state', options)
options.merge(class: 'block col-12 field')
)
end

# rubocop:disable MethodLength, WordArray
Expand Down
33 changes: 18 additions & 15 deletions app/views/idv/sessions/index.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,40 @@
| Do not use real personal information. This is for demo purposes only.

.mb3.h6.caps.red = t('idv.titles.welcome')
= form_tag(idv_sessions_path, method: 'post', novalidate: true)
= form_tag(idv_sessions_path, method: 'post')
h2.heading = t('idv.titles.session.basic')
p Please provide all of the following:
.mb4.sm-col-7
= label_tag 'first_name', t('idv.form.first_name')
= block_text_field_tag 'first_name', nil, required: true
= label_tag 'last_name', t('idv.form.last_name')
= block_text_field_tag 'last_name', nil, required: true
.mb2
= label_tag 'first_name', t('idv.form.first_name')
= block_text_field_tag 'first_name', nil, required: true
.mb2
= label_tag 'last_name', t('idv.form.last_name')
= block_text_field_tag 'last_name', nil, required: true
h2.heading = t('idv.titles.session.dob')
.mb4.sm-col-5
= label_tag 'dob', t('idv.form.dob'), class: 'hide'
= block_date_field_tag 'dob', nil, required: true
h2.heading = t('idv.titles.session.ssn')
.mb4.sm-col-5
= label_tag 'ssn', t('idv.form.ssn'), class: 'hide'
= block_text_field_tag 'ssn', nil, required: true, pattern: '[0-9]*', \
'data-custom-message': t('idv.errors.invalid_ssn')
= block_text_field_tag 'ssn', nil, required: true, pattern: '[0-9]*'
h2.heading = t('idv.titles.session.address')
.mb4.sm-col-7
= label_tag 'address1', t('idv.form.address1')
= block_text_field_tag 'address1', nil, required: true
= label_tag 'address2', t('idv.form.address2')
= block_text_field_tag 'address2', nil
= label_tag 'city', t('idv.form.city')
= block_text_field_tag 'city', nil, required: true
.mb2
= label_tag 'address1', t('idv.form.address1')
= block_text_field_tag 'address1', nil, required: true
.mb2
= label_tag 'address2', t('idv.form.address2')
= block_text_field_tag 'address2', nil
.mb2
= label_tag 'city', t('idv.form.city')
= block_text_field_tag 'city', nil, required: true
.clearfix.mxn1
.sm-col.sm-col-6.px1
= label_tag 'state', t('idv.form.state')
= us_states_territories_select_tag required: true
.sm-col.sm-col-6.px1
= label_tag 'zipcode', t('idv.form.zipcode')
= block_text_field_tag 'zipcode', nil, required: true, pattern: '[0-9]*', \
'data-custom-message': t('idv.errors.invalid_zipcode')
= block_text_field_tag 'zipcode', nil, required: true, pattern: '[0-9]*'
button type='submit' class='btn btn-primary' = 'Continue verifying'
5 changes: 0 additions & 5 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ en:
time you log in you’ll need to open the application and retrieve a code.
confirmation:
show_hdr: Create a Password
value_missing: This field is required
pattern_mismatch: Please enter information in the correct format

links:
resend: Resend
Expand Down Expand Up @@ -149,9 +147,6 @@ en:
city: City
state: State
zipcode: ZIP Code
errors:
invalid_ssn: 'Please enter a valid SSN in the format of 123-45-6789'
invalid_zipcode: 'Please enter a valid Zip Code in the format of 20006'
titles:
intro: Help us identify you
expectations: We need some information from you
Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
"dependencies": {
"classlist.js": "^1.1.20150312",
"formatter.js-pebble": "^0.1.8",
"gentleform": "^2.0.1",
"jquery": "^2.2.3",
"h5f": "^1.1.1",
"zxcvbn": "^4.3.0"
},
"devDependencies": {
Expand All @@ -26,4 +25,4 @@
"pa11y-crawl": "^0.2.3",
"pa11y-reporter-full-json": "^0.0.1"
}
}
}

0 comments on commit a3cf4c1

Please sign in to comment.