A Pen by Chuks Festus on CodePen.
Created
July 25, 2017 14:01
-
-
Save ChuksFestus/4139cb4cdc90dacfa7f8e25461cf4a3b to your computer and use it in GitHub Desktop.
Frontend form validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="container"> | |
<form class="registration" id="registration"> | |
<h1>Registration Form</h1> | |
<label for="username"> | |
<span>Username</span> | |
<input type="text" id="username" minlength="3" required> | |
<ul class="input-requirements"> | |
<li>At least 3 characters long</li> | |
<li>Must only contain letters and numbers (no special characters)</li> | |
</ul> | |
</label> | |
<label for="password"> | |
<span>Password</span> | |
<input type="password" id="password" maxlength="100" minlength="8" required> | |
<ul class="input-requirements"> | |
<li>At least 8 characters long (and less than 100 characters)</li> | |
<li>Contains at least 1 number</li> | |
<li>Contains at least 1 lowercase letter</li> | |
<li>Contains at least 1 uppercase letter</li> | |
<li>Contains a special character (e.g. @ !)</li> | |
</ul> | |
</label> | |
<label for="password_repeat"> | |
<span>Repeat Password</span> | |
<input type="password" id="password_repeat" maxlength="100" minlength="8" required> | |
</label> | |
<br> | |
<input type="submit"> | |
</form> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ---------------------------- | |
CustomValidation prototype | |
- Keeps track of the list of invalidity messages for this input | |
- Keeps track of what validity checks need to be performed for this input | |
- Performs the validity checks and sends feedback to the front end | |
---------------------------- */ | |
function CustomValidation(input) { | |
this.invalidities = []; | |
this.validityChecks = []; | |
//add reference to the input node | |
this.inputNode = input; | |
//trigger method to attach the listener | |
this.registerListener(); | |
} | |
CustomValidation.prototype = { | |
addInvalidity: function(message) { | |
this.invalidities.push(message); | |
}, | |
getInvalidities: function() { | |
return this.invalidities.join('. \n'); | |
}, | |
checkValidity: function(input) { | |
for (var i = 0; i < this.validityChecks.length; i++) { | |
var isInvalid = this.validityChecks[i].isInvalid(input); | |
if (isInvalid) { | |
this.addInvalidity(this.validityChecks[i].invalidityMessage); | |
} | |
var requirementElement = this.validityChecks[i].element; | |
if (requirementElement) { | |
if (isInvalid) { | |
requirementElement.classList.add('invalid'); | |
requirementElement.classList.remove('valid'); | |
} else { | |
requirementElement.classList.remove('invalid'); | |
requirementElement.classList.add('valid'); | |
} | |
} // end if requirementElement | |
} // end for | |
}, | |
checkInput: function() { // checkInput now encapsulated | |
this.inputNode.CustomValidation.invalidities = []; | |
this.checkValidity(this.inputNode); | |
if (this.inputNode.CustomValidation.invalidities.length === 0 && this.inputNode.value !== '') { | |
this.inputNode.setCustomValidity(''); | |
} else { | |
var message = this.inputNode.CustomValidation.getInvalidities(); | |
this.inputNode.setCustomValidity(message); | |
} | |
}, | |
registerListener: function() { //register the listener here | |
var CustomValidation = this; | |
this.inputNode.addEventListener('keyup', function() { | |
CustomValidation.checkInput(); | |
}); | |
} | |
}; | |
/* ---------------------------- | |
Validity Checks | |
The arrays of validity checks for each input | |
Comprised of three things | |
1. isInvalid() - the function to determine if the input fulfills a particular requirement | |
2. invalidityMessage - the error message to display if the field is invalid | |
3. element - The element that states the requirement | |
---------------------------- */ | |
var usernameValidityChecks = [{ | |
isInvalid: function(input) { | |
return input.value.length < 3; | |
}, | |
invalidityMessage: 'This input needs to be at least 3 characters', | |
element: document.querySelector('label[for="username"] .input-requirements li:nth-child(1)') | |
}, { | |
isInvalid: function(input) { | |
var illegalCharacters = input.value.match(/[^a-zA-Z0-9]/g); | |
return illegalCharacters ? true : false; | |
}, | |
invalidityMessage: 'Only letters and numbers are allowed', | |
element: document.querySelector('label[for="username"] .input-requirements li:nth-child(2)') | |
}]; | |
var passwordValidityChecks = [{ | |
isInvalid: function(input) { | |
return input.value.length < 8 | input.value.length > 100; | |
}, | |
invalidityMessage: 'This input needs to be between 8 and 100 characters', | |
element: document.querySelector('label[for="password"] .input-requirements li:nth-child(1)') | |
}, { | |
isInvalid: function(input) { | |
return !input.value.match(/[0-9]/g); | |
}, | |
invalidityMessage: 'At least 1 number is required', | |
element: document.querySelector('label[for="password"] .input-requirements li:nth-child(2)') | |
}, { | |
isInvalid: function(input) { | |
return !input.value.match(/[a-z]/g); | |
}, | |
invalidityMessage: 'At least 1 lowercase letter is required', | |
element: document.querySelector('label[for="password"] .input-requirements li:nth-child(3)') | |
}, { | |
isInvalid: function(input) { | |
return !input.value.match(/[A-Z]/g); | |
}, | |
invalidityMessage: 'At least 1 uppercase letter is required', | |
element: document.querySelector('label[for="password"] .input-requirements li:nth-child(4)') | |
}, { | |
isInvalid: function(input) { | |
return !input.value.match(/[\!\@\#\$\%\^\&\*]/g); | |
}, | |
invalidityMessage: 'You need one of the required special characters', | |
element: document.querySelector('label[for="password"] .input-requirements li:nth-child(5)') | |
}]; | |
var passwordRepeatValidityChecks = [{ | |
isInvalid: function() { | |
return passwordRepeatInput.value != passwordInput.value; | |
}, | |
invalidityMessage: 'This password needs to match the first one' | |
}]; | |
/* ---------------------------- | |
Setup CustomValidation | |
Setup the CustomValidation prototype for each input | |
Also sets which array of validity checks to use for that input | |
---------------------------- */ | |
var usernameInput = document.getElementById('username'); | |
var passwordInput = document.getElementById('password'); | |
var passwordRepeatInput = document.getElementById('password_repeat'); | |
usernameInput.CustomValidation = new CustomValidation(usernameInput); | |
usernameInput.CustomValidation.validityChecks = usernameValidityChecks; | |
passwordInput.CustomValidation = new CustomValidation(passwordInput); | |
passwordInput.CustomValidation.validityChecks = passwordValidityChecks; | |
passwordRepeatInput.CustomValidation = new CustomValidation(passwordRepeatInput); | |
passwordRepeatInput.CustomValidation.validityChecks = passwordRepeatValidityChecks; | |
/* ---------------------------- | |
Event Listeners | |
---------------------------- */ | |
var inputs = document.querySelectorAll('input:not([type="submit"])'); | |
var submit = document.querySelector('input[type="submit"'); | |
var form = document.getElementById('registration'); | |
function validate() { | |
for (var i = 0; i < inputs.length; i++) { | |
inputs[i].CustomValidation.checkInput(); | |
} | |
} | |
submit.addEventListener('click', validate); | |
form.addEventListener('submit', validate); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* FormHack v1.2.0 (formhack.io) */ | |
/* Config ----------------------------- */ | |
:root { | |
/* Font */ | |
--fh-font-family: 'Source Sans Pro', sans-serif; | |
--fh-font-size: 15px; | |
--fh-font-color: rgb(40, 40, 40); | |
/* Borders */ | |
--fh-border-radius: 3px; | |
--fh-border-width: 1px; | |
--fh-border-style: solid; | |
--fh-border-color: rgb(200, 200, 200); | |
/* Inputs, Textareas, Select, Option */ | |
--fh-input-height: 40px; | |
--fh-input-width: 100%; | |
--fh-input-max-width: 400px; | |
--fh-input-bg-color: rgb(250,250,250); | |
--fh-focus-bg-color: var(--fh-input-bg-color); | |
--fh-focus-border-color: #000; | |
--fh-focus-font-color: var(--fh-font-color); | |
/* Select Vendor Styling */ | |
--fh-select-vendor-styling: none; /* comment this out to maintain vendor styling */ | |
/* Buttons & Input Submits */ | |
--fh-button-height: 40px; | |
--fh-button-width: 100%; | |
--fh-button-max-width: 200px; | |
--fh-button-font-color: var(--fh-font-color); | |
--fh-button-bg-color: rgba(255, 219, 58,0.3); | |
--fh-button-hover-bg-color: rgb(255, 219, 58); | |
--fh-button-hover-font-color: var(--fh-font-color); | |
/* Layout */ | |
--fh-layout-display: block; | |
--fh-layout-margin: 10px auto; /* change to "10px auto" to center */ | |
--fh-layout-text-align: center; | |
} | |
/* Global Reset Styles ------------------ */ | |
input, | |
textarea, | |
select, | |
option, | |
optgroup, | |
button, | |
legend, | |
fieldset { | |
box-sizing: border-box; | |
outline: none; | |
font-family: var(--fh-font-family); | |
font-size: var(--fh-font-size); | |
color: var(--fh-font-color); | |
vertical-align: top; | |
display: var(--fh-layout-display); | |
margin: var(--fh-layout-margin); | |
text-align: var(--fh-layout-text-align); | |
} | |
datalist { | |
font-family: var(--fh-font-family); | |
font-size: var(--fh-font-size); | |
} | |
label { | |
display: block; | |
margin: var(--fh-layout-margin); | |
text-align: var(--fh-layout-text-align); | |
margin-bottom: 20px; | |
position: relative; | |
padding: 0 20px; | |
} | |
/* Input & Textarea ------------------ */ | |
/* Fields with standard width */ | |
input[type="text"], | |
input[type="email"], | |
input[type="password"], | |
input[type="search"], | |
input[type="color"], | |
input[type="date"], | |
input[type="datetime-local"], | |
input[type="month"], | |
input[type="number"], | |
input[type="tel"], | |
input[type="time"], | |
input[type="url"], | |
input[type="week"], | |
input[list], | |
input[type="file"], | |
select, | |
textarea { | |
width: var(--fh-input-width); | |
max-width: var(--fh-input-max-width); | |
padding: calc( var(--fh-input-height) / 5 ); | |
background-color: var(--fh-input-bg-color); | |
border-radius: var(--fh-border-radius); | |
border-width: var(--fh-border-width); | |
border-style: var(--fh-border-style); | |
border-color: var(--fh-border-color); | |
} | |
/* Fields with standard height */ | |
input[type="text"], | |
input[type="email"], | |
input[type="password"], | |
input[type="search"], | |
input[type="color"], | |
input[type="date"], | |
input[type="datetime-local"], | |
input[type="month"], | |
input[type="number"], | |
input[type="tel"], | |
input[type="time"], | |
input[type="url"], | |
input[type="week"], | |
input[list] { | |
height: var(--fh-input-height); | |
-webkit-appearance: none; | |
} | |
/* Other */ | |
textarea { | |
-webkit-appearance: none; | |
overflow: auto; | |
} | |
input[type="range"] { | |
height: var(--fh-input-height); | |
width: var(--fh-input-width); | |
max-width: var(--fh-input-max-width); | |
} | |
input[type="file"] { | |
min-height: var(--fh-input-height); | |
} | |
input[type="search"] { | |
height: var(--fh-input-height); | |
-webkit-appearance: none; | |
} | |
input[type="search"]::-webkit-search-cancel-button, | |
input[type="search"]::-webkit-search-decoration { | |
-webkit-appearance: none; | |
} | |
input[type="checkbox"], | |
input[type="radio"] { | |
display: inline-block; | |
vertical-align: middle; | |
} | |
/* For checkbox and radio to be centered, need to wrap the input and label in a span - | |
/* .checkbox-container { | |
/* display: block; | |
/* text-align: center; | |
/* } | |
/* Select ------------------ */ | |
select { | |
height: var(--fh-input-height); | |
-webkit-appearance: var(--fh-select-vendor-styling, menulist); | |
-moz-appearance: var(--fh-select-vendor-styling, menulist); | |
-ms-appearance: var(--fh-select-vendor-styling, menulist); | |
-o-appearance: var(--fh-select-vendor-styling, menulist); | |
} | |
select[multiple] { | |
height: auto; | |
min-height: var(--fh-input-height); | |
padding: 0; | |
} | |
select[multiple] option { | |
margin: 0; | |
padding: calc( var(--fh-input-height) / 5 ); | |
} | |
/* Fieldset ------------------ */ | |
fieldset { | |
padding: 0; | |
border: 0; | |
} | |
legend { | |
padding: 0; | |
font-weight: inherit; | |
} | |
/* Buttons, Input Type Submit/Reset ------------------ */ | |
button, | |
input[type="button"], | |
input[type="submit"], | |
input[type="reset"], | |
input[type="image"] { | |
height: var(--fh-button-height); | |
width: var(--fh-button-width); | |
max-width: var(--fh-button-max-width); | |
background-color: var(--fh-button-bg-color); | |
padding: calc( var(--fh-input-height) / 5 ); | |
cursor: pointer; | |
color: var(--fh-button-font-color); | |
font-weight: 700; | |
-webkit-appearance: none; | |
-moz-appearance: none; | |
border-radius: var(--fh-border-radius); | |
border-width: var(--fh-border-width); | |
border-style: var(--fh-border-style); | |
border-color: var(--fh-border-color); | |
box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.2); | |
} | |
input[type="image"] { | |
text-align: center; | |
padding: calc( var(--fh-input-height) / 5 ); | |
} | |
/* States ------------------ */ | |
input[disabled], | |
textarea[disabled], | |
select[disabled], | |
option[disabled], | |
button[disabled] { | |
cursor: not-allowed; | |
} | |
input:focus, | |
textarea:focus, | |
select:focus, | |
option:focus, | |
button:focus { | |
background-color: var(--fh-focus-bg-color); | |
border-color: var(--fh-focus-border-color); | |
} | |
input[type="checkbox"]:focus, | |
input[type="radio"]:focus { | |
outline: var(--fh-focus-border-color) solid 2px; | |
} | |
button:hover, | |
input[type="button"]:hover, | |
input[type="submit"]:hover, | |
input[type="reset"]:hover, | |
button:focus, | |
input[type="button"]:focus, | |
input[type="submit"]:focus, | |
input[type="reset"]:focus { | |
background-color: var(--fh-button-hover-bg-color); | |
color: var(--fh-button-hover-font-color); | |
} | |
/* Custom ------------------ */ | |
input:not([type="submit"]):valid { | |
border-color: #2ecc71; | |
} | |
/* Hide and show related .input-requirements when interacting with input */ | |
input:not([type="submit"]) + .input-requirements { | |
overflow: hidden; | |
max-height: 0; | |
transition: max-height 1s ease-out; | |
} | |
input:not([type="submit"]):hover + .input-requirements, | |
input:not([type="submit"]):focus + .input-requirements, | |
input:not([type="submit"]):active + .input-requirements { | |
max-height: 1000px; /* any large number (bigger then the .input-requirements list) */ | |
transition: max-height 1s ease-in; | |
} | |
/* RESET */ | |
html, | |
body, | |
div, | |
span, | |
applet, | |
object, | |
iframe, | |
h1, | |
h2, | |
h3, | |
h4, | |
h5, | |
h6, | |
p, | |
blockquote, | |
pre, | |
a, | |
abbr, | |
acronym, | |
address, | |
big, | |
cite, | |
code, | |
del, | |
dfn, | |
em, | |
img, | |
ins, | |
kbd, | |
q, | |
s, | |
samp, | |
small, | |
strike, | |
strong, | |
sub, | |
sup, | |
tt, | |
var, | |
b, | |
u, | |
i, | |
center, | |
dl, | |
dt, | |
dd, | |
ol, | |
ul, | |
li, | |
fieldset, | |
form, | |
label, | |
legend, | |
table, | |
caption, | |
tbody, | |
tfoot, | |
thead, | |
tr, | |
th, | |
td, | |
article, | |
aside, | |
canvas, | |
details, | |
embed, | |
figure, | |
figcaption, | |
footer, | |
header, | |
hgroup, | |
menu, | |
nav, | |
output, | |
ruby, | |
section, | |
summary, | |
time, | |
mark, | |
audio, | |
video { | |
margin: 0; | |
padding: 0; | |
border: 0; | |
font: inherit; | |
font-size: 100%; | |
vertical-align: baseline; | |
} | |
article, | |
aside, | |
details, | |
figcaption, | |
figure, | |
footer, | |
header, | |
hgroup, | |
menu, | |
nav, | |
section { | |
display: block; | |
} | |
ul { | |
list-style: none; | |
} | |
blockquote, | |
q { | |
quotes: none; | |
} | |
blockquote:before, | |
blockquote:after, | |
q:before, | |
q:after { | |
content: ''; | |
content: none; | |
} | |
table { | |
border-collapse: collapse; | |
border-spacing: 0; | |
} | |
html { | |
box-sizing: border-box; | |
} | |
*, | |
*:before, | |
*:after { | |
box-sizing: inherit; | |
} | |
html { | |
font-size: 62.5%; | |
} | |
body { | |
font-size: 1.6rem; | |
line-height: 1.6; | |
font-family: 'Source Sans Pro'; | |
background-color: rgb(245, 245, 245); | |
} | |
h1 { | |
font-size: 2.5rem; | |
padding: 15px 20px; | |
background-color: rgba(255, 219, 58, 0.3); | |
margin-bottom: 20px; | |
text-align: center; | |
} | |
a { | |
text-decoration: underline; | |
color: inherit; | |
} | |
.container { | |
margin: 80px auto; | |
width: 90%; | |
max-width: 800px; | |
} | |
.registration { | |
background-color: #fff; | |
max-width: 600px; | |
margin: 0 auto; | |
padding-bottom: 20px; | |
box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.3); | |
border-bottom: 5px solid #ffdb3a; | |
} | |
.input-requirements { | |
font-size: 1.3rem; | |
font-style: italic; | |
text-align: left; | |
list-style: disc; | |
list-style-position: inside; | |
max-width: 400px; | |
margin: 10px auto; | |
color: rgb(150, 150, 150); | |
} | |
.input-requirements li.invalid { | |
color: #e74c3c; | |
} | |
.input-requirements li.valid { | |
color: #2ecc71; | |
} | |
.input-requirements li.valid:after { | |
display: inline-block; | |
padding-left: 10px; | |
content: "\2713"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment