Skip to content

Instantly share code, notes, and snippets.

@ChuksFestus
Created July 25, 2017 14:01
Show Gist options
  • Save ChuksFestus/4139cb4cdc90dacfa7f8e25461cf4a3b to your computer and use it in GitHub Desktop.
Save ChuksFestus/4139cb4cdc90dacfa7f8e25461cf4a3b to your computer and use it in GitHub Desktop.
Frontend form validation
<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>
/* ----------------------------
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);
/* 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