Skip to content

Instantly share code, notes, and snippets.

@beseidel
Created July 20, 2019 19:51
Show Gist options
  • Save beseidel/c8574574347bd663c7ec7a4ac2bd8e02 to your computer and use it in GitHub Desktop.
Save beseidel/c8574574347bd663c7ec7a4ac2bd8e02 to your computer and use it in GitHub Desktop.
Labels in Input Animation, Form Validation
<form class="validate-form">
<div class="form-group">
<input type="email" class="form-control" id="exampleInputEmail1" placeholder="[email protected]" required title="please enter an email address">
<label for="exampleInputEmail1">Email address</label>
</div>
<div class="form-group">
<input type="password" class="form-control validate-form" id="examplePassword" placeholder="Numbers Letters Symbols" required >
<label for="examplePassword">Password</label>
</div>
</form>
(function ($) {
$.fn.scrupulous = function (args) {
//future homes for options as needed
var options = {
beforeSubmit: null, // pre validation processing
valid: null, //Pass a valid function through args
invalid: null, //Pass an invalid function through args
namespace : 'scrupulous',
errorClassName: 'error-message', //class name of the error label
parentClassName: 'form-group', //class name of the parent element where the error label is appended
defaultErrorMessage: 'This field has an error', //default error message if no title is provided
setErrorMessage: null // used to set custom HTML5 validationMessage
};
$.extend( options, args );
var $forms = this,
$inputs = $forms.find('select, input, textarea'),
emailPattern = "[^@]+@[^@]+\.[a-zA-Z]{2,6}",
browser = {},
$el,$form,$formGroup,elId,validity,errorMessage;
//stop everything if checkValidity does not exist, and I'm talking to you <= IE9.
if(typeof document.createElement( 'input' ).checkValidity != 'function') {
$forms.on('submit.' + options.namespace, function(e){
if(options.valid !== null) {
//e.preventDefault();
return options.valid.call(this);
}
else {
return true;
}
});
return false;
//stop everything else
}
//prevent calling scrupulous again.
if(this.hasClass('scrupulous')) {
return false;
}
$forms.addClass('scrupulous');
$forms.find('input[type="email"]').attr('pattern',emailPattern);
$forms.attr('novalidate',true); //set all forms to novalidate to avoid default browser validation
/*----------------------------------------------
equalTo(el);
function that checks if a value is equal to another value
based on the id value contained in data-equal-to attribue.
----------------------------------------------*/
var equalTo = function(el,parent){
var equalToParentId;
if ( typeof parent !== 'undefined'){
equalToParentId = parent;
}
else {
equalToParentId = $(el).attr('data-equal-to');
}
if($('#' + equalToParentId).length >= 0) {
// Compare to another input's value
if (el.value != $('#' + equalToParentId).val()) {
return false;
} else {
return true;
}
} else {
// Plain Object / Primitive evaluation
if (window.console){
console.log('No data-equal-to element defined.');
}
return (el.value == equalToParentId);
}
};
/*----------------------------------------------
luhnCheck(el)
Returns if an inputs value passes the standard
luhn check alg
----------------------------------------------*/
var luhnCheck = function(el){
var number = el.value;
var
arr = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9],
len = number.length,
bit = 1,
sum = 0,
val;
/** no way to validate a masked card, will have to let the backend handle it **/
if ( number.match(/^[xX*-]+\d{4}$/g) ){
return true;
}
while (len) {
val = parseInt(number.charAt(--len), 10);
sum += (bit ^= 1) ? arr[val] : val;
}
return sum && sum % 10 === 0;
};
/*----------------------------------------------
checkboxValidity(el)
function to check if any checkboxes/radios
are checked then validate that section of
the form
----------------------------------------------*/
var checkboxValidity = function(el){
var inputName = el.name,
isChecked = false;
$('input[name="' + inputName + '"]').each(function(){
if(this.checked === true){
isChecked = true;
}
});
if(!isChecked && el.required === true){
return false;
}
else {
return true;
}
};
/** for browsers that don't support input type = number **/
var numberTypeValidity = function($el){
var min,max,step,val=Number($el.val());
if(!$.isNumeric( $el.val())){
return false;
}
if (typeof $el.attr("max") !== 'undefined') {
max = Number($el.attr("max"));
if ( max < val ){
return false;
}
}
if (typeof $el.attr("min") !== 'undefined') {
min = Number($el.attr("min"));
if ( min > val ){
return false;
}
}
if ( typeof $el.attr("step") !== 'undefined' ){
step = Number($el.attr("step"));
if ( val % step !== 0 ){
return false;
}
}
return true;
};
var isNumberField = function($el){
return $el.is("input") && $el.attr("type") !== 'undefined' && $el.attr("type").toLowerCase() == "number";
};
/*----------------------------------------------
setValid($el)
function that removes all invalid classes and
error labels.
----------------------------------------------*/
var setValid = function($el) {
//dont validate on hidden inputs
if(!$el.is(':hidden')) {
$el.addClass('valid');
$el.removeClass('invalid');
$formGroup = $el.parents('.' + options.parentClassName);
//let Developer know that form-group does not exist
if($formGroup.length === 0) {
//no form group, check and see if we have an input group
$formGroup = $el.parents('.input-group');
if($formGroup.length === 0) {
/*
Don't think we need this on valid anymore.
if(window.console){
console.log('Warning: Scrupulous needs a .form-group, .input-group or parentClassName element to append errors. id: ' + $el.attr('id'));
}*/
return false;
}
}
$formGroup.addClass('has-success');
$formGroup.removeClass('has-error');
$formGroup.find('.' + options.errorClassName).remove();
}
};
/*----------------------------------------------
setInvalid($el)
function that addes invalid classes and appends
error message labels to the parent div.
----------------------------------------------*/
var setInvalid = function($el) {
//dont validate on hidden inputs
if(!$el.is(':hidden')) {
$el.addClass('invalid');
$el.removeClass('valid');
$formGroup = $el.parents('.' + options.parentClassName);
//let Developer know that form-group does not exist
if($formGroup.length === 0) {
//no form group, check and see if we have an input group
$formGroup = $el.parents('.input-group');
if($formGroup.length === 0) {
if(window.console){
console.log('Warning: Scrupulous needs a .form-group, .input-group or parentClassName element to append errors. id: ' + $el.attr('id'));
}
return false;
}
}
$formGroup.addClass('has-error');
$formGroup.removeClass('has-success');
var originalValidationMessage = $el[0].validationMessage;
if(options.setErrorMessage !== null){
options.setErrorMessage.apply(this, $el);
}
errorMessage = $el[0].validationMessage;
if (typeof errorMessage === 'undefined' || errorMessage.length === 0 || errorMessage === originalValidationMessage){
errorMessage = $el.attr('title');
}
if(errorMessage === undefined) {
errorMessage = options.defaultErrorMessage;
}
$el[0].setCustomValidity("");
//only append if there isn't one. helpful with radios and checkboxes
if($formGroup.find('.' + options.errorClassName).length === 0) {
$formGroup.append('<label class="' + options.errorClassName + ' inactive" for="' + $el.attr('id') + '">' + errorMessage + '</label>');
}
var t = setTimeout(function(){
$('.' + options.errorClassName).removeClass('inactive');
},10);
}
};
var validityChecker = function(el){
var elValidity = el.checkValidity();
$el = $(el);
//if it is an equal-to check status
if($(el).attr('data-equal-to') !== undefined){
elValidity = equalTo(el);
}
if($(el).attr('data-not-equal-to') !== undefined){
elValidity = !equalTo(el);
}
if($(el).attr('data-not-equal-to-with-base') !== undefined){
elValidity = elValidity && !equalTo(el,$(el).attr('data-not-equal-to-with-base'));
}
if($el.is(':checkbox') || $el.is(':radio')){
elValidity = checkboxValidity(el);
}
if ( ! browser.inputtype.number && isNumberField($(el)) ){
/** browser doesn't support number type **/
elValidity = numberTypeValidity($el);
}
if($(el).attr('data-creditcard') !== undefined){
if ( elValidity ) {
/** first see if html pattern matches, if so, do luhn check. otherwise return false **/
elValidity = luhnCheck( el );
}
}
if(elValidity === true){
setValid($el);
}
else {
setInvalid($el);
}
};
/**
* Load browser support for HTML5 elements.
*/
var loadInputTypeSupport = function(){
var types = "search,number,range,color,tel,url,email,date,month,week,time,datetime,datetime-local";
var typeArray = types.split(",");
var input = document.createElement( 'input' );
browser.inputtype = {};
for ( var a = 0; a < typeArray.length; a++ ){
input.setAttribute("type","text");
input.setAttribute("type",typeArray[a]);
if ( input.type !== 'text' ){
browser.inputtype[typeArray[a]] = true;
}
else {
browser.inputtype[typeArray[a]] = false;
}
}
};
loadInputTypeSupport();
$inputs.each(function(){
// bind Check Validity on blur for all inputs
$(this).on('blur.' + options.namespace, function(){
var $this = $(this);
// trim the value and save back to form input before continuing.
$this.val($.trim($this.val()));
if($this.attr('type') === 'number' && isNaN($this.val())){
//exist because letters in a number field register as a blank value
$this.val('');
}
if($this.val() !== '') {
validityChecker(this);
}
else {
//if the form is blank AND required we need to rip out the valid classes
if($this.is(':required')) {
$this.removeClass('valid').parentsUntil('form-group').removeClass('has-success');
}
}
});
if ( ! $(this).is("select") ) {
// if the input isn't a select, bind change keyup and mouseup
$( this ).on( 'change.' + options.namespace + ' keyup.' + options.namespace + ' mouseup.' + options.namespace, function(){validityEventCheck(this);} );
}
else {
// If we are a select, only bind change and input.. not the others, some browsers are cranky
$( this ).on( 'change.' + options.namespace + ' input.' + options.namespace, function(){validityEventCheck(this);} );
}
});
var validityEventCheck = function(el){
var elValidity = el.checkValidity();
$el = $( el );
if ( $el.is( ':disabled' ) !== true ) {
if ( $el.hasClass( 'fn-equal-to' ) ) {
elValidity = equalTo( this );
}
if ( $el.hasClass( 'fn-notequal-to' ) ) {
elValidity = ! equalTo( this );
}
if ( $el.is( ':checkbox' ) || $el.is( ':radio' ) ) {
elValidity = checkboxValidity( this );
}
if ( elValidity === true ) {
setValid( $el );
}
}
};
//Check Validity for all elements on submit
$forms.on('submit.' + options.namespace,function(e){
$form = $(this);
if(typeof options.beforeSubmit === "function") {
options.beforeSubmit.call(this);
}
$form.find('select, input, textarea').not(':disabled').each(function(){
validityChecker(this);
});
if($form.find('.has-error').length > 0){
//unsuccessful validation
var errorScrollTop = $form.find('.has-error:first').offset().top - 100;
if(errorScrollTop < $(window).scrollTop()) {
$("html, body").animate({ scrollTop: errorScrollTop }, 300);
}
$form.find('.has-error .invalid:first').focus();
//call the invalid callback, rely on that to return true or false to submit the form
if(options.invalid !== null) {
return options.invalid.call(this);
}
//prevent the form from submitting no matter what
e.preventDefault();
}
else {
//successful validation
//call the invalid callback, rely on that to return true or false to submit the form
if(options.valid !== null) {
return options.valid.call(this);
}
else {
return true;
}
}
return false;
});
};
})( jQuery );
$(function(){
$('.validate-form').scrupulous();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
form {
padding: 36px 48px;
max-width: 640px;
margin: auto;
}
.form-control {
height: 56px;
padding: 20px 13px 0 18px;
border: 3px solid #333;
border-radius: 0;
box-shadow: 1px 2px 3px rgba(50,50,50,.5);
margin-bottom: 18px;
}
.form-group {
position: relative;
}
.form-group label {
position: absolute;
left: 18px;
top: 15px;
transition: all .3s ease-out;
}
.form-control::-webkit-input-placeholder { /* Chrome/Opera/Safari */
color: white;
}
input.form-control:focus {
border-color: #144A6A;
box-shadow: 0 0 3px rgba(20,74,106,.67);
}
input.form-control:focus::-webkit-input-placeholder {
color: #ececec;
}
input.form-control:focus ~ label {
top: 10px;
left: 19px;
font-size: 10px;
line-height: 1;
color: #144A6A;
text-transform: uppercase;
font-weight: bold;
}
input.form-control:valid::-webkit-input-placeholder {
color: #ececec;
}
input.form-control:valid ~ label {
top: 10px;
left: 19px;
font-size: 10px;
line-height: 1;
color: #144A6A;
text-transform: uppercase;
font-weight: bold;
}
input.form-control.invalid {
/* color: #CD1622; */
border-color: #CD1622;
box-shadow: 0 1px 3px #CD1622;
}
input.form-control.invalid ~ label {
top: 10px;
left: 19px;
font-size: 10px;
line-height: 1;
color: #CD1622;
text-transform: uppercase;
font-weight: bold;
}
.form-group.has-error {
position: relative;
}
.form-group.has-error .error-message {
position: absolute;
top: -19px;
left: auto;
background: #CD1622;
color: white;
padding: 6px 9px;
right: 0;
z-index: 10;
}
.has-success .form-control {
border-color: #144A6A;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment