Skip to content

Instantly share code, notes, and snippets.

@jeff-kilbride
Last active May 3, 2016 17:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeff-kilbride/47c5342a5e458cc8fcbb0efbc341c3aa to your computer and use it in GitHub Desktop.
Save jeff-kilbride/47c5342a5e458cc8fcbb0efbc341c3aa to your computer and use it in GitHub Desktop.
Adding bootstrap validation styling to angular with a custom directive
// Directive for applying bootstrap validation styles.
/*
Sample HTML block.
<div class="form-group has-feedback" bs-style>
<label for="fName">First Name</label>
<input type="text" class="form-control" name="fName" id="fName" ng-model="vm.fName"
ng-minlength="2" ng-maxlength="20" required/>
<span class="form-control-feedback glyphicon"></span>
<span class="help-block ng-hide"></span>
</div>
Sample HTML block with "short" option.
<div class="form-group has-feedback" bs-style="short">
<label for="fName">First Name</label>
<input type="text" class="form-control" name="fName" id="fName" ng-model="vm.fName"
ng-minlength="2" ng-maxlength="20" required/>
<span class="form-control-feedback glyphicon"></span>
<span class="help-block ng-hide"></span>
</div>
*/
'use strict';
angular
.module('app')
.directive('bsStyle', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
// Find the input in this form-group.
for (var el of ['input', 'select', 'textarea']) {
var input = element.find(el);
if (!input.length) continue;
// Get the ngModelController for this input.
var ngm = input.controller('ngModel');
// Add a $watchGroup to handle the bootstrap validation styling.
// Monitor the $valid, $dirty and $touched, and $error values
// independently, so the listener fires when any of them change.
scope.$watchGroup([
function isValid() { return ngm.$valid; },
function isTD() { return ngm.$touched && ngm.$dirty; },
function hasError() { return Object.keys(ngm.$error).join(); }
],
function updateStyles(newValues, oldValues) {
// This should only happen in the initialization phase. Ignore it.
if (angular.equals(newValues, oldValues)) return;
// Don't do anything unless the input is $touched and $dirty.
if (!newValues[1]) return;
// Toggle the bootstrap 'has-success' and 'has-error' classes
// on the form-group based on $valid.
element.toggleClass('has-success', newValues[0])
.toggleClass('has-error', !newValues[0]);
// Update the icons and error messages based on $valid and $error.
var spans = element.find('span');
for (var i = 0; i < spans.length; i++) {
var s = spans.eq(i);
if (s.hasClass('form-control-feedback')) {
s.toggleClass('glyphicon-ok', newValues[0])
.toggleClass('glyphicon-remove', !newValues[0]);
}
if (s.hasClass('help-block')) {
// Only update the error message when not $valid.
if (!newValues[0]) {
var label = element.find('label').text(),
short = attrs.bsStyle === 'short',
e = ngm.$error;
// Create an error message based on the triggered error,
// the 'short' attribute, and the form-group's label.
// Customize these based on the validations used.
if (short) {
if (e.required) s.text('Required.');
else if (e.minlength) s.text('Too short.');
else if (e.maxlength) s.text('Too long.');
else if (e.mask) s.text('Not Valid.');
}
else {
if (e.required)
s.text(label + ' is required.');
else if (e.minlength)
s.text(label + ' must be at least ' +
input.attr('ng-minlength') + ' chars.');
else if (e.maxlength)
s.text(label + ' must be less than ' +
input.attr('ng-maxlength') + ' chars.');
else if (e.mask)
s.text(label + ' is not valid.');
}
}
// Display / hide the message based on $valid.
s.toggleClass('ng-hide', newValues[0]);
}
}
}
);
// Stop any extra loop processing.
break;
}
}
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment