Last active
October 20, 2016 15:07
-
-
Save bowheart/590fa7c295e2733277a1 to your computer and use it in GitHub Desktop.
When the user is filling out input fields, it's nice to have some visual feedback. This library performs non-strict input validation. It highlights fields red or green and animates a little 'x' or checkmark in from the right. This helps the user know if they made a mistake and encourages them to keep going.
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
/** | |
* Another library by Joshua Claunch | |
* https://github.com/bowheart | |
* https://gist.github.com/bowheart | |
* | |
* A jQuery-dependent form feedback library. | |
*/ | |
$(function() { | |
var fontIsLoaded = function(fontName, content, callback) { | |
callback || (callback = content) && (content = ''); | |
var testSpan = $('<span>' + (content || 'mmmmwwww') + '</span>'); | |
testSpan.css({ | |
fontFamily: 'arial', | |
fontSize: '100px', | |
position: 'absolute', | |
top: '-999%', | |
left: '-999%', | |
visibility: 'hidden' | |
}); | |
$('body').append(testSpan); | |
var testSize = testSpan.width(); | |
testSpan.css('fontFamily', fontName + ', arial'); | |
var counter = 0; | |
var interval = window.setInterval(function() { | |
var resultSize = testSpan.width(); | |
var isLoaded = testSize !== resultSize; | |
if (++counter >= 20 || isLoaded) { | |
testSpan.remove(); | |
window.clearInterval(interval); | |
callback(isLoaded); | |
} | |
}, 50); | |
}; | |
// Make sure font-awesome gets loaded | |
fontIsLoaded('FontAwesome', 'aa<i class="fa-times"></i>', function(isLoaded) { | |
if (isLoaded) return; | |
$('head').append('<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">'); | |
}); | |
var defaultConfig = { | |
selector: 'form input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]):not([type="hidden"]), form select', | |
colors: { | |
invalid: '#d44', | |
invalidLight: '#fff1f1', | |
valid: '#4d4', | |
validLight: '#f1fff1' | |
}, | |
animationDistance: 20, | |
borderWidth: 1, | |
markerSize: 16, | |
offset: 0, | |
overrideSelects: true, | |
validation: { | |
creditcard: function(val) { return val.indexOf('_') === -1; }, | |
cvv: function(val) { return val.length === 3 || val.length === 4; }, | |
email: function(val) { return /^[A-Z0-9\.\-_\+]+@([A-Z0-9\-]+\.)+[A-Z]{2,4}$/.test(val.toUpperCase()); }, | |
phone: function(val) { return val.indexOf('_') === -1 && val.length >=10; }, | |
zip: (function() { | |
var length = Number($('input[name="zip"], input[name="billingZip"]').first().attr('maxlength')); | |
return function(val) { return val.length === length || val.length >= 6; }; | |
})() | |
} | |
}; | |
// Allow user to define custom config (window.confirmationConfig). Put that into the default config: | |
var config = $.extend(true, {}, defaultConfig, window.confirmationConfig); | |
// Point the global config object to the combined config object now -- Allow user to change values later | |
//window.confirmationConfig = config; // not implementing for now. | |
var css = '.valid-marker { color: ' + config.colors.valid + '; font-size: ' + config.markerSize + 'px; opacity: 0; position: absolute; top: 0; right: 0; }' | |
+ '.valid-marker i { display: inline-block; vertical-align: middle; }' | |
+ '.valid-marker:after { content: ""; display: inline-block; height: 100%; vertical-align: middle; }' | |
+ '.invalid-input {' | |
+ 'border-color: ' + config.colors.invalid + ' !important;' | |
+ 'border-style: solid !important;' | |
+ 'box-shadow: inset 0 0 0 200px ' + config.colors.invalidLight + ', 0 0 0 ' + config.borderWidth + 'px ' + config.colors.invalid + '; }' | |
+ '.invalid-input + .valid-marker { color: ' + config.colors.invalid + '; }' | |
+ '.valid-input {' | |
+ 'border-color: ' + config.colors.valid + ' !important;' | |
+ 'border-style: solid !important;' | |
+ 'box-shadow: inset 0 0 0 200px ' + config.colors.validLight + ', 0 0 0 ' + config.borderWidth + 'px ' + config.colors.valid + '; }'; | |
var blob = new Blob([css], {type: 'text/css'}); | |
$('head').append('<link rel="stylesheet" href="' + window.URL.createObjectURL(blob) + '">'); | |
var Confirmer = function(el) { | |
var self = this; | |
self.el = el; | |
self.$el = $(el); | |
if (self.el.tagName === 'INPUT') { | |
self.$el.blur(function() { | |
self.handleBlur(); | |
}); | |
} else if (self.el.tagName === 'SELECT') { | |
self.$el.focusout(function() { | |
self.handleBlur(); | |
self.$el.change(function() { | |
self.$el.trigger('focusout'); | |
}) | |
}); | |
} | |
self.$el.keypress(function(event) { | |
if (event.which !== 13) return; // We're only handling the enter key | |
this.tagName === 'INPUT' ? self.$el.trigger('blur') : self.$el.trigger('focusout'); | |
}); | |
self.$el | |
.on('validate', self.validate.bind(self)) | |
.on('markValid', self.markValid.bind(self)) | |
.on('markInvalid', self.markInvalid.bind(self)); | |
}; | |
Confirmer.prototype = { | |
// Check if input is valid, mark the field accordingly | |
handleBlur: function() { | |
this.validate(); | |
var inputs = this.$el.parents('form').find('input, select'); | |
inputs.each(function() { | |
if ($(this).val()) $(this).trigger('validate'); | |
}); | |
}, | |
// Apply class '.invalid-input' to this element, animate a little x in from the right | |
markInvalid: function() { | |
var self = this; | |
self.$el.removeClass('valid-input').addClass('invalid-input'); | |
var marker = self.$el.next('.valid-marker'); | |
if (marker.length) { | |
if (marker.find('i').hasClass('fa-times')) return; | |
marker.stop().animate({right: self.offset, opacity: 0}, 150, function() { | |
$(this).find('i').removeClass('fa-check').addClass('fa-times'); | |
$(this).animate({right: self.offset + self.distance, opacity: 1}, 150); | |
}); | |
} else { | |
self.createMarker(); | |
} | |
}, | |
// Apply class '.valid-input' to this element, animate a little checkmark in from the right | |
markValid: function() { | |
var self = this; | |
self.$el.removeClass('invalid-input').addClass('valid-input'); | |
var marker = self.$el.next('.valid-marker'); | |
if (marker.length) { | |
if (marker.find('i').hasClass('fa-check')) return; | |
marker.stop().animate({right: self.offset, opacity: 0}, 150, function() { | |
$(this).find('i').removeClass('fa-times').addClass('fa-check'); | |
$(this).animate({right: self.offset + self.distance, opacity: 1}, 150); | |
}); | |
} else { | |
self.createMarker(true); | |
} | |
}, | |
createMarker: function(valid) { | |
if (this.el.tagName === 'SELECT' && !config.overrideSelects) return; | |
var marker = $('<span class="valid-marker"><i class="fa fa-' + (valid ? 'check' : 'times') + '"></i></span>') | |
.css({ | |
height: this.$el.outerHeight(), | |
top: (this.$el.offset().top - this.$el.parent().offset().top) + 'px' | |
}); | |
this.$el.css({appearance: 'none', WebkitAppearance: 'none', MozAppearance: 'none'}); | |
this.$el.after(marker).parent().css('position', 'relative'); | |
marker.animate({right: this.offset + this.distance, opacity: 1}, 150); | |
}, | |
isValid: function() { | |
var val = this.$el.val(), | |
name = this.$el.prop('name').toLowerCase(), | |
valid = name.indexOf('zip') > -1 ? config.validation.zip(val) | |
: name.indexOf('cvv') > -1 ? config.validation.cvv(val) | |
: name.indexOf('phone') > -1 ? config.validation.phone(val) | |
: name.indexOf('card') > -1 ? config.validation.creditcard(val) | |
: name.indexOf('email') > -1 ? config.validation.email(val) | |
: val && val.length; | |
return valid && val && val.length; | |
}, | |
validate: function() { | |
this.isValid() ? this.markValid() : this.markInvalid(); | |
}, | |
get distance() { | |
return config.animationDistance; | |
}, | |
get offset() { | |
var offsetLeft = this.$el.offset().left - this.$el.parent().offset().left, | |
offsetRight = this.$el.parent().outerWidth() - (offsetLeft + this.$el.outerWidth()); | |
return offsetRight - 16 - config.offset; | |
} | |
}; | |
// Create a new Confirmer object for every object selected by config.selector | |
$(config.selector).each(function() { | |
var confirmer = new Confirmer(this); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment